UNPKG

framer-motion

Version:

A simple and powerful React animation library

1,417 lines (1,373 loc) 341 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var React = require('react'); var heyListen = require('hey-listen'); var sync = require('framesync'); var popmotion = require('popmotion'); var styleValueTypes = require('style-value-types'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespace(React); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var sync__default = /*#__PURE__*/_interopDefaultLegacy(sync); /** * Browser-safe usage of process */ var mockProcess = { env: { NODE_ENV: "production" } }; var safeProcess = typeof process === "undefined" ? mockProcess : process; // eslint-disable-next-line import/no-default-export var process$1 = safeProcess; var createDefinition = function (propNames) { return ({ isEnabled: function (props) { return propNames.some(function (name) { return !!props[name]; }); }, }); }; var featureDefinitions = { measureLayout: createDefinition(["layout", "layoutId", "drag"]), animation: createDefinition([ "animate", "exit", "variants", "whileHover", "whileTap", "whileFocus", "whileDrag", "whileInView", ]), exit: createDefinition(["exit"]), drag: createDefinition(["drag", "dragControls"]), focus: createDefinition(["whileFocus"]), hover: createDefinition(["whileHover", "onHoverStart", "onHoverEnd"]), tap: createDefinition(["whileTap", "onTap", "onTapStart", "onTapCancel"]), pan: createDefinition([ "onPan", "onPanStart", "onPanSessionStart", "onPanEnd", ]), inView: createDefinition([ "whileInView", "onViewportEnter", "onViewportLeave", ]), }; function loadFeatures(features) { for (var key in features) { if (features[key] === null) continue; if (key === "projectionNodeConstructor") { featureDefinitions.projectionNodeConstructor = features[key]; } else { featureDefinitions[key].Component = features[key]; } } } var LazyContext = React.createContext({ strict: false }); var featureNames = Object.keys(featureDefinitions); var numFeatures = featureNames.length; /** * Load features via renderless components based on the provided MotionProps. */ function useFeatures(props, visualElement, preloadedFeatures) { var features = []; var lazyContext = React.useContext(LazyContext); if (!visualElement) return null; /** * If we're in development mode, check to make sure we're not rendering a motion component * as a child of LazyMotion, as this will break the file-size benefits of using it. */ if (process$1.env.NODE_ENV !== "production" && preloadedFeatures && lazyContext.strict) { heyListen.invariant(false, "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead."); } for (var i = 0; i < numFeatures; i++) { var name_1 = featureNames[i]; var _a = featureDefinitions[name_1], isEnabled = _a.isEnabled, Component = _a.Component; /** * It might be possible in the future to use this moment to * dynamically request functionality. In initial tests this * was producing a lot of duplication amongst bundles. */ if (isEnabled(props) && Component) { features.push(React__namespace.createElement(Component, tslib.__assign({ key: name_1 }, props, { visualElement: visualElement }))); } } return features; } /** * @public */ var MotionConfigContext = React.createContext({ transformPagePoint: function (p) { return p; }, isStatic: false, reducedMotion: "never", }); var MotionContext = React.createContext({}); function useVisualElementContext() { return React.useContext(MotionContext).visualElement; } /** * @public */ var PresenceContext = React.createContext(null); var isBrowser = typeof document !== "undefined"; var useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect; // Does this device prefer reduced motion? Returns `null` server-side. var prefersReducedMotion = { current: null }; var hasDetected = false; function initPrefersReducedMotion() { hasDetected = true; if (!isBrowser) return; if (window.matchMedia) { var motionMediaQuery_1 = window.matchMedia("(prefers-reduced-motion)"); var setReducedMotionPreferences = function () { return (prefersReducedMotion.current = motionMediaQuery_1.matches); }; motionMediaQuery_1.addListener(setReducedMotionPreferences); setReducedMotionPreferences(); } else { prefersReducedMotion.current = false; } } /** * A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting. * * This can be used to implement changes to your UI based on Reduced Motion. For instance, replacing motion-sickness inducing * `x`/`y` animations with `opacity`, disabling the autoplay of background videos, or turning off parallax motion. * * It will actively respond to changes and re-render your components with the latest setting. * * ```jsx * export function Sidebar({ isOpen }) { * const shouldReduceMotion = useReducedMotion() * const closedX = shouldReduceMotion ? 0 : "-100%" * * return ( * <motion.div animate={{ * opacity: isOpen ? 1 : 0, * x: isOpen ? 0 : closedX * }} /> * ) * } * ``` * * @return boolean * * @public */ function useReducedMotion() { /** * Lazy initialisation of prefersReducedMotion */ !hasDetected && initPrefersReducedMotion(); var _a = tslib.__read(React.useState(prefersReducedMotion.current), 1), shouldReduceMotion = _a[0]; /** * TODO See if people miss automatically updating shouldReduceMotion setting */ return shouldReduceMotion; } function useReducedMotionConfig() { var reducedMotionPreference = useReducedMotion(); var reducedMotion = React.useContext(MotionConfigContext).reducedMotion; if (reducedMotion === "never") { return false; } else if (reducedMotion === "always") { return true; } else { return reducedMotionPreference; } } function useVisualElement(Component, visualState, props, createVisualElement) { var lazyContext = React.useContext(LazyContext); var parent = useVisualElementContext(); var presenceContext = React.useContext(PresenceContext); var shouldReduceMotion = useReducedMotionConfig(); var visualElementRef = React.useRef(undefined); /** * If we haven't preloaded a renderer, check to see if we have one lazy-loaded */ if (!createVisualElement) createVisualElement = lazyContext.renderer; if (!visualElementRef.current && createVisualElement) { visualElementRef.current = createVisualElement(Component, { visualState: visualState, parent: parent, props: props, presenceId: presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.id, blockInitialAnimation: (presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.initial) === false, shouldReduceMotion: shouldReduceMotion, }); } var visualElement = visualElementRef.current; useIsomorphicLayoutEffect(function () { visualElement === null || visualElement === void 0 ? void 0 : visualElement.syncRender(); }); React.useEffect(function () { var _a; (_a = visualElement === null || visualElement === void 0 ? void 0 : visualElement.animationState) === null || _a === void 0 ? void 0 : _a.animateChanges(); }); useIsomorphicLayoutEffect(function () { return function () { return visualElement === null || visualElement === void 0 ? void 0 : visualElement.notifyUnmount(); }; }, []); return visualElement; } function isRefObject(ref) { return (typeof ref === "object" && Object.prototype.hasOwnProperty.call(ref, "current")); } /** * Creates a ref function that, when called, hydrates the provided * external ref and VisualElement. */ function useMotionRef(visualState, visualElement, externalRef) { return React.useCallback(function (instance) { var _a; instance && ((_a = visualState.mount) === null || _a === void 0 ? void 0 : _a.call(visualState, instance)); if (visualElement) { instance ? visualElement.mount(instance) : visualElement.unmount(); } if (externalRef) { if (typeof externalRef === "function") { externalRef(instance); } else if (isRefObject(externalRef)) { externalRef.current = instance; } } }, /** * Only pass a new ref callback to React if we've received a visual element * factory. Otherwise we'll be mounting/remounting every time externalRef * or other dependencies change. */ [visualElement]); } /** * Decides if the supplied variable is an array of variant labels */ function isVariantLabels(v) { return Array.isArray(v); } /** * Decides if the supplied variable is variant label */ function isVariantLabel(v) { return typeof v === "string" || isVariantLabels(v); } /** * Creates an object containing the latest state of every MotionValue on a VisualElement */ function getCurrent(visualElement) { var current = {}; visualElement.forEachValue(function (value, key) { return (current[key] = value.get()); }); return current; } /** * Creates an object containing the latest velocity of every MotionValue on a VisualElement */ function getVelocity$1(visualElement) { var velocity = {}; visualElement.forEachValue(function (value, key) { return (velocity[key] = value.getVelocity()); }); return velocity; } function resolveVariantFromProps(props, definition, custom, currentValues, currentVelocity) { var _a; if (currentValues === void 0) { currentValues = {}; } if (currentVelocity === void 0) { currentVelocity = {}; } /** * If the variant definition is a function, resolve. */ if (typeof definition === "function") { definition = definition(custom !== null && custom !== void 0 ? custom : props.custom, currentValues, currentVelocity); } /** * If the variant definition is a variant label, or * the function returned a variant label, resolve. */ if (typeof definition === "string") { definition = (_a = props.variants) === null || _a === void 0 ? void 0 : _a[definition]; } /** * At this point we've resolved both functions and variant labels, * but the resolved variant label might itself have been a function. * If so, resolve. This can only have returned a valid target object. */ if (typeof definition === "function") { definition = definition(custom !== null && custom !== void 0 ? custom : props.custom, currentValues, currentVelocity); } return definition; } function resolveVariant(visualElement, definition, custom) { var props = visualElement.getProps(); return resolveVariantFromProps(props, definition, custom !== null && custom !== void 0 ? custom : props.custom, getCurrent(visualElement), getVelocity$1(visualElement)); } function checkIfControllingVariants(props) { var _a; return (typeof ((_a = props.animate) === null || _a === void 0 ? void 0 : _a.start) === "function" || isVariantLabel(props.initial) || isVariantLabel(props.animate) || isVariantLabel(props.whileHover) || isVariantLabel(props.whileDrag) || isVariantLabel(props.whileTap) || isVariantLabel(props.whileFocus) || isVariantLabel(props.exit)); } function checkIfVariantNode(props) { return Boolean(checkIfControllingVariants(props) || props.variants); } function getCurrentTreeVariants(props, context) { if (checkIfControllingVariants(props)) { var initial = props.initial, animate = props.animate; return { initial: initial === false || isVariantLabel(initial) ? initial : undefined, animate: isVariantLabel(animate) ? animate : undefined, }; } return props.inherit !== false ? context : {}; } function useCreateMotionContext(props) { var _a = getCurrentTreeVariants(props, React.useContext(MotionContext)), initial = _a.initial, animate = _a.animate; return React.useMemo(function () { return ({ initial: initial, animate: animate }); }, [variantLabelsAsDependency(initial), variantLabelsAsDependency(animate)]); } function variantLabelsAsDependency(prop) { return Array.isArray(prop) ? prop.join(" ") : prop; } /** * 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; } function addUniqueItem(arr, item) { arr.indexOf(item) === -1 && arr.push(item); } function removeItem(arr, item) { var index = arr.indexOf(item); index > -1 && arr.splice(index, 1); } // Adapted from array-move function moveItem(_a, fromIndex, toIndex) { var _b = tslib.__read(_a), arr = _b.slice(0); var startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex; if (startIndex >= 0 && startIndex < arr.length) { var endIndex = toIndex < 0 ? arr.length + toIndex : toIndex; var _c = tslib.__read(arr.splice(fromIndex, 1), 1), item = _c[0]; arr.splice(endIndex, 0, item); } return arr; } var SubscriptionManager = /** @class */ (function () { function SubscriptionManager() { this.subscriptions = []; } SubscriptionManager.prototype.add = function (handler) { var _this = this; addUniqueItem(this.subscriptions, handler); return function () { return removeItem(_this.subscriptions, handler); }; }; SubscriptionManager.prototype.notify = function (a, b, c) { var numSubscriptions = this.subscriptions.length; if (!numSubscriptions) return; if (numSubscriptions === 1) { /** * If there's only a single handler we can just call it without invoking a loop. */ this.subscriptions[0](a, b, c); } else { for (var i = 0; i < numSubscriptions; i++) { /** * Check whether the handler exists before firing as it's possible * the subscriptions were modified during this loop running. */ var handler = this.subscriptions[i]; handler && handler(a, b, c); } } }; SubscriptionManager.prototype.getSize = function () { return this.subscriptions.length; }; SubscriptionManager.prototype.clear = function () { this.subscriptions.length = 0; }; return SubscriptionManager; }()); 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) { var _this = this; /** * Duration, in milliseconds, since last updating frame. * * @internal */ this.timeDelta = 0; /** * Timestamp of the last time this `MotionValue` was updated. * * @internal */ this.lastUpdated = 0; /** * Functions to notify when the `MotionValue` updates. * * @internal */ this.updateSubscribers = new SubscriptionManager(); /** * Functions to notify when the velocity updates. * * @internal */ this.velocityUpdateSubscribers = new SubscriptionManager(); /** * Functions to notify when the `MotionValue` updates and `render` is set to `true`. * * @internal */ this.renderSubscribers = new SubscriptionManager(); /** * 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 = v; // Update timestamp var _a = sync.getFrameData(), delta = _a.delta, timestamp = _a.timestamp; if (_this.lastUpdated !== timestamp) { _this.timeDelta = delta; _this.lastUpdated = timestamp; sync__default["default"].postRender(_this.scheduleVelocityCheck); } // Update update subscribers if (_this.prev !== _this.current) { _this.updateSubscribers.notify(_this.current); } // Update velocity subscribers if (_this.velocityUpdateSubscribers.getSize()) { _this.velocityUpdateSubscribers.notify(_this.getVelocity()); } // Update render subscribers if (render) { _this.renderSubscribers.notify(_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["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; _this.velocityUpdateSubscribers.notify(_this.getVelocity()); } }; this.hasAnimated = false; this.prev = this.current = init; this.canTrackVelocity = isFloat(this.current); } /** * 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.. * * ```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) { return this.updateSubscribers.add(subscription); }; MotionValue.prototype.clearListeners = function () { this.updateSubscribers.clear(); }; /** * 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) { // Render immediately subscription(this.get()); return this.renderSubscribers.add(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; }; /** * @public */ MotionValue.prototype.getPrevious = function () { return this.prev; }; /** * 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 popmotion.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.hasAnimated = true; _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.clear(); this.renderSubscribers.clear(); this.stop(); }; return MotionValue; }()); /** * @internal */ function motionValue(init) { return new MotionValue(init); } var isMotionValue = function (value) { return Boolean(value !== null && typeof value === "object" && value.getVelocity); }; /** * Converts seconds to milliseconds * * @param seconds - Time in seconds. * @return milliseconds - Converted time in milliseconds. */ var secondsToMilliseconds = function (seconds) { return seconds * 1000; }; var easingLookup = { linear: popmotion.linear, easeIn: popmotion.easeIn, easeInOut: popmotion.easeInOut, easeOut: popmotion.easeOut, circIn: popmotion.circIn, circInOut: popmotion.circInOut, circOut: popmotion.circOut, backIn: popmotion.backIn, backInOut: popmotion.backInOut, backOut: popmotion.backOut, anticipate: popmotion.anticipate, bounceIn: popmotion.bounceIn, bounceInOut: popmotion.bounceInOut, bounceOut: popmotion.bounceOut, }; 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 _a = tslib.__read(definition, 4), x1 = _a[0], y1 = _a[1], x2 = _a[2], y2 = _a[3]; return popmotion.cubicBezier(x1, y1, x2, y2); } else if (typeof definition === "string") { // Else lookup from table heyListen.invariant(easingLookup[definition] !== undefined, "Invalid easing type '".concat(definition, "'")); return easingLookup[definition]; } return definition; }; var isEasingArray = function (ease) { return Array.isArray(ease) && typeof ease[0] !== "number"; }; /** * 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; }; var isKeyframesTarget = function (v) { return Array.isArray(v); }; var underDampedSpring = function () { return ({ type: "spring", stiffness: 500, damping: 25, restSpeed: 10, }); }; var criticallyDampedSpring = function (to) { return ({ type: "spring", stiffness: 550, damping: to === 0 ? 2 * Math.sqrt(550) : 30, restSpeed: 10, }); }; var linearTween = function () { return ({ type: "keyframes", 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: criticallyDampedSpring, scaleY: criticallyDampedSpring, scale: criticallyDampedSpring, opacity: linearTween, backgroundColor: linearTween, color: linearTween, default: criticallyDampedSpring, }; var getDefaultTransition = function (valueKey, to) { var transitionFactory; if (isKeyframesTarget(to)) { transitionFactory = keyframes; } else { transitionFactory = defaultTransitions[valueKey] || defaultTransitions.default; } return tslib.__assign({ to: to }, transitionFactory(to)); }; var int = tslib.__assign(tslib.__assign({}, styleValueTypes.number), { transform: Math.round }); var numberValueTypes = { // Border props borderWidth: styleValueTypes.px, borderTopWidth: styleValueTypes.px, borderRightWidth: styleValueTypes.px, borderBottomWidth: styleValueTypes.px, borderLeftWidth: styleValueTypes.px, borderRadius: styleValueTypes.px, radius: styleValueTypes.px, borderTopLeftRadius: styleValueTypes.px, borderTopRightRadius: styleValueTypes.px, borderBottomRightRadius: styleValueTypes.px, borderBottomLeftRadius: styleValueTypes.px, // Positioning props width: styleValueTypes.px, maxWidth: styleValueTypes.px, height: styleValueTypes.px, maxHeight: styleValueTypes.px, size: styleValueTypes.px, top: styleValueTypes.px, right: styleValueTypes.px, bottom: styleValueTypes.px, left: styleValueTypes.px, // Spacing props padding: styleValueTypes.px, paddingTop: styleValueTypes.px, paddingRight: styleValueTypes.px, paddingBottom: styleValueTypes.px, paddingLeft: styleValueTypes.px, margin: styleValueTypes.px, marginTop: styleValueTypes.px, marginRight: styleValueTypes.px, marginBottom: styleValueTypes.px, marginLeft: styleValueTypes.px, // Transform props rotate: styleValueTypes.degrees, rotateX: styleValueTypes.degrees, rotateY: styleValueTypes.degrees, rotateZ: styleValueTypes.degrees, scale: styleValueTypes.scale, scaleX: styleValueTypes.scale, scaleY: styleValueTypes.scale, scaleZ: styleValueTypes.scale, skew: styleValueTypes.degrees, skewX: styleValueTypes.degrees, skewY: styleValueTypes.degrees, distance: styleValueTypes.px, translateX: styleValueTypes.px, translateY: styleValueTypes.px, translateZ: styleValueTypes.px, x: styleValueTypes.px, y: styleValueTypes.px, z: styleValueTypes.px, perspective: styleValueTypes.px, transformPerspective: styleValueTypes.px, opacity: styleValueTypes.alpha, originX: styleValueTypes.progressPercentage, originY: styleValueTypes.progressPercentage, originZ: styleValueTypes.px, // Misc zIndex: int, // SVG fillOpacity: styleValueTypes.alpha, strokeOpacity: styleValueTypes.alpha, numOctaves: int, }; /** * A map of default value types for common values */ var defaultValueTypes = tslib.__assign(tslib.__assign({}, numberValueTypes), { // Color props color: styleValueTypes.color, backgroundColor: styleValueTypes.color, outlineColor: styleValueTypes.color, fill: styleValueTypes.color, stroke: styleValueTypes.color, // Border props borderColor: styleValueTypes.color, borderTopColor: styleValueTypes.color, borderRightColor: styleValueTypes.color, borderBottomColor: styleValueTypes.color, borderLeftColor: styleValueTypes.color, filter: styleValueTypes.filter, WebkitFilter: styleValueTypes.filter }); /** * Gets the default ValueType for the provided value key */ var getDefaultValueType = function (key) { return defaultValueTypes[key]; }; function getAnimatableNone(key, value) { var _a; var defaultValueType = getDefaultValueType(key); if (defaultValueType !== styleValueTypes.filter) defaultValueType = styleValueTypes.complex; // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value); } var instantAnimationState = { current: false, }; 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; }; /** * Decide whether a transition is defined on a given Transition. * This filters out orchestration options and returns true * if any options are left. */ function isTransitionDefined(_a) { _a.when; _a.delay; _a.delayChildren; _a.staggerChildren; _a.staggerDirection; _a.repeat; _a.repeatType; _a.repeatDelay; _a.from; var transition = tslib.__rest(_a, ["when", "delay", "delayChildren", "staggerChildren", "staggerDirection", "repeat", "repeatType", "repeatDelay", "from"]); return !!Object.keys(transition).length; } var legacyRepeatWarning = false; /** * Convert Framer Motion's Transition type into Popmotion-compatible options. */ function convertTransitionToAnimationOptions(_a) { var ease = _a.ease, times = _a.times, yoyo = _a.yoyo, flip = _a.flip, loop = _a.loop, transition = tslib.__rest(_a, ["ease", "times", "yoyo", "flip", "loop"]); var options = tslib.__assign({}, transition); if (times) options["offset"] = times; /** * Convert any existing durations from seconds to milliseconds */ if (transition.duration) options["duration"] = secondsToMilliseconds(transition.duration); if (transition.repeatDelay) options.repeatDelay = secondsToMilliseconds(transition.repeatDelay); /** * Map easing names to Popmotion's easing functions */ if (ease) { options["ease"] = isEasingArray(ease) ? ease.map(easingDefinitionToFunction) : easingDefinitionToFunction(ease); } /** * Support legacy transition API */ if (transition.type === "tween") options.type = "keyframes"; /** * TODO: These options are officially removed from the API. */ if (yoyo || loop || flip) { heyListen.warning(!legacyRepeatWarning, "yoyo, loop and flip have been removed from the API. Replace with repeat and repeatType options."); legacyRepeatWarning = true; if (yoyo) { options.repeatType = "reverse"; } else if (loop) { options.repeatType = "loop"; } else if (flip) { options.repeatType = "mirror"; } options.repeat = loop || yoyo || flip || transition.repeat; } /** * TODO: Popmotion 9 has the ability to automatically detect whether to use * a keyframes or spring animation, but does so by detecting velocity and other spring options. * It'd be good to introduce a similar thing here. */ if (transition.type !== "spring") options.type = "keyframes"; return options; } /** * Get the delay for a value by checking Transition with decreasing specificity. */ function getDelayFromTransition(transition, key) { var _a, _b; var valueTransition = getValueTransition(transition, key) || {}; return (_b = (_a = valueTransition.delay) !== null && _a !== void 0 ? _a : transition.delay) !== null && _b !== void 0 ? _b : 0; } function hydrateKeyframes(options) { if (Array.isArray(options.to) && options.to[0] === null) { options.to = tslib.__spreadArray([], tslib.__read(options.to), false); options.to[0] = options.from; } return options; } function getPopmotionAnimationOptions(transition, options, key) { var _a; if (Array.isArray(options.to)) { (_a = transition.duration) !== null && _a !== void 0 ? _a : (transition.duration = 0.8); } hydrateKeyframes(options); /** * Get a default transition if none is determined to be defined. */ if (!isTransitionDefined(transition)) { transition = tslib.__assign(tslib.__assign({}, transition), getDefaultTransition(key, options.to)); } return tslib.__assign(tslib.__assign({}, options), convertTransitionToAnimationOptions(transition)); } /** * */ function getAnimation(key, value, target, transition, onComplete) { var _a; var valueTransition = getValueTransition(transition, key); var origin = (_a = valueTransition.from) !== null && _a !== void 0 ? _a : value.get(); var isTargetAnimatable = isAnimatable(key, target); if (origin === "none" && isTargetAnimatable && typeof target === "string") { /** * If we're trying to animate from "none", try and get an animatable version * of the target. This could be improved to work both ways. */ origin = getAnimatableNone(key, target); } else if (isZero(origin) && typeof target === "string") { origin = getZeroUnit(target); } else if (!Array.isArray(target) && isZero(target) && typeof origin === "string") { target = getZeroUnit(origin); } var isOriginAnimatable = isAnimatable(key, origin); heyListen.warning(isOriginAnimatable === isTargetAnimatable, "You are trying to animate ".concat(key, " from \"").concat(origin, "\" to \"").concat(target, "\". ").concat(origin, " is not an animatable value - to enable this animation set ").concat(origin, " to a value animatable to ").concat(target, " via the `style` property.")); function start() { var options = { from: origin, to: target, velocity: value.getVelocity(), onComplete: onComplete, onUpdate: function (v) { return value.set(v); }, }; return valueTransition.type === "inertia" || valueTransition.type === "decay" ? popmotion.inertia(tslib.__assign(tslib.__assign({}, options), valueTransition)) : popmotion.animate(tslib.__assign(tslib.__assign({}, getPopmotionAnimationOptions(valueTransition, options, key)), { onUpdate: function (v) { var _a; options.onUpdate(v); (_a = valueTransition.onUpdate) === null || _a === void 0 ? void 0 : _a.call(valueTransition, v); }, onComplete: function () { var _a; options.onComplete(); (_a = valueTransition.onComplete) === null || _a === void 0 ? void 0 : _a.call(valueTransition); } })); } function set() { var _a, _b; var finalTarget = resolveFinalValueInKeyframes(target); value.set(finalTarget); onComplete(); (_a = valueTransition === null || valueTransition === void 0 ? void 0 : valueTransition.onUpdate) === null || _a === void 0 ? void 0 : _a.call(valueTransition, finalTarget); (_b = valueTransition === null || valueTransition === void 0 ? void 0 : valueTransition.onComplete) === null || _b === void 0 ? void 0 : _b.call(valueTransition); return { stop: function () { } }; } return !isOriginAnimatable || !isTargetAnimatable || valueTransition.type === false ? set : start; } function isZero(value) { return (value === 0 || (typeof value === "string" && parseFloat(value) === 0 && value.indexOf(" ") === -1)); } function getZeroUnit(potentialUnitType) { return typeof potentialUnitType === "number" ? 0 : getAnimatableNone("", potentialUnitType); } function getValueTransition(transition, key) { return transition[key] || transition["default"] || transition; } /** * Start animation on a MotionValue. This function is an interface between * Framer Motion and Popmotion * * @internal */ function startAnimation(key, value, target, transition) { if (transition === void 0) { transition = {}; } if (instantAnimationState.current) { transition = { type: false }; } return value.start(function (onComplete) { var delayTimer; var controls; var animation = getAnimation(key, value, target, transition, onComplete); var delay = getDelayFromTransition(transition, key); var start = function () { return (controls = animation()); }; if (delay) { delayTimer = window.setTimeout(start, secondsToMilliseconds(delay)); } else { start(); } return function () { clearTimeout(delayTimer); controls === null || controls === void 0 ? void 0 : controls.stop(); }; }); } /** * Animate a single value or a `MotionValue`. * * The first argument is either a `MotionValue` to animate, or an initial animation value. * * The second is either a value to animate to, or an array of keyframes to animate through. * * The third argument can be either tween or spring options, and optional lifecycle methods: `onUpdate`, `onPlay`, `onComplete`, `onRepeat` and `onStop`. * * Returns `AnimationPlaybackControls`, currently just a `stop` method. * * ```javascript * const x = useMotionValue(0) * * useEffect(() => { * const controls = animate(x, 100, { * type: "spring", * stiffness: 2000, * onComplete: v => {} * }) * * return controls.stop * }) * ``` * * @public */ function animate(from, to, transition) { if (transition === void 0) { transition = {}; } var value = isMotionValue(from) ? from : motionValue(from); startAnimation("", value, to, transition); return { stop: function () { return value.stop(); }, isAnimating: function () { return value.isAnimating(); }, }; } var borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"]; var numBorders = borders.length; var asNumber = function (value) { return typeof value === "string" ? parseFloat(value) : value; }; var isPx = function (value) { return typeof value === "number" || styleValueTypes.px.test(value); }; function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) { var _a, _b, _c, _d; if (shouldCrossfadeOpacity) { target.opacity = popmotion.mix(0, // (follow?.opacity as number) ?? 0, // TODO Reinstate this if only child (_a = lead.opacity) !== null && _a !== void 0 ? _a : 1, easeCrossfadeIn(progress)); target.opacityExit = popmotion.mix((_b = follow.opacity) !== null && _b !== void 0 ? _b : 1, 0, easeCrossfadeOut(progress)); } else if (isOnlyMember) { target.opacity = popmotion.mix((_c = follow.opacity) !== null && _c !== void 0 ? _c : 1, (_d = lead.opacity) !== null && _d !== void 0 ? _d : 1, progress); } /** * Mix border radius */ for (var i = 0; i < numBorders; i++) { var borderLabel = "border".concat(borders[i], "Radius"); var followRadius = getRadius(follow, borderLabel); var leadRadius = getRadius(lead, borderLabel); if (followRadius === undefined && leadRadius === undefined) continue; followRadius || (followRadius = 0); leadRadius || (leadRadius = 0); var canMix = followRadius === 0 || leadRadius === 0 || isPx(followRadius) === isPx(leadRadius); if (canMix) { target[borderLabel] = Math.max(popmotion.mix(asNumber(followRadius), asNumber(leadRadius), progress), 0); if (styleValueTypes.percent.test(leadRadius) || styleValueTypes.percent.test(followRadius)) { target[borderLabel] += "%"; } } else { target[borderLabel] = leadRadius; } } /** * Mix rotation */ if (follow.rotate || lead.rotate) { target.rotate = popmotion.mix(follow.rotate || 0, lead.rotate || 0, progress); } } function getRadius(values, radiusName) { var _a; return (_a = values[radiusName]) !== null && _a !== void 0 ? _a : values.borderRadius; } // /** // * We only want to mix the background color if there's a follow element // * that we're not crossfading opacity between. For instance with switch // * AnimateSharedLayout animations, this helps the illusion of a continuous // * element being animated but also cuts down on the number of paints triggered // * for elements where opacity is doing that work for us. // */ // if ( // !hasFollowElement && // latestLeadValues.backgroundColor && // latestFollowValues.backgroundColor // ) { // /** // * This isn't ideal performance-wise as mixColor is creating a new function every frame. // * We could probably create a mixer that runs at the start of the animation but // * the idea behind the crossfader is that it runs dynamically between two potentially // * changing targets (ie opacity or borderRadius may be animating independently via variants) // */ // leadState.backgroundColor = followState.backgroundColor = mixColor( // latestFollowValues.backgroundColor as string, // latestLeadValues.backgroundColor as string // )(p) // } var easeCrossfadeIn = compress(0, 0.5, popmotion.circOut); var easeCrossfadeOut = compress(0.5, 0.95, popmotion.linear); function compress(min, max, easing) { return function (p) { // Could replace ifs with clamp if (p < min) return 0; if (p > max) return 1; return easing(popmotion.progress(min, max, p)); }; } /** * Reset an axis to the provided origin box. * * This is a mutative operation. */ function copyAxisInto(axis, originAxis) { axis.min = originAxis.min; axis.max = originAxis.max; } /** * Reset a box to the provided origin box. * * This is a mutative operation. */ function copyBoxInto(box, originBox) { copyAxisInto(box.x, originBox.x); copyAxisInto(box.y, originBox.y); } function isIdentityScale(scale) { return scale === undefined || scale === 1; } function hasScale(_a) { var scale = _a.scale, scaleX = _a.scaleX, scaleY = _a.scaleY; return (!isIdentityScale(scale) || !isIdentityScale(scaleX) || !isIdentityScale(scaleY)); } function hasTransform(values) { return (hasScale(values) || hasTranslate(values.x) || hasTranslate(values.y) || values.z || values.rotate || values.rotateX || values.rotateY); } function hasTranslate(value) { return value && value !== "0%"; } /** * Scales a point based on a factor and an originPoint */ function scalePoint(point, scale, originPoint) { var distanceFromOrigin = point - originPoint; var scaled = scale * distanceFromOrigin; return originPoint + scaled; } /** * Applies a translate/scale delta to a point */ function applyPointDelta(point, translate, scale, originPoint, boxScale) { if (boxScale !== undefined) { point = scalePoint(point, boxScale, originPoint); } return scalePoint(point, scale, originPoint) + translate; } /** * Applies a translate/scale delta to an axis */ function applyAxisDelta(axis, translate, scale, originPoint, boxScale) { if (translate === void 0) { translate = 0; } if (scale === void 0) { scale = 1; } axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale); axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale); } /** * Applies a translate/scale delta to a box */ function applyBoxDelta(box, _a) { var x = _a.x, y = _a.y; applyAxisDelta(box.x, x.translate, x.scale, x.originPoint); applyAxisDelta(box.y, y.translate, y.scale, y.originPoint); } /** * Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms * in a tree upon our box before then calculating how to project it into our desired viewport-relative box * * This is the final nested loop within updateLayoutDelta for future refactoring */ function applyTreeDeltas(box, treeScale, treePath, isSharedTransition) { var _a, _b; if (isSharedTransition === void 0) { isSharedTransition = false; } var treeLength = treePath.length; if (!treeLength) return; // Reset the treeScale treeScale.x = treeScale.y = 1; var node; var delta; for (var i = 0; i < treeLength; i++) { node = treePath[i]; delta = node.projectionDelta; if (((_b = (_a = node.instance) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.display) === "contents") continue; if (isSharedTransition && node.options.layoutScroll && node.scroll && node !== node.root) { transformBox(box, { x: -node.scroll.x, y: -node.scroll.y }); } if (delta) {