Files
Basilosaurusrex f027651f9b main repo
2025-11-24 18:09:40 +01:00

3034 lines
107 KiB
JavaScript

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var featureBundle = require('./feature-bundle-v2Gb94eA.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);
/**
* Taken from https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx
*/
/**
* Set a given ref to a given value
* This utility takes care of different types of refs: callback refs and RefObject(s)
*/
function setRef(ref, value) {
if (typeof ref === "function") {
return ref(value);
}
else if (ref !== null && ref !== undefined) {
ref.current = value;
}
}
/**
* A utility to compose multiple refs together
* Accepts callback refs and RefObject(s)
*/
function composeRefs(...refs) {
return (node) => {
let hasCleanup = false;
const cleanups = refs.map((ref) => {
const cleanup = setRef(ref, node);
if (!hasCleanup && typeof cleanup === "function") {
hasCleanup = true;
}
return cleanup;
});
// React <19 will log an error to the console if a callback ref returns a
// value. We don't use ref cleanups internally so this will only happen if a
// user's ref callback returns a value, which we only expect if they are
// using the cleanup functionality added in React 19.
if (hasCleanup) {
return () => {
for (let i = 0; i < cleanups.length; i++) {
const cleanup = cleanups[i];
if (typeof cleanup === "function") {
cleanup();
}
else {
setRef(refs[i], null);
}
}
};
}
};
}
/**
* A custom hook that composes multiple refs
* Accepts callback refs and RefObject(s)
*/
function useComposedRefs(...refs) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return React__namespace.useCallback(composeRefs(...refs), refs);
}
/**
* 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, root }) {
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(featureBundle.MotionConfigContext);
const composedRef = useComposedRefs(ref, children?.ref);
/**
* 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;
const parent = root ?? document.head;
parent.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 (parent.contains(style)) {
parent.removeChild(style);
}
};
}, [isPresent]);
return (jsxRuntime.jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React__namespace.cloneElement(children, { ref: composedRef }) }));
}
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, root }) => {
const presenceChildren = featureBundle.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, root: root, children: children }));
}
return (jsxRuntime.jsx(featureBundle.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", root }) => {
const [isParentPresent, safeToRemove] = featureBundle.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 = featureBundle.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);
featureBundle.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(featureBundle.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, root: root, 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);
featureBundle.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(featureBundle.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(featureBundle.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;
featureBundle.loadFeatures(loadedFeatures);
}
React.useEffect(() => {
if (isLazyBundle(features)) {
features().then(({ renderer, ...loadedFeatures }) => {
featureBundle.loadFeatures(loadedFeatures);
loadedRenderer.current = renderer;
setIsLoaded(true);
});
}
}, []);
return (jsxRuntime.jsx(featureBundle.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 && featureBundle.loadExternalIsValidProp(isValidProp);
/**
* Inherit props from any parent MotionConfig components
*/
config = { ...React.useContext(featureBundle.MotionConfigContext), ...config };
/**
* Don't allow isStatic to change between renders as it affects how many hooks
* motion components fire.
*/
config.isStatic = featureBundle.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(featureBundle.MotionConfigContext.Provider, { value: context, children: children }));
}
const ReorderContext = React.createContext(null);
function createMotionProxy(preloadedFeatures, createVisualElement) {
if (typeof Proxy === "undefined") {
return featureBundle.createMotionComponent;
}
/**
* 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 factory = (Component, options) => {
return featureBundle.createMotionComponent(Component, options, preloadedFeatures, createVisualElement);
};
/**
* Support for deprecated`motion(Component)` pattern
*/
const deprecatedFactoryFunction = (Component, options) => {
if (process.env.NODE_ENV !== "production") {
motionUtils.warnOnce(false, "motion() is deprecated. Use motion.create() instead.");
}
return factory(Component, options);
};
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 factory;
/**
* If this element doesn't exist in the component cache, create it and cache.
*/
if (!componentCache.has(key)) {
componentCache.set(key, featureBundle.createMotionComponent(key, undefined, preloadedFeatures, createVisualElement));
}
return componentCache.get(key);
},
});
}
const motion = /*@__PURE__*/ createMotionProxy(featureBundle.featureBundle, featureBundle.createDomVisualElement);
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 = featureBundle.useConstant(() => motion[as]);
const order = [];
const isReordering = React.useRef(false);
motionUtils.invariant(Boolean(values), "Reorder.Group must be provided a values prop", "reorder-values");
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 = featureBundle.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(featureBundle.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.
*/
featureBundle.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 = featureBundle.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 = featureBundle.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", "reorder-item-child");
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 if (next.startsWith("<")) {
return Math.max(0, prev + parseFloat(next.slice(1)));
}
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 || "keyframes"];
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", "repeat-count-high");
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 featureBundle.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 featureBundle.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 featureBundle.SVGVisualElement(options)
: new featureBundle.HTMLVisualElement(options);
node.mount(element);
featureBundle.visualElementStore.set(element, node);
}
function createObjectVisualElement(subject) {
const options = {
presenceContext: null,
props: {},
visualState: {
renderState: {
output: {},
},
latestValues: {},
},
};
const node = new ObjectVisualElement(options);
node.mount(subject);
featureBundle.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(featureBundle.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.", "no-valid-elements");
for (let i = 0; i < numSubjects; i++) {
const thisSubject = subjects[i];
motionUtils.invariant(thisSubject !== null, "You're trying to perform an animation on null. Ensure that selectors are correctly finding elements and refs are correctly hydrated.", "animate-null");
const createVisualElement = thisSubject instanceof Element
? createDOMVisualElement
: createObjectVisualElement;
if (!featureBundle.visualElementStore.has(thisSubject)) {
createVisualElement(thisSubject);
}
const visualElement = featureBundle.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(...featureBundle.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 = [];
let animationOnComplete;
if (isSequence(subjectOrSequence)) {
animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope);
}
else {
// Extract top-level onComplete so it doesn't get applied per-value
const { onComplete, ...rest } = options || {};
if (typeof onComplete === "function") {
animationOnComplete = onComplete;
}
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, rest, scope);
}
const animation = new motionDom.GroupAnimationWithThen(animations);
if (animationOnComplete) {
animation.finished.then(animationOnComplete);
}
if (scope) {
scope.animations.push(animation);
animation.finished.then(() => {
motionUtils.removeItem(scope.animations, 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 elements provided.", "no-valid-elements");
/**
* 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();
/**
* 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: (time) => {
measure(element, options.target, info);
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(motionDom.frameData.timestamp);
}
motionDom.frame.preUpdate(notifyAll);
};
const notifyAll = () => {
for (const handler of containerHandlers) {
handler.notify();
}
};
const listener = () => motionDom.frame.read(measureAll);
scrollListeners.set(container, listener);
const target = getEventTarget(container);
window.addEventListener("resize", listener, { passive: true });
if (container !== document.documentElement) {
resizeListeners.set(container, motionDom.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.iterationDuration * 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();
}
const m = /*@__PURE__*/ createMotionProxy();
function useUnmountEffect(callback) {
return React.useEffect(() => () => callback(), []);
}
/**
* @public
*/
const domAnimation = {
renderer: featureBundle.createDomVisualElement,
...featureBundle.animations,
...featureBundle.gestureAnimations,
};
/**
* @public
*/
const domMax = {
...domAnimation,
...featureBundle.drag,
...featureBundle.layout,
};
/**
* @public
*/
const domMin = {
renderer: featureBundle.createDomVisualElement,
...featureBundle.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]);
}
const createScrollMotionValues = () => ({
scrollX: motionDom.motionValue(0),
scrollY: motionDom.motionValue(0),
scrollXProgress: motionDom.motionValue(0),
scrollYProgress: motionDom.motionValue(0),
});
const isRefPending = (ref) => {
if (!ref)
return false;
return !ref.current;
};
function useScroll({ container, target, ...options } = {}) {
const values = featureBundle.useConstant(createScrollMotionValues);
const scrollAnimation = React.useRef(null);
const needsStart = React.useRef(false);
const start = React.useCallback(() => {
scrollAnimation.current = 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,
});
return () => {
scrollAnimation.current?.();
};
}, [container, target, JSON.stringify(options.offset)]);
featureBundle.useIsomorphicLayoutEffect(() => {
needsStart.current = false;
if (isRefPending(container) || isRefPending(target)) {
needsStart.current = true;
return;
}
else {
return start();
}
}, [start]);
React.useEffect(() => {
if (needsStart.current) {
motionUtils.invariant(!isRefPending(container), "Container ref is defined but not hydrated", "use-scroll-ref");
motionUtils.invariant(!isRefPending(target), "Target ref is defined but not hydrated", "use-scroll-ref");
return start();
}
else {
return;
}
}, [start]);
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(featureBundle.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(featureBundle.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 featureBundle.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
*/
!featureBundle.hasReducedMotionListener.current && featureBundle.initPrefersReducedMotion();
const [shouldReduceMotion] = React.useState(featureBundle.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.", "reduced-motion-disabled");
}
/**
* TODO See if people miss automatically updating shouldReduceMotion setting
*/
return shouldReduceMotion;
}
function useReducedMotionConfig() {
const reducedMotionPreference = useReducedMotion();
const { reducedMotion } = React.useContext(featureBundle.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 && featureBundle.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 {
featureBundle.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(featureBundle.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 = featureBundle.useConstant(() => ({
current: null, // Will be hydrated by React
animations: [],
}));
const animate = featureBundle.useConstant(() => createScopedAnimate(scope));
useUnmountEffect(() => {
scope.animations.forEach((animation) => animation.stop());
scope.animations.length = 0;
});
return [scope, animate];
}
function useAnimateMini() {
const scope = featureBundle.useConstant(() => ({
current: null, // Will be hydrated by React
animations: [],
}));
const animate = featureBundle.useConstant(() => createScopedWaapiAnimate(scope));
useUnmountEffect(() => {
scope.animations.forEach((animation) => animation.stop());
});
return [scope, animate];
}
/**
* Creates `LegacyAnimationControls`, which can be used to manually start, stop
* and sequence animations on one or more components.
*
* The returned `LegacyAnimationControls` 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 = featureBundle.useConstant(animationControls);
featureBundle.useIsomorphicLayoutEffect(controls.mount, []);
return controls;
}
const useAnimation = useAnimationControls;
function usePresenceData() {
const context = React.useContext(featureBundle.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 featureBundle.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);
});
}
/**
* Cancels a drag gesture.
*
* ```jsx
* dragControls.cancel()
* ```
*
* @public
*/
cancel() {
this.componentControls.forEach((controls) => {
controls.cancel();
});
}
/**
* Stops a drag gesture.
*
* ```jsx
* dragControls.stop()
* ```
*
* @public
*/
stop() {
this.componentControls.forEach((controls) => {
controls.stop();
});
}
}
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 featureBundle.useConstant(createDragControls);
}
/**
* Checks if a component is a `motion` component.
*/
function isMotionComponent(component) {
return (component !== null &&
typeof component === "object" &&
featureBundle.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[featureBundle.motionComponentSymbol];
}
return undefined;
}
function useInstantLayoutTransition() {
return startTransition;
}
function startTransition(callback) {
if (!featureBundle.rootProjectionNode.current)
return;
featureBundle.rootProjectionNode.current.isUpdating = false;
featureBundle.rootProjectionNode.current.blockUpdate();
callback && callback();
}
function useResetProjection() {
const reset = React.useCallback(() => {
const root = featureBundle.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;
}
function usePageInView() {
const [isInView, setIsInView] = React.useState(true);
React.useEffect(() => {
const handleVisibilityChange = () => setIsInView(!document.hidden);
if (document.hidden) {
handleVisibilityChange();
}
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
return isInView;
}
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[featureBundle.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 = featureBundle.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 featureBundle.VisualElement {
constructor() {
super(...arguments);
this.measureInstanceViewportBox = featureBundle.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 = featureBundle.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 = featureBundle.useConstant(() => {
return new StateVisualElement({
props: {
onUpdate: (v) => {
setAnimationState({ ...v });
},
},
visualState,
presenceContext: null,
}, { initialState });
});
React.useLayoutEffect(() => {
element.mount({});
return () => element.unmount();
}, [element]);
const startAnimation = featureBundle.useConstant(() => (animationDefinition) => {
return featureBundle.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: featureBundle.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(featureBundle.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.LayoutGroupContext = featureBundle.LayoutGroupContext;
exports.MotionConfigContext = featureBundle.MotionConfigContext;
exports.MotionContext = featureBundle.MotionContext;
exports.PresenceContext = featureBundle.PresenceContext;
exports.SwitchLayoutGroupContext = featureBundle.SwitchLayoutGroupContext;
exports.VisualElement = featureBundle.VisualElement;
exports.addPointerEvent = featureBundle.addPointerEvent;
exports.addPointerInfo = featureBundle.addPointerInfo;
exports.addScaleCorrector = featureBundle.addScaleCorrector;
exports.animateVisualElement = featureBundle.animateVisualElement;
exports.animations = featureBundle.animations;
exports.buildTransform = featureBundle.buildTransform;
exports.calcLength = featureBundle.calcLength;
exports.createBox = featureBundle.createBox;
exports.delay = featureBundle.delay;
exports.distance = featureBundle.distance;
exports.distance2D = featureBundle.distance2D;
exports.filterProps = featureBundle.filterProps;
exports.isBrowser = featureBundle.isBrowser;
exports.isValidMotionProp = featureBundle.isValidMotionProp;
exports.makeUseVisualState = featureBundle.makeUseVisualState;
exports.optimizedAppearDataAttribute = featureBundle.optimizedAppearDataAttribute;
exports.resolveMotionValue = featureBundle.resolveMotionValue;
exports.useIsPresent = featureBundle.useIsPresent;
exports.useIsomorphicLayoutEffect = featureBundle.useIsomorphicLayoutEffect;
exports.usePresence = featureBundle.usePresence;
exports.visualElementStore = featureBundle.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.PopChild = PopChild;
exports.PresenceChild = PresenceChild;
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.startOptimizedAppearAnimation = startOptimizedAppearAnimation;
exports.unwrapMotionComponent = unwrapMotionComponent;
exports.useAnimate = useAnimate;
exports.useAnimateMini = useAnimateMini;
exports.useAnimation = useAnimation;
exports.useAnimationControls = useAnimationControls;
exports.useAnimationFrame = useAnimationFrame;
exports.useComposedRefs = useComposedRefs;
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.usePageInView = usePageInView;
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]; }
});
});