UNPKG

react-view-router

Version:
233 lines (231 loc) 6.48 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React from 'react'; import PropTypes from 'prop-types'; import TransitionGroupContext from 'react-transition-group/TransitionGroupContext'; import { ENTERED, ENTERING, EXITING } from './Transition'; function areChildrenDifferent(oldChildren, newChildren) { if (oldChildren === newChildren) return false; if (/*#__PURE__*/React.isValidElement(oldChildren) && /*#__PURE__*/React.isValidElement(newChildren) && oldChildren.key != null && oldChildren.key === newChildren.key) { return false; } return true; } /** * Enum of modes for SwitchTransition component * @enum { string } */ export var modes = { out: 'out-in', in: 'in-out', together: 'together' }; var callHook = (element, name, cb) => (...args) => { element.props[name] && element.props[name](...args); cb(); }; var leaveRenders = { [modes.out]: ({ current, changeState }) => /*#__PURE__*/React.cloneElement(current, { in: false, onExited: callHook(current, 'onExited', () => { changeState(ENTERING, null); }) }), [modes.in]: ({ current, changeState, children }) => [current, /*#__PURE__*/React.cloneElement(children, { in: true, onEntered: callHook(children, 'onEntered', () => { changeState(ENTERING); }) })], [modes.together]: ({ current, changeState, children }) => [/*#__PURE__*/React.cloneElement(current, { in: false }), /*#__PURE__*/React.cloneElement(children, { in: true, onEntered: callHook(children, 'onEntered', () => { changeState(ENTERED, /*#__PURE__*/React.cloneElement(children, { in: true })); }) })] }; var enterRenders = { [modes.out]: ({ children, changeState }) => /*#__PURE__*/React.cloneElement(children, { in: true, onEntered: callHook(children, 'onEntered', () => { changeState(ENTERED, /*#__PURE__*/React.cloneElement(children, { in: true })); }) }), [modes.in]: ({ current, children, changeState }) => [/*#__PURE__*/React.cloneElement(current, { in: false, onExited: callHook(current, 'onExited', () => { changeState(ENTERED, /*#__PURE__*/React.cloneElement(children, { in: true })); }) }), /*#__PURE__*/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. * * **Note**: If you want the animation to happen simultaneously * (that is, to have the old child removed and a new child inserted **at the same time**), * you should use * [`TransitionGroup`](https://reactcommunity.org/react-transition-group/transition-group) * instead. * * ```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 { constructor(...args) { super(...args); _defineProperty(this, "state", { status: ENTERED, current: null }); _defineProperty(this, "appeared", false); _defineProperty(this, "changeState", (status, current = this.state.current) => { this.setState({ status, current }); }); } 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: /*#__PURE__*/React.cloneElement(props.children, { in: true }) }; } render() { var _this$props = this.props, children = _this$props.children, mode = _this$props.mode, _this$state = this.state, status = _this$state.status, current = _this$state.current; var data = { children, current, changeState: this.changeState, status }; var component; // eslint-disable-next-line default-case switch (status) { case ENTERING: component = enterRenders[mode](data); break; case EXITING: component = leaveRenders[mode](data); break; case ENTERED: component = current; } return /*#__PURE__*/React.createElement(TransitionGroupContext.Provider, { value: { isMounting: !this.appeared } }, component); } } 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'|'together'} */ mode: PropTypes.oneOf([modes.in, modes.out, modes.together]), /** * Any `Transition` or `CSSTransition` component. */ children: PropTypes.oneOfType([PropTypes.element.isRequired]) }; SwitchTransition.defaultProps = { mode: modes.out }; export default SwitchTransition;