@moxy/react-page-swapper
Version:
An orchestrator that eases out the implementation of page transitions
276 lines (226 loc) • 9.06 kB
JavaScript
"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;