text: import React from 'react';
  import PropTypes from 'prop-types';
  import { ENTERED, ENTERING, EXITING } from './Transition'
  import TransitionGroupContext from './TransitionGroupContext';
  
  function areChildrenDifferent(oldChildren, newChildren) {
    if (oldChildren === newChildren) return false;
    if (
      React.isValidElement(oldChildren) &&
      React.isValidElement(newChildren) &&
      oldChildren.key != null &&
      oldChildren.key === newChildren.key
    ) {
      return false;
    }
    return true;
  }
  
  /**
   * Enum of modes for SwitchTransition component
   * @enum { string }
   */
  export const modes = {
    out: 'out-in',
    in: 'in-out'
  };
  
  const callHook = (element, name, cb) => (...args) => {
    element.props[name] && element.props[name](...args)
    cb()
  }
  
  const leaveRenders = {
    [modes.out]: ({ current, changeState }) =>
      React.cloneElement(current, {
        in: false,
        onExited: callHook(current, 'onExited', () => {
          changeState(ENTERING, null);
        })
      }),
    [modes.in]: ({ current, changeState, children }) => [
      current,
      React.cloneElement(children, {
        in: true,
        onEntered: callHook(children, 'onEntered', () => {
          changeState(ENTERING);
        })
      })
    ]
  };
  
  const enterRenders = {
    [modes.out]: ({ children, changeState }) =>
      React.cloneElement(children, {
        in: true,
        onEntered: callHook(children, 'onEntered', () => {
          changeState(ENTERED, React.cloneElement(children, { in: true }));
        })
      }),
    [modes.in]: ({ current, children, changeState }) => [
      React.cloneElement(current, {
        in: false,
        onExited: callHook(current, 'onExited', () => {
          changeState(ENTERED, React.cloneElement(children, { in: true }));
        })
      }),
      React.cloneElement(children, {
        in: true
      })
    ]
  };
  
  /**
   * A transition component inspired by the [vue transition modes](https://vuejs.org/v2/guide/transitions.html#Transition-Modes).
   * You can use it when you want to control the render between state transitions.
   * Based on the selected mode and the child's key which is the `Transition` or `CSSTransition` component, the `SwitchTransition` makes a consistent transition between them.
   *
   * If the `out-in` mode is selected, the `SwitchTransition` waits until the old child leaves and then inserts a new child.
   * If the `in-out` mode is selected, the `SwitchTransition` inserts a new child first, waits for the new child to enter and then removes the old child
   *
   * ```jsx
   *
   * function App() {
   *  const [state, setState] = useState(false);
   *  return (
   *    <SwitchTransition>
   *      <CSSTransition
   *        key={state ? "Goodbye, world!" : "Hello, world!"}
   *        addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}
   *        classNames='fade'
   *       >
   *        <button onClick={() => setState(state => !state)}>
   *          {state ? "Goodbye, world!" : "Hello, world!"}
   *        </button>
   *      </CSSTransition>
   *    </SwitchTransition>
   *  )
   * }
   * ```
   *
   * ```css
   * .fade-enter{
   *    opacity: 0;
   * }
   * .fade-exit{
   *    opacity: 1;
   * }
   * .fade-enter-active{
   *    opacity: 1;
   * }
   * .fade-exit-active{
   *    opacity: 0;
   * }
   * .fade-enter-active,
   * .fade-exit-active{
   *    transition: opacity 500ms;
   * }
   * ```
   */
  class SwitchTransition extends React.Component {
    state = {
      status: ENTERED,
      current: null
    };
  
    appeared = false;
  
    componentDidMount() {
      this.appeared = true;
    }
  
    static getDerivedStateFromProps(props, state) {
      if (props.children == null) {
        return {
          current: null
        }
      }
  
      if (state.status === ENTERING && props.mode === modes.in) {
        return {
          status: ENTERING
        };
      }
  
      if (state.current && areChildrenDifferent(state.current, props.children)) {
        return {
          status: EXITING
        };
      }
  
      return {
        current: React.cloneElement(props.children, {
          in: true
        })
      };
    }
  
    changeState = (status, current = this.state.current) => {
      this.setState({
        status,
        current
      });
    };
  
    render() {
      const {
        props: { children, mode },
        state: { status, current }
      } = this;
  
      const data = { children, current, changeState: this.changeState, status };
      let component
      switch (status) {
        case ENTERING:
          component = enterRenders[mode](data);
          break;
        case EXITING:
          component = leaveRenders[mode](data)
          break;
        case ENTERED:
          component = current;
      }
  
      return (
        <TransitionGroupContext.Provider value={{ isMounting: !this.appeared }}>
          {component}
        </TransitionGroupContext.Provider>
      )
    }
  }
  
  
  SwitchTransition.propTypes = {
    /**
     * Transition modes.
     * `out-in`: Current element transitions out first, then when complete, the new element transitions in.
     * `in-out: New element transitions in first, then when complete, the current element transitions out.`
     *
     * @type {'out-in'|'in-out'}
     */
    mode: PropTypes.oneOf([modes.in, modes.out]),
    /**
     * Any `Transition` or `CSSTransition` component
     */
    children: PropTypes.oneOfType([
      PropTypes.element.isRequired,
    ]),
  }
  
  SwitchTransition.defaultProps = {
    mode: modes.out
  }
  
  export default SwitchTransition;
  