react-view-router
Version:
react-view-router
239 lines (237 loc) • 6.95 kB
JavaScript
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
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 const modes = {
out: 'out-in',
in: 'in-out',
together: 'together'
};
const callHook = (element, name, cb) => (...args) => {
element.props[name] && element.props[name](...args);
cb();
};
const 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
}));
})
})]
};
const 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() {
const {
props: {
children,
mode
},
state: {
status,
current
}
} = this;
const data = {
children,
current,
changeState: this.changeState,
status
};
let 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;