%PDF- %PDF-
Direktori : /home/vacivi36/SiteVacivitta/vacivitta/node_modules/framer-motion/dist/cjs/ |
Current File : /home/vacivi36/SiteVacivitta/vacivitta/node_modules/framer-motion/dist/cjs/index.js |
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsxRuntime = require('react/jsx-runtime'); var React = require('react'); var create = require('./create-DCF2FFGK.js'); var motionDom = require('motion-dom'); var motionUtils = require('motion-utils'); function _interopNamespaceDefault(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__*/_interopNamespaceDefault(React); /** * Measurement functionality has to be within a separate component * to leverage snapshot lifecycle. */ class PopChildMeasure extends React__namespace.Component { getSnapshotBeforeUpdate(prevProps) { const element = this.props.childRef.current; if (element && prevProps.isPresent && !this.props.isPresent) { const parent = element.offsetParent; const parentWidth = motionDom.isHTMLElement(parent) ? parent.offsetWidth || 0 : 0; const size = this.props.sizeRef.current; size.height = element.offsetHeight || 0; size.width = element.offsetWidth || 0; size.top = element.offsetTop; size.left = element.offsetLeft; size.right = parentWidth - size.width - size.left; } return null; } /** * Required with getSnapshotBeforeUpdate to stop React complaining. */ componentDidUpdate() { } render() { return this.props.children; } } function PopChild({ children, isPresent, anchorX }) { const id = React.useId(); const ref = React.useRef(null); const size = React.useRef({ width: 0, height: 0, top: 0, left: 0, right: 0, }); const { nonce } = React.useContext(create.MotionConfigContext); /** * We create and inject a style block so we can apply this explicit * sizing in a non-destructive manner by just deleting the style block. * * We can't apply size via render as the measurement happens * in getSnapshotBeforeUpdate (post-render), likewise if we apply the * styles directly on the DOM node, we might be overwriting * styles set via the style prop. */ React.useInsertionEffect(() => { const { width, height, top, left, right } = size.current; if (isPresent || !ref.current || !width || !height) return; const x = anchorX === "left" ? `left: ${left}` : `right: ${right}`; ref.current.dataset.motionPopId = id; const style = document.createElement("style"); if (nonce) style.nonce = nonce; document.head.appendChild(style); if (style.sheet) { style.sheet.insertRule(` [data-motion-pop-id="${id}"] { position: absolute !important; width: ${width}px !important; height: ${height}px !important; ${x}px !important; top: ${top}px !important; } `); } return () => { if (document.head.contains(style)) { document.head.removeChild(style); } }; }, [isPresent]); return (jsxRuntime.jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref }) })); } const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, }) => { const presenceChildren = create.useConstant(newChildrenMap); const id = React.useId(); let isReusedContext = true; let context = React.useMemo(() => { isReusedContext = false; return { id, initial, isPresent, custom, onExitComplete: (childId) => { presenceChildren.set(childId, true); for (const isComplete of presenceChildren.values()) { if (!isComplete) return; // can stop searching when any is incomplete } onExitComplete && onExitComplete(); }, register: (childId) => { presenceChildren.set(childId, false); return () => presenceChildren.delete(childId); }, }; }, [isPresent, presenceChildren, onExitComplete]); /** * If the presence of a child affects the layout of the components around it, * we want to make a new context value to ensure they get re-rendered * so they can detect that layout change. */ if (presenceAffectsLayout && isReusedContext) { context = { ...context }; } React.useMemo(() => { presenceChildren.forEach((_, key) => presenceChildren.set(key, false)); }, [isPresent]); /** * If there's no `motion` components to fire exit animations, we want to remove this * component immediately. */ React__namespace.useEffect(() => { !isPresent && !presenceChildren.size && onExitComplete && onExitComplete(); }, [isPresent]); if (mode === "popLayout") { children = (jsxRuntime.jsx(PopChild, { isPresent: isPresent, anchorX: anchorX, children: children })); } return (jsxRuntime.jsx(create.PresenceContext.Provider, { value: context, children: children })); }; function newChildrenMap() { return new Map(); } const getChildKey = (child) => child.key || ""; function onlyElements(children) { const filtered = []; // We use forEach here instead of map as map mutates the component key by preprending `.$` React.Children.forEach(children, (child) => { if (React.isValidElement(child)) filtered.push(child); }); return filtered; } /** * `AnimatePresence` enables the animation of components that have been removed from the tree. * * When adding/removing more than a single child, every child **must** be given a unique `key` prop. * * Any `motion` components that have an `exit` property defined will animate out when removed from * the tree. * * ```jsx * import { motion, AnimatePresence } from 'framer-motion' * * export const Items = ({ items }) => ( * <AnimatePresence> * {items.map(item => ( * <motion.div * key={item.id} * initial={{ opacity: 0 }} * animate={{ opacity: 1 }} * exit={{ opacity: 0 }} * /> * ))} * </AnimatePresence> * ) * ``` * * You can sequence exit animations throughout a tree using variants. * * If a child contains multiple `motion` components with `exit` props, it will only unmount the child * once all `motion` components have finished animating out. Likewise, any components using * `usePresence` all need to call `safeToRemove`. * * @public */ const AnimatePresence = ({ children, custom, initial = true, onExitComplete, presenceAffectsLayout = true, mode = "sync", propagate = false, anchorX = "left", }) => { const [isParentPresent, safeToRemove] = create.usePresence(propagate); /** * Filter any children that aren't ReactElements. We can only track components * between renders with a props.key. */ const presentChildren = React.useMemo(() => onlyElements(children), [children]); /** * Track the keys of the currently rendered children. This is used to * determine which children are exiting. */ const presentKeys = propagate && !isParentPresent ? [] : presentChildren.map(getChildKey); /** * If `initial={false}` we only want to pass this to components in the first render. */ const isInitialRender = React.useRef(true); /** * A ref containing the currently present children. When all exit animations * are complete, we use this to re-render the component with the latest children * *committed* rather than the latest children *rendered*. */ const pendingPresentChildren = React.useRef(presentChildren); /** * Track which exiting children have finished animating out. */ const exitComplete = create.useConstant(() => new Map()); /** * Save children to render as React state. To ensure this component is concurrent-safe, * we check for exiting children via an effect. */ const [diffedChildren, setDiffedChildren] = React.useState(presentChildren); const [renderedChildren, setRenderedChildren] = React.useState(presentChildren); create.useIsomorphicLayoutEffect(() => { isInitialRender.current = false; pendingPresentChildren.current = presentChildren; /** * Update complete status of exiting children. */ for (let i = 0; i < renderedChildren.length; i++) { const key = getChildKey(renderedChildren[i]); if (!presentKeys.includes(key)) { if (exitComplete.get(key) !== true) { exitComplete.set(key, false); } } else { exitComplete.delete(key); } } }, [renderedChildren, presentKeys.length, presentKeys.join("-")]); const exitingChildren = []; if (presentChildren !== diffedChildren) { let nextChildren = [...presentChildren]; /** * Loop through all the currently rendered components and decide which * are exiting. */ for (let i = 0; i < renderedChildren.length; i++) { const child = renderedChildren[i]; const key = getChildKey(child); if (!presentKeys.includes(key)) { nextChildren.splice(i, 0, child); exitingChildren.push(child); } } /** * If we're in "wait" mode, and we have exiting children, we want to * only render these until they've all exited. */ if (mode === "wait" && exitingChildren.length) { nextChildren = exitingChildren; } setRenderedChildren(onlyElements(nextChildren)); setDiffedChildren(presentChildren); /** * Early return to ensure once we've set state with the latest diffed * children, we can immediately re-render. */ return null; } if (process.env.NODE_ENV !== "production" && mode === "wait" && renderedChildren.length > 1) { console.warn(`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`); } /** * If we've been provided a forceRender function by the LayoutGroupContext, * we can use it to force a re-render amongst all surrounding components once * all components have finished animating out. */ const { forceRender } = React.useContext(create.LayoutGroupContext); return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderedChildren.map((child) => { const key = getChildKey(child); const isPresent = propagate && !isParentPresent ? false : presentChildren === renderedChildren || presentKeys.includes(key); const onExit = () => { if (exitComplete.has(key)) { exitComplete.set(key, true); } else { return; } let isEveryExitComplete = true; exitComplete.forEach((isExitComplete) => { if (!isExitComplete) isEveryExitComplete = false; }); if (isEveryExitComplete) { forceRender?.(); setRenderedChildren(pendingPresentChildren.current); propagate && safeToRemove?.(); onExitComplete && onExitComplete(); } }; return (jsxRuntime.jsx(PresenceChild, { isPresent: isPresent, initial: !isInitialRender.current || initial ? undefined : false, custom: custom, presenceAffectsLayout: presenceAffectsLayout, mode: mode, onExitComplete: isPresent ? undefined : onExit, anchorX: anchorX, children: child }, key)); }) })); }; /** * Note: Still used by components generated by old versions of Framer * * @deprecated */ const DeprecatedLayoutGroupContext = React.createContext(null); const notify = (node) => !node.isLayoutDirty && node.willUpdate(false); function nodeGroup() { const nodes = new Set(); const subscriptions = new WeakMap(); const dirtyAll = () => nodes.forEach(notify); return { add: (node) => { nodes.add(node); subscriptions.set(node, node.addEventListener("willUpdate", dirtyAll)); }, remove: (node) => { nodes.delete(node); const unsubscribe = subscriptions.get(node); if (unsubscribe) { unsubscribe(); subscriptions.delete(node); } dirtyAll(); }, dirty: dirtyAll, }; } function useIsMounted() { const isMounted = React.useRef(false); create.useIsomorphicLayoutEffect(() => { isMounted.current = true; return () => { isMounted.current = false; }; }, []); return isMounted; } function useForceUpdate() { const isMounted = useIsMounted(); const [forcedRenderCount, setForcedRenderCount] = React.useState(0); const forceRender = React.useCallback(() => { isMounted.current && setForcedRenderCount(forcedRenderCount + 1); }, [forcedRenderCount]); /** * Defer this to the end of the next animation frame in case there are multiple * synchronous calls. */ const deferredForceRender = React.useCallback(() => motionDom.frame.postRender(forceRender), [forceRender]); return [deferredForceRender, forcedRenderCount]; } const shouldInheritGroup = (inherit) => inherit === true; const shouldInheritId = (inherit) => shouldInheritGroup(inherit === true) || inherit === "id"; const LayoutGroup = ({ children, id, inherit = true }) => { const layoutGroupContext = React.useContext(create.LayoutGroupContext); const deprecatedLayoutGroupContext = React.useContext(DeprecatedLayoutGroupContext); const [forceRender, key] = useForceUpdate(); const context = React.useRef(null); const upstreamId = layoutGroupContext.id || deprecatedLayoutGroupContext; if (context.current === null) { if (shouldInheritId(inherit) && upstreamId) { id = id ? upstreamId + "-" + id : upstreamId; } context.current = { id, group: shouldInheritGroup(inherit) ? layoutGroupContext.group || nodeGroup() : nodeGroup(), }; } const memoizedContext = React.useMemo(() => ({ ...context.current, forceRender }), [key]); return (jsxRuntime.jsx(create.LayoutGroupContext.Provider, { value: memoizedContext, children: children })); }; /** * Used in conjunction with the `m` component to reduce bundle size. * * `m` is a version of the `motion` component that only loads functionality * critical for the initial render. * * `LazyMotion` can then be used to either synchronously or asynchronously * load animation and gesture support. * * ```jsx * // Synchronous loading * import { LazyMotion, m, domAnimation } from "framer-motion" * * function App() { * return ( * <LazyMotion features={domAnimation}> * <m.div animate={{ scale: 2 }} /> * </LazyMotion> * ) * } * * // Asynchronous loading * import { LazyMotion, m } from "framer-motion" * * function App() { * return ( * <LazyMotion features={() => import('./path/to/domAnimation')}> * <m.div animate={{ scale: 2 }} /> * </LazyMotion> * ) * } * ``` * * @public */ function LazyMotion({ children, features, strict = false }) { const [, setIsLoaded] = React.useState(!isLazyBundle(features)); const loadedRenderer = React.useRef(undefined); /** * If this is a synchronous load, load features immediately */ if (!isLazyBundle(features)) { const { renderer, ...loadedFeatures } = features; loadedRenderer.current = renderer; create.loadFeatures(loadedFeatures); } React.useEffect(() => { if (isLazyBundle(features)) { features().then(({ renderer, ...loadedFeatures }) => { create.loadFeatures(loadedFeatures); loadedRenderer.current = renderer; setIsLoaded(true); }); } }, []); return (jsxRuntime.jsx(create.LazyContext.Provider, { value: { renderer: loadedRenderer.current, strict }, children: children })); } function isLazyBundle(features) { return typeof features === "function"; } /** * `MotionConfig` is used to set configuration options for all children `motion` components. * * ```jsx * import { motion, MotionConfig } from "framer-motion" * * export function App() { * return ( * <MotionConfig transition={{ type: "spring" }}> * <motion.div animate={{ x: 100 }} /> * </MotionConfig> * ) * } * ``` * * @public */ function MotionConfig({ children, isValidProp, ...config }) { isValidProp && create.loadExternalIsValidProp(isValidProp); /** * Inherit props from any parent MotionConfig components */ config = { ...React.useContext(create.MotionConfigContext), ...config }; /** * Don't allow isStatic to change between renders as it affects how many hooks * motion components fire. */ config.isStatic = create.useConstant(() => config.isStatic); /** * Creating a new config context object will re-render every `motion` component * every time it renders. So we only want to create a new one sparingly. */ const context = React.useMemo(() => config, [ JSON.stringify(config.transition), config.transformPagePoint, config.reducedMotion, ]); return (jsxRuntime.jsx(create.MotionConfigContext.Provider, { value: context, children: children })); } const ReorderContext = React.createContext(null); function createDOMMotionComponentProxy(componentFactory) { if (typeof Proxy === "undefined") { return componentFactory; } /** * A cache of generated `motion` components, e.g `motion.div`, `motion.input` etc. * Rather than generating them anew every render. */ const componentCache = new Map(); const deprecatedFactoryFunction = (...args) => { if (process.env.NODE_ENV !== "production") { motionUtils.warnOnce(false, "motion() is deprecated. Use motion.create() instead."); } return componentFactory(...args); }; return new Proxy(deprecatedFactoryFunction, { /** * Called when `motion` is referenced with a prop: `motion.div`, `motion.input` etc. * The prop name is passed through as `key` and we can use that to generate a `motion` * DOM component with that name. */ get: (_target, key) => { if (key === "create") return componentFactory; /** * If this element doesn't exist in the component cache, create it and cache. */ if (!componentCache.has(key)) { componentCache.set(key, componentFactory(key)); } return componentCache.get(key); }, }); } const motion = /*@__PURE__*/ createDOMMotionComponentProxy(create.createMotionComponent); function checkReorder(order, value, offset, velocity) { if (!velocity) return order; const index = order.findIndex((item) => item.value === value); if (index === -1) return order; const nextOffset = velocity > 0 ? 1 : -1; const nextItem = order[index + nextOffset]; if (!nextItem) return order; const item = order[index]; const nextLayout = nextItem.layout; const nextItemCenter = motionDom.mixNumber(nextLayout.min, nextLayout.max, 0.5); if ((nextOffset === 1 && item.layout.max + offset > nextItemCenter) || (nextOffset === -1 && item.layout.min + offset < nextItemCenter)) { return motionUtils.moveItem(order, index, index + nextOffset); } return order; } function ReorderGroupComponent({ children, as = "ul", axis = "y", onReorder, values, ...props }, externalRef) { const Component = create.useConstant(() => motion[as]); const order = []; const isReordering = React.useRef(false); motionUtils.invariant(Boolean(values), "Reorder.Group must be provided a values prop"); const context = { axis, registerItem: (value, layout) => { // If the entry was already added, update it rather than adding it again const idx = order.findIndex((entry) => value === entry.value); if (idx !== -1) { order[idx].layout = layout[axis]; } else { order.push({ value: value, layout: layout[axis] }); } order.sort(compareMin); }, updateOrder: (item, offset, velocity) => { if (isReordering.current) return; const newOrder = checkReorder(order, item, offset, velocity); if (order !== newOrder) { isReordering.current = true; onReorder(newOrder .map(getValue) .filter((value) => values.indexOf(value) !== -1)); } }, }; React.useEffect(() => { isReordering.current = false; }); return (jsxRuntime.jsx(Component, { ...props, ref: externalRef, ignoreStrict: true, children: jsxRuntime.jsx(ReorderContext.Provider, { value: context, children: children }) })); } const ReorderGroup = /*@__PURE__*/ React.forwardRef(ReorderGroupComponent); function getValue(item) { return item.value; } function compareMin(a, b) { return a.layout.min - b.layout.min; } /** * Creates a `MotionValue` to track the state and velocity of a value. * * Usually, these are created automatically. For advanced use-cases, like use with `useTransform`, you can create `MotionValue`s externally and pass them into the animated component via the `style` prop. * * ```jsx * export const MyComponent = () => { * const scale = useMotionValue(1) * * return <motion.div style={{ scale }} /> * } * ``` * * @param initial - The initial state. * * @public */ function useMotionValue(initial) { const value = create.useConstant(() => motionDom.motionValue(initial)); /** * If this motion value is being used in static mode, like on * the Framer canvas, force components to rerender when the motion * value is updated. */ const { isStatic } = React.useContext(create.MotionConfigContext); if (isStatic) { const [, setLatest] = React.useState(initial); React.useEffect(() => value.on("change", setLatest), []); } return value; } function useCombineMotionValues(values, combineValues) { /** * Initialise the returned motion value. This remains the same between renders. */ const value = useMotionValue(combineValues()); /** * Create a function that will update the template motion value with the latest values. * This is pre-bound so whenever a motion value updates it can schedule its * execution in Framesync. If it's already been scheduled it won't be fired twice * in a single frame. */ const updateValue = () => value.set(combineValues()); /** * Synchronously update the motion value with the latest values during the render. * This ensures that within a React render, the styles applied to the DOM are up-to-date. */ updateValue(); /** * Subscribe to all motion values found within the template. Whenever any of them change, * schedule an update. */ create.useIsomorphicLayoutEffect(() => { const scheduleUpdate = () => motionDom.frame.preRender(updateValue, false, true); const subscriptions = values.map((v) => v.on("change", scheduleUpdate)); return () => { subscriptions.forEach((unsubscribe) => unsubscribe()); motionDom.cancelFrame(updateValue); }; }); return value; } function useComputed(compute) { /** * Open session of collectMotionValues. Any MotionValue that calls get() * will be saved into this array. */ motionDom.collectMotionValues.current = []; compute(); const value = useCombineMotionValues(motionDom.collectMotionValues.current, compute); /** * Synchronously close session of collectMotionValues. */ motionDom.collectMotionValues.current = undefined; return value; } function useTransform(input, inputRangeOrTransformer, outputRange, options) { if (typeof input === "function") { return useComputed(input); } const transformer = typeof inputRangeOrTransformer === "function" ? inputRangeOrTransformer : motionDom.transform(inputRangeOrTransformer, outputRange, options); return Array.isArray(input) ? useListTransform(input, transformer) : useListTransform([input], ([latest]) => transformer(latest)); } function useListTransform(values, transformer) { const latest = create.useConstant(() => []); return useCombineMotionValues(values, () => { latest.length = 0; const numValues = values.length; for (let i = 0; i < numValues; i++) { latest[i] = values[i].get(); } return transformer(latest); }); } function useDefaultMotionValue(value, defaultValue = 0) { return motionDom.isMotionValue(value) ? value : useMotionValue(defaultValue); } function ReorderItemComponent({ children, style = {}, value, as = "li", onDrag, layout = true, ...props }, externalRef) { const Component = create.useConstant(() => motion[as]); const context = React.useContext(ReorderContext); const point = { x: useDefaultMotionValue(style.x), y: useDefaultMotionValue(style.y), }; const zIndex = useTransform([point.x, point.y], ([latestX, latestY]) => latestX || latestY ? 1 : "unset"); motionUtils.invariant(Boolean(context), "Reorder.Item must be a child of Reorder.Group"); const { axis, registerItem, updateOrder } = context; return (jsxRuntime.jsx(Component, { drag: axis, ...props, dragSnapToOrigin: true, style: { ...style, x: point.x, y: point.y, zIndex }, layout: layout, onDrag: (event, gesturePoint) => { const { velocity } = gesturePoint; velocity[axis] && updateOrder(value, point[axis].get(), velocity[axis]); onDrag && onDrag(event, gesturePoint); }, onLayoutMeasure: (measured) => registerItem(value, measured), ref: externalRef, ignoreStrict: true, children: children })); } const ReorderItem = /*@__PURE__*/ React.forwardRef(ReorderItemComponent); var namespace = /*#__PURE__*/Object.freeze({ __proto__: null, Group: ReorderGroup, Item: ReorderItem }); function isDOMKeyframes(keyframes) { return typeof keyframes === "object" && !Array.isArray(keyframes); } function resolveSubjects(subject, keyframes, scope, selectorCache) { if (typeof subject === "string" && isDOMKeyframes(keyframes)) { return motionDom.resolveElements(subject, scope, selectorCache); } else if (subject instanceof NodeList) { return Array.from(subject); } else if (Array.isArray(subject)) { return subject; } else { return [subject]; } } function calculateRepeatDuration(duration, repeat, _repeatDelay) { return duration * (repeat + 1); } /** * Given a absolute or relative time definition and current/prev time state of the sequence, * calculate an absolute time for the next keyframes. */ function calcNextTime(current, next, prev, labels) { if (typeof next === "number") { return next; } else if (next.startsWith("-") || next.startsWith("+")) { return Math.max(0, current + parseFloat(next)); } else if (next === "<") { return prev; } else { return labels.get(next) ?? current; } } function eraseKeyframes(sequence, startTime, endTime) { for (let i = 0; i < sequence.length; i++) { const keyframe = sequence[i]; if (keyframe.at > startTime && keyframe.at < endTime) { motionUtils.removeItem(sequence, keyframe); // If we remove this item we have to push the pointer back one i--; } } } function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) { /** * Erase every existing value between currentTime and targetTime, * this will essentially splice this timeline into any currently * defined ones. */ eraseKeyframes(sequence, startTime, endTime); for (let i = 0; i < keyframes.length; i++) { sequence.push({ value: keyframes[i], at: motionDom.mixNumber(startTime, endTime, offset[i]), easing: motionUtils.getEasingForSegment(easing, i), }); } } /** * Take an array of times that represent repeated keyframes. For instance * if we have original times of [0, 0.5, 1] then our repeated times will * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back * down to a 0-1 scale. */ function normalizeTimes(times, repeat) { for (let i = 0; i < times.length; i++) { times[i] = times[i] / (repeat + 1); } } function compareByTime(a, b) { if (a.at === b.at) { if (a.value === null) return 1; if (b.value === null) return -1; return 0; } else { return a.at - b.at; } } const defaultSegmentEasing = "easeInOut"; const MAX_REPEAT = 20; function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) { const defaultDuration = defaultTransition.duration || 0.3; const animationDefinitions = new Map(); const sequences = new Map(); const elementCache = {}; const timeLabels = new Map(); let prevTime = 0; let currentTime = 0; let totalDuration = 0; /** * Build the timeline by mapping over the sequence array and converting * the definitions into keyframes and offsets with absolute time values. * These will later get converted into relative offsets in a second pass. */ for (let i = 0; i < sequence.length; i++) { const segment = sequence[i]; /** * If this is a timeline label, mark it and skip the rest of this iteration. */ if (typeof segment === "string") { timeLabels.set(segment, currentTime); continue; } else if (!Array.isArray(segment)) { timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels)); continue; } let [subject, keyframes, transition = {}] = segment; /** * If a relative or absolute time value has been specified we need to resolve * it in relation to the currentTime. */ if (transition.at !== undefined) { currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels); } /** * Keep track of the maximum duration in this definition. This will be * applied to currentTime once the definition has been parsed. */ let maxDuration = 0; const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => { const valueKeyframesAsList = keyframesAsList(valueKeyframes); const { delay = 0, times = motionDom.defaultOffset(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition; let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition; /** * Resolve stagger() if defined. */ const calculatedDelay = typeof delay === "function" ? delay(elementIndex, numSubjects) : delay; /** * If this animation should and can use a spring, generate a spring easing function. */ const numKeyframes = valueKeyframesAsList.length; const createGenerator = motionDom.isGenerator(type) ? type : generators?.[type]; if (numKeyframes <= 2 && createGenerator) { /** * As we're creating an easing function from a spring, * ideally we want to generate it using the real distance * between the two keyframes. However this isn't always * possible - in these situations we use 0-100. */ let absoluteDelta = 100; if (numKeyframes === 2 && isNumberKeyframesArray(valueKeyframesAsList)) { const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0]; absoluteDelta = Math.abs(delta); } const springTransition = { ...remainingTransition }; if (duration !== undefined) { springTransition.duration = motionUtils.secondsToMilliseconds(duration); } const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator); ease = springEasing.ease; duration = springEasing.duration; } duration ?? (duration = defaultDuration); const startTime = currentTime + calculatedDelay; /** * If there's only one time offset of 0, fill in a second with length 1 */ if (times.length === 1 && times[0] === 0) { times[1] = 1; } /** * Fill out if offset if fewer offsets than keyframes */ const remainder = times.length - valueKeyframesAsList.length; remainder > 0 && motionDom.fillOffset(times, remainder); /** * If only one value has been set, ie [1], push a null to the start of * the keyframe array. This will let us mark a keyframe at this point * that will later be hydrated with the previous value. */ valueKeyframesAsList.length === 1 && valueKeyframesAsList.unshift(null); /** * Handle repeat options */ if (repeat) { motionUtils.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20"); duration = calculateRepeatDuration(duration, repeat); const originalKeyframes = [...valueKeyframesAsList]; const originalTimes = [...times]; ease = Array.isArray(ease) ? [...ease] : [ease]; const originalEase = [...ease]; for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) { valueKeyframesAsList.push(...originalKeyframes); for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) { times.push(originalTimes[keyframeIndex] + (repeatIndex + 1)); ease.push(keyframeIndex === 0 ? "linear" : motionUtils.getEasingForSegment(originalEase, keyframeIndex - 1)); } } normalizeTimes(times, repeat); } const targetTime = startTime + duration; /** * Add keyframes, mapping offsets to absolute time. */ addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime); maxDuration = Math.max(calculatedDelay + duration, maxDuration); totalDuration = Math.max(targetTime, totalDuration); }; if (motionDom.isMotionValue(subject)) { const subjectSequence = getSubjectSequence(subject, sequences); resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence)); } else { const subjects = resolveSubjects(subject, keyframes, scope, elementCache); const numSubjects = subjects.length; /** * For every element in this segment, process the defined values. */ for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) { /** * Cast necessary, but we know these are of this type */ keyframes = keyframes; transition = transition; const thisSubject = subjects[subjectIndex]; const subjectSequence = getSubjectSequence(thisSubject, sequences); for (const key in keyframes) { resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects); } } } prevTime = currentTime; currentTime += maxDuration; } /** * For every element and value combination create a new animation. */ sequences.forEach((valueSequences, element) => { for (const key in valueSequences) { const valueSequence = valueSequences[key]; /** * Arrange all the keyframes in ascending time order. */ valueSequence.sort(compareByTime); const keyframes = []; const valueOffset = []; const valueEasing = []; /** * For each keyframe, translate absolute times into * relative offsets based on the total duration of the timeline. */ for (let i = 0; i < valueSequence.length; i++) { const { at, value, easing } = valueSequence[i]; keyframes.push(value); valueOffset.push(motionUtils.progress(0, totalDuration, at)); valueEasing.push(easing || "easeOut"); } /** * If the first keyframe doesn't land on offset: 0 * provide one by duplicating the initial keyframe. This ensures * it snaps to the first keyframe when the animation starts. */ if (valueOffset[0] !== 0) { valueOffset.unshift(0); keyframes.unshift(keyframes[0]); valueEasing.unshift(defaultSegmentEasing); } /** * If the last keyframe doesn't land on offset: 1 * provide one with a null wildcard value. This will ensure it * stays static until the end of the animation. */ if (valueOffset[valueOffset.length - 1] !== 1) { valueOffset.push(1); keyframes.push(null); } if (!animationDefinitions.has(element)) { animationDefinitions.set(element, { keyframes: {}, transition: {}, }); } const definition = animationDefinitions.get(element); definition.keyframes[key] = keyframes; definition.transition[key] = { ...defaultTransition, duration: totalDuration, ease: valueEasing, times: valueOffset, ...sequenceTransition, }; } }); return animationDefinitions; } function getSubjectSequence(subject, sequences) { !sequences.has(subject) && sequences.set(subject, {}); return sequences.get(subject); } function getValueSequence(name, sequences) { if (!sequences[name]) sequences[name] = []; return sequences[name]; } function keyframesAsList(keyframes) { return Array.isArray(keyframes) ? keyframes : [keyframes]; } function getValueTransition(transition, key) { return transition && transition[key] ? { ...transition, ...transition[key], } : { ...transition }; } const isNumber = (keyframe) => typeof keyframe === "number"; const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber); function isObjectKey(key, object) { return key in object; } class ObjectVisualElement extends create.VisualElement { constructor() { super(...arguments); this.type = "object"; } readValueFromInstance(instance, key) { if (isObjectKey(key, instance)) { const value = instance[key]; if (typeof value === "string" || typeof value === "number") { return value; } } return undefined; } getBaseTargetFromProps() { return undefined; } removeValueFromRenderState(key, renderState) { delete renderState.output[key]; } measureInstanceViewportBox() { return create.createBox(); } build(renderState, latestValues) { Object.assign(renderState.output, latestValues); } renderInstance(instance, { output }) { Object.assign(instance, output); } sortInstanceNodePosition() { return 0; } } function createDOMVisualElement(element) { const options = { presenceContext: null, props: {}, visualState: { renderState: { transform: {}, transformOrigin: {}, style: {}, vars: {}, attrs: {}, }, latestValues: {}, }, }; const node = motionDom.isSVGElement(element) && !motionDom.isSVGSVGElement(element) ? new create.SVGVisualElement(options) : new create.HTMLVisualElement(options); node.mount(element); create.visualElementStore.set(element, node); } function createObjectVisualElement(subject) { const options = { presenceContext: null, props: {}, visualState: { renderState: { output: {}, }, latestValues: {}, }, }; const node = new ObjectVisualElement(options); node.mount(subject); create.visualElementStore.set(subject, node); } function isSingleValue(subject, keyframes) { return (motionDom.isMotionValue(subject) || typeof subject === "number" || (typeof subject === "string" && !isDOMKeyframes(keyframes))); } /** * Implementation */ function animateSubject(subject, keyframes, options, scope) { const animations = []; if (isSingleValue(subject, keyframes)) { animations.push(create.animateSingleValue(subject, isDOMKeyframes(keyframes) ? keyframes.default || keyframes : keyframes, options ? options.default || options : options)); } else { const subjects = resolveSubjects(subject, keyframes, scope); const numSubjects = subjects.length; motionUtils.invariant(Boolean(numSubjects), "No valid elements provided."); for (let i = 0; i < numSubjects; i++) { const thisSubject = subjects[i]; const createVisualElement = thisSubject instanceof Element ? createDOMVisualElement : createObjectVisualElement; if (!create.visualElementStore.has(thisSubject)) { createVisualElement(thisSubject); } const visualElement = create.visualElementStore.get(thisSubject); const transition = { ...options }; /** * Resolve stagger function if provided. */ if ("delay" in transition && typeof transition.delay === "function") { transition.delay = transition.delay(i, numSubjects); } animations.push(...create.animateTarget(visualElement, { ...keyframes, transition }, {})); } } return animations; } function animateSequence(sequence, options, scope) { const animations = []; const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring: motionDom.spring }); animationDefinitions.forEach(({ keyframes, transition }, subject) => { animations.push(...animateSubject(subject, keyframes, transition)); }); return animations; } function isSequence(value) { return Array.isArray(value) && value.some(Array.isArray); } /** * Creates an animation function that is optionally scoped * to a specific element. */ function createScopedAnimate(scope) { /** * Implementation */ function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) { let animations = []; if (isSequence(subjectOrSequence)) { animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope); } else { animations = animateSubject(subjectOrSequence, optionsOrKeyframes, options, scope); } const animation = new motionDom.GroupAnimationWithThen(animations); if (scope) { scope.animations.push(animation); } return animation; } return scopedAnimate; } const animate = createScopedAnimate(); function animateElements(elementOrSelector, keyframes, options, scope) { const elements = motionDom.resolveElements(elementOrSelector, scope); const numElements = elements.length; motionUtils.invariant(Boolean(numElements), "No valid element provided."); /** * WAAPI doesn't support interrupting animations. * * Therefore, starting animations requires a three-step process: * 1. Stop existing animations (write styles to DOM) * 2. Resolve keyframes (read styles from DOM) * 3. Create new animations (write styles to DOM) * * The hybrid `animate()` function uses AsyncAnimation to resolve * keyframes before creating new animations, which removes style * thrashing. Here, we have much stricter filesize constraints. * Therefore we do this in a synchronous way that ensures that * at least within `animate()` calls there is no style thrashing. * * In the motion-native-animate-mini-interrupt benchmark this * was 80% faster than a single loop. */ const animationDefinitions = []; /** * Step 1: Build options and stop existing animations (write) */ for (let i = 0; i < numElements; i++) { const element = elements[i]; const elementTransition = { ...options }; /** * Resolve stagger function if provided. */ if (typeof elementTransition.delay === "function") { elementTransition.delay = elementTransition.delay(i, numElements); } for (const valueName in keyframes) { let valueKeyframes = keyframes[valueName]; if (!Array.isArray(valueKeyframes)) { valueKeyframes = [valueKeyframes]; } const valueOptions = { ...motionDom.getValueTransition(elementTransition, valueName), }; valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration)); valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay)); /** * If there's an existing animation playing on this element then stop it * before creating a new one. */ const map = motionDom.getAnimationMap(element); const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || ""); const currentAnimation = map.get(key); currentAnimation && currentAnimation.stop(); animationDefinitions.push({ map, key, unresolvedKeyframes: valueKeyframes, options: { ...valueOptions, element, name: valueName, allowFlatten: !elementTransition.type && !elementTransition.ease, }, }); } } /** * Step 2: Resolve keyframes (read) */ for (let i = 0; i < animationDefinitions.length; i++) { const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i]; const { element, name, pseudoElement } = animationOptions; if (!pseudoElement && unresolvedKeyframes[0] === null) { unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name); } motionDom.fillWildcards(unresolvedKeyframes); motionDom.applyPxDefaults(unresolvedKeyframes, name); /** * If we only have one keyframe, explicitly read the initial keyframe * from the computed style. This is to ensure consistency with WAAPI behaviour * for restarting animations, for instance .play() after finish, when it * has one vs two keyframes. */ if (!pseudoElement && unresolvedKeyframes.length < 2) { unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name)); } animationOptions.keyframes = unresolvedKeyframes; } /** * Step 3: Create new animations (write) */ const animations = []; for (let i = 0; i < animationDefinitions.length; i++) { const { map, key, options: animationOptions } = animationDefinitions[i]; const animation = new motionDom.NativeAnimation(animationOptions); map.set(key, animation); animation.finished.finally(() => map.delete(key)); animations.push(animation); } return animations; } const createScopedWaapiAnimate = (scope) => { function scopedAnimate(elementOrSelector, keyframes, options) { return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope)); } return scopedAnimate; }; const animateMini = /*@__PURE__*/ createScopedWaapiAnimate(); const resizeHandlers = new WeakMap(); let observer; function getElementSize(target, borderBoxSize) { if (borderBoxSize) { const { inlineSize, blockSize } = borderBoxSize[0]; return { width: inlineSize, height: blockSize }; } else if (motionDom.isSVGElement(target) && "getBBox" in target) { return target.getBBox(); } else { return { width: target.offsetWidth, height: target.offsetHeight, }; } } function notifyTarget({ target, contentRect, borderBoxSize, }) { resizeHandlers.get(target)?.forEach((handler) => { handler({ target, contentSize: contentRect, get size() { return getElementSize(target, borderBoxSize); }, }); }); } function notifyAll(entries) { entries.forEach(notifyTarget); } function createResizeObserver() { if (typeof ResizeObserver === "undefined") return; observer = new ResizeObserver(notifyAll); } function resizeElement(target, handler) { if (!observer) createResizeObserver(); const elements = motionDom.resolveElements(target); elements.forEach((element) => { let elementHandlers = resizeHandlers.get(element); if (!elementHandlers) { elementHandlers = new Set(); resizeHandlers.set(element, elementHandlers); } elementHandlers.add(handler); observer?.observe(element); }); return () => { elements.forEach((element) => { const elementHandlers = resizeHandlers.get(element); elementHandlers?.delete(handler); if (!elementHandlers?.size) { observer?.unobserve(element); } }); }; } const windowCallbacks = new Set(); let windowResizeHandler; function createWindowResizeHandler() { windowResizeHandler = () => { const size = { width: window.innerWidth, height: window.innerHeight, }; const info = { target: window, size, contentSize: size, }; windowCallbacks.forEach((callback) => callback(info)); }; window.addEventListener("resize", windowResizeHandler); } function resizeWindow(callback) { windowCallbacks.add(callback); if (!windowResizeHandler) createWindowResizeHandler(); return () => { windowCallbacks.delete(callback); if (!windowCallbacks.size && windowResizeHandler) { windowResizeHandler = undefined; } }; } function resize(a, b) { return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b); } /** * A time in milliseconds, beyond which we consider the scroll velocity to be 0. */ const maxElapsed = 50; const createAxisInfo = () => ({ current: 0, offset: [], progress: 0, scrollLength: 0, targetOffset: 0, targetLength: 0, containerLength: 0, velocity: 0, }); const createScrollInfo = () => ({ time: 0, x: createAxisInfo(), y: createAxisInfo(), }); const keys = { x: { length: "Width", position: "Left", }, y: { length: "Height", position: "Top", }, }; function updateAxisInfo(element, axisName, info, time) { const axis = info[axisName]; const { length, position } = keys[axisName]; const prev = axis.current; const prevTime = info.time; axis.current = element[`scroll${position}`]; axis.scrollLength = element[`scroll${length}`] - element[`client${length}`]; axis.offset.length = 0; axis.offset[0] = 0; axis.offset[1] = axis.scrollLength; axis.progress = motionUtils.progress(0, axis.scrollLength, axis.current); const elapsed = time - prevTime; axis.velocity = elapsed > maxElapsed ? 0 : motionUtils.velocityPerSecond(axis.current - prev, elapsed); } function updateScrollInfo(element, info, time) { updateAxisInfo(element, "x", info, time); updateAxisInfo(element, "y", info, time); info.time = time; } function calcInset(element, container) { const inset = { x: 0, y: 0 }; let current = element; while (current && current !== container) { if (motionDom.isHTMLElement(current)) { inset.x += current.offsetLeft; inset.y += current.offsetTop; current = current.offsetParent; } else if (current.tagName === "svg") { /** * This isn't an ideal approach to measuring the offset of <svg /> tags. * It would be preferable, given they behave like HTMLElements in most ways * to use offsetLeft/Top. But these don't exist on <svg />. Likewise we * can't use .getBBox() like most SVG elements as these provide the offset * relative to the SVG itself, which for <svg /> is usually 0x0. */ const svgBoundingBox = current.getBoundingClientRect(); current = current.parentElement; const parentBoundingBox = current.getBoundingClientRect(); inset.x += svgBoundingBox.left - parentBoundingBox.left; inset.y += svgBoundingBox.top - parentBoundingBox.top; } else if (current instanceof SVGGraphicsElement) { const { x, y } = current.getBBox(); inset.x += x; inset.y += y; let svg = null; let parent = current.parentNode; while (!svg) { if (parent.tagName === "svg") { svg = parent; } parent = current.parentNode; } current = svg; } else { break; } } return inset; } const namedEdges = { start: 0, center: 0.5, end: 1, }; function resolveEdge(edge, length, inset = 0) { let delta = 0; /** * If we have this edge defined as a preset, replace the definition * with the numerical value. */ if (edge in namedEdges) { edge = namedEdges[edge]; } /** * Handle unit values */ if (typeof edge === "string") { const asNumber = parseFloat(edge); if (edge.endsWith("px")) { delta = asNumber; } else if (edge.endsWith("%")) { edge = asNumber / 100; } else if (edge.endsWith("vw")) { delta = (asNumber / 100) * document.documentElement.clientWidth; } else if (edge.endsWith("vh")) { delta = (asNumber / 100) * document.documentElement.clientHeight; } else { edge = asNumber; } } /** * If the edge is defined as a number, handle as a progress value. */ if (typeof edge === "number") { delta = length * edge; } return inset + delta; } const defaultOffset = [0, 0]; function resolveOffset(offset, containerLength, targetLength, targetInset) { let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset; let targetPoint = 0; let containerPoint = 0; if (typeof offset === "number") { /** * If we're provided offset: [0, 0.5, 1] then each number x should become * [x, x], so we default to the behaviour of mapping 0 => 0 of both target * and container etc. */ offsetDefinition = [offset, offset]; } else if (typeof offset === "string") { offset = offset.trim(); if (offset.includes(" ")) { offsetDefinition = offset.split(" "); } else { /** * If we're provided a definition like "100px" then we want to apply * that only to the top of the target point, leaving the container at 0. * Whereas a named offset like "end" should be applied to both. */ offsetDefinition = [offset, namedEdges[offset] ? offset : `0`]; } } targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset); containerPoint = resolveEdge(offsetDefinition[1], containerLength); return targetPoint - containerPoint; } const ScrollOffset = { Enter: [ [0, 1], [1, 1], ], Exit: [ [0, 0], [1, 0], ], Any: [ [1, 0], [0, 1], ], All: [ [0, 0], [1, 1], ], }; const point = { x: 0, y: 0 }; function getTargetSize(target) { return "getBBox" in target && target.tagName !== "svg" ? target.getBBox() : { width: target.clientWidth, height: target.clientHeight }; } function resolveOffsets(container, info, options) { const { offset: offsetDefinition = ScrollOffset.All } = options; const { target = container, axis = "y" } = options; const lengthLabel = axis === "y" ? "height" : "width"; const inset = target !== container ? calcInset(target, container) : point; /** * Measure the target and container. If they're the same thing then we * use the container's scrollWidth/Height as the target, from there * all other calculations can remain the same. */ const targetSize = target === container ? { width: container.scrollWidth, height: container.scrollHeight } : getTargetSize(target); const containerSize = { width: container.clientWidth, height: container.clientHeight, }; /** * Reset the length of the resolved offset array rather than creating a new one. * TODO: More reusable data structures for targetSize/containerSize would also be good. */ info[axis].offset.length = 0; /** * Populate the offset array by resolving the user's offset definition into * a list of pixel scroll offets. */ let hasChanged = !info[axis].interpolate; const numOffsets = offsetDefinition.length; for (let i = 0; i < numOffsets; i++) { const offset = resolveOffset(offsetDefinition[i], containerSize[lengthLabel], targetSize[lengthLabel], inset[axis]); if (!hasChanged && offset !== info[axis].interpolatorOffsets[i]) { hasChanged = true; } info[axis].offset[i] = offset; } /** * If the pixel scroll offsets have changed, create a new interpolator function * to map scroll value into a progress. */ if (hasChanged) { info[axis].interpolate = motionDom.interpolate(info[axis].offset, motionDom.defaultOffset(offsetDefinition), { clamp: false }); info[axis].interpolatorOffsets = [...info[axis].offset]; } info[axis].progress = motionUtils.clamp(0, 1, info[axis].interpolate(info[axis].current)); } function measure(container, target = container, info) { /** * Find inset of target within scrollable container */ info.x.targetOffset = 0; info.y.targetOffset = 0; if (target !== container) { let node = target; while (node && node !== container) { info.x.targetOffset += node.offsetLeft; info.y.targetOffset += node.offsetTop; node = node.offsetParent; } } info.x.targetLength = target === container ? target.scrollWidth : target.clientWidth; info.y.targetLength = target === container ? target.scrollHeight : target.clientHeight; info.x.containerLength = container.clientWidth; info.y.containerLength = container.clientHeight; /** * In development mode ensure scroll containers aren't position: static as this makes * it difficult to measure their relative positions. */ if (process.env.NODE_ENV !== "production") { if (container && target && target !== container) { motionUtils.warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly."); } } } function createOnScrollHandler(element, onScroll, info, options = {}) { return { measure: () => measure(element, options.target, info), update: (time) => { updateScrollInfo(element, info, time); if (options.offset || options.target) { resolveOffsets(element, info, options); } }, notify: () => onScroll(info), }; } const scrollListeners = new WeakMap(); const resizeListeners = new WeakMap(); const onScrollHandlers = new WeakMap(); const getEventTarget = (element) => element === document.scrollingElement ? window : element; function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) { if (!container) return motionUtils.noop; let containerHandlers = onScrollHandlers.get(container); /** * Get the onScroll handlers for this container. * If one isn't found, create a new one. */ if (!containerHandlers) { containerHandlers = new Set(); onScrollHandlers.set(container, containerHandlers); } /** * Create a new onScroll handler for the provided callback. */ const info = createScrollInfo(); const containerHandler = createOnScrollHandler(container, onScroll, info, options); containerHandlers.add(containerHandler); /** * Check if there's a scroll event listener for this container. * If not, create one. */ if (!scrollListeners.has(container)) { const measureAll = () => { for (const handler of containerHandlers) handler.measure(); }; const updateAll = () => { for (const handler of containerHandlers) { handler.update(motionDom.frameData.timestamp); } }; const notifyAll = () => { for (const handler of containerHandlers) handler.notify(); }; const listener = () => { motionDom.frame.read(measureAll); motionDom.frame.read(updateAll); motionDom.frame.preUpdate(notifyAll); }; scrollListeners.set(container, listener); const target = getEventTarget(container); window.addEventListener("resize", listener, { passive: true }); if (container !== document.documentElement) { resizeListeners.set(container, resize(container, listener)); } target.addEventListener("scroll", listener, { passive: true }); listener(); } const listener = scrollListeners.get(container); motionDom.frame.read(listener, false, true); return () => { motionDom.cancelFrame(listener); /** * Check if we even have any handlers for this container. */ const currentHandlers = onScrollHandlers.get(container); if (!currentHandlers) return; currentHandlers.delete(containerHandler); if (currentHandlers.size) return; /** * If no more handlers, remove the scroll listener too. */ const scrollListener = scrollListeners.get(container); scrollListeners.delete(container); if (scrollListener) { getEventTarget(container).removeEventListener("scroll", scrollListener); resizeListeners.get(container)?.(); window.removeEventListener("resize", scrollListener); } }; } const timelineCache = new Map(); function scrollTimelineFallback(options) { const currentTime = { value: 0 }; const cancel = scrollInfo((info) => { currentTime.value = info[options.axis].progress * 100; }, options); return { currentTime, cancel }; } function getTimeline({ source, container, ...options }) { const { axis } = options; if (source) container = source; const containerCache = timelineCache.get(container) ?? new Map(); timelineCache.set(container, containerCache); const targetKey = options.target ?? "self"; const targetCache = containerCache.get(targetKey) ?? {}; const axisKey = axis + (options.offset ?? []).join(","); if (!targetCache[axisKey]) { targetCache[axisKey] = !options.target && motionDom.supportsScrollTimeline() ? new ScrollTimeline({ source: container, axis }) : scrollTimelineFallback({ container, ...options }); } return targetCache[axisKey]; } function attachToAnimation(animation, options) { const timeline = getTimeline(options); return animation.attachTimeline({ timeline: options.target ? undefined : timeline, observe: (valueAnimation) => { valueAnimation.pause(); return motionDom.observeTimeline((progress) => { valueAnimation.time = valueAnimation.duration * progress; }, timeline); }, }); } /** * If the onScroll function has two arguments, it's expecting * more specific information about the scroll from scrollInfo. */ function isOnScrollWithInfo(onScroll) { return onScroll.length === 2; } function attachToFunction(onScroll, options) { if (isOnScrollWithInfo(onScroll)) { return scrollInfo((info) => { onScroll(info[options.axis].progress, info); }, options); } else { return motionDom.observeTimeline(onScroll, getTimeline(options)); } } function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) { if (!container) return motionUtils.noop; const optionsWithDefaults = { axis, container, ...options }; return typeof onScroll === "function" ? attachToFunction(onScroll, optionsWithDefaults) : attachToAnimation(onScroll, optionsWithDefaults); } const thresholds = { some: 0, all: 1, }; function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) { const elements = motionDom.resolveElements(elementOrSelector); const activeIntersections = new WeakMap(); const onIntersectionChange = (entries) => { entries.forEach((entry) => { const onEnd = activeIntersections.get(entry.target); /** * If there's no change to the intersection, we don't need to * do anything here. */ if (entry.isIntersecting === Boolean(onEnd)) return; if (entry.isIntersecting) { const newOnEnd = onStart(entry.target, entry); if (typeof newOnEnd === "function") { activeIntersections.set(entry.target, newOnEnd); } else { observer.unobserve(entry.target); } } else if (typeof onEnd === "function") { onEnd(entry); activeIntersections.delete(entry.target); } }); }; const observer = new IntersectionObserver(onIntersectionChange, { root, rootMargin, threshold: typeof amount === "number" ? amount : thresholds[amount], }); elements.forEach((element) => observer.observe(element)); return () => observer.disconnect(); } function getOriginIndex(from, total) { if (from === "first") { return 0; } else { const lastIndex = total - 1; return from === "last" ? lastIndex : lastIndex / 2; } } function stagger(duration = 0.1, { startDelay = 0, from = 0, ease } = {}) { return (i, total) => { const fromIndex = typeof from === "number" ? from : getOriginIndex(from, total); const distance = Math.abs(fromIndex - i); let delay = duration * distance; if (ease) { const maxDelay = total * duration; const easingFunction = motionUtils.easingDefinitionToFunction(ease); delay = easingFunction(delay / maxDelay) * maxDelay; } return startDelay + delay; }; } const createMinimalMotionComponent = /*@__PURE__*/ create.createMotionComponentFactory(); const m = /*@__PURE__*/ createDOMMotionComponentProxy(createMinimalMotionComponent); function useUnmountEffect(callback) { return React.useEffect(() => () => callback(), []); } /** * @public */ const domAnimation = { renderer: create.createDomVisualElement, ...create.animations, ...create.gestureAnimations, }; /** * @public */ const domMax = { ...domAnimation, ...create.drag, ...create.layout, }; /** * @public */ const domMin = { renderer: create.createDomVisualElement, ...create.animations, }; function useMotionValueEvent(value, event, callback) { /** * useInsertionEffect will create subscriptions before any other * effects will run. Effects run upwards through the tree so it * can be that binding a useLayoutEffect higher up the tree can * miss changes from lower down the tree. */ React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]); } function refWarning(name, ref) { motionUtils.warning(Boolean(!ref || ref.current), `You have defined a ${name} options but the provided ref is not yet hydrated, probably because it's defined higher up the tree. Try calling useScroll() in the same component as the ref, or setting its \`layoutEffect: false\` option.`); } const createScrollMotionValues = () => ({ scrollX: motionDom.motionValue(0), scrollY: motionDom.motionValue(0), scrollXProgress: motionDom.motionValue(0), scrollYProgress: motionDom.motionValue(0), }); function useScroll({ container, target, layoutEffect = true, ...options } = {}) { const values = create.useConstant(createScrollMotionValues); const useLifecycleEffect = layoutEffect ? create.useIsomorphicLayoutEffect : React.useEffect; useLifecycleEffect(() => { refWarning("target", target); refWarning("container", container); return scroll((_progress, { x, y, }) => { values.scrollX.set(x.current); values.scrollXProgress.set(x.progress); values.scrollY.set(y.current); values.scrollYProgress.set(y.progress); }, { ...options, container: container?.current || undefined, target: target?.current || undefined, }); }, [container, target, JSON.stringify(options.offset)]); return values; } /** * @deprecated useElementScroll is deprecated. Convert to useScroll({ container: ref }) */ function useElementScroll(ref) { if (process.env.NODE_ENV === "development") { motionUtils.warnOnce(false, "useElementScroll is deprecated. Convert to useScroll({ container: ref })."); } return useScroll({ container: ref }); } /** * @deprecated useViewportScroll is deprecated. Convert to useScroll() */ function useViewportScroll() { if (process.env.NODE_ENV !== "production") { motionUtils.warnOnce(false, "useViewportScroll is deprecated. Convert to useScroll()."); } return useScroll(); } /** * Combine multiple motion values into a new one using a string template literal. * * ```jsx * import { * motion, * useSpring, * useMotionValue, * useMotionTemplate * } from "framer-motion" * * function Component() { * const shadowX = useSpring(0) * const shadowY = useMotionValue(0) * const shadow = useMotionTemplate`drop-shadow(${shadowX}px ${shadowY}px 20px rgba(0,0,0,0.3))` * * return <motion.div style={{ filter: shadow }} /> * } * ``` * * @public */ function useMotionTemplate(fragments, ...values) { /** * Create a function that will build a string from the latest motion values. */ const numFragments = fragments.length; function buildValue() { let output = ``; for (let i = 0; i < numFragments; i++) { output += fragments[i]; const value = values[i]; if (value) { output += motionDom.isMotionValue(value) ? value.get() : value; } } return output; } return useCombineMotionValues(values.filter(motionDom.isMotionValue), buildValue); } function useSpring(source, options = {}) { const { isStatic } = React.useContext(create.MotionConfigContext); const getFromSource = () => (motionDom.isMotionValue(source) ? source.get() : source); // isStatic will never change, allowing early hooks return if (isStatic) { return useTransform(getFromSource); } const value = useMotionValue(getFromSource()); React.useInsertionEffect(() => { return motionDom.attachSpring(value, source, options); }, [value, JSON.stringify(options)]); return value; } function useAnimationFrame(callback) { const initialTimestamp = React.useRef(0); const { isStatic } = React.useContext(create.MotionConfigContext); React.useEffect(() => { if (isStatic) return; const provideTimeSinceStart = ({ timestamp, delta }) => { if (!initialTimestamp.current) initialTimestamp.current = timestamp; callback(timestamp - initialTimestamp.current, delta); }; motionDom.frame.update(provideTimeSinceStart, true); return () => motionDom.cancelFrame(provideTimeSinceStart); }, [callback]); } function useTime() { const time = useMotionValue(0); useAnimationFrame((t) => time.set(t)); return time; } /** * Creates a `MotionValue` that updates when the velocity of the provided `MotionValue` changes. * * ```javascript * const x = useMotionValue(0) * const xVelocity = useVelocity(x) * const xAcceleration = useVelocity(xVelocity) * ``` * * @public */ function useVelocity(value) { const velocity = useMotionValue(value.getVelocity()); const updateVelocity = () => { const latest = value.getVelocity(); velocity.set(latest); /** * If we still have velocity, schedule an update for the next frame * to keep checking until it is zero. */ if (latest) motionDom.frame.update(updateVelocity); }; useMotionValueEvent(value, "change", () => { // Schedule an update to this value at the end of the current frame. motionDom.frame.update(updateVelocity, false, true); }); return velocity; } class WillChangeMotionValue extends motionDom.MotionValue { constructor() { super(...arguments); this.isEnabled = false; } add(name) { if (motionDom.transformProps.has(name) || motionDom.acceleratedValues.has(name)) { this.isEnabled = true; this.update(); } } update() { this.set(this.isEnabled ? "transform" : "auto"); } } function useWillChange() { return create.useConstant(() => new WillChangeMotionValue("auto")); } /** * 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 */ !create.hasReducedMotionListener.current && create.initPrefersReducedMotion(); const [shouldReduceMotion] = React.useState(create.prefersReducedMotion.current); if (process.env.NODE_ENV !== "production") { motionUtils.warnOnce(shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected."); } /** * TODO See if people miss automatically updating shouldReduceMotion setting */ return shouldReduceMotion; } function useReducedMotionConfig() { const reducedMotionPreference = useReducedMotion(); const { reducedMotion } = React.useContext(create.MotionConfigContext); if (reducedMotion === "never") { return false; } else if (reducedMotion === "always") { return true; } else { return reducedMotionPreference; } } function stopAnimation(visualElement) { visualElement.values.forEach((value) => value.stop()); } function setVariants(visualElement, variantLabels) { const reversedLabels = [...variantLabels].reverse(); reversedLabels.forEach((key) => { const variant = visualElement.getVariant(key); variant && create.setTarget(visualElement, variant); if (visualElement.variantChildren) { visualElement.variantChildren.forEach((child) => { setVariants(child, variantLabels); }); } }); } function setValues(visualElement, definition) { if (Array.isArray(definition)) { return setVariants(visualElement, definition); } else if (typeof definition === "string") { return setVariants(visualElement, [definition]); } else { create.setTarget(visualElement, definition); } } /** * @public */ function animationControls() { /** * Track whether the host component has mounted. */ let hasMounted = false; /** * A collection of linked component animation controls. */ const subscribers = new Set(); const controls = { subscribe(visualElement) { subscribers.add(visualElement); return () => void subscribers.delete(visualElement); }, start(definition, transitionOverride) { motionUtils.invariant(hasMounted, "controls.start() should only be called after a component has mounted. Consider calling within a useEffect hook."); const animations = []; subscribers.forEach((visualElement) => { animations.push(create.animateVisualElement(visualElement, definition, { transitionOverride, })); }); return Promise.all(animations); }, set(definition) { motionUtils.invariant(hasMounted, "controls.set() should only be called after a component has mounted. Consider calling within a useEffect hook."); return subscribers.forEach((visualElement) => { setValues(visualElement, definition); }); }, stop() { subscribers.forEach((visualElement) => { stopAnimation(visualElement); }); }, mount() { hasMounted = true; return () => { hasMounted = false; controls.stop(); }; }, }; return controls; } function useAnimate() { const scope = create.useConstant(() => ({ current: null, // Will be hydrated by React animations: [], })); const animate = create.useConstant(() => createScopedAnimate(scope)); useUnmountEffect(() => { scope.animations.forEach((animation) => animation.stop()); }); return [scope, animate]; } function useAnimateMini() { const scope = create.useConstant(() => ({ current: null, // Will be hydrated by React animations: [], })); const animate = create.useConstant(() => createScopedWaapiAnimate(scope)); useUnmountEffect(() => { scope.animations.forEach((animation) => animation.stop()); }); return [scope, animate]; } /** * Creates `AnimationControls`, which can be used to manually start, stop * and sequence animations on one or more components. * * The returned `AnimationControls` should be passed to the `animate` property * of the components you want to animate. * * These components can then be animated with the `start` method. * * ```jsx * import * as React from 'react' * import { motion, useAnimation } from 'framer-motion' * * export function MyComponent(props) { * const controls = useAnimation() * * controls.start({ * x: 100, * transition: { duration: 0.5 }, * }) * * return <motion.div animate={controls} /> * } * ``` * * @returns Animation controller with `start` and `stop` methods * * @public */ function useAnimationControls() { const controls = create.useConstant(animationControls); create.useIsomorphicLayoutEffect(controls.mount, []); return controls; } const useAnimation = useAnimationControls; function usePresenceData() { const context = React.useContext(create.PresenceContext); return context ? context.custom : undefined; } /** * Attaches an event listener directly to the provided DOM element. * * Bypassing React's event system can be desirable, for instance when attaching non-passive * event handlers. * * ```jsx * const ref = useRef(null) * * useDomEvent(ref, 'wheel', onWheel, { passive: false }) * * return <div ref={ref} /> * ``` * * @param ref - React.RefObject that's been provided to the element you want to bind the listener to. * @param eventName - Name of the event you want listen for. * @param handler - Function to fire when receiving the event. * @param options - Options to pass to `Event.addEventListener`. * * @public */ function useDomEvent(ref, eventName, handler, options) { React.useEffect(() => { const element = ref.current; if (handler && element) { return create.addDomEvent(element, eventName, handler, options); } }, [ref, eventName, handler, options]); } /** * Can manually trigger a drag gesture on one or more `drag`-enabled `motion` components. * * ```jsx * const dragControls = useDragControls() * * function startDrag(event) { * dragControls.start(event, { snapToCursor: true }) * } * * return ( * <> * <div onPointerDown={startDrag} /> * <motion.div drag="x" dragControls={dragControls} /> * </> * ) * ``` * * @public */ class DragControls { constructor() { this.componentControls = new Set(); } /** * Subscribe a component's internal `VisualElementDragControls` to the user-facing API. * * @internal */ subscribe(controls) { this.componentControls.add(controls); return () => this.componentControls.delete(controls); } /** * Start a drag gesture on every `motion` component that has this set of drag controls * passed into it via the `dragControls` prop. * * ```jsx * dragControls.start(e, { * snapToCursor: true * }) * ``` * * @param event - PointerEvent * @param options - Options * * @public */ start(event, options) { this.componentControls.forEach((controls) => { controls.start(event.nativeEvent || event, options); }); } } const createDragControls = () => new DragControls(); /** * Usually, dragging is initiated by pressing down on a `motion` component with a `drag` prop * and moving it. For some use-cases, for instance clicking at an arbitrary point on a video scrubber, we * might want to initiate that dragging from a different component than the draggable one. * * By creating a `dragControls` using the `useDragControls` hook, we can pass this into * the draggable component's `dragControls` prop. It exposes a `start` method * that can start dragging from pointer events on other components. * * ```jsx * const dragControls = useDragControls() * * function startDrag(event) { * dragControls.start(event, { snapToCursor: true }) * } * * return ( * <> * <div onPointerDown={startDrag} /> * <motion.div drag="x" dragControls={dragControls} /> * </> * ) * ``` * * @public */ function useDragControls() { return create.useConstant(createDragControls); } /** * Checks if a component is a `motion` component. */ function isMotionComponent(component) { return (component !== null && typeof component === "object" && create.motionComponentSymbol in component); } /** * Unwraps a `motion` component and returns either a string for `motion.div` or * the React component for `motion(Component)`. * * If the component is not a `motion` component it returns undefined. */ function unwrapMotionComponent(component) { if (isMotionComponent(component)) { return component[create.motionComponentSymbol]; } return undefined; } function useInstantLayoutTransition() { return startTransition; } function startTransition(callback) { if (!create.rootProjectionNode.current) return; create.rootProjectionNode.current.isUpdating = false; create.rootProjectionNode.current.blockUpdate(); callback && callback(); } function useResetProjection() { const reset = React.useCallback(() => { const root = create.rootProjectionNode.current; if (!root) return; root.resetTree(); }, []); return reset; } /** * Cycles through a series of visual properties. Can be used to toggle between or cycle through animations. It works similar to `useState` in React. It is provided an initial array of possible states, and returns an array of two arguments. * * An index value can be passed to the returned `cycle` function to cycle to a specific index. * * ```jsx * import * as React from "react" * import { motion, useCycle } from "framer-motion" * * export const MyComponent = () => { * const [x, cycleX] = useCycle(0, 50, 100) * * return ( * <motion.div * animate={{ x: x }} * onTap={() => cycleX()} * /> * ) * } * ``` * * @param items - items to cycle through * @returns [currentState, cycleState] * * @public */ function useCycle(...items) { const index = React.useRef(0); const [item, setItem] = React.useState(items[index.current]); const runCycle = React.useCallback((next) => { index.current = typeof next !== "number" ? motionUtils.wrap(0, items.length, index.current + 1) : next; setItem(items[index.current]); }, // The array will change on each call, but by putting items.length at // the front of this array, we guarantee the dependency comparison will match up // eslint-disable-next-line react-hooks/exhaustive-deps [items.length, ...items]); return [item, runCycle]; } function useInView(ref, { root, margin, amount, once = false, initial = false, } = {}) { const [isInView, setInView] = React.useState(initial); React.useEffect(() => { if (!ref.current || (once && isInView)) return; const onEnter = () => { setInView(true); return once ? undefined : () => setInView(false); }; const options = { root: (root && root.current) || undefined, margin, amount, }; return inView(ref.current, onEnter, options); }, [root, ref, margin, once, amount]); return isInView; } function useInstantTransition() { const [forceUpdate, forcedRenderCount] = useForceUpdate(); const startInstantLayoutTransition = useInstantLayoutTransition(); const unlockOnFrameRef = React.useRef(-1); React.useEffect(() => { /** * Unblock after two animation frames, otherwise this will unblock too soon. */ motionDom.frame.postRender(() => motionDom.frame.postRender(() => { /** * If the callback has been called again after the effect * triggered this 2 frame delay, don't unblock animations. This * prevents the previous effect from unblocking the current * instant transition too soon. This becomes more likely when * used in conjunction with React.startTransition(). */ if (forcedRenderCount !== unlockOnFrameRef.current) return; motionUtils.MotionGlobalConfig.instantAnimations = false; })); }, [forcedRenderCount]); return (callback) => { startInstantLayoutTransition(() => { motionUtils.MotionGlobalConfig.instantAnimations = true; forceUpdate(); callback(); unlockOnFrameRef.current = forcedRenderCount + 1; }); }; } function disableInstantTransitions() { motionUtils.MotionGlobalConfig.instantAnimations = false; } const appearAnimationStore = new Map(); const appearComplete = new Map(); const appearStoreId = (elementId, valueName) => { const key = motionDom.transformProps.has(valueName) ? "transform" : valueName; return `${elementId}: ${key}`; }; function handoffOptimizedAppearAnimation(elementId, valueName, frame) { const storeId = appearStoreId(elementId, valueName); const optimisedAnimation = appearAnimationStore.get(storeId); if (!optimisedAnimation) { return null; } const { animation, startTime } = optimisedAnimation; function cancelAnimation() { window.MotionCancelOptimisedAnimation?.(elementId, valueName, frame); } /** * We can cancel the animation once it's finished now that we've synced * with Motion. * * Prefer onfinish over finished as onfinish is backwards compatible with * older browsers. */ animation.onfinish = cancelAnimation; if (startTime === null || window.MotionHandoffIsComplete?.(elementId)) { /** * If the startTime is null, this animation is the Paint Ready detection animation * and we can cancel it immediately without handoff. * * Or if we've already handed off the animation then we're now interrupting it. * In which case we need to cancel it. */ cancelAnimation(); return null; } else { return startTime; } } /** * A single time to use across all animations to manually set startTime * and ensure they're all in sync. */ let startFrameTime; /** * A dummy animation to detect when Chrome is ready to start * painting the page and hold off from triggering the real animation * until then. We only need one animation to detect paint ready. * * https://bugs.chromium.org/p/chromium/issues/detail?id=1406850 */ let readyAnimation; /** * Keep track of animations that were suspended vs cancelled so we * can easily resume them when we're done measuring layout. */ const suspendedAnimations = new Set(); function resumeSuspendedAnimations() { suspendedAnimations.forEach((data) => { data.animation.play(); data.animation.startTime = data.startTime; }); suspendedAnimations.clear(); } function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) { // Prevent optimised appear animations if Motion has already started animating. if (window.MotionIsMounted) { return; } const id = element.dataset[create.optimizedAppearDataId]; if (!id) return; window.MotionHandoffAnimation = handoffOptimizedAppearAnimation; const storeId = appearStoreId(id, name); if (!readyAnimation) { readyAnimation = motionDom.startWaapiAnimation(element, name, [keyframes[0], keyframes[0]], /** * 10 secs is basically just a super-safe duration to give Chrome * long enough to get the animation ready. */ { duration: 10000, ease: "linear" }); appearAnimationStore.set(storeId, { animation: readyAnimation, startTime: null, }); /** * If there's no readyAnimation then there's been no instantiation * of handoff animations. */ window.MotionHandoffAnimation = handoffOptimizedAppearAnimation; window.MotionHasOptimisedAnimation = (elementId, valueName) => { if (!elementId) return false; /** * Keep a map of elementIds that have started animating. We check * via ID instead of Element because of hydration errors and * pre-hydration checks. We also actively record IDs as they start * animating rather than simply checking for data-appear-id as * this attrbute might be present but not lead to an animation, for * instance if the element's appear animation is on a different * breakpoint. */ if (!valueName) { return appearComplete.has(elementId); } const animationId = appearStoreId(elementId, valueName); return Boolean(appearAnimationStore.get(animationId)); }; window.MotionHandoffMarkAsComplete = (elementId) => { if (appearComplete.has(elementId)) { appearComplete.set(elementId, true); } }; window.MotionHandoffIsComplete = (elementId) => { return appearComplete.get(elementId) === true; }; /** * We only need to cancel transform animations as * they're the ones that will interfere with the * layout animation measurements. */ window.MotionCancelOptimisedAnimation = (elementId, valueName, frame, canResume) => { const animationId = appearStoreId(elementId, valueName); const data = appearAnimationStore.get(animationId); if (!data) return; if (frame && canResume === undefined) { /** * Wait until the end of the subsequent frame to cancel the animation * to ensure we don't remove the animation before the main thread has * had a chance to resolve keyframes and render. */ frame.postRender(() => { frame.postRender(() => { data.animation.cancel(); }); }); } else { data.animation.cancel(); } if (frame && canResume) { suspendedAnimations.add(data); frame.render(resumeSuspendedAnimations); } else { appearAnimationStore.delete(animationId); /** * If there are no more animations left, we can remove the cancel function. * This will let us know when we can stop checking for conflicting layout animations. */ if (!appearAnimationStore.size) { window.MotionCancelOptimisedAnimation = undefined; } } }; window.MotionCheckAppearSync = (visualElement, valueName, value) => { const appearId = create.getOptimisedAppearId(visualElement); if (!appearId) return; const valueIsOptimised = window.MotionHasOptimisedAnimation?.(appearId, valueName); const externalAnimationValue = visualElement.props.values?.[valueName]; if (!valueIsOptimised || !externalAnimationValue) return; const removeSyncCheck = value.on("change", (latestValue) => { if (externalAnimationValue.get() !== latestValue) { window.MotionCancelOptimisedAnimation?.(appearId, valueName); removeSyncCheck(); } }); return removeSyncCheck; }; } const startAnimation = () => { readyAnimation.cancel(); const appearAnimation = motionDom.startWaapiAnimation(element, name, keyframes, options); /** * Record the time of the first started animation. We call performance.now() once * here and once in handoff to ensure we're getting * close to a frame-locked time. This keeps all animations in sync. */ if (startFrameTime === undefined) { startFrameTime = performance.now(); } appearAnimation.startTime = startFrameTime; appearAnimationStore.set(storeId, { animation: appearAnimation, startTime: startFrameTime, }); if (onReady) onReady(appearAnimation); }; appearComplete.set(id, false); if (readyAnimation.ready) { readyAnimation.ready.then(startAnimation).catch(motionUtils.noop); } else { startAnimation(); } } const createObject = () => ({}); class StateVisualElement extends create.VisualElement { constructor() { super(...arguments); this.measureInstanceViewportBox = create.createBox; } build() { } resetTransform() { } restoreTransform() { } removeValueFromRenderState() { } renderInstance() { } scrapeMotionValuesFromProps() { return createObject(); } getBaseTargetFromProps() { return undefined; } readValueFromInstance(_state, key, options) { return options.initialState[key] || 0; } sortInstanceNodePosition() { return 0; } } const useVisualState = create.makeUseVisualState({ scrapeMotionValuesFromProps: createObject, createRenderState: createObject, }); /** * This is not an officially supported API and may be removed * on any version. */ function useAnimatedState(initialState) { const [animationState, setAnimationState] = React.useState(initialState); const visualState = useVisualState({}, false); const element = create.useConstant(() => { return new StateVisualElement({ props: { onUpdate: (v) => { setAnimationState({ ...v }); }, }, visualState, presenceContext: null, }, { initialState }); }); React.useLayoutEffect(() => { element.mount({}); return () => element.unmount(); }, [element]); const startAnimation = create.useConstant(() => (animationDefinition) => { return create.animateVisualElement(element, animationDefinition); }); return [animationState, startAnimation]; } let id = 0; const AnimateSharedLayout = ({ children }) => { React__namespace.useEffect(() => { motionUtils.invariant(false, "AnimateSharedLayout is deprecated: https://www.framer.com/docs/guide-upgrade/##shared-layout-animations"); }, []); return (jsxRuntime.jsx(LayoutGroup, { id: create.useConstant(() => `asl-${id++}`), children: children })); }; // Keep things reasonable and avoid scale: Infinity. In practise we might need // to add another value, opacity, that could interpolate scaleX/Y [0,0.01] => [0,1] // to simply hide content at unreasonable scales. const maxScale = 100000; const invertScale = (scale) => scale > 0.001 ? 1 / scale : maxScale; let hasWarned = false; /** * Returns a `MotionValue` each for `scaleX` and `scaleY` that update with the inverse * of their respective parent scales. * * This is useful for undoing the distortion of content when scaling a parent component. * * By default, `useInvertedScale` will automatically fetch `scaleX` and `scaleY` from the nearest parent. * By passing other `MotionValue`s in as `useInvertedScale({ scaleX, scaleY })`, it will invert the output * of those instead. * * ```jsx * const MyComponent = () => { * const { scaleX, scaleY } = useInvertedScale() * return <motion.div style={{ scaleX, scaleY }} /> * } * ``` * * @deprecated */ function useInvertedScale(scale) { let parentScaleX = useMotionValue(1); let parentScaleY = useMotionValue(1); const { visualElement } = React.useContext(create.MotionContext); motionUtils.invariant(!!(scale || visualElement), "If no scale values are provided, useInvertedScale must be used within a child of another motion component."); motionUtils.warning(hasWarned, "useInvertedScale is deprecated and will be removed in 3.0. Use the layout prop instead."); hasWarned = true; if (scale) { parentScaleX = scale.scaleX || parentScaleX; parentScaleY = scale.scaleY || parentScaleY; } else if (visualElement) { parentScaleX = visualElement.getValue("scaleX", 1); parentScaleY = visualElement.getValue("scaleY", 1); } const scaleX = useTransform(parentScaleX, invertScale); const scaleY = useTransform(parentScaleY, invertScale); return { scaleX, scaleY }; } exports.FlatTree = create.FlatTree; exports.LayoutGroupContext = create.LayoutGroupContext; exports.MotionConfigContext = create.MotionConfigContext; exports.MotionContext = create.MotionContext; exports.PresenceContext = create.PresenceContext; exports.SwitchLayoutGroupContext = create.SwitchLayoutGroupContext; exports.VisualElement = create.VisualElement; exports.addPointerEvent = create.addPointerEvent; exports.addPointerInfo = create.addPointerInfo; exports.addScaleCorrector = create.addScaleCorrector; exports.animateVisualElement = create.animateVisualElement; exports.animations = create.animations; exports.buildTransform = create.buildTransform; exports.calcLength = create.calcLength; exports.createBox = create.createBox; exports.createRendererMotionComponent = create.createRendererMotionComponent; exports.delay = create.delay; exports.distance = create.distance; exports.distance2D = create.distance2D; exports.filterProps = create.filterProps; exports.isBrowser = create.isBrowser; exports.isValidMotionProp = create.isValidMotionProp; exports.makeUseVisualState = create.makeUseVisualState; exports.optimizedAppearDataAttribute = create.optimizedAppearDataAttribute; exports.resolveMotionValue = create.resolveMotionValue; exports.useIsPresent = create.useIsPresent; exports.useIsomorphicLayoutEffect = create.useIsomorphicLayoutEffect; exports.usePresence = create.usePresence; exports.visualElementStore = create.visualElementStore; Object.defineProperty(exports, "MotionGlobalConfig", { enumerable: true, get: function () { return motionUtils.MotionGlobalConfig; } }); exports.AnimatePresence = AnimatePresence; exports.AnimateSharedLayout = AnimateSharedLayout; exports.DeprecatedLayoutGroupContext = DeprecatedLayoutGroupContext; exports.DragControls = DragControls; exports.LayoutGroup = LayoutGroup; exports.LazyMotion = LazyMotion; exports.MotionConfig = MotionConfig; exports.Reorder = namespace; exports.WillChangeMotionValue = WillChangeMotionValue; exports.animate = animate; exports.animateMini = animateMini; exports.animationControls = animationControls; exports.createScopedAnimate = createScopedAnimate; exports.disableInstantTransitions = disableInstantTransitions; exports.domAnimation = domAnimation; exports.domMax = domMax; exports.domMin = domMin; exports.inView = inView; exports.isMotionComponent = isMotionComponent; exports.m = m; exports.motion = motion; exports.scroll = scroll; exports.scrollInfo = scrollInfo; exports.stagger = stagger; exports.startOptimizedAppearAnimation = startOptimizedAppearAnimation; exports.unwrapMotionComponent = unwrapMotionComponent; exports.useAnimate = useAnimate; exports.useAnimateMini = useAnimateMini; exports.useAnimation = useAnimation; exports.useAnimationControls = useAnimationControls; exports.useAnimationFrame = useAnimationFrame; exports.useCycle = useCycle; exports.useDeprecatedAnimatedState = useAnimatedState; exports.useDeprecatedInvertedScale = useInvertedScale; exports.useDomEvent = useDomEvent; exports.useDragControls = useDragControls; exports.useElementScroll = useElementScroll; exports.useForceUpdate = useForceUpdate; exports.useInView = useInView; exports.useInstantLayoutTransition = useInstantLayoutTransition; exports.useInstantTransition = useInstantTransition; exports.useMotionTemplate = useMotionTemplate; exports.useMotionValue = useMotionValue; exports.useMotionValueEvent = useMotionValueEvent; exports.usePresenceData = usePresenceData; exports.useReducedMotion = useReducedMotion; exports.useReducedMotionConfig = useReducedMotionConfig; exports.useResetProjection = useResetProjection; exports.useScroll = useScroll; exports.useSpring = useSpring; exports.useTime = useTime; exports.useTransform = useTransform; exports.useUnmountEffect = useUnmountEffect; exports.useVelocity = useVelocity; exports.useViewportScroll = useViewportScroll; exports.useWillChange = useWillChange; Object.keys(motionDom).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return motionDom[k]; } }); }); Object.keys(motionUtils).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return motionUtils[k]; } }); });