pose-core
Version:
Factory for Pose animation state machines
465 lines (457 loc) • 20.4 kB
JavaScript
import { __rest, __assign, __spreadArrays } from 'tslib';
import { invariant } from 'hey-listen';
var getPoseValues = function (_a) {
var transition = _a.transition,
flip = _a.flip,
delay = _a.delay,
delayChildren = _a.delayChildren,
staggerChildren = _a.staggerChildren,
staggerDirection = _a.staggerDirection,
afterChildren = _a.afterChildren,
beforeChildren = _a.beforeChildren,
preTransition = _a.preTransition,
applyAtStart = _a.applyAtStart,
applyAtEnd = _a.applyAtEnd,
props = __rest(_a, ["transition", "flip", "delay", "delayChildren", "staggerChildren", "staggerDirection", "afterChildren", "beforeChildren", "preTransition", "applyAtStart", "applyAtEnd"]);
return props;
};
var selectPoses = function (_a) {
var label = _a.label,
props = _a.props,
values = _a.values,
parentValues = _a.parentValues,
ancestorValues = _a.ancestorValues,
onChange = _a.onChange,
passive = _a.passive,
initialPose = _a.initialPose,
poses = __rest(_a, ["label", "props", "values", "parentValues", "ancestorValues", "onChange", "passive", "initialPose"]);
return poses;
};
var selectAllValues = function (values, selectValue) {
var allValues = {};
values.forEach(function (value, key) {
return allValues[key] = selectValue(value);
});
return allValues;
};
var resolveProp = function (target, props) {
return typeof target === 'function' ? target(props) : target;
};
var poseDefault = function (pose, prop, defaultValue, resolveProps) {
return pose && pose[prop] !== undefined ? resolveProp(pose[prop], resolveProps) : defaultValue;
};
var startChildAnimations = function (children, next, pose, props) {
var animations = [];
var delay = poseDefault(pose, 'delayChildren', 0, props);
var stagger = poseDefault(pose, 'staggerChildren', 0, props);
var staggerDirection = poseDefault(pose, 'staggerDirection', 1, props);
var maxStaggerDuration = (children.size - 1) * stagger;
var generateStaggerDuration = staggerDirection === 1 ? function (i) {
return i * stagger;
} : function (i) {
return maxStaggerDuration - i * stagger;
};
Array.from(children).forEach(function (child, i) {
animations.push(child.set(next, {
delay: delay + generateStaggerDuration(i)
}));
});
return animations;
};
var resolveTransition = function (transition, key, value, props, convertTransitionDefinition, getInstantTransition) {
var resolvedTransition;
if (typeof transition === 'function') {
var resolvedTransitionMap = transition(props);
resolvedTransition = resolveTransition(resolvedTransitionMap, key, value, props, convertTransitionDefinition, getInstantTransition);
} else if (transition[key] || transition.default) {
var keyTransition = transition[key] || transition.default;
if (typeof keyTransition === 'function') {
resolvedTransition = keyTransition(props);
} else {
resolvedTransition = keyTransition;
}
} else {
resolvedTransition = transition;
}
return resolvedTransition === false ? getInstantTransition(value, props) : convertTransitionDefinition(value, resolvedTransition, props);
};
var findInsertionIndex = function (poseList, priorityList, priorityIndex) {
var insertionIndex = 0;
for (var i = priorityIndex - 1; i >= 0; i--) {
var nextHighestPriorityIndex = poseList.indexOf(priorityList[i]);
if (nextHighestPriorityIndex !== -1) {
insertionIndex = nextHighestPriorityIndex + 1;
break;
}
}
return insertionIndex;
};
var applyValues = function (toApply, values, props, setValue, setValueNative) {
invariant(typeof toApply === 'object', 'applyAtStart and applyAtEnd must be of type object');
return Object.keys(toApply).forEach(function (key) {
var valueToSet = resolveProp(toApply[key], props);
values.has(key) ? setValue(values.get(key), valueToSet) : setValueNative(key, valueToSet, props);
});
};
var createPoseSetter = function (setterProps) {
var state = setterProps.state,
poses = setterProps.poses,
startAction = setterProps.startAction,
stopAction = setterProps.stopAction,
getInstantTransition = setterProps.getInstantTransition,
addActionDelay = setterProps.addActionDelay,
getTransitionProps = setterProps.getTransitionProps,
resolveTarget = setterProps.resolveTarget,
transformPose = setterProps.transformPose,
posePriority = setterProps.posePriority,
convertTransitionDefinition = setterProps.convertTransitionDefinition,
setValue = setterProps.setValue,
setValueNative = setterProps.setValueNative,
forceRender = setterProps.forceRender;
return function (next, nextProps, propagate) {
if (nextProps === void 0) {
nextProps = {};
}
if (propagate === void 0) {
propagate = true;
}
var children = state.children,
values = state.values,
props = state.props,
activeActions = state.activeActions,
activePoses = state.activePoses;
var _a = nextProps.delay,
delay = _a === void 0 ? 0 : _a;
var hasChildren = children.size;
var baseTransitionProps = __assign(__assign({}, props), nextProps);
var nextPose = poses[next];
var getChildAnimations = function () {
return hasChildren && propagate ? startChildAnimations(children, next, nextPose, baseTransitionProps) : [];
};
var getParentAnimations = function () {
if (!nextPose) return [];
var applyAtStart = nextPose.applyAtStart;
if (applyAtStart) {
applyValues(applyAtStart, values, baseTransitionProps, setValue, setValueNative);
if (forceRender) forceRender(baseTransitionProps);
}
if (transformPose) nextPose = transformPose(nextPose, next, state);
var preTransition = nextPose.preTransition,
getTransition = nextPose.transition,
applyAtEnd = nextPose.applyAtEnd;
if (preTransition) preTransition(baseTransitionProps);
var animations = Object.keys(getPoseValues(nextPose)).map(function (key) {
var valuePoses = activePoses.has(key) ? activePoses.get(key) : (activePoses.set(key, []), activePoses.get(key));
var existingIndex = valuePoses.indexOf(next);
if (existingIndex !== -1) valuePoses.splice(existingIndex, 1);
var priority = posePriority ? posePriority.indexOf(next) : 0;
var insertionIndex = priority <= 0 ? 0 : findInsertionIndex(valuePoses, posePriority, priority);
valuePoses.splice(insertionIndex, 0, next);
return insertionIndex === 0 ? new Promise(function (complete) {
var value = values.get(key);
var transitionProps = __assign(__assign({}, baseTransitionProps), { key: key,
value: value });
var target = resolveTarget(value, resolveProp(nextPose[key], transitionProps));
if (activeActions.has(key)) stopAction(activeActions.get(key));
var resolveTransitionProps = __assign(__assign({ to: target }, transitionProps), getTransitionProps(value, target, transitionProps));
var transition = resolveTransition(getTransition, key, value, resolveTransitionProps, convertTransitionDefinition, getInstantTransition);
var poseDelay = delay || resolveProp(nextPose.delay, transitionProps);
if (poseDelay) {
transition = addActionDelay(poseDelay, transition);
}
activeActions.set(key, startAction(value, transition, complete));
}) : Promise.resolve();
});
return applyAtEnd ? [Promise.all(animations).then(function () {
applyValues(applyAtEnd, values, baseTransitionProps, setValue, setValueNative);
})] : animations;
};
if (nextPose && hasChildren) {
if (resolveProp(nextPose.beforeChildren, baseTransitionProps)) {
return Promise.all(getParentAnimations()).then(function () {
return Promise.all(getChildAnimations());
});
} else if (resolveProp(nextPose.afterChildren, baseTransitionProps)) {
return Promise.all(getChildAnimations()).then(function () {
return Promise.all(getParentAnimations());
});
}
}
return Promise.all(__spreadArrays(getParentAnimations(), getChildAnimations()));
};
};
var DEFAULT_INITIAL_POSE = 'init';
var isScale = function (key) {
return key.includes('scale');
};
var defaultReadValueFromSource = function (key) {
return isScale(key) ? 1 : 0;
};
var readValueFromPose = function (pose, key, props) {
var valueToResolve = pose.applyAtEnd && pose.applyAtEnd[key] !== undefined ? pose.applyAtEnd[key] : pose[key] !== undefined ? pose[key] : pose.applyAtStart && pose.applyAtStart[key] !== undefined ? pose.applyAtStart[key] : 0;
return resolveProp(valueToResolve, props);
};
var getPosesToSearch = function (pose) {
var posesToSearch = Array.isArray(pose) ? pose : [pose];
posesToSearch.push(DEFAULT_INITIAL_POSE);
return posesToSearch;
};
var getInitialValue = function (poses, key, initialPose, props, readValueFromSource, activePoses) {
if (readValueFromSource === void 0) {
readValueFromSource = defaultReadValueFromSource;
}
var posesToSearch = getPosesToSearch(initialPose);
var pose = posesToSearch.filter(Boolean).find(function (name) {
var thisPose = poses[name];
return thisPose && (thisPose[key] !== undefined || thisPose.applyAtStart && thisPose.applyAtStart[key] !== undefined || thisPose.applyAtEnd && thisPose.applyAtEnd[key] !== undefined);
});
activePoses.set(key, [pose || DEFAULT_INITIAL_POSE]);
return pose ? readValueFromPose(poses[pose], key, props) : readValueFromSource(key, props);
};
var createValues = function (values, _a) {
var userSetValues = _a.userSetValues,
createValue = _a.createValue,
convertValue = _a.convertValue,
readValueFromSource = _a.readValueFromSource,
initialPose = _a.initialPose,
poses = _a.poses,
activePoses = _a.activePoses,
props = _a.props;
return function (key) {
if (values.has(key)) return;
var value;
if (userSetValues && userSetValues[key] !== undefined) {
value = convertValue(userSetValues[key], key, props);
} else {
var initValue = getInitialValue(poses, key, initialPose, props, readValueFromSource, activePoses);
value = createValue(initValue, key, props);
}
values.set(key, value);
};
};
var scrapeValuesFromPose = function (values, props) {
return function (key) {
var pose = props.poses[key];
Object.keys(getPoseValues(pose)).forEach(createValues(values, props));
};
};
var getAncestorValue = function (key, fromParent, ancestors) {
if (fromParent === true) {
return ancestors[0] && ancestors[0].values.get(key);
} else {
var foundAncestor = ancestors.find(function (_a) {
var label = _a.label;
return label === fromParent;
});
return foundAncestor && foundAncestor.values.get(key);
}
};
var bindPassiveValues = function (values, _a) {
var passive = _a.passive,
ancestorValues = _a.ancestorValues,
createValue = _a.createValue,
readValue = _a.readValue,
props = _a.props;
return function (key) {
var _a = passive[key],
valueKey = _a[0],
passiveProps = _a[1],
fromParent = _a[2];
var valueToBind = fromParent && ancestorValues.length ? getAncestorValue(valueKey, fromParent, ancestorValues) : values.has(valueKey) ? values.get(valueKey) : false;
if (!valueToBind) return;
var newValue = createValue(readValue(valueToBind), key, props, {
passiveParentKey: valueKey,
passiveParent: valueToBind,
passiveProps: passiveProps
});
values.set(key, newValue);
};
};
var setNativeValues = function (_a) {
var setValueNative = _a.setValueNative,
initialPose = _a.initialPose,
props = _a.props,
poses = _a.poses;
var valuesHaveSet = new Set();
var setValues = function (pose, propKey) {
if (pose[propKey]) {
for (var key in pose[propKey]) {
if (!valuesHaveSet.has(key)) {
valuesHaveSet.add(key);
setValueNative(key, resolveProp(pose[propKey][key], props), props);
}
}
}
};
getPosesToSearch(initialPose).forEach(function (poseKey) {
var pose = poses[poseKey];
if (pose) {
setValues(pose, 'applyAtEnd');
setValues(pose, 'applyAtStart');
}
});
};
var createValueMap = function (props) {
var poses = props.poses,
passive = props.passive;
var values = new Map();
Object.keys(poses).forEach(scrapeValuesFromPose(values, props));
setNativeValues(props);
if (passive) Object.keys(passive).forEach(bindPassiveValues(values, props));
return values;
};
var applyDefaultTransition = function (pose, key, defaultTransitions) {
return __assign(__assign({}, pose), { transition: defaultTransitions.has(key) ? defaultTransitions.get(key) : defaultTransitions.get('default') });
};
var generateTransitions = function (poses, defaultTransitions) {
Object.keys(poses).forEach(function (key) {
var pose = poses[key];
invariant(typeof pose === 'object', "Pose '" + key + "' is of invalid type. All poses should be objects.");
poses[key] = pose.transition !== undefined ? pose : applyDefaultTransition(pose, key, defaultTransitions);
});
return poses;
};
var sortByReversePriority = function (priorityOrder) {
return function (a, b) {
var aP = priorityOrder.indexOf(a);
var bP = priorityOrder.indexOf(b);
if (aP === -1 && bP !== -1) return -1;
if (aP !== -1 && bP === -1) return 1;
return aP - bP;
};
};
var poseFactory = function (_a) {
var getDefaultProps = _a.getDefaultProps,
defaultTransitions = _a.defaultTransitions,
bindOnChange = _a.bindOnChange,
startAction = _a.startAction,
stopAction = _a.stopAction,
readValue = _a.readValue,
readValueFromSource = _a.readValueFromSource,
resolveTarget = _a.resolveTarget,
setValue = _a.setValue,
setValueNative = _a.setValueNative,
createValue = _a.createValue,
convertValue = _a.convertValue,
getInstantTransition = _a.getInstantTransition,
getTransitionProps = _a.getTransitionProps,
addActionDelay = _a.addActionDelay,
selectValueToRead = _a.selectValueToRead,
convertTransitionDefinition = _a.convertTransitionDefinition,
transformPose = _a.transformPose,
posePriority = _a.posePriority,
forceRender = _a.forceRender,
extendAPI = _a.extendAPI;
return function (config) {
var parentValues = config.parentValues,
_a = config.ancestorValues,
ancestorValues = _a === void 0 ? [] : _a;
if (parentValues) ancestorValues.unshift({ values: parentValues });
var activeActions = new Map();
var activePoses = new Map();
var children = new Set();
var poses = generateTransitions(selectPoses(config), defaultTransitions);
var _b = config.props,
props = _b === void 0 ? {} : _b;
if (getDefaultProps) props = __assign(__assign({}, getDefaultProps(config)), props);
var passive = config.passive,
userSetValues = config.values,
_c = config.initialPose,
initialPose = _c === void 0 ? DEFAULT_INITIAL_POSE : _c;
var values = createValueMap({
poses: poses,
passive: passive,
ancestorValues: ancestorValues,
readValue: readValue,
setValueNative: setValueNative,
createValue: createValue,
convertValue: convertValue,
readValueFromSource: readValueFromSource,
userSetValues: userSetValues,
initialPose: initialPose,
activePoses: activePoses,
props: props
});
var state = {
activeActions: activeActions,
activePoses: activePoses,
children: children,
props: props,
values: values
};
var onChange = config.onChange;
if (onChange) Object.keys(onChange).forEach(bindOnChange(values, onChange));
var set = createPoseSetter({
state: state,
poses: poses,
getInstantTransition: getInstantTransition,
getTransitionProps: getTransitionProps,
convertTransitionDefinition: convertTransitionDefinition,
setValue: setValue,
setValueNative: setValueNative,
startAction: startAction,
stopAction: stopAction,
resolveTarget: resolveTarget,
addActionDelay: addActionDelay,
transformPose: transformPose,
posePriority: posePriority,
forceRender: forceRender
});
var has = function (poseName) {
return !!poses[poseName];
};
var api = {
set: set,
unset: function (poseName, poseProps) {
var posesToSet = [];
activePoses.forEach(function (valuePoses) {
var poseIndex = valuePoses.indexOf(poseName);
if (poseIndex === -1) return;
var currentPose = valuePoses[0];
valuePoses.splice(poseIndex, 1);
var nextPose = valuePoses[0];
if (nextPose === currentPose) return;
if (posesToSet.indexOf(nextPose) === -1) {
posesToSet.push(nextPose);
}
});
var animationsToResolve = posesToSet.sort(sortByReversePriority(posePriority)).map(function (poseToSet) {
return set(poseToSet, poseProps, false);
});
children.forEach(function (child) {
return animationsToResolve.push(child.unset(poseName));
});
return Promise.all(animationsToResolve);
},
get: function (valueName) {
return valueName ? selectValueToRead(values.get(valueName)) : selectAllValues(values, selectValueToRead);
},
has: has,
setProps: function (newProps) {
return state.props = __assign(__assign({}, state.props), newProps);
},
_addChild: function (childConfig, factory) {
var child = factory(__assign(__assign({ initialPose: initialPose }, childConfig), { ancestorValues: __spreadArrays([{ label: config.label, values: values }], ancestorValues) }));
children.add(child);
return child;
},
removeChild: function (child) {
return children.delete(child);
},
clearChildren: function () {
children.forEach(function (child) {
return child.destroy();
});
children.clear();
},
destroy: function () {
activeActions.forEach(stopAction);
children.forEach(function (child) {
return child.destroy();
});
}
};
return extendAPI(api, state, config);
};
};
export default poseFactory;