<commit_before>import _ from 'lodash'
import React, { PropTypes } from 'react'
import cx from 'classnames'

import ModalHeader from './ModalHeader'
import ModalContent from './ModalContent'
import ModalActions from './ModalActions'
import ModalDescription from './ModalDescription'
import Icon from '../../elements/Icon'
import Portal from '../../addons/Portal'

import {
  AutoControlledComponent as Component,
  customPropTypes,
  getElementType,
  getUnhandledProps,
  isBrowser,
  makeDebugger,
  META,
  useKeyOnly,
} from '../../lib'

const debug = makeDebugger('modal')

const _meta = {
  name: 'Modal',
  type: META.TYPES.MODULE,
  props: {
    size: ['fullscreen', 'large', 'small'],
    dimmer: ['inverted', 'blurring'],
  },
}

/**
 * A modal displays content that temporarily blocks interactions with the main view of a site
 * @see Confirm
 * @see Portal
 */
class Modal extends Component {
  static propTypes = {
    /** An element type to render as (string or function). */
    as: customPropTypes.as,

    /** Primary content. */
    children: PropTypes.node,

    /** Additional classes. */
    className: PropTypes.string,

    /** Icon */
    closeIcon: PropTypes.oneOfType([
      PropTypes.node,
      PropTypes.object,
      PropTypes.bool,
    ]),

    /** A modal can reduce its complexity */
    basic: PropTypes.bool,

    /** Initial value of open. */
    defaultOpen: PropTypes.bool,

    /** A modal can appear in a dimmer */
    dimmer: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.oneOf(_meta.props.dimmer),
    ]),

    /** The node where the modal should mount.. */
    mountNode: PropTypes.any,

    /** Called when a close event happens */
    onClose: PropTypes.func,

    /** Called when the portal is mounted on the DOM */
    onMount: PropTypes.func,

    /** Called when an open event happens */
    onOpen: PropTypes.func,

    /** Called when the portal is unmounted from the DOM */
    onUnmount: PropTypes.func,

    /** Controls whether or not the Modal is displayed. */
    open: PropTypes.bool,

    /** A modal can vary in size */
    size: PropTypes.oneOf(_meta.props.size),

    /**
     * NOTE: Any unhandled props that are defined in Portal are passed-through
     * to the wrapping Portal.
     */
  }

  static defaultProps = {
    dimmer: true,
    // Do not access document when server side rendering
    mountNode: isBrowser ? document.body : null,
  }

  static autoControlledProps = [
    'open',
  ]

  static _meta = _meta
  static Header = ModalHeader
  static Content = ModalContent
  static Description = ModalDescription
  static Actions = ModalActions

  state = {}

  componentWillUnmount() {
    debug('componentWillUnmount()')
    this.handlePortalUnmount()
  }

  handleClose = (e) => {
    debug('close()')

    const { onClose } = this.props
    if (onClose) onClose(e, this.props)

    this.trySetState({ open: false })
  }

  handleOpen = (e) => {
    debug('open()')

    const { onOpen } = this.props
    if (onOpen) onOpen(e, this.props)

    this.trySetState({ open: true })
  }

  handlePortalMount = (e) => {
    debug('handlePortalMount()')
    const { dimmer, mountNode } = this.props

    if (dimmer) {
      debug('adding dimmer')
      mountNode.classList.add('dimmable', 'dimmed')

      if (dimmer === 'blurring') {
        debug('adding blurred dimmer')
        mountNode.classList.add('blurring')
      }
    }

    this.setPosition()

    const { onMount } = this.props
    if (onMount) onMount(e, this.props)
  }

  handlePortalUnmount = (e) => {
    debug('handlePortalUnmount()')

    // Always remove all dimmer classes.
    // If the dimmer value changes while the modal is open, then removing its
    // current value could leave cruft classes previously added.
    const { mountNode } = this.props
    mountNode.classList.remove('blurring', 'dimmable', 'dimmed', 'scrollable')

    cancelAnimationFrame(this.animationRequestId)

    const { onUnmount } = this.props
    if (onUnmount) onUnmount(e, this.props)
  }

  setPosition = () => {
    if (this._modalNode) {
      const { mountNode } = this.props
      const { height } = this._modalNode.getBoundingClientRect()
      const scrolling = height >= window.innerHeight

      const newState = {
        marginTop: -Math.round(height / 2),
        scrolling,
      }

      // add/remove scrolling class on body
      if (!this.state.scrolling && scrolling) {
        mountNode.classList.add('scrolling')
      } else if (this.state.scrolling && !scrolling) {
        mountNode.classList.remove('scrolling')
      }

      if (!_.isEqual(newState, this.state)) {
        this.setState(newState)
      }
    }

    this.animationRequestId = requestAnimationFrame(this.setPosition)
  }

  render() {
    const { open } = this.state
    const { basic, children, className, closeIcon, dimmer, mountNode, size } = this.props

    // Short circuit when server side rendering
    if (!isBrowser) return null

    const { marginTop, scrolling } = this.state
    const classes = cx(
      'ui',
      size,
      useKeyOnly(basic, 'basic'),
      useKeyOnly(scrolling, 'scrolling'),
      'modal transition visible active',
      className,
    )
    const unhandled = getUnhandledProps(Modal, this.props)
    const portalPropNames = _.keys(Portal.propTypes)

    const rest = _.omit(unhandled, portalPropNames)
    const portalProps = _.pick(unhandled, portalPropNames)<commit_msg>fix(Modal): prevent unnecessary renders (#973)<commit_after>import _ from 'lodash'
import React, { PropTypes } from 'react'
import cx from 'classnames'

import ModalHeader from './ModalHeader'
import ModalContent from './ModalContent'
import ModalActions from './ModalActions'
import ModalDescription from './ModalDescription'
import Icon from '../../elements/Icon'
import Portal from '../../addons/Portal'

import {
  AutoControlledComponent as Component,
  customPropTypes,
  getElementType,
  getUnhandledProps,
  isBrowser,
  makeDebugger,
  META,
  useKeyOnly,
} from '../../lib'

const debug = makeDebugger('modal')

const _meta = {
  name: 'Modal',
  type: META.TYPES.MODULE,
  props: {
    size: ['fullscreen', 'large', 'small'],
    dimmer: ['inverted', 'blurring'],
  },
}

/**
 * A modal displays content that temporarily blocks interactions with the main view of a site
 * @see Confirm
 * @see Portal
 */
class Modal extends Component {
  static propTypes = {
    /** An element type to render as (string or function). */
    as: customPropTypes.as,

    /** Primary content. */
    children: PropTypes.node,

    /** Additional classes. */
    className: PropTypes.string,

    /** Icon */
    closeIcon: PropTypes.oneOfType([
      PropTypes.node,
      PropTypes.object,
      PropTypes.bool,
    ]),

    /** A modal can reduce its complexity */
    basic: PropTypes.bool,

    /** Initial value of open. */
    defaultOpen: PropTypes.bool,

    /** A modal can appear in a dimmer */
    dimmer: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.oneOf(_meta.props.dimmer),
    ]),

    /** The node where the modal should mount.. */
    mountNode: PropTypes.any,

    /** Called when a close event happens */
    onClose: PropTypes.func,

    /** Called when the portal is mounted on the DOM */
    onMount: PropTypes.func,

    /** Called when an open event happens */
    onOpen: PropTypes.func,

    /** Called when the portal is unmounted from the DOM */
    onUnmount: PropTypes.func,

    /** Controls whether or not the Modal is displayed. */
    open: PropTypes.bool,

    /** A modal can vary in size */
    size: PropTypes.oneOf(_meta.props.size),

    /**
     * NOTE: Any unhandled props that are defined in Portal are passed-through
     * to the wrapping Portal.
     */
  }

  static defaultProps = {
    dimmer: true,
    // Do not access document when server side rendering
    mountNode: isBrowser ? document.body : null,
  }

  static autoControlledProps = [
    'open',
  ]

  static _meta = _meta
  static Header = ModalHeader
  static Content = ModalContent
  static Description = ModalDescription
  static Actions = ModalActions

  state = {}

  componentWillUnmount() {
    debug('componentWillUnmount()')
    this.handlePortalUnmount()
  }

  handleClose = (e) => {
    debug('close()')

    const { onClose } = this.props
    if (onClose) onClose(e, this.props)

    this.trySetState({ open: false })
  }

  handleOpen = (e) => {
    debug('open()')

    const { onOpen } = this.props
    if (onOpen) onOpen(e, this.props)

    this.trySetState({ open: true })
  }

  handlePortalMount = (e) => {
    debug('handlePortalMount()')
    const { dimmer, mountNode } = this.props

    if (dimmer) {
      debug('adding dimmer')
      mountNode.classList.add('dimmable', 'dimmed')

      if (dimmer === 'blurring') {
        debug('adding blurred dimmer')
        mountNode.classList.add('blurring')
      }
    }

    this.setPosition()

    const { onMount } = this.props
    if (onMount) onMount(e, this.props)
  }

  handlePortalUnmount = (e) => {
    debug('handlePortalUnmount()')

    // Always remove all dimmer classes.
    // If the dimmer value changes while the modal is open, then removing its
    // current value could leave cruft classes previously added.
    const { mountNode } = this.props
    mountNode.classList.remove('blurring', 'dimmable', 'dimmed', 'scrollable')

    cancelAnimationFrame(this.animationRequestId)

    const { onUnmount } = this.props
    if (onUnmount) onUnmount(e, this.props)
  }

  setPosition = () => {
    if (this._modalNode) {
      const { mountNode } = this.props
      const { height } = this._modalNode.getBoundingClientRect()

      const marginTop = -Math.round(height / 2)
      const scrolling = height >= window.innerHeight

      const newState = {}

      if (this.state.marginTop !== marginTop) {
        newState.marginTop = marginTop
      }

      if (this.state.scrolling !== scrolling) {
        newState.scrolling = scrolling

        if (scrolling) {
          mountNode.classList.add('scrolling')
        } else {
          mountNode.classList.remove('scrolling')
        }
      }

      if (Object.keys(newState).length > 0) this.setState(newState)
    }

    this.animationRequestId = requestAnimationFrame(this.setPosition)
  }

  render() {
    const { open } = this.state
    const { basic, children, className, closeIcon, dimmer, mountNode, size } = this.props

    // Short circuit when server side rendering
    if (!isBrowser) return null

    const { marginTop, scrolling } = this.state
    const classes = cx(
      'ui',
      size,
      useKeyOnly(basic, 'basic'),
      useKeyOnly(scrolling, 'scrolling'),
      'modal transition visible active',
      className,
    )
    const unhandled = getUnhandledProps(Modal, this.props)
    const portalPropNames = _.keys(Portal.propTypes)

    const rest = _.omit(unhandled, portalPropNames)
    const portalProps = _.pick(unhandled, portalPropNames)
{'fraction_non_alphanumeric': 0.1198577031834352, 'fraction_numerical': 0.0005472954483261881, 'mean_word_length': 2.78852798894264, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5752737', 'n_tokens_mistral': 3328, 'n_tokens_neox': 3240, 'n_words': 908}