CSSTransition.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
  3. import _inheritsLoose from "@babel/runtime/helpers/esm/inheritsLoose";
  4. import PropTypes from 'prop-types';
  5. import addOneClass from 'dom-helpers/addClass';
  6. import removeOneClass from 'dom-helpers/removeClass';
  7. import React from 'react';
  8. import Transition from './Transition';
  9. import { classNamesShape } from './utils/PropTypes';
  10. import { forceReflow } from './utils/reflow';
  11. var _addClass = function addClass(node, classes) {
  12. return node && classes && classes.split(' ').forEach(function (c) {
  13. return addOneClass(node, c);
  14. });
  15. };
  16. var removeClass = function removeClass(node, classes) {
  17. return node && classes && classes.split(' ').forEach(function (c) {
  18. return removeOneClass(node, c);
  19. });
  20. };
  21. /**
  22. * A transition component inspired by the excellent
  23. * [ng-animate](https://docs.angularjs.org/api/ngAnimate) library, you should
  24. * use it if you're using CSS transitions or animations. It's built upon the
  25. * [`Transition`](https://reactcommunity.org/react-transition-group/transition)
  26. * component, so it inherits all of its props.
  27. *
  28. * `CSSTransition` applies a pair of class names during the `appear`, `enter`,
  29. * and `exit` states of the transition. The first class is applied and then a
  30. * second `*-active` class in order to activate the CSS transition. After the
  31. * transition, matching `*-done` class names are applied to persist the
  32. * transition state.
  33. *
  34. * ```jsx
  35. * function App() {
  36. * const [inProp, setInProp] = useState(false);
  37. * return (
  38. * <div>
  39. * <CSSTransition in={inProp} timeout={200} classNames="my-node">
  40. * <div>
  41. * {"I'll receive my-node-* classes"}
  42. * </div>
  43. * </CSSTransition>
  44. * <button type="button" onClick={() => setInProp(true)}>
  45. * Click to Enter
  46. * </button>
  47. * </div>
  48. * );
  49. * }
  50. * ```
  51. *
  52. * When the `in` prop is set to `true`, the child component will first receive
  53. * the class `example-enter`, then the `example-enter-active` will be added in
  54. * the next tick. `CSSTransition` [forces a
  55. * reflow](https://github.com/reactjs/react-transition-group/blob/5007303e729a74be66a21c3e2205e4916821524b/src/CSSTransition.js#L208-L215)
  56. * between before adding the `example-enter-active`. This is an important trick
  57. * because it allows us to transition between `example-enter` and
  58. * `example-enter-active` even though they were added immediately one after
  59. * another. Most notably, this is what makes it possible for us to animate
  60. * _appearance_.
  61. *
  62. * ```css
  63. * .my-node-enter {
  64. * opacity: 0;
  65. * }
  66. * .my-node-enter-active {
  67. * opacity: 1;
  68. * transition: opacity 200ms;
  69. * }
  70. * .my-node-exit {
  71. * opacity: 1;
  72. * }
  73. * .my-node-exit-active {
  74. * opacity: 0;
  75. * transition: opacity 200ms;
  76. * }
  77. * ```
  78. *
  79. * `*-active` classes represent which styles you want to animate **to**, so it's
  80. * important to add `transition` declaration only to them, otherwise transitions
  81. * might not behave as intended! This might not be obvious when the transitions
  82. * are symmetrical, i.e. when `*-enter-active` is the same as `*-exit`, like in
  83. * the example above (minus `transition`), but it becomes apparent in more
  84. * complex transitions.
  85. *
  86. * **Note**: If you're using the
  87. * [`appear`](http://reactcommunity.org/react-transition-group/transition#Transition-prop-appear)
  88. * prop, make sure to define styles for `.appear-*` classes as well.
  89. */
  90. var CSSTransition = /*#__PURE__*/function (_React$Component) {
  91. _inheritsLoose(CSSTransition, _React$Component);
  92. function CSSTransition() {
  93. var _this;
  94. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  95. args[_key] = arguments[_key];
  96. }
  97. _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
  98. _this.appliedClasses = {
  99. appear: {},
  100. enter: {},
  101. exit: {}
  102. };
  103. _this.onEnter = function (maybeNode, maybeAppearing) {
  104. var _this$resolveArgument = _this.resolveArguments(maybeNode, maybeAppearing),
  105. node = _this$resolveArgument[0],
  106. appearing = _this$resolveArgument[1];
  107. _this.removeClasses(node, 'exit');
  108. _this.addClass(node, appearing ? 'appear' : 'enter', 'base');
  109. if (_this.props.onEnter) {
  110. _this.props.onEnter(maybeNode, maybeAppearing);
  111. }
  112. };
  113. _this.onEntering = function (maybeNode, maybeAppearing) {
  114. var _this$resolveArgument2 = _this.resolveArguments(maybeNode, maybeAppearing),
  115. node = _this$resolveArgument2[0],
  116. appearing = _this$resolveArgument2[1];
  117. var type = appearing ? 'appear' : 'enter';
  118. _this.addClass(node, type, 'active');
  119. if (_this.props.onEntering) {
  120. _this.props.onEntering(maybeNode, maybeAppearing);
  121. }
  122. };
  123. _this.onEntered = function (maybeNode, maybeAppearing) {
  124. var _this$resolveArgument3 = _this.resolveArguments(maybeNode, maybeAppearing),
  125. node = _this$resolveArgument3[0],
  126. appearing = _this$resolveArgument3[1];
  127. var type = appearing ? 'appear' : 'enter';
  128. _this.removeClasses(node, type);
  129. _this.addClass(node, type, 'done');
  130. if (_this.props.onEntered) {
  131. _this.props.onEntered(maybeNode, maybeAppearing);
  132. }
  133. };
  134. _this.onExit = function (maybeNode) {
  135. var _this$resolveArgument4 = _this.resolveArguments(maybeNode),
  136. node = _this$resolveArgument4[0];
  137. _this.removeClasses(node, 'appear');
  138. _this.removeClasses(node, 'enter');
  139. _this.addClass(node, 'exit', 'base');
  140. if (_this.props.onExit) {
  141. _this.props.onExit(maybeNode);
  142. }
  143. };
  144. _this.onExiting = function (maybeNode) {
  145. var _this$resolveArgument5 = _this.resolveArguments(maybeNode),
  146. node = _this$resolveArgument5[0];
  147. _this.addClass(node, 'exit', 'active');
  148. if (_this.props.onExiting) {
  149. _this.props.onExiting(maybeNode);
  150. }
  151. };
  152. _this.onExited = function (maybeNode) {
  153. var _this$resolveArgument6 = _this.resolveArguments(maybeNode),
  154. node = _this$resolveArgument6[0];
  155. _this.removeClasses(node, 'exit');
  156. _this.addClass(node, 'exit', 'done');
  157. if (_this.props.onExited) {
  158. _this.props.onExited(maybeNode);
  159. }
  160. };
  161. _this.resolveArguments = function (maybeNode, maybeAppearing) {
  162. return _this.props.nodeRef ? [_this.props.nodeRef.current, maybeNode] // here `maybeNode` is actually `appearing`
  163. : [maybeNode, maybeAppearing];
  164. };
  165. _this.getClassNames = function (type) {
  166. var classNames = _this.props.classNames;
  167. var isStringClassNames = typeof classNames === 'string';
  168. var prefix = isStringClassNames && classNames ? classNames + "-" : '';
  169. var baseClassName = isStringClassNames ? "" + prefix + type : classNames[type];
  170. var activeClassName = isStringClassNames ? baseClassName + "-active" : classNames[type + "Active"];
  171. var doneClassName = isStringClassNames ? baseClassName + "-done" : classNames[type + "Done"];
  172. return {
  173. baseClassName: baseClassName,
  174. activeClassName: activeClassName,
  175. doneClassName: doneClassName
  176. };
  177. };
  178. return _this;
  179. }
  180. var _proto = CSSTransition.prototype;
  181. _proto.addClass = function addClass(node, type, phase) {
  182. var className = this.getClassNames(type)[phase + "ClassName"];
  183. var _this$getClassNames = this.getClassNames('enter'),
  184. doneClassName = _this$getClassNames.doneClassName;
  185. if (type === 'appear' && phase === 'done' && doneClassName) {
  186. className += " " + doneClassName;
  187. } // This is to force a repaint,
  188. // which is necessary in order to transition styles when adding a class name.
  189. if (phase === 'active') {
  190. if (node) forceReflow(node);
  191. }
  192. if (className) {
  193. this.appliedClasses[type][phase] = className;
  194. _addClass(node, className);
  195. }
  196. };
  197. _proto.removeClasses = function removeClasses(node, type) {
  198. var _this$appliedClasses$ = this.appliedClasses[type],
  199. baseClassName = _this$appliedClasses$.base,
  200. activeClassName = _this$appliedClasses$.active,
  201. doneClassName = _this$appliedClasses$.done;
  202. this.appliedClasses[type] = {};
  203. if (baseClassName) {
  204. removeClass(node, baseClassName);
  205. }
  206. if (activeClassName) {
  207. removeClass(node, activeClassName);
  208. }
  209. if (doneClassName) {
  210. removeClass(node, doneClassName);
  211. }
  212. };
  213. _proto.render = function render() {
  214. var _this$props = this.props,
  215. _ = _this$props.classNames,
  216. props = _objectWithoutPropertiesLoose(_this$props, ["classNames"]);
  217. return /*#__PURE__*/React.createElement(Transition, _extends({}, props, {
  218. onEnter: this.onEnter,
  219. onEntered: this.onEntered,
  220. onEntering: this.onEntering,
  221. onExit: this.onExit,
  222. onExiting: this.onExiting,
  223. onExited: this.onExited
  224. }));
  225. };
  226. return CSSTransition;
  227. }(React.Component);
  228. CSSTransition.defaultProps = {
  229. classNames: ''
  230. };
  231. CSSTransition.propTypes = process.env.NODE_ENV !== "production" ? _extends({}, Transition.propTypes, {
  232. /**
  233. * The animation classNames applied to the component as it appears, enters,
  234. * exits or has finished the transition. A single name can be provided, which
  235. * will be suffixed for each stage, e.g. `classNames="fade"` applies:
  236. *
  237. * - `fade-appear`, `fade-appear-active`, `fade-appear-done`
  238. * - `fade-enter`, `fade-enter-active`, `fade-enter-done`
  239. * - `fade-exit`, `fade-exit-active`, `fade-exit-done`
  240. *
  241. * A few details to note about how these classes are applied:
  242. *
  243. * 1. They are _joined_ with the ones that are already defined on the child
  244. * component, so if you want to add some base styles, you can use
  245. * `className` without worrying that it will be overridden.
  246. *
  247. * 2. If the transition component mounts with `in={false}`, no classes are
  248. * applied yet. You might be expecting `*-exit-done`, but if you think
  249. * about it, a component cannot finish exiting if it hasn't entered yet.
  250. *
  251. * 2. `fade-appear-done` and `fade-enter-done` will _both_ be applied. This
  252. * allows you to define different behavior for when appearing is done and
  253. * when regular entering is done, using selectors like
  254. * `.fade-enter-done:not(.fade-appear-done)`. For example, you could apply
  255. * an epic entrance animation when element first appears in the DOM using
  256. * [Animate.css](https://daneden.github.io/animate.css/). Otherwise you can
  257. * simply use `fade-enter-done` for defining both cases.
  258. *
  259. * Each individual classNames can also be specified independently like:
  260. *
  261. * ```js
  262. * classNames={{
  263. * appear: 'my-appear',
  264. * appearActive: 'my-active-appear',
  265. * appearDone: 'my-done-appear',
  266. * enter: 'my-enter',
  267. * enterActive: 'my-active-enter',
  268. * enterDone: 'my-done-enter',
  269. * exit: 'my-exit',
  270. * exitActive: 'my-active-exit',
  271. * exitDone: 'my-done-exit',
  272. * }}
  273. * ```
  274. *
  275. * If you want to set these classes using CSS Modules:
  276. *
  277. * ```js
  278. * import styles from './styles.css';
  279. * ```
  280. *
  281. * you might want to use camelCase in your CSS file, that way could simply
  282. * spread them instead of listing them one by one:
  283. *
  284. * ```js
  285. * classNames={{ ...styles }}
  286. * ```
  287. *
  288. * @type {string | {
  289. * appear?: string,
  290. * appearActive?: string,
  291. * appearDone?: string,
  292. * enter?: string,
  293. * enterActive?: string,
  294. * enterDone?: string,
  295. * exit?: string,
  296. * exitActive?: string,
  297. * exitDone?: string,
  298. * }}
  299. */
  300. classNames: classNamesShape,
  301. /**
  302. * A `<Transition>` callback fired immediately after the 'enter' or 'appear' class is
  303. * applied.
  304. *
  305. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  306. *
  307. * @type Function(node: HtmlElement, isAppearing: bool)
  308. */
  309. onEnter: PropTypes.func,
  310. /**
  311. * A `<Transition>` callback fired immediately after the 'enter-active' or
  312. * 'appear-active' class is applied.
  313. *
  314. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  315. *
  316. * @type Function(node: HtmlElement, isAppearing: bool)
  317. */
  318. onEntering: PropTypes.func,
  319. /**
  320. * A `<Transition>` callback fired immediately after the 'enter' or
  321. * 'appear' classes are **removed** and the `done` class is added to the DOM node.
  322. *
  323. * **Note**: when `nodeRef` prop is passed, `node` is not passed.
  324. *
  325. * @type Function(node: HtmlElement, isAppearing: bool)
  326. */
  327. onEntered: PropTypes.func,
  328. /**
  329. * A `<Transition>` callback fired immediately after the 'exit' class is
  330. * applied.
  331. *
  332. * **Note**: when `nodeRef` prop is passed, `node` is not passed
  333. *
  334. * @type Function(node: HtmlElement)
  335. */
  336. onExit: PropTypes.func,
  337. /**
  338. * A `<Transition>` callback fired immediately after the 'exit-active' is applied.
  339. *
  340. * **Note**: when `nodeRef` prop is passed, `node` is not passed
  341. *
  342. * @type Function(node: HtmlElement)
  343. */
  344. onExiting: PropTypes.func,
  345. /**
  346. * A `<Transition>` callback fired immediately after the 'exit' classes
  347. * are **removed** and the `exit-done` class is added to the DOM node.
  348. *
  349. * **Note**: when `nodeRef` prop is passed, `node` is not passed
  350. *
  351. * @type Function(node: HtmlElement)
  352. */
  353. onExited: PropTypes.func
  354. }) : {};
  355. export default CSSTransition;