UNPKG

@atlaskit/motion

Version:

A set of utilities to apply motion in your application.

207 lines (198 loc) 7.84 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useExitingPersistence = exports.default = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } /** * Internally we will be playing with an element that will always have a key defined. */ /** * Internal data passed to child motions. */ // We define empty context here so the object doesn't change. var emptyContext = { // Motions will always appear if not inside a exiting persistence component. appear: true, isExiting: false }; /** * __Exiting context__ * * An exiting context. */ var ExitingContext = /*#__PURE__*/(0, _react.createContext)(emptyContext); /** * This method will wrap any React element with a context provider. We're using context (instead of * cloneElement) so we can communicate between multiple elements without the need of prop drilling * (results in a better API for consumers). */ var wrapChildWithContextProvider = function wrapChildWithContextProvider(child) { var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : emptyContext; return /*#__PURE__*/_react.default.createElement(ExitingContext.Provider, { key: "".concat(child.key, "-provider"), value: value }, child); }; /** * This function will convert all children types to an array while also filtering out non-valid React elements. */ var childrenToArray = function childrenToArray(children) { var childrenAsArray = []; // We convert children to an array using this helper method as it will add keys to children that do not // have them, such as when we have hardcoded children that are conditionally rendered. _react.Children.toArray(children).forEach(function (child) { // We ignore any boolean children to make our code a little more simple later on, // and also filter out any falsies (empty strings, nulls, and undefined). if (typeof child !== 'boolean' && Boolean(child)) { // Children WILL have a key after being forced into an array using the React.Children helper. childrenAsArray.push(child); } }); return childrenAsArray; }; var spliceNewElementsIntoPrevious = function spliceNewElementsIntoPrevious(current, previous) { var splicedChildren = previous.concat([]); var previousMap = childrenToObj(previous); for (var i = 0; i < current.length; i++) { var child = current[i]; var childIsNew = !previousMap[child.key]; if (childIsNew) { // This will insert the new element after the previous element. splicedChildren.splice(i + 1, 0, child); } } return splicedChildren; }; var childrenToObj = function childrenToObj(children) { return children.reduce(function (acc, child) { acc[child.key] = child; return acc; }, {}); }; var getMissingKeys = function getMissingKeys(current, previous) { var currentMapKeys = new Set(current.map(function (child) { return child.key; })); var missing = new Set(); for (var i = 0; i < previous.length; i++) { var element = previous[i]; var _key = element.key; if (!currentMapKeys.has(_key)) { missing.add(_key); } } return missing; }; /** * How does this component work? * * It looks at changes in its children to see what is removed. * * If a child is removed it clones it and wraps it with context providing an `onFinish` callback. * * The cloned child will call the `onFinish` when it finishes its exit animation, * which lets `ExitingPersistence` know to stop rendering it. */ /** * __ExitingPersistence__ * * Useful for enabling elements to persist and animate away when they are removed from the DOM. * * - [Examples](https://atlaskit.atlassian.com/packages/design-system/motion/docs/entering-motions) */ var ExitingPersistence = /*#__PURE__*/(0, _react.memo)(function (_ref) { var _ref$appear = _ref.appear, appear = _ref$appear === void 0 ? false : _ref$appear, children = _ref.children, exitThenEnter = _ref.exitThenEnter; var _useState = (0, _react.useState)([null, children]), _useState2 = (0, _slicedToArray2.default)(_useState, 2), stateChildren = _useState2[0], setChildren = _useState2[1]; var _useState3 = (0, _react.useState)([]), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), exitingChildren = _useState4[0], setExitingChildren = _useState4[1]; var _useState5 = (0, _react.useState)(function () { return { appear: appear, isExiting: false }; }), _useState6 = (0, _slicedToArray2.default)(_useState5, 2), defaultContext = _useState6[0], setDefaultContext = _useState6[1]; (0, _react.useEffect)(function () { if (!defaultContext.appear) { setDefaultContext({ appear: true, isExiting: false }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** * NOTE: This is a workaround for the test case written in Jira where the stateChildren is a boolean value because * useState is mocked to return a boolean value. */ if (typeof stateChildren === 'boolean') { return children; } var _stateChildren = (0, _slicedToArray2.default)(stateChildren, 2), previousChildren = _stateChildren[0], currentChildren = _stateChildren[1]; var previous = childrenToArray(previousChildren); var current = childrenToArray(currentChildren); if (currentChildren !== children) { setChildren([currentChildren, children]); } var missingKeys = getMissingKeys(current, previous); var isSomeChildRemoved = !!missingKeys.size; var visibleChildren = current; if (isSomeChildRemoved) { visibleChildren = spliceNewElementsIntoPrevious(current, previous); } if (exitThenEnter) { if (exitingChildren.length) { visibleChildren = exitingChildren; } else { var nextExitingChildren = visibleChildren.filter(function (child) { return missingKeys.has(child.key); }); if (nextExitingChildren.length) { setExitingChildren(nextExitingChildren); } } } if (missingKeys.size) { visibleChildren = visibleChildren.map(function (child) { var isExiting = missingKeys.has(child.key); return wrapChildWithContextProvider(child, { appear: true, isExiting: isExiting, onFinish: isExiting ? function () { missingKeys.delete(child.key); if (missingKeys.size === 0) { setChildren([null, children]); setExitingChildren([]); } } : undefined }); }); } else { visibleChildren = visibleChildren.map(function (child) { return wrapChildWithContextProvider(child, defaultContext); }); } return visibleChildren; }); var useExitingPersistence = exports.useExitingPersistence = function useExitingPersistence() { return (0, _react.useContext)(ExitingContext); }; ExitingPersistence.displayName = 'ExitingPersistence'; var _default = exports.default = ExitingPersistence;