UNPKG

@moxy/react-page-swapper

Version:

An orchestrator that eases out the implementation of page transitions

276 lines (226 loc) 9.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _omit2 = _interopRequireDefault(require("lodash/omit")); var _react = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _reactTransitionGroup = require("react-transition-group"); var _propTypes = _interopRequireDefault(require("prop-types")); var _memoizeOne = _interopRequireDefault(require("memoize-one")); var _SwapTransition = _interopRequireDefault(require("./SwapTransition")); var _nodeKey = require("./node-key"); var _layout = require("./layout"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class PageSwapper extends _react.Component { constructor(_props) { super(_props); _defineProperty(this, "state", {}); _defineProperty(this, "containerRef", (0, _react.createRef)()); _defineProperty(this, "ref", (0, _react.createRef)()); _defineProperty(this, "remainingSwapAcks", 0); _defineProperty(this, "raf", void 0); _defineProperty(this, "halfSwap", void 0); _defineProperty(this, "buildContainerProps", (0, _memoizeOne.default)(props => (0, _omit2.default)(props, ['node', 'nodeKey', 'mode', 'animation', 'children', 'updateScroll', 'onSwapBegin', 'onSwapEnd']))); _defineProperty(this, "handleRef", ref => { if (ref) { this.ref.current = ref; } }); _defineProperty(this, "handleEntered", () => { this.remainingSwapAcks -= 1; if (!this.isSwapping()) { this.finishSwap(); } }); _defineProperty(this, "handleExited", () => { this.remainingSwapAcks -= 1; if (this.halfSwap) { this.halfSwap(); } else { this.finishSwap(); } }); this.state = this.buildState(); } componentDidUpdate() { if (!this.isSwapping()) { if (this.isOutOfSync()) { this.beginSwap(); } else { this.maybeUpdateNode(); } } } componentWillUnmount() { cancelAnimationFrame(this.raf); } render() { const { children } = this.props; const { node, nodeKey, prevNodeKey, in: inProp, mode, animation, style } = this.state; return /*#__PURE__*/_react.default.createElement(_reactTransitionGroup.TransitionGroup, Object.assign({ ref: this.containerRef }, this.buildContainerProps(this.props)), nodeKey && /*#__PURE__*/_react.default.createElement(_SwapTransition.default, { key: nodeKey, in: inProp, node: node, nodeKey: nodeKey, prevNodeKey: prevNodeKey, mode: mode, animation: animation, style: style, ref: this.handleRef, onEntered: this.handleEntered, onExited: this.handleExited }, children)); } isSwapping() { return this.remainingSwapAcks > 0; } isOutOfSync() { const currentNodeKey = this.state.nodeKey; const nodeKey = this.props.nodeKey || (0, _nodeKey.getRandomNodeKey)(this.props.node); return nodeKey !== currentNodeKey; } beginSwap() { var _this$props$onSwapBeg, _this$props, _document$activeEleme; const state = this.buildState(); const { nodeKey, prevNodeKey } = state; const element = (0, _reactDom.findDOMNode)(this.ref.current); const containerElement = (0, _reactDom.findDOMNode)(this.containerRef.current); (_this$props$onSwapBeg = (_this$props = this.props).onSwapBegin) === null || _this$props$onSwapBeg === void 0 ? void 0 : _this$props$onSwapBeg.call(_this$props, { nodeKey: prevNodeKey, nextNodeKey: nodeKey }); this.remainingSwapAcks = 2; cancelAnimationFrame(this.raf); // Prepare exiting: // - Lock the container size // - Blur activeElement if any // - Apply the animation and out-of-flow styles const unlockSize = (0, _layout.lockContainerSize)(containerElement); (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.blur(); this.setState({ animation: state.animation, style: (0, _layout.buildExitStyle)(element) }, () => { // Need to wait an animation frame so that the styles are applied // This is especially necessary for Safari, to avoid "flickering" this.raf = requestAnimationFrame(() => { if (state.mode === 'out-in') { // Finally start the swap by making the current node exit. this.setState({ node: null, nodeKey: null }); // Declare what to do after it exists, which will be used on the handleExited function this.halfSwap = () => { // Make the new node enter. this.setState(state, () => { // Now that we have the current node, its dimensions are being counted // towards the document flow, meaning we can now update the scroll // and unlock size this.props.updateScroll({ nodeKey }); unlockSize(); }); }; } else { this.halfSwap = undefined; // Finally start the swap! this.setState(state, () => { // Now that we have the current node, its dimensions are being counted // towards the document flow, meaning we can now update the scroll // and unlock size this.props.updateScroll({ nodeKey }); unlockSize(); }); } }); }); } finishSwap() { var _this$props$onSwapEnd, _this$props2; const { nodeKey, prevNodeKey } = this.state; (_this$props$onSwapEnd = (_this$props2 = this.props).onSwapEnd) === null || _this$props$onSwapEnd === void 0 ? void 0 : _this$props$onSwapEnd.call(_this$props2, { nodeKey, prevNodeKey }); if (this.isOutOfSync()) { this.beginSwap(); } else { this.maybeUpdateNode(); } } maybeUpdateNode() { if (this.props.node !== this.state.node) { this.setState({ node: this.props.node }); } } buildState() { var _this$props$nodeKey; const { node, mode, animation } = this.props; const { nodeKey: currentNodeKey } = this.state; const nodeKey = (_this$props$nodeKey = this.props.nodeKey) !== null && _this$props$nodeKey !== void 0 ? _this$props$nodeKey : (0, _nodeKey.getRandomNodeKey)(node); const modeStr = typeof mode === 'function' ? mode({ prevNodeKey: currentNodeKey, nodeKey }) : mode; const animationStr = typeof animation === 'function' ? animation({ prevNodeKey: currentNodeKey, nodeKey }) : animation; return { node, nodeKey, prevNodeKey: currentNodeKey, mode: modeStr, animation: animationStr, style: (0, _layout.buildEnterStyle)() }; } // eslint-disable-next-line react/sort-comp } exports.default = PageSwapper; _defineProperty(PageSwapper, "propTypes", { node: _propTypes.default.element.isRequired, nodeKey: _propTypes.default.string, mode: _propTypes.default.oneOfType([_propTypes.default.oneOf(['simultaneous', 'out-in']), _propTypes.default.func]), animation: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func]), children: _propTypes.default.func.isRequired, updateScroll: _propTypes.default.func, onSwapBegin: _propTypes.default.func, onSwapEnd: _propTypes.default.func }); _defineProperty(PageSwapper, "defaultProps", { mode: 'simultaneous', updateScroll: () => window.scrollTo(0, 0) }); module.exports = exports.default;