react-pose
Version:
A declarative animation library for React
488 lines (478 loc) • 19.3 kB
JavaScript
import { __assign, __extends, __rest } from 'tslib';
import React__default, { PureComponent, createContext, createElement, forwardRef, Component, cloneElement, Children } from 'react';
import poseFactory from 'popmotion-pose';
import isValidProp from '@emotion/is-prop-valid';
import { invariant, warning } from 'hey-listen';
var hasChanged = function (prev, next) {
if (prev === next)
return false;
var prevIsArray = Array.isArray(prev);
var nextIsArray = Array.isArray(next);
if (prevIsArray !== nextIsArray || (!prevIsArray && !nextIsArray)) {
return true;
}
else if (prevIsArray && nextIsArray) {
var numPrev = prev.length;
var numNext = next.length;
if (numPrev !== numNext)
return true;
for (var i = 0; i < numPrev; i++) {
if (prev[i] !== next[i])
return true;
}
}
return false;
};
var pickAssign = function (shouldPick, sources) {
return sources.reduce(function (picked, source) {
for (var key in source) {
if (shouldPick(key)) {
picked[key] = source[key];
}
}
return picked;
}, {});
};
var _a = createContext({}), PoseParentConsumer = _a.Consumer, PoseParentProvider = _a.Provider;
var calcPopFromFlowStyle = function (el) {
var offsetTop = el.offsetTop, offsetLeft = el.offsetLeft, offsetWidth = el.offsetWidth, offsetHeight = el.offsetHeight;
return {
position: 'absolute',
top: offsetTop,
left: offsetLeft,
width: offsetWidth,
height: offsetHeight
};
};
var hasPose = function (pose, key) {
return Array.isArray(pose) ? pose.indexOf(key) !== -1 : pose === key;
};
var objectToMap = function (obj) {
return Object.keys(obj).reduce(function (map, key) {
map.set(key, { raw: obj[key] });
return map;
}, new Map());
};
var testAlwaysTrue = function () { return true; };
var filterProps = function (_a) {
var elementType = _a.elementType, poseConfig = _a.poseConfig, onValueChange = _a.onValueChange, innerRef = _a.innerRef, _pose = _a._pose, pose = _a.pose, initialPose = _a.initialPose, poseKey = _a.poseKey, onPoseComplete = _a.onPoseComplete, getParentPoseConfig = _a.getParentPoseConfig, registerChild = _a.registerChild, onUnmount = _a.onUnmount, getInitialPoseFromParent = _a.getInitialPoseFromParent, popFromFlow = _a.popFromFlow, values = _a.values, parentValues = _a.parentValues, onDragStart = _a.onDragStart, onDragEnd = _a.onDragEnd, onPressStart = _a.onPressStart, onPressEnd = _a.onPressEnd, props = __rest(_a, ["elementType", "poseConfig", "onValueChange", "innerRef", "_pose", "pose", "initialPose", "poseKey", "onPoseComplete", "getParentPoseConfig", "registerChild", "onUnmount", "getInitialPoseFromParent", "popFromFlow", "values", "parentValues", "onDragStart", "onDragEnd", "onPressStart", "onPressEnd"]);
return props;
};
var PoseElement = (function (_super) {
__extends(PoseElement, _super);
function PoseElement(props) {
var _this = _super.call(this, props) || this;
_this.children = new Set();
_this.childrenHandlers = {
registerChild: function (props) {
_this.children.add(props);
if (_this.poser)
_this.flushChildren();
},
onUnmount: function (child) { return _this.poser.removeChild(child); },
getParentPoseConfig: function () { return _this.poseConfig; },
getInitialPoseFromParent: function () { return _this.getInitialPose(); }
};
_this.setRef = function (ref) {
warning(ref === null || (ref instanceof Element && _this.ref === undefined), 'ref must be provided to the same DOM component for the entire lifecycle of a posed component.');
_this.ref = ref;
var innerRef = _this.props.innerRef;
if (!innerRef)
return;
if (typeof innerRef === 'function') {
innerRef(ref);
}
else {
innerRef.current = ref;
}
};
_this.shouldForwardProp =
typeof _this.props.elementType === 'string' ? isValidProp : testAlwaysTrue;
var poseConfig = _this.props.poseConfig;
_this.poseConfig =
typeof poseConfig === 'function'
? poseConfig(filterProps(props))
: poseConfig;
return _this;
}
PoseElement.prototype.getInitialPose = function () {
var _a = this.props, getInitialPoseFromParent = _a.getInitialPoseFromParent, pose = _a.pose, _pose = _a._pose, initialPose = _a.initialPose;
if (initialPose) {
return initialPose;
}
else {
var parentPose = getInitialPoseFromParent && getInitialPoseFromParent();
var initialPoses = (Array.isArray(parentPose)
? parentPose
: [parentPose])
.concat(pose, _pose)
.filter(Boolean);
return initialPoses.length > 0 ? initialPoses : undefined;
}
};
PoseElement.prototype.getFirstPose = function () {
var _a = this.props, initialPose = _a.initialPose, pose = _a.pose, _pose = _a._pose;
if (!initialPose)
return;
var firstPose = (Array.isArray(pose) ? pose : [pose])
.concat(_pose)
.filter(Boolean);
return firstPose.length === 1 ? firstPose[0] : firstPose;
};
PoseElement.prototype.getSetProps = function () {
var props = filterProps(this.props);
if (this.props.popFromFlow && this.ref && this.ref instanceof HTMLElement) {
if (!this.popStyle) {
props.style = __assign(__assign({}, props.style), calcPopFromFlowStyle(this.ref));
this.popStyle = props.style;
}
else {
props.style = this.popStyle;
}
}
else {
this.popStyle = null;
}
return props;
};
PoseElement.prototype.componentDidMount = function () {
var _this = this;
invariant(this.ref instanceof Element, "No valid DOM ref found. If you're converting an existing component via posed(Component), you must ensure you're passing the ref to the host DOM node via the React.forwardRef function.");
var _a = this.props, onValueChange = _a.onValueChange, registerChild = _a.registerChild, values = _a.values, parentValues = _a.parentValues, onDragStart = _a.onDragStart, onDragEnd = _a.onDragEnd, onPressStart = _a.onPressStart, onPressEnd = _a.onPressEnd;
var config = __assign(__assign({}, this.poseConfig), { initialPose: this.getInitialPose(), values: values || this.poseConfig.values, parentValues: parentValues ? objectToMap(parentValues) : undefined, props: this.getSetProps(), onDragStart: onDragStart,
onDragEnd: onDragEnd,
onPressStart: onPressStart,
onPressEnd: onPressEnd, onChange: onValueChange });
if (!registerChild) {
this.initPoser(poseFactory(this.ref, config));
}
else {
registerChild({
element: this.ref,
poseConfig: config,
onRegistered: function (poser) { return _this.initPoser(poser); }
});
}
};
PoseElement.prototype.getSnapshotBeforeUpdate = function () {
var _a = this.props, pose = _a.pose, _pose = _a._pose;
if (hasPose(pose, 'flip') || hasPose(_pose, 'flip'))
this.poser.measure();
return null;
};
PoseElement.prototype.componentDidUpdate = function (prevProps) {
var _a = this.props, pose = _a.pose, _pose = _a._pose, poseKey = _a.poseKey;
this.poser.setProps(this.getSetProps());
if (poseKey !== prevProps.poseKey ||
hasChanged(prevProps.pose, pose) ||
pose === 'flip') {
this.setPose(pose);
}
if (_pose !== prevProps._pose || _pose === 'flip')
this.setPose(_pose);
};
PoseElement.prototype.componentWillUnmount = function () {
if (!this.poser)
return;
var onUnmount = this.props.onUnmount;
if (onUnmount)
onUnmount(this.poser);
this.poser.destroy();
};
PoseElement.prototype.initPoser = function (poser) {
this.poser = poser;
this.flushChildren();
var firstPose = this.getFirstPose();
if (firstPose)
this.setPose(firstPose);
};
PoseElement.prototype.setPose = function (pose) {
var _this = this;
var onPoseComplete = this.props.onPoseComplete;
var poseList = Array.isArray(pose) ? pose : [pose];
Promise.all(poseList.map(function (key) { return key && _this.poser.set(key); })).then(function () { return onPoseComplete && onPoseComplete(pose); });
};
PoseElement.prototype.flushChildren = function () {
var _this = this;
this.children.forEach(function (_a) {
var element = _a.element, poseConfig = _a.poseConfig, onRegistered = _a.onRegistered;
return onRegistered(_this.poser.addChild(element, poseConfig));
});
this.children.clear();
};
PoseElement.prototype.render = function () {
var elementType = this.props.elementType;
return (React__default.createElement(PoseParentProvider, { value: this.childrenHandlers }, createElement(elementType, pickAssign(this.shouldForwardProp, [
this.getSetProps(),
{ ref: this.setRef }
]))));
};
return PoseElement;
}(PureComponent));
var supportedElements = [
'a',
'article',
'aside',
'audio',
'b',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dialog',
'div',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'i',
'iframe',
'img',
'input',
'label',
'legend',
'li',
'nav',
'object',
'ol',
'option',
'p',
'param',
'picture',
'pre',
'progress',
'q',
'section',
'select',
'span',
'strong',
'table',
'tbody',
'td',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'ul',
'video',
'circle',
'clipPath',
'defs',
'ellipse',
'g',
'image',
'line',
'linearGradient',
'mask',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'stop',
'svg',
'text',
'tspan'
];
var componentCache = new Map();
var createComponentFactory = function (key) {
var componentFactory = function (poseConfig) {
if (poseConfig === void 0) { poseConfig = {}; }
return forwardRef(function (_a, ref) {
var _b = _a.withParent, withParent = _b === void 0 ? true : _b, props = __rest(_a, ["withParent"]);
warning(props.innerRef === undefined, 'innerRef is deprecated. Please use ref instead.');
return !withParent || props.parentValues ? (React__default.createElement(PoseElement, __assign({ poseConfig: poseConfig, innerRef: ref, elementType: key }, props))) : (React__default.createElement(PoseParentConsumer, null, function (parentCtx) { return (React__default.createElement(PoseElement, __assign({ poseConfig: poseConfig, elementType: key, innerRef: ref }, props, parentCtx))); }));
});
};
componentCache.set(key, componentFactory);
return componentFactory;
};
var getComponentFactory = function (key) {
return componentCache.has(key)
? componentCache.get(key)
: createComponentFactory(key);
};
var posed = (function (component) {
return getComponentFactory(component);
});
supportedElements.reduce(function (acc, key) {
acc[key] = createComponentFactory(key);
return acc;
}, posed);
var getKey = function (child) {
invariant(child && child.key !== null, 'Every child of Transition must be given a unique key');
var childKey = typeof child.key === 'number' ? child.key.toString() : child.key;
return childKey.replace('.$', '');
};
var prependProps = function (element, props) {
return createElement(element.type, __assign(__assign({ key: element.key, ref: element.ref }, props), element.props));
};
var handleTransition = function (_a, _b) {
var displayedChildren = _b.displayedChildren, finishedLeaving = _b.finishedLeaving, hasInitialized = _b.hasInitialized, indexedChildren = _b.indexedChildren, scheduleChildRemoval = _b.scheduleChildRemoval;
var incomingChildren = _a.children, preEnterPose = _a.preEnterPose, enterPose = _a.enterPose, exitPose = _a.exitPose, animateOnMount = _a.animateOnMount, enterAfterExit = _a.enterAfterExit, flipMove = _a.flipMove, onRest = _a.onRest, propsForChildren = __rest(_a, ["children", "preEnterPose", "enterPose", "exitPose", "animateOnMount", "enterAfterExit", "flipMove", "onRest"]);
var targetChildren = makeChildList(incomingChildren);
var nextState = {
displayedChildren: []
};
if (process.env.NODE_ENV !== 'production') {
warning(!propsForChildren.onPoseComplete, "<Transition/> (or <PoseGroup/>) doesn't accept onPoseComplete prop.");
}
var prevKeys = displayedChildren.map(getKey);
var nextKeys = targetChildren.map(getKey);
var hasPropsForChildren = Object.keys(propsForChildren).length !== 0;
var entering = new Set(nextKeys.filter(function (key) { return finishedLeaving.hasOwnProperty(key) || prevKeys.indexOf(key) === -1; }));
entering.forEach(function (key) { return delete finishedLeaving[key]; });
var leaving = [];
var newlyLeaving = {};
prevKeys.forEach(function (key) {
if (entering.has(key)) {
return;
}
var isLeaving = finishedLeaving.hasOwnProperty(key);
if (!isLeaving && nextKeys.indexOf(key) !== -1) {
return;
}
leaving.push(key);
if (!isLeaving) {
finishedLeaving[key] = false;
newlyLeaving[key] = true;
}
});
var moving = new Set(prevKeys.filter(function (key, i) {
return !entering.has(key) || leaving.indexOf(key) === -1;
}));
targetChildren.forEach(function (child) {
var newChildProps = {};
if (entering.has(child.key)) {
if (hasInitialized || animateOnMount) {
newChildProps.initialPose = preEnterPose;
}
newChildProps._pose = enterPose;
}
else if (moving.has(child.key) && flipMove) {
newChildProps._pose = [enterPose, 'flip'];
}
else {
newChildProps._pose = enterPose;
}
var newChild = cloneElement(child, newChildProps);
indexedChildren[child.key] = newChild;
nextState.displayedChildren.push(hasPropsForChildren ? prependProps(newChild, propsForChildren) : newChild);
});
leaving.forEach(function (key) {
var child = indexedChildren[key];
var newChild = newlyLeaving[key]
? cloneElement(child, {
_pose: exitPose,
onPoseComplete: function (pose) {
if (pose === exitPose)
scheduleChildRemoval(key);
var onPoseComplete = child.props.onPoseComplete;
if (onPoseComplete)
onPoseComplete(pose);
},
popFromFlow: flipMove
})
: child;
var insertionIndex = prevKeys.indexOf(key);
indexedChildren[child.key] = newChild;
nextState.displayedChildren.splice(insertionIndex, 0, hasPropsForChildren ? prependProps(newChild, propsForChildren) : newChild);
});
return nextState;
};
var handleChildrenTransitions = (function (props, state) {
var newState = handleTransition(props, state);
newState.hasInitialized = true;
return newState;
});
var makeChildList = function (children) {
var list = [];
Children.forEach(children, function (child) { return child && list.push(child); });
return list;
};
var Transition = (function (_super) {
__extends(Transition, _super);
function Transition() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
displayedChildren: [],
finishedLeaving: {},
hasInitialized: false,
indexedChildren: {},
scheduleChildRemoval: function (key) { return _this.removeChild(key); }
};
return _this;
}
Transition.prototype.removeChild = function (key) {
var _a = this.state, displayedChildren = _a.displayedChildren, finishedLeaving = _a.finishedLeaving;
var _b = this.props, enterAfterExit = _b.enterAfterExit, onRest = _b.onRest;
if (!finishedLeaving.hasOwnProperty(key))
return;
finishedLeaving[key] = true;
if (!Object.keys(finishedLeaving).every(function (leavingKey) { return finishedLeaving[leavingKey]; })) {
return;
}
var targetChildren = displayedChildren.filter(function (child) { return !finishedLeaving.hasOwnProperty(child.key); });
var newState = enterAfterExit
? __assign({ finishedLeaving: {} }, handleChildrenTransitions(__assign(__assign({}, this.props), { enterAfterExit: false }), __assign(__assign({}, this.state), { displayedChildren: targetChildren }))) : {
finishedLeaving: {},
displayedChildren: targetChildren
};
this.setState(newState, onRest);
};
Transition.prototype.shouldComponentUpdate = function (nextProps, nextState) {
return this.state !== nextState;
};
Transition.prototype.render = function () {
return this.state.displayedChildren;
};
Transition.defaultProps = {
flipMove: false,
enterAfterExit: false,
preEnterPose: 'exit',
enterPose: 'enter',
exitPose: 'exit'
};
Transition.getDerivedStateFromProps = handleChildrenTransitions;
return Transition;
}(Component));
var PoseGroup = (function (_super) {
__extends(PoseGroup, _super);
function PoseGroup() {
return _super !== null && _super.apply(this, arguments) || this;
}
PoseGroup.prototype.render = function () {
return createElement(Transition, __assign({}, this.props));
};
PoseGroup.defaultProps = {
flipMove: true
};
return PoseGroup;
}(Component));
export default posed;
export { Transition, PoseGroup };