UNPKG

@moxy/react-page-swapper

Version:

An orchestrator that eases out the implementation of page transitions

251 lines (216 loc) 7.3 kB
import _omit from "lodash/omit"; 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; } import React, { Component, createRef } from 'react'; import { findDOMNode } from 'react-dom'; import { TransitionGroup } from 'react-transition-group'; import PropTypes from 'prop-types'; import memoizeOne from 'memoize-one'; import SwapTransition from './SwapTransition'; import { getRandomNodeKey } from './node-key'; import { lockContainerSize, buildEnterStyle, buildExitStyle } from './layout'; export default class PageSwapper extends Component { constructor(_props) { super(_props); _defineProperty(this, "state", {}); _defineProperty(this, "containerRef", createRef()); _defineProperty(this, "ref", createRef()); _defineProperty(this, "remainingSwapAcks", 0); _defineProperty(this, "raf", void 0); _defineProperty(this, "halfSwap", void 0); _defineProperty(this, "buildContainerProps", memoizeOne(props => _omit(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.createElement(TransitionGroup, Object.assign({ ref: this.containerRef }, this.buildContainerProps(this.props)), nodeKey && /*#__PURE__*/React.createElement(SwapTransition, { 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 || 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 = findDOMNode(this.ref.current); const containerElement = 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 = lockContainerSize(containerElement); (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.blur(); this.setState({ animation: state.animation, style: 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 : 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: buildEnterStyle() }; } // eslint-disable-next-line react/sort-comp } _defineProperty(PageSwapper, "propTypes", { node: PropTypes.element.isRequired, nodeKey: PropTypes.string, mode: PropTypes.oneOfType([PropTypes.oneOf(['simultaneous', 'out-in']), PropTypes.func]), animation: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), children: PropTypes.func.isRequired, updateScroll: PropTypes.func, onSwapBegin: PropTypes.func, onSwapEnd: PropTypes.func }); _defineProperty(PageSwapper, "defaultProps", { mode: 'simultaneous', updateScroll: () => window.scrollTo(0, 0) });