framer-motion
Version:
<p align="center"> <img src="https://user-images.githubusercontent.com/38039349/60953119-d3c6f300-a2fc-11e9-9596-4978e5d52180.png" width="176" height="170" alt="Framer Motion" /> </p>
1,293 lines (1,271 loc) • 177 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var tslib_1 = require('tslib');
var React = require('react');
var sync = require('framesync');
var sync__default = _interopDefault(sync);
var popcorn = require('@popmotion/popcorn');
var styler = require('stylefire');
var styler__default = _interopDefault(styler);
var heyListen = require('hey-listen');
var styleValueTypes = require('style-value-types');
var popmotion = require('popmotion');
var easingLookup = require('@popmotion/easing');
/**
* Uses the ref that is passed in, or creates a new one
* @param external - External ref
* @internal
*/
function useExternalRef(external) {
// We're conditionally calling `useRef` here which is sort of naughty as hooks
// shouldn't be called conditionally. However, Framer Motion will break if this
// condition changes anyway. It might be possible to use an invariant here to
// make it explicit, but I expect changing `ref` is not normal behaviour.
var ref = !external || typeof external === "function" ? React.useRef(null) : external;
React.useEffect(function () {
if (external && typeof external === "function") {
external(ref.current);
return function () { return external(null); };
}
}, []);
return ref;
}
var isFloat = function (value) {
return !isNaN(parseFloat(value));
};
/**
* `MotionValue` is used to track the state and velocity of motion values.
*
* @public
*/
var MotionValue = /** @class */ (function () {
/**
* @param init - The initiating value
* @param config - Optional configuration options
*
* - `transformer`: A function to transform incoming values with.
*
* @internal
*/
function MotionValue(init, _a) {
var _this = this;
var _b = _a === void 0 ? {} : _a, transformer = _b.transformer, parent = _b.parent;
/**
* Duration, in milliseconds, since last updating frame.
*
* @internal
*/
this.timeDelta = 0;
/**
* Timestamp of the last time this `MotionValue` was updated.
*
* @internal
*/
this.lastUpdated = 0;
/**
* Tracks whether this value can output a velocity. Currently this is only true
* if the value is numerical, but we might be able to widen the scope here and support
* other value types.
*
* @internal
*/
this.canTrackVelocity = false;
this.updateAndNotify = function (v, render) {
if (render === void 0) { render = true; }
_this.prev = _this.current;
_this.current = _this.transformer ? _this.transformer(v) : v;
if (_this.updateSubscribers && _this.prev !== _this.current) {
_this.updateSubscribers.forEach(_this.notifySubscriber);
}
if (_this.children) {
_this.children.forEach(_this.setChild);
}
if (render && _this.renderSubscribers) {
_this.renderSubscribers.forEach(_this.notifySubscriber);
}
// Update timestamp
var _a = sync.getFrameData(), delta = _a.delta, timestamp = _a.timestamp;
if (_this.lastUpdated !== timestamp) {
_this.timeDelta = delta;
_this.lastUpdated = timestamp;
sync__default.postRender(_this.scheduleVelocityCheck);
}
};
/**
* Notify a subscriber with the latest value.
*
* This is an instanced and bound function to prevent generating a new
* function once per frame.
*
* @param subscriber - The subscriber to notify.
*
* @internal
*/
this.notifySubscriber = function (subscriber) {
subscriber(_this.current);
};
/**
* Schedule a velocity check for the next frame.
*
* This is an instanced and bound function to prevent generating a new
* function once per frame.
*
* @internal
*/
this.scheduleVelocityCheck = function () { return sync__default.postRender(_this.velocityCheck); };
/**
* Updates `prev` with `current` if the value hasn't been updated this frame.
* This ensures velocity calculations return `0`.
*
* This is an instanced and bound function to prevent generating a new
* function once per frame.
*
* @internal
*/
this.velocityCheck = function (_a) {
var timestamp = _a.timestamp;
if (timestamp !== _this.lastUpdated) {
_this.prev = _this.current;
}
};
/**
* Updates child `MotionValue`.
*
* @param child - Child `MotionValue`.
*
* @internal
*/
this.setChild = function (child) { return child.set(_this.current); };
this.parent = parent;
this.transformer = transformer;
this.set(init, false);
this.canTrackVelocity = isFloat(this.current);
}
/**
* Creates a new `MotionValue` that's subscribed to the output of this one.
*
* @param config - Optional configuration options
*
* - `transformer`: A function to transform incoming values with.
*
* @internal
*/
MotionValue.prototype.addChild = function (config) {
if (config === void 0) { config = {}; }
var child = new MotionValue(this.current, tslib_1.__assign({ parent: this }, config));
if (!this.children)
this.children = new Set();
this.children.add(child);
return child;
};
/**
* Stops a `MotionValue` from being subscribed to this one.
*
* @param child - The subscribed `MotionValue`
*
* @internal
*/
MotionValue.prototype.removeChild = function (child) {
if (!this.children) {
return;
}
this.children.delete(child);
};
/**
* Subscribes a subscriber function to a subscription list.
*
* @param subscriptions - A `Set` of subscribers.
* @param subscription - A subscriber function.
*/
MotionValue.prototype.subscribeTo = function (subscriptions, subscription) {
var _this = this;
var updateSubscriber = function () { return subscription(_this.current); };
subscriptions.add(updateSubscriber);
return function () { return subscriptions.delete(updateSubscriber); };
};
/**
* Adds a function that will be notified when the `MotionValue` is updated.
*
* It returns a function that, when called, will cancel the subscription.
*
* When calling `onChange` inside a React component, it should be wrapped with the
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
* from the `useEffect` function to ensure you don't add duplicate subscribers..
*
* @library
*
* ```jsx
* function MyComponent() {
* const x = useMotionValue(0)
* const y = useMotionValue(0)
* const opacity = useMotionValue(1)
*
* useEffect(() => {
* function updateOpacity() {
* const maxXY = Math.max(x.get(), y.get())
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
* opacity.set(newOpacity)
* }
*
* const unsubscribeX = x.onChange(updateOpacity)
* const unsubscribeY = y.onChange(updateOpacity)
*
* return () => {
* unsubscribeX()
* unsubscribeY()
* }
* }, [])
*
* return <Frame x={x} />
* }
* ```
*
* @motion
*
* ```jsx
* export const MyComponent = () => {
* const x = useMotionValue(0)
* const y = useMotionValue(0)
* const opacity = useMotionValue(1)
*
* useEffect(() => {
* function updateOpacity() {
* const maxXY = Math.max(x.get(), y.get())
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
* opacity.set(newOpacity)
* }
*
* const unsubscribeX = x.onChange(updateOpacity)
* const unsubscribeY = y.onChange(updateOpacity)
*
* return () => {
* unsubscribeX()
* unsubscribeY()
* }
* }, [])
*
* return <motion.div style={{ x }} />
* }
* ```
*
* @internalremarks
*
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
*
* ```jsx
* useOnChange(x, () => {})
* ```
*
* @param subscriber - A function that receives the latest value.
* @returns A function that, when called, will cancel this subscription.
*
* @public
*/
MotionValue.prototype.onChange = function (subscription) {
if (!this.updateSubscribers)
this.updateSubscribers = new Set();
return this.subscribeTo(this.updateSubscribers, subscription);
};
/**
* Adds a function that will be notified when the `MotionValue` requests a render.
*
* @param subscriber - A function that's provided the latest value.
* @returns A function that, when called, will cancel this subscription.
*
* @internal
*/
MotionValue.prototype.onRenderRequest = function (subscription) {
if (!this.renderSubscribers)
this.renderSubscribers = new Set();
// Render immediately
this.notifySubscriber(subscription);
return this.subscribeTo(this.renderSubscribers, subscription);
};
/**
* Attaches a passive effect to the `MotionValue`.
*
* @internal
*/
MotionValue.prototype.attach = function (passiveEffect) {
this.passiveEffect = passiveEffect;
};
/**
* Sets the state of the `MotionValue`.
*
* @remarks
*
* ```jsx
* const x = useMotionValue(0)
* x.set(10)
* ```
*
* @param latest - Latest value to set.
* @param render - Whether to notify render subscribers. Defaults to `true`
*
* @public
*/
MotionValue.prototype.set = function (v, render) {
if (render === void 0) { render = true; }
if (!render || !this.passiveEffect) {
this.updateAndNotify(v, render);
}
else {
this.passiveEffect(v, this.updateAndNotify);
}
};
/**
* Returns the latest state of `MotionValue`
*
* @returns - The latest state of `MotionValue`
*
* @public
*/
MotionValue.prototype.get = function () {
return this.current;
};
/**
* Returns the latest velocity of `MotionValue`
*
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
*
* @public
*/
MotionValue.prototype.getVelocity = function () {
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
return this.canTrackVelocity
? // These casts could be avoided if parseFloat would be typed better
popcorn.velocityPerSecond(parseFloat(this.current) -
parseFloat(this.prev), this.timeDelta)
: 0;
};
/**
* Registers a new animation to control this `MotionValue`. Only one
* animation can drive a `MotionValue` at one time.
*
* ```jsx
* value.start()
* ```
*
* @param animation - A function that starts the provided animation
*
* @internal
*/
MotionValue.prototype.start = function (animation) {
var _this = this;
this.stop();
return new Promise(function (resolve) {
_this.stopAnimation = animation(resolve);
}).then(function () { return _this.clearAnimation(); });
};
/**
* Stop the currently active animation.
*
* @public
*/
MotionValue.prototype.stop = function () {
if (this.stopAnimation)
this.stopAnimation();
this.clearAnimation();
};
/**
* Returns `true` if this value is currently animating.
*
* @public
*/
MotionValue.prototype.isAnimating = function () {
return !!this.stopAnimation;
};
MotionValue.prototype.clearAnimation = function () {
this.stopAnimation = null;
};
/**
* Destroy and clean up subscribers to this `MotionValue`.
*
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
* created a `MotionValue` via the `motionValue` function.
*
* @public
*/
MotionValue.prototype.destroy = function () {
this.updateSubscribers && this.updateSubscribers.clear();
this.renderSubscribers && this.renderSubscribers.clear();
this.parent && this.parent.removeChild(this);
this.stop();
};
return MotionValue;
}());
/**
* @internal
*/
function motionValue(init, opts) {
return new MotionValue(init, opts);
}
/**
* Creates a constant value over the lifecycle of a component.
*
* Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
* a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
* you can ensure that initialisers don't execute twice or more.
*/
function useConstant(init) {
var ref = React.useRef(null);
if (ref.current === null) {
ref.current = init();
}
return ref.current;
}
var isMotionValue = function (value) {
return value instanceof MotionValue;
};
var session = null;
var syncRenderSession = {
isOpen: function () { return session !== null; },
open: function () {
heyListen.invariant(!session, "Sync render session already open");
session = [];
},
flush: function () {
heyListen.invariant(session !== null, "No sync render session found");
session && session.forEach(function (styler) { return styler.render(); });
session = null;
},
push: function (styler) {
heyListen.invariant(session !== null, "No sync render session found");
session && session.push(styler);
},
};
// Creating a styler factory for the `onUpdate` prop allows all values
// to fire and the `onUpdate` prop will only fire once per frame
var updateStyler = styler.createStylerFactory({
onRead: function () { return null; },
onRender: function (state, _a) {
var onUpdate = _a.onUpdate;
return onUpdate(state);
},
});
var MotionValuesMap = /** @class */ (function () {
function MotionValuesMap() {
this.hasMounted = false;
this.values = new Map();
this.unsubscribers = new Map();
}
MotionValuesMap.prototype.has = function (key) {
return this.values.has(key);
};
MotionValuesMap.prototype.set = function (key, value) {
this.values.set(key, value);
if (this.hasMounted) {
this.bindValueToOutput(key, value);
}
};
MotionValuesMap.prototype.get = function (key, defaultValue) {
var value = this.values.get(key);
if (value === undefined && defaultValue !== undefined) {
value = new MotionValue(defaultValue);
this.set(key, value);
}
return value;
};
MotionValuesMap.prototype.forEach = function (callback) {
return this.values.forEach(callback);
};
MotionValuesMap.prototype.bindValueToOutput = function (key, value) {
var _this = this;
var onRender = function (v) { return _this.output && _this.output(key, v); };
var unsubscribeOnRender = value.onRenderRequest(onRender);
var onChange = function (v) {
_this.onUpdate && _this.onUpdate.set(key, v);
};
var unsubscribeOnChange = value.onChange(onChange);
if (this.unsubscribers.has(key)) {
this.unsubscribers.get(key)();
}
this.unsubscribers.set(key, function () {
unsubscribeOnRender();
unsubscribeOnChange();
});
};
MotionValuesMap.prototype.setOnUpdate = function (onUpdate) {
this.onUpdate = undefined;
if (onUpdate) {
this.onUpdate = updateStyler({ onUpdate: onUpdate });
}
};
MotionValuesMap.prototype.setTransformTemplate = function (transformTemplate) {
if (this.transformTemplate !== transformTemplate) {
this.transformTemplate = transformTemplate;
this.updateTransformTemplate();
}
};
MotionValuesMap.prototype.getTransformTemplate = function () {
return this.transformTemplate;
};
MotionValuesMap.prototype.updateTransformTemplate = function () {
if (this.output) {
this.output("transform", this.transformTemplate);
}
};
MotionValuesMap.prototype.mount = function (output) {
var _this = this;
this.hasMounted = true;
if (output)
this.output = output;
this.values.forEach(function (value, key) { return _this.bindValueToOutput(key, value); });
this.updateTransformTemplate();
};
MotionValuesMap.prototype.unmount = function () {
var _this = this;
this.values.forEach(function (_value, key) {
var unsubscribe = _this.unsubscribers.get(key);
unsubscribe && unsubscribe();
});
};
return MotionValuesMap;
}());
var specialMotionValueProps = new Set(["dragOriginX", "dragOriginY"]);
var useMotionValues = function (props) {
var motionValues = useConstant(function () {
var map = new MotionValuesMap();
/**
* Loop through every prop and add any detected `MotionValue`s. This is SVG-specific
* code that should be extracted, perhaps considered hollistically with `useMotionStyles`.
*
* <motion.circle cx={motionValue(0)} />
*/
for (var key in props) {
if (isMotionValue(props[key]) &&
!specialMotionValueProps.has(key)) {
map.set(key, props[key]);
}
}
return map;
});
motionValues.setOnUpdate(props.onUpdate);
motionValues.setTransformTemplate(props.transformTemplate);
return motionValues;
};
/**
* `useEffect` gets resolved bottom-up. We defer some optional functionality to child
* components, so to ensure everything runs correctly we export the ref-binding logic
* to a new component rather than in `useMotionValues`.
*/
var MountMotionValuesComponent = function (_a, ref) {
var values = _a.values, isStatic = _a.isStatic;
React.useEffect(function () {
heyListen.invariant(ref.current instanceof Element, "No `ref` found. Ensure components created with `motion.custom` forward refs using `React.forwardRef`");
var domStyler = styler__default(ref.current, {
preparseOutput: false,
enableHardwareAcceleration: !isStatic,
});
values.mount(function (key, value) {
domStyler.set(key, value);
if (syncRenderSession.isOpen()) {
syncRenderSession.push(domStyler);
}
});
return function () { return values.unmount(); };
}, []);
return null;
};
var MountMotionValues = React.memo(React.forwardRef(MountMotionValuesComponent));
var createValueResolver = function (resolver) { return function (values) {
var resolvedValues = {};
values.forEach(function (value, key) { return (resolvedValues[key] = resolver(value)); });
return resolvedValues;
}; };
var resolveCurrent = createValueResolver(function (value) { return value.get(); });
var transformOriginProps = new Set(["originX", "originY", "originZ"]);
var isTransformOriginProp = function (key) { return transformOriginProps.has(key); };
var buildStyleAttr = function (values, styleProp, isStatic) {
var motionValueStyles = resolveCurrent(values);
var transformTemplate = values.getTransformTemplate();
if (transformTemplate) {
// If `transform` has been manually set as a string, pass that through the template
// otherwise pass it forward to Stylefire's style property builder
motionValueStyles.transform = styleProp.transform
? transformTemplate({}, styleProp.transform)
: transformTemplate;
}
return styler.buildStyleProperty(tslib_1.__assign({}, styleProp, motionValueStyles), !isStatic);
};
var useMotionStyles = function (values, styleProp, isStatic, transformValues) {
if (styleProp === void 0) { styleProp = {}; }
var style = {};
var prevMotionStyles = React.useRef({}).current;
for (var key in styleProp) {
var thisStyle = styleProp[key];
if (isMotionValue(thisStyle)) {
// If this is a motion value, add it to our MotionValuesMap
values.set(key, thisStyle);
}
else if (!isStatic &&
(styler.isTransformProp(key) || isTransformOriginProp(key))) {
// Or if it's a transform prop, create a motion value (or update an existing one)
// to ensure Stylefire can reconcile all the transform values together.
// A further iteration on this would be to create a single styler per component that gets
// used in the DOM renderer's buildStyleAttr *and* animations, then we would only
// have to convert animating values to `MotionValues` (we could probably remove this entire function).
// The only architectural consideration is to allow Stylefire to have elements mounted after
// a styler is created.
if (!values.has(key)) {
// If it doesn't exist as a motion value, create it
values.set(key, motionValue(thisStyle));
}
else {
// Otherwise only update it if it's changed from a previous render
if (thisStyle !== prevMotionStyles[key]) {
var value = values.get(key);
value.set(thisStyle);
}
}
prevMotionStyles[key] = thisStyle;
}
else {
style[key] = thisStyle;
}
}
return transformValues ? transformValues(style) : style;
};
var isKeyframesTarget = function (v) {
return Array.isArray(v);
};
var isCustomValue = function (v) {
return Boolean(v && typeof v === "object" && v.mix && v.toValue);
};
var resolveFinalValueInKeyframes = function (v) {
// TODO maybe throw if v.length - 1 is placeholder token?
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
};
var auto = {
test: function (v) { return v === "auto"; },
parse: function (v) { return v; },
};
var dimensionTypes = [styleValueTypes.number, styleValueTypes.px, styleValueTypes.percent, styleValueTypes.degrees, styleValueTypes.vw, styleValueTypes.vh, auto];
var valueTypes = dimensionTypes.concat([styleValueTypes.color, styleValueTypes.complex]);
var testValueType = function (v) { return function (type) { return type.test(v); }; };
var getDimensionValueType = function (v) {
return dimensionTypes.find(testValueType(v));
};
var getValueType = function (v) { return valueTypes.find(testValueType(v)); };
var underDampedSpring = function () { return ({
type: "spring",
stiffness: 500,
damping: 25,
restDelta: 0.5,
restSpeed: 10,
}); };
var overDampedSpring = function (to) { return ({
type: "spring",
stiffness: 700,
damping: to === 0 ? 100 : 35,
}); };
var linearTween = function () { return ({
ease: "linear",
duration: 0.3,
}); };
var keyframes = function (values) { return ({
type: "keyframes",
duration: 0.8,
values: values,
}); };
var defaultTransitions = {
x: underDampedSpring,
y: underDampedSpring,
z: underDampedSpring,
rotate: underDampedSpring,
rotateX: underDampedSpring,
rotateY: underDampedSpring,
rotateZ: underDampedSpring,
scaleX: overDampedSpring,
scaleY: overDampedSpring,
scale: overDampedSpring,
opacity: linearTween,
backgroundColor: linearTween,
color: linearTween,
default: overDampedSpring,
};
var getDefaultTransition = function (valueKey, to) {
var transitionFactory;
if (isKeyframesTarget(to)) {
transitionFactory = keyframes;
}
else {
transitionFactory =
defaultTransitions[valueKey] || defaultTransitions.default;
}
return tslib_1.__assign({ to: to }, transitionFactory(to));
};
/**
* A Popmotion action that accepts a single `to` prop. When it starts, it immediately
* updates with `to` and then completes. By using this we can compose instant transitions
* in with the same logic that applies `delay` or returns a `Promise` etc.
*
* Accepting `duration` is a little bit of a hack that simply defers the completetion of
* the animation until after the duration finishes. This is for situations when you're **only**
* animating non-animatable values and then setting something on `transitionEnd`. Really
* you want this to fire after the "animation" finishes, rather than instantly.
*
* ```
* animate={{
* display: 'block',
* transitionEnd: { display: 'none' }
* }}
* ```
*/
var just = function (_a) {
var to = _a.to, duration = _a.duration;
return popmotion.action(function (_a) {
var update = _a.update, complete = _a.complete;
update(to);
duration ? popmotion.delay(duration).start({ complete: complete }) : complete();
});
};
var easingDefinitionToFunction = function (definition) {
if (Array.isArray(definition)) {
// If cubic bezier definition, create bezier curve
heyListen.invariant(definition.length === 4, "Cubic bezier arrays must contain four numerical values.");
var x1 = definition[0], y1 = definition[1], x2 = definition[2], y2 = definition[3];
return easingLookup.cubicBezier(x1, y1, x2, y2);
}
else if (typeof definition === "string") {
// Else lookup from table
heyListen.invariant(easingLookup[definition] !== undefined, "Invalid easing type '" + definition + "'");
return easingLookup[definition];
}
return definition;
};
var isEasingArray = function (ease) {
return Array.isArray(ease) && typeof ease[0] !== "number";
};
var isDurationAnimation = function (v) {
return v.hasOwnProperty("duration") || v.hasOwnProperty("repeatDelay");
};
/**
* Check if a value is animatable. Examples:
*
* ✅: 100, "100px", "#fff"
* ❌: "block", "url(2.jpg)"
* @param value
*
* @internal
*/
var isAnimatable = function (key, value) {
// If the list of keys tat might be non-animatable grows, replace with Set
if (key === "zIndex")
return false;
// If it's a number or a keyframes array, we can animate it. We might at some point
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
// but for now lets leave it like this for performance reasons
if (typeof value === "number" || Array.isArray(value))
return true;
if (typeof value === "string" && // It's animatable if we have a string
styleValueTypes.complex.test(value) && // And it contains numbers and/or colors
!value.startsWith("url(") // Unless it starts with "url("
) {
return true;
}
return false;
};
/**
* Converts seconds to milliseconds
*
* @param seconds - Time in seconds.
* @return milliseconds - Converted time in milliseconds.
*/
var secondsToMilliseconds = function (seconds) { return seconds * 1000; };
var transitions = { tween: popmotion.tween, spring: popmotion.spring, keyframes: popmotion.keyframes, inertia: popmotion.inertia, just: just };
var transitionOptionParser = {
tween: function (opts) {
if (opts.ease) {
var ease = isEasingArray(opts.ease) ? opts.ease[0] : opts.ease;
opts.ease = easingDefinitionToFunction(ease);
}
return opts;
},
keyframes: function (_a) {
var from = _a.from, to = _a.to, velocity = _a.velocity, opts = tslib_1.__rest(_a, ["from", "to", "velocity"]);
if (opts.values && opts.values[0] === null) {
var values = opts.values.slice();
values[0] = from;
opts.values = values;
}
if (opts.ease) {
opts.easings = isEasingArray(opts.ease)
? opts.ease.map(easingDefinitionToFunction)
: easingDefinitionToFunction(opts.ease);
}
opts.ease = easingLookup.linear;
return opts;
},
};
var isTransitionDefined = function (_a) {
var when = _a.when, delay = _a.delay, delayChildren = _a.delayChildren, staggerChildren = _a.staggerChildren, staggerDirection = _a.staggerDirection, transition = tslib_1.__rest(_a, ["when", "delay", "delayChildren", "staggerChildren", "staggerDirection"]);
return Object.keys(transition).length;
};
var getTransitionDefinition = function (key, to, transitionDefinition) {
var delay = transitionDefinition ? transitionDefinition.delay : 0;
// If no object, return default transition
// A better way to handle this would be to deconstruct out all the shared Orchestration props
// and see if there's any props remaining
if (transitionDefinition === undefined ||
!isTransitionDefined(transitionDefinition)) {
return tslib_1.__assign({ delay: delay }, getDefaultTransition(key, to));
}
var valueTransitionDefinition = transitionDefinition[key] ||
transitionDefinition.default ||
transitionDefinition;
if (valueTransitionDefinition.type === false) {
return {
delay: valueTransitionDefinition.hasOwnProperty("delay")
? valueTransitionDefinition.delay
: delay,
to: isKeyframesTarget(to)
? to[to.length - 1]
: to,
type: "just",
};
}
else if (isKeyframesTarget(to)) {
return tslib_1.__assign({ values: to, duration: 0.8, delay: delay, ease: "linear" }, valueTransitionDefinition, {
// This animation must be keyframes if we're animating through an array
type: "keyframes" });
}
else {
return tslib_1.__assign({ type: "tween", to: to,
delay: delay }, valueTransitionDefinition);
}
};
var preprocessOptions = function (type, opts) {
return transitionOptionParser[type]
? transitionOptionParser[type](opts)
: opts;
};
var getAnimation = function (key, value, target, transition) {
var origin = value.get();
var isOriginAnimatable = isAnimatable(key, origin);
var isTargetAnimatable = isAnimatable(key, target);
// TODO we could probably improve this check to ensure both values are of the same type -
// for instance 100 to #fff. This might live better in Popmotion.
heyListen.warning(isOriginAnimatable === isTargetAnimatable, "You are trying to animate " + key + " from \"" + origin + "\" to " + target + ". \"" + origin + "\" is not an animatable value - to enable this animation set " + origin + " to a value animatable to " + target + " via the `style` property.");
// Parse the `transition` prop and return options for the Popmotion animation
var _a = getTransitionDefinition(key, target, transition), _b = _a.type, type = _b === void 0 ? "tween" : _b, transitionDefinition = tslib_1.__rest(_a, ["type"]);
// If this is an animatable pair of values, return an animation, otherwise use `just`
var actionFactory = isOriginAnimatable && isTargetAnimatable
? transitions[type]
: just;
var opts = preprocessOptions(type, tslib_1.__assign({ from: origin, velocity: value.getVelocity() }, transitionDefinition));
// Convert duration from Framer Motion's seconds into Popmotion's milliseconds
if (isDurationAnimation(opts)) {
if (opts.duration) {
opts.duration = secondsToMilliseconds(opts.duration);
}
if (opts.repeatDelay) {
opts.repeatDelay = secondsToMilliseconds(opts.repeatDelay);
}
}
return [actionFactory, opts];
};
/**
* Start animation on a value. This function completely encapsulates Popmotion-specific logic.
*
* @internal
*/
function startAnimation(key, value, target, _a) {
var _b = _a.delay, delay = _b === void 0 ? 0 : _b, transition = tslib_1.__rest(_a, ["delay"]);
return value.start(function (complete) {
var activeAnimation;
var _a = getAnimation(key, value, target, transition), animationFactory = _a[0], _b = _a[1], valueDelay = _b.delay, options = tslib_1.__rest(_b, ["delay"]);
if (valueDelay !== undefined) {
delay = valueDelay;
}
var animate = function () {
var animation = animationFactory(options);
// Bind animation opts to animation
activeAnimation = animation.start({
update: function (v) { return value.set(v); },
complete: complete,
});
};
// If we're delaying this animation, only resolve it **after** the delay to
// ensure the value's resolve velocity is up-to-date.
if (delay) {
activeAnimation = popmotion.delay(secondsToMilliseconds(delay)).start({
complete: animate,
});
}
else {
animate();
}
return function () {
if (activeAnimation)
activeAnimation.stop();
};
});
}
/**
* Get the current value of every `MotionValue`
* @param values -
*/
var getCurrent = function (values) {
var current = {};
values.forEach(function (value, key) { return (current[key] = value.get()); });
return current;
};
/**
* Get the current velocity of every `MotionValue`
* @param values -
*/
var getVelocity = function (values) {
var velocity = {};
values.forEach(function (value, key) { return (velocity[key] = value.getVelocity()); });
return velocity;
};
/**
* Check if value is a function that returns a `Target`. A generic typeof === 'function'
* check, just helps with typing.
* @param p -
*/
var isTargetResolver = function (p) {
return typeof p === "function";
};
/**
* Check if value is a list of variant labels
* @param v -
*/
var isVariantLabels = function (v) { return Array.isArray(v); };
/**
* Check if value is a numerical string, ie "100" or "100px"
*/
var isNumericalString = function (v) { return /^\d*\.?\d+$/.test(v); };
/**
* Control animations for a single component
* @internal
*/
var ValueAnimationControls = /** @class */ (function () {
function ValueAnimationControls(_a) {
var _this = this;
var values = _a.values, readValueFromSource = _a.readValueFromSource, makeTargetAnimatable = _a.makeTargetAnimatable;
/**
* The component's variants, as provided by `variants`
*/
this.variants = {};
/**
* A set of values that we animate back to when a value is cleared of all overrides.
*/
this.baseTarget = {};
/**
* A series of target overrides that we can animate to/from when overrides are set/cleared.
*/
this.overrides = [];
/**
* A series of target overrides as they were originally resolved.
*/
this.resolvedOverrides = [];
/**
* A Set of currently active override indexes
*/
this.activeOverrides = new Set();
/**
* A Set of value keys that are currently animating.
*/
this.isAnimating = new Set();
/**
* Check if the associated `MotionValueMap` has a key with the provided string.
* Pre-bound to the class so we can provide directly to the `filter` in `checkForNewValues`.
*/
this.hasValue = function (key) { return !_this.values.has(key); };
this.values = values;
this.readValueFromSource = readValueFromSource;
this.makeTargetAnimatable = makeTargetAnimatable;
this.values.forEach(function (value, key) { return (_this.baseTarget[key] = value.get()); });
}
/**
* Set the reference to the component's props.
* @param props -
*/
ValueAnimationControls.prototype.setProps = function (props) {
this.props = props;
};
/**
* Set the reference to the component's variants
* @param variants -
*/
ValueAnimationControls.prototype.setVariants = function (variants) {
if (variants)
this.variants = variants;
};
/**
* Set the component's default transition
* @param transition -
*/
ValueAnimationControls.prototype.setDefaultTransition = function (transition) {
if (transition)
this.defaultTransition = transition;
};
/**
* Set motion values without animation.
*
* @param target -
* @param isActive -
*/
ValueAnimationControls.prototype.setValues = function (_a, _b) {
var _this = this;
var _c = _b === void 0 ? {} : _b, _d = _c.isActive, isActive = _d === void 0 ? new Set() : _d, priority = _c.priority;
var transition = _a.transition, transitionEnd = _a.transitionEnd, target = tslib_1.__rest(_a, ["transition", "transitionEnd"]);
target = this.transformValues(tslib_1.__assign({}, target, transitionEnd));
return Object.keys(target).forEach(function (key) {
if (isActive.has(key))
return;
isActive.add(key);
var targetValue = resolveFinalValueInKeyframes(target[key]);
if (_this.values.has(key)) {
var value = _this.values.get(key);
value && value.set(targetValue);
}
else {
_this.values.set(key, motionValue(targetValue));
}
if (!priority)
_this.baseTarget[key] = targetValue;
});
};
/**
* Allows `transformValues` to be set by a component that allows us to
* transform the values in a given `Target`. This allows Framer Library
* to extend Framer Motion to animate `Color` variables etc. Currently we have
* to manually support these extended types here in Framer Motion.
*
* @param values -
*/
ValueAnimationControls.prototype.transformValues = function (values) {
var transformValues = this.props.transformValues;
return transformValues ? transformValues(values) : values;
};
/**
* Check a `Target` for new values we haven't animated yet, and add them
* to the `MotionValueMap`.
*
* Currently there's functionality here that is DOM-specific, we should allow
* this functionality to be injected by the factory that creates DOM-specific
* components.
*
* @param target -
*/
ValueAnimationControls.prototype.checkForNewValues = function (target) {
var newValueKeys = Object.keys(target).filter(this.hasValue);
var numNewValues = newValueKeys.length;
if (!numNewValues)
return;
for (var i = 0; i < numNewValues; i++) {
var key = newValueKeys[i];
var targetValue = target[key];
var value = null;
// If this is a keyframes value, we can attempt to use the first value in the
// array as that's going to be the first value of the animation anyway
if (Array.isArray(targetValue)) {
value = targetValue[0];
}
// If it isn't a keyframes or the first keyframes value was set as `null`, read the
// value from the DOM. It might be worth investigating whether to check props (for SVG)
// or props.style (for HTML) if the value exists there before attempting to read.
if (value === null) {
value = this.readValueFromSource(key);
heyListen.invariant(value !== null, "No initial value for \"" + key + "\" can be inferred. Ensure an initial value for \"" + key + "\" is defined on the component.");
}
if (typeof value === "string" && isNumericalString(value)) {
// If this is a number read as a string, ie "0" or "200", convert it to a number
value = parseFloat(value);
}
else if (!getValueType(value) && styleValueTypes.complex.test(targetValue)) {
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
value = styleValueTypes.complex.getAnimatableNone(targetValue);
}
this.values.set(key, motionValue(value));
this.baseTarget[key] = value;
}
};
/**
* Resolve a variant from its label or resolver into an actual `Target` we can animate to.
* @param variant -
*/
ValueAnimationControls.prototype.resolveVariant = function (variant) {
if (!variant) {
return {
target: undefined,
transition: undefined,
transitionEnd: undefined,
};
}
if (isTargetResolver(variant)) {
// resolve current and velocity
variant = variant(this.props.custom, getCurrent(this.values), getVelocity(this.values));
}
var _a = variant.transition, transition = _a === void 0 ? this.defaultTransition : _a, transitionEnd = variant.transitionEnd, target = tslib_1.__rest(variant, ["transition", "transitionEnd"]);
return { transition: transition, transitionEnd: transitionEnd, target: target };
};
/**
* Get the highest active override priority index
*/
ValueAnimationControls.prototype.getHighestPriority = function () {
if (!this.activeOverrides.size)
return 0;
return Math.max.apply(Math, Array.from(this.activeOverrides));
};
/**
* Set an override. We add this layer of indirection so if, for instance, a tap gesture
* starts and overrides a hover gesture, when we clear the tap gesture and fallback to the
* hover gesture, if that hover gesture has changed in the meantime we can go to that rather
* than the one that was resolved when the hover gesture animation started.
*
* @param definition -
* @param overrideIndex -
*/
ValueAnimationControls.prototype.setOverride = function (definition, overrideIndex) {
this.overrides[overrideIndex] = definition;
if (this.children) {
this.children.forEach(function (child) {
return child.setOverride(definition, overrideIndex);
});
}
};
/**
* Start an override animation.
* @param overrideIndex -
*/
ValueAnimationControls.prototype.startOverride = function (overrideIndex) {
var override = this.overrides[overrideIndex];
if (override) {
return this.start(override, { priority: overrideIndex });
}
};
/**
* Clear an override. We check every value we animated to in this override to see if
* its present on any lower-priority overrides. If not, we animate it back to its base target.
* @param overrideIndex -
*/
ValueAnimationControls.prototype.clearOverride = function (overrideIndex) {
var _this = this;
if (this.children) {
this.children.forEach(function (child) { return child.clearOverride(overrideIndex); });
}
var override = this.overrides[overrideIndex];
if (!override)
return;
this.activeOverrides.delete(overrideIndex);
var highest = this.getHighestPriority();
this.resetIsAnimating();
if (highest) {
var highestOverride = this.overrides[highest];
highestOverride && this.startOverride(highest);
}
// Figure out which remaining values were affected by the override and animate those
var overrideTarget = this.resolvedOverrides[overrideIndex];
if (!overrideTarget)
return;
var remainingValues = {};
for (var key in this.baseTarget) {
if (overrideTarget[key] !== undefined) {
remainingValues[key] = this.baseTarget[key];
}
}
this.onStart();
this.animate(remainingValues).then(function () { return _this.onComplete(); });
};
/**
* Apply a target/variant without any animation
*/
ValueAnimationControls.prototype.apply = function (definition) {
if (Array.isArray(definition)) {
return this.applyVariantLabels(definition);
}
else if (typeof definition === "string") {
return this.applyVariantLabels([definition]);
}
else {
this.setValues(definition);
}
};
/**
* Apply variant labels without animation
*/
ValueAnimationControls.prototype.applyVariantLabels = function (variantLabelList) {
var _this = this;
var isActive = new Set();
var reversedList = variantLabelList.slice().reverse();
reversedList.forEach(function (key) {
var _a = _this.resolveVariant(_this.variants[key]), target = _a.target, transitionEnd = _a.transitionEnd;
if (transitionEnd) {
_this.setValues(transitionEnd, { isActive: isActive });
}
if (target) {
_this.setValues(target, { isActive: isActive });
}
if (_this.children && _this.children.size) {
_this.children.forEach(function (child) {
return child.applyVariantLabels(variantLabelList);
});
}
});
};
ValueAnimationControls.prototype.start = function (definition, opts) {
var _this = this;
if (opts === void 0) { opts = {}; }
if (opts.priority) {
this.activeOverrides.add(opts.priority);
}
this.resetIsAnimating(opts.priority);
var animation;
if (isVariantLabels(definition)) {
animation = this.animateVariantLabels(definition, opts);
}
else if (typeof definition === "string") {
animation = this.animateVariant(definition, opts);
}
else {
animation = this.animate(definition, opts);
}
this.onStart();
return animation.then(function () { return _this.onComplete(); });
};
ValueAnimationControls.prototype.animate = function (animationDefinition, _a) {
var _this = this;
var _b = _a === void 0 ? {} : _a, _c = _b.delay, delay = _c === void 0 ? 0 : _c, _d = _b.priority, priority = _d === void 0 ? 0 : _d, transitionOverride = _b.transitionOverride;
var _e = this.resolveVariant(animationDefinition), target = _e.target, transition = _e.transition, transitionEnd = _e.transitionEnd;
if (transitionOverride) {
transition = transitionOverride;
}
if (!target)
return Promise.resolve();
target = this.transformValues(target);
if (transitionEnd) {
transitionEnd = this.transformValues(transitionEnd);
}
this.checkForNewValues(target);
if (this.makeTargetAnimatable) {
var animatable = this.makeTargetAnimatable(target, transitionEnd);
target = animatable.target;
transitionEnd = animatable.transitionEnd;
}
if (priority) {
this.resolvedOverrides[priority] = target;
}
this.checkForNewValues(target);
var animations = [];
for (var key in target) {
var value = this.values.get(key);
if (!value || !target || target[key] === undefined)
continue;
var valueTarget = target[key];
if (!priority) {
this.baseTarget[key] = resolveFinalValueInKeyframes(valueTarget);
}
if (this.isAnimating.has(key))
continue;
this.isAnimating.add(key);
animations.push(startAnimation(key, value, valueTarget, tslib_1.__assign({ delay: delay }, transition)));
}
var allAnimations = Promise.all(animations);
return transitionEnd
? allA