Transition.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. "use strict";
  2. exports.__esModule = true;
  3. exports.default = exports.EXITING = exports.ENTERED = exports.ENTERING = exports.EXITED = exports.UNMOUNTED = void 0;
  4. var _propTypes = _interopRequireDefault(require("prop-types"));
  5. var _react = _interopRequireDefault(require("react"));
  6. var _reactDom = _interopRequireDefault(require("react-dom"));
  7. var _config = _interopRequireDefault(require("./config"));
  8. var _PropTypes = require("./utils/PropTypes");
  9. var _TransitionGroupContext = _interopRequireDefault(require("./TransitionGroupContext"));
  10. var _reflow = require("./utils/reflow");
  11. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  12. function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
  13. function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
  14. var UNMOUNTED = 'unmounted';
  15. exports.UNMOUNTED = UNMOUNTED;
  16. var EXITED = 'exited';
  17. exports.EXITED = EXITED;
  18. var ENTERING = 'entering';
  19. exports.ENTERING = ENTERING;
  20. var ENTERED = 'entered';
  21. exports.ENTERED = ENTERED;
  22. var EXITING = 'exiting';
  23. /**
  24. * The Transition component lets you describe a transition from one component
  25. * state to another _over time_ with a simple declarative API. Most commonly
  26. * it's used to animate the mounting and unmounting of a component, but can also
  27. * be used to describe in-place transition states as well.
  28. *
  29. * ---
  30. *
  31. * **Note**: `Transition` is a platform-agnostic base component. If you're using
  32. * transitions in CSS, you'll probably want to use
  33. * [`CSSTransition`](https://reactcommunity.org/react-transition-group/css-transition)
  34. * instead. It inherits all the features of `Transition`, but contains
  35. * additional features necessary to play nice with CSS transitions (hence the
  36. * name of the component).
  37. *
  38. * ---
  39. *
  40. * By default the `Transition` component does not alter the behavior of the
  41. * component it renders, it only tracks "enter" and "exit" states for the
  42. * components. It's up to you to give meaning and effect to those states. For
  43. * example we can add styles to a component when it enters or exits:
  44. *
  45. * ```jsx
  46. * import { Transition } from 'react-transition-group';
  47. *
  48. * const duration = 300;
  49. *
  50. * const defaultStyle = {
  51. * transition: `opacity ${duration}ms ease-in-out`,
  52. * opacity: 0,
  53. * }
  54. *
  55. * const transitionStyles = {
  56. * entering: { opacity: 1 },
  57. * entered: { opacity: 1 },
  58. * exiting: { opacity: 0 },
  59. * exited: { opacity: 0 },
  60. * };
  61. *
  62. * const Fade = ({ in: inProp }) => (
  63. * <Transition in={inProp} timeout={duration}>
  64. * {state => (
  65. * <div style={{
  66. * ...defaultStyle,
  67. * ...transitionStyles[state]
  68. * }}>
  69. * I'm a fade Transition!
  70. * </div>
  71. * )}
  72. * </Transition>
  73. * );
  74. * ```
  75. *
  76. * There are 4 main states a Transition can be in:
  77. * - `'entering'`
  78. * - `'entered'`
  79. * - `'exiting'`
  80. * - `'exited'`
  81. *
  82. * Transition state is toggled via the `in` prop. When `true` the component
  83. * begins the "Enter" stage. During this stage, the component will shift from
  84. * its current transition state, to `'entering'` for the duration of the
  85. * transition and then to the `'entered'` stage once it's complete. Let's take
  86. * the following example (we'll use the
  87. * [useState](https://reactjs.org/docs/hooks-reference.html#usestate) hook):
  88. *
  89. * ```jsx
  90. * function App() {
  91. * const [inProp, setInProp] = useState(false);
  92. * return (
  93. * <div>
  94. * <Transition in={inProp} timeout={500}>
  95. * {state => (
  96. * // ...
  97. * )}
  98. * </Transition>
  99. * <button onClick={() => setInProp(true)}>
  100. * Click to Enter
  101. * </button>
  102. * </div>
  103. * );
  104. * }
  105. * ```
  106. *
  107. * When the button is clicked the component will shift to the `'entering'` state
  108. * and stay there for 500ms (the value of `timeout`) before it finally switches
  109. * to `'entered'`.
  110. *
  111. * When `in` is `false` the same thing happens except the state moves from
  112. * `'exiting'` to `'exited'`.
  113. */
  114. exports.EXITING = EXITING;
  115. var Transition = /*#__PURE__*/function (_React$Component) {
  116. _inheritsLoose(Transition, _React$Component);
  117. function Transition(props, context) {
  118. var _this;
  119. _this = _React$Component.call(this, props, context) || this;
  120. var parentGroup = context; // In the context of a TransitionGroup all enters are really appears
  121. var appear = parentGroup && !parentGroup.isMounting ? props.enter : props.appear;
  122. var initialStatus;
  123. _this.appearStatus = null;
  124. if (props.in) {
  125. if (appear) {
  126. initialStatus = EXITED;
  127. _this.appearStatus = ENTERING;
  128. } else {
  129. initialStatus = ENTERED;
  130. }
  131. } else {
  132. if (props.unmountOnExit || props.mountOnEnter) {
  133. initialStatus = UNMOUNTED;
  134. } else {
  135. initialStatus = EXITED;
  136. }
  137. }
  138. _this.state = {
  139. status: initialStatus
  140. };
  141. _this.nextCallback = null;
  142. return _this;
  143. }
  144. Transition.getDerivedStateFromProps = function getDerivedStateFromProps(_ref, prevState) {
  145. var nextIn = _ref.in;
  146. if (nextIn && prevState.status === UNMOUNTED) {
  147. return {
  148. status: EXITED
  149. };
  150. }
  151. return null;
  152. } // getSnapshotBeforeUpdate(prevProps) {
  153. // let nextStatus = null
  154. // if (prevProps !== this.props) {
  155. // const { status } = this.state
  156. // if (this.props.in) {
  157. // if (status !== ENTERING && status !== ENTERED) {
  158. // nextStatus = ENTERING
  159. // }
  160. // } else {
  161. // if (status === ENTERING || status === ENTERED) {
  162. // nextStatus = EXITING
  163. // }
  164. // }
  165. // }
  166. // return { nextStatus }
  167. // }
  168. ;
  169. var _proto = Transition.prototype;
  170. _proto.componentDidMount = function componentDidMount() {
  171. this.updateStatus(true, this.appearStatus);
  172. };
  173. _proto.componentDidUpdate = function componentDidUpdate(prevProps) {
  174. var nextStatus = null;
  175. if (prevProps !== this.props) {
  176. var status = this.state.status;
  177. if (this.props.in) {
  178. if (status !== ENTERING && status !== ENTERED) {
  179. nextStatus = ENTERING;
  180. }
  181. } else {
  182. if (status === ENTERING || status === ENTERED) {
  183. nextStatus = EXITING;
  184. }
  185. }
  186. }
  187. this.updateStatus(false, nextStatus);
  188. };
  189. _proto.componentWillUnmount = function componentWillUnmount() {
  190. this.cancelNextCallback();
  191. };
  192. _proto.getTimeouts = function getTimeouts() {
  193. var timeout = this.props.timeout;
  194. var exit, enter, appear;
  195. exit = enter = appear = timeout;
  196. if (timeout != null && typeof timeout !== 'number') {
  197. exit = timeout.exit;
  198. enter = timeout.enter; // TODO: remove fallback for next major
  199. appear = timeout.appear !== undefined ? timeout.appear : enter;
  200. }
  201. return {
  202. exit: exit,
  203. enter: enter,
  204. appear: appear
  205. };
  206. };
  207. _proto.updateStatus = function updateStatus(mounting, nextStatus) {
  208. if (mounting === void 0) {
  209. mounting = false;
  210. }
  211. if (nextStatus !== null) {
  212. // nextStatus will always be ENTERING or EXITING.
  213. this.cancelNextCallback();
  214. if (nextStatus === ENTERING) {
  215. if (this.props.unmountOnExit || this.props.mountOnEnter) {
  216. var node = this.props.nodeRef ? this.props.nodeRef.current : _reactDom.default.findDOMNode(this); // https://github.com/reactjs/react-transition-group/pull/749
  217. // With unmountOnExit or mountOnEnter, the enter animation should happen at the transition between `exited` and `entering`.
  218. // To make the animation happen, we have to separate each rendering and avoid being processed as batched.
  219. if (node) (0, _reflow.forceReflow)(node);
  220. }
  221. this.performEnter(mounting);
  222. } else {
  223. this.performExit();
  224. }
  225. } else if (this.props.unmountOnExit && this.state.status === EXITED) {
  226. this.setState({
  227. status: UNMOUNTED
  228. });
  229. }
  230. };
  231. _proto.performEnter = function performEnter(mounting) {
  232. var _this2 = this;
  233. var enter = this.props.enter;
  234. var appearing = this.context ? this.context.isMounting : mounting;
  235. var _ref2 = this.props.nodeRef ? [appearing] : [_reactDom.default.findDOMNode(this), appearing],
  236. maybeNode = _ref2[0],
  237. maybeAppearing = _ref2[1];
  238. var timeouts = this.getTimeouts();
  239. var enterTimeout = appearing ? timeouts.appear : timeouts.enter; // no enter animation skip right to ENTERED
  240. // if we are mounting and running this it means appear _must_ be set
  241. if (!mounting && !enter || _config.default.disabled) {
  242. this.safeSetState({
  243. status: ENTERED
  244. }, function () {
  245. _this2.props.onEntered(maybeNode);
  246. });
  247. return;
  248. }
  249. this.props.onEnter(maybeNode, maybeAppearing);
  250. this.safeSetState({
  251. status: ENTERING
  252. }, function () {
  253. _this2.props.onEntering(maybeNode, maybeAppearing);
  254. _this2.onTransitionEnd(enterTimeout, function () {
  255. _this2.safeSetState({
  256. status: ENTERED
  257. }, function () {
  258. _this2.props.onEntered(maybeNode, maybeAppearing);
  259. });
  260. });
  261. });
  262. };
  263. _proto.performExit = function performExit() {
  264. var _this3 = this;
  265. var exit = this.props.exit;
  266. var timeouts = this.getTimeouts();
  267. var maybeNode = this.props.nodeRef ? undefined : _reactDom.default.findDOMNode(this); // no exit animation skip right to EXITED
  268. if (!exit || _config.default.disabled) {
  269. this.safeSetState({
  270. status: EXITED
  271. }, function () {
  272. _this3.props.onExited(maybeNode);
  273. });
  274. return;
  275. }
  276. this.props.onExit(maybeNode);
  277. this.safeSetState({
  278. status: EXITING
  279. }, function () {
  280. _this3.props.onExiting(maybeNode);
  281. _this3.onTransitionEnd(timeouts.exit, function () {
  282. _this3.safeSetState({
  283. status: EXITED
  284. }, function () {
  285. _this3.props.onExited(maybeNode);
  286. });
  287. });
  288. });
  289. };
  290. _proto.cancelNextCallback = function cancelNextCallback() {
  291. if (this.nextCallback !== null) {
  292. this.nextCallback.cancel();
  293. this.nextCallback = null;
  294. }
  295. };
  296. _proto.safeSetState = function safeSetState(nextState, callback) {
  297. // This shouldn't be necessary, but there are weird race conditions with
  298. // setState callbacks and unmounting in testing, so always make sure that
  299. // we can cancel any pending setState callbacks after we unmount.
  300. callback = this.setNextCallback(callback);
  301. this.setState(nextState, callback);
  302. };
  303. _proto.setNextCallback = function setNextCallback(callback) {
  304. var _this4 = this;
  305. var active = true;
  306. this.nextCallback = function (event) {
  307. if (active) {
  308. active = false;
  309. _this4.nextCallback = null;
  310. callback(event);
  311. }
  312. };
  313. this.nextCallback.cancel = function () {
  314. active = false;
  315. };
  316. return this.nextCallback;
  317. };
  318. _proto.onTransitionEnd = function onTransitionEnd(timeout, handler) {
  319. this.setNextCallback(handler);
  320. var node = this.props.nodeRef ? this.props.nodeRef.current : _reactDom.default.findDOMNode(this);
  321. var doesNotHaveTimeoutOrListener = timeout == null && !this.props.addEndListener;
  322. if (!node || doesNotHaveTimeoutOrListener) {
  323. setTimeout(this.nextCallback, 0);
  324. return;
  325. }
  326. if (this.props.addEndListener) {
  327. var _ref3 = this.props.nodeRef ? [this.nextCallback] : [node, this.nextCallback],
  328. maybeNode = _ref3[0],
  329. maybeNextCallback = _ref3[1];
  330. this.props.addEndListener(maybeNode, maybeNextCallback);
  331. }
  332. if (timeout != null) {
  333. setTimeout(this.nextCallback, timeout);
  334. }
  335. };
  336. _proto.render = function render() {
  337. var status = this.state.status;
  338. if (status === UNMOUNTED) {
  339. return null;
  340. }
  341. var _this$props = this.props,
  342. children = _this$props.children,
  343. _in = _this$props.in,
  344. _mountOnEnter = _this$props.mountOnEnter,
  345. _unmountOnExit = _this$props.unmountOnExit,
  346. _appear = _this$props.appear,
  347. _enter = _this$props.enter,
  348. _exit = _this$props.exit,
  349. _timeout = _this$props.timeout,
  350. _addEndListener = _this$props.addEndListener,
  351. _onEnter = _this$props.onEnter,
  352. _onEntering = _this$props.onEntering,
  353. _onEntered = _this$props.onEntered,
  354. _onExit = _this$props.onExit,
  355. _onExiting = _this$props.onExiting,
  356. _onExited = _this$props.onExited,
  357. _nodeRef = _this$props.nodeRef,
  358. childProps = _objectWithoutPropertiesLoose(_this$props, ["children", "in", "mountOnEnter", "unmountOnExit", "appear", "enter", "exit", "timeout", "addEndListener", "onEnter", "onEntering", "onEntered", "onExit", "onExiting", "onExited", "nodeRef"]);
  359. return (
  360. /*#__PURE__*/
  361. // allows for nested Transitions
  362. _react.default.createElement(_TransitionGroupContext.default.Provider, {
  363. value: null
  364. }, typeof children === 'function' ? children(status, childProps) : _react.default.cloneElement(_react.default.Children.only(children), childProps))
  365. );
  366. };
  367. return Transition;
  368. }(_react.default.Component);
  369. Transition.contextType = _TransitionGroupContext.default;
  370. Transition.propTypes = process.env.NODE_ENV !== "production" ? {
  371. /**
  372. * A React reference to DOM element that need to transition:
  373. * https://stackoverflow.com/a/51127130/4671932
  374. *
  375. * - When `nodeRef` prop is used, `node` is not passed to callback functions
  376. * (e.g. `onEnter`) because user already has direct access to the node.
  377. * - When changing `key` prop of `Transition` in a `TransitionGroup` a new
  378. * `nodeRef` need to be provided to `Transition` with changed `key` prop
  379. * (see
  380. * [test/CSSTransition-test.js](https://github.com/reactjs/react-transition-group/blob/13435f897b3ab71f6e19d724f145596f5910581c/test/CSSTransition-test.js#L362-L437)).
  381. */
  382. nodeRef: _propTypes.default.shape({
  383. current: typeof Element === 'undefined' ? _propTypes.default.any : function (propValue, key, componentName, location, propFullName, secret) {
  384. var value = propValue[key];
  385. return _propTypes.default.instanceOf(value && 'ownerDocument' in value ? value.ownerDocument.defaultView.Element : Element)(propValue, key, componentName, location, propFullName, secret);
  386. }
  387. }),
  388. /**
  389. * A `function` child can be used instead of a React element. This function is
  390. * called with the current transition status (`'entering'`, `'entered'`,
  391. * `'exiting'`, `'exited'`), which can be used to apply context
  392. * specific props to a component.
  393. *
  394. * ```jsx
  395. * <Transition in={this.state.in} timeout={150}>
  396. * {state => (
  397. * <MyComponent className={`fade fade-${state}`} />
  398. * )}
  399. * </Transition>
  400. * ```
  401. */
  402. children: _propTypes.default.oneOfType([_propTypes.default.func.isRequired, _propTypes.default.element.isRequired]).isRequired,
  403. /**
  404. * Show the component; triggers the enter or exit states
  405. */
  406. in: _propTypes.default.bool,
  407. /**
  408. * By default the child component is mounted immediately along with
  409. * the parent `Transition` component. If you want to "lazy mount" the component on the
  410. * first `in={true}` you can set `mountOnEnter`. After the first enter transition the component will stay
  411. * mounted, even on "exited", unless you also specify `unmountOnExit`.
  412. */
  413. mountOnEnter: _propTypes.default.bool,
  414. /**
  415. * By default the child component stays mounted after it reaches the `'exited'` state.
  416. * Set `unmountOnExit` if you'd prefer to unmount the component after it finishes exiting.
  417. */
  418. unmountOnExit: _propTypes.default.bool,
  419. /**
  420. * By default the child component does not perform the enter transition when
  421. * it first mounts, regardless of the value of `in`. If you want this
  422. * behavior, set both `appear` and `in` to `true`.
  423. *
  424. * > **Note**: there are no special appear states like `appearing`/`appeared`, this prop
  425. * > only adds an additional enter transition. However, in the
  426. * > `<CSSTransition>` component that first enter transition does result in
  427. * > additional `.appear-*` classes, that way you can choose to style it
  428. * > differently.
  429. */
  430. appear: _propTypes.default.bool,
  431. /**
  432. * Enable or disable enter transitions.
  433. */
  434. enter: _propTypes.default.bool,
  435. /**
  436. * Enable or disable exit transitions.
  437. */
  438. exit: _propTypes.default.bool,
  439. /**
  440. * The duration of the transition, in milliseconds.
  441. * Required unless `addEndListener` is provided.
  442. *
  443. * You may specify a single timeout for all transitions:
  444. *
  445. * ```jsx
  446. * timeout={500}
  447. * ```
  448. *
  449. * or individually:
  450. *
  451. * ```jsx
  452. * timeout={{
  453. * appear: 500,
  454. * enter: 300,
  455. * exit: 500,
  456. * }}
  457. * ```
  458. *
  459. * - `appear` defaults to the value of `enter`
  460. * - `enter` defaults to `0`
  461. * - `exit` defaults to `0`
  462. *
  463. * @type {number | { enter?: number, exit?: number, appear?: number }}
  464. */
  465. timeout: function timeout(props) {
  466. var pt = _PropTypes.timeoutsShape;
  467. if (!props.addEndListener) pt = pt.isRequired;
  468. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  469. args[_key - 1] = arguments[_key];
  470. }
  471. return pt.apply(void 0, [props].concat(args));
  472. },
  473. /**
  474. * Add a custom transition end trigger. Called with the transitioning
  475. * DOM node and a `done` callback. Allows for more fine grained transition end
  476. * logic. Timeouts are still used as a fallback if provided.
  477. *
  478. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  479. *
  480. * ```jsx
  481. * addEndListener={(node, done) => {
  482. * // use the css transitionend event to mark the finish of a transition
  483. * node.addEventListener('transitionend', done, false);
  484. * }}
  485. * ```
  486. */
  487. addEndListener: _propTypes.default.func,
  488. /**
  489. * Callback fired before the "entering" status is applied. An extra parameter
  490. * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
  491. *
  492. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  493. *
  494. * @type Function(node: HtmlElement, isAppearing: bool) -> void
  495. */
  496. onEnter: _propTypes.default.func,
  497. /**
  498. * Callback fired after the "entering" status is applied. An extra parameter
  499. * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
  500. *
  501. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  502. *
  503. * @type Function(node: HtmlElement, isAppearing: bool)
  504. */
  505. onEntering: _propTypes.default.func,
  506. /**
  507. * Callback fired after the "entered" status is applied. An extra parameter
  508. * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount
  509. *
  510. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  511. *
  512. * @type Function(node: HtmlElement, isAppearing: bool) -> void
  513. */
  514. onEntered: _propTypes.default.func,
  515. /**
  516. * Callback fired before the "exiting" status is applied.
  517. *
  518. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  519. *
  520. * @type Function(node: HtmlElement) -> void
  521. */
  522. onExit: _propTypes.default.func,
  523. /**
  524. * Callback fired after the "exiting" status is applied.
  525. *
  526. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  527. *
  528. * @type Function(node: HtmlElement) -> void
  529. */
  530. onExiting: _propTypes.default.func,
  531. /**
  532. * Callback fired after the "exited" status is applied.
  533. *
  534. * **Note**: when `nodeRef` prop is passed, `node` is not passed
  535. *
  536. * @type Function(node: HtmlElement) -> void
  537. */
  538. onExited: _propTypes.default.func
  539. } : {}; // Name the function so it is clearer in the documentation
  540. function noop() {}
  541. Transition.defaultProps = {
  542. in: false,
  543. mountOnEnter: false,
  544. unmountOnExit: false,
  545. appear: false,
  546. enter: true,
  547. exit: true,
  548. onEnter: noop,
  549. onEntering: noop,
  550. onEntered: noop,
  551. onExit: noop,
  552. onExiting: noop,
  553. onExited: noop
  554. };
  555. Transition.UNMOUNTED = UNMOUNTED;
  556. Transition.EXITED = EXITED;
  557. Transition.ENTERING = ENTERING;
  558. Transition.ENTERED = ENTERED;
  559. Transition.EXITING = EXITING;
  560. var _default = Transition;
  561. exports.default = _default;