UNPKG

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
'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