main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { isHTMLElement } from 'motion-dom';
import * as React from 'react';
import { useId, useRef, useContext, useInsertionEffect } from 'react';
import { MotionConfigContext } from '../../context/MotionConfigContext.mjs';
import { useComposedRefs } from '../../utils/use-composed-ref.mjs';
/**
* Measurement functionality has to be within a separate component
* to leverage snapshot lifecycle.
*/
class PopChildMeasure extends React.Component {
getSnapshotBeforeUpdate(prevProps) {
const element = this.props.childRef.current;
if (element && prevProps.isPresent && !this.props.isPresent) {
const parent = element.offsetParent;
const parentWidth = 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 = useId();
const ref = useRef(null);
const size = useRef({
width: 0,
height: 0,
top: 0,
left: 0,
right: 0,
});
const { nonce } = useContext(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.
*/
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 (jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, children: React.cloneElement(children, { ref: composedRef }) }));
}
export { PopChild };

View File

@@ -0,0 +1,64 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { useId, useMemo } from 'react';
import { PresenceContext } from '../../context/PresenceContext.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
import { PopChild } from './PopChild.mjs';
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, root }) => {
const presenceChildren = useConstant(newChildrenMap);
const id = useId();
let isReusedContext = true;
let context = 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 };
}
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.useEffect(() => {
!isPresent &&
!presenceChildren.size &&
onExitComplete &&
onExitComplete();
}, [isPresent]);
if (mode === "popLayout") {
children = (jsx(PopChild, { isPresent: isPresent, anchorX: anchorX, root: root, children: children }));
}
return (jsx(PresenceContext.Provider, { value: context, children: children }));
};
function newChildrenMap() {
return new Map();
}
export { PresenceChild };

View File

@@ -0,0 +1,166 @@
"use client";
import { jsx, Fragment } from 'react/jsx-runtime';
import { useMemo, useRef, useState, useContext } from 'react';
import { LayoutGroupContext } from '../../context/LayoutGroupContext.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
import { useIsomorphicLayoutEffect } from '../../utils/use-isomorphic-effect.mjs';
import { PresenceChild } from './PresenceChild.mjs';
import { usePresence } from './use-presence.mjs';
import { onlyElements, getChildKey } from './utils.mjs';
/**
* `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] = usePresence(propagate);
/**
* Filter any children that aren't ReactElements. We can only track components
* between renders with a props.key.
*/
const presentChildren = 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 = 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 = useRef(presentChildren);
/**
* Track which exiting children have finished animating out.
*/
const exitComplete = 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] = useState(presentChildren);
const [renderedChildren, setRenderedChildren] = useState(presentChildren);
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 } = useContext(LayoutGroupContext);
return (jsx(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 (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));
}) }));
};
export { AnimatePresence };

View File

@@ -0,0 +1,10 @@
"use client";
import { useContext } from 'react';
import { PresenceContext } from '../../context/PresenceContext.mjs';
function usePresenceData() {
const context = useContext(PresenceContext);
return context ? context.custom : undefined;
}
export { usePresenceData };

View File

@@ -0,0 +1,71 @@
"use client";
import { useContext, useId, useEffect, useCallback } from 'react';
import { PresenceContext } from '../../context/PresenceContext.mjs';
/**
* When a component is the child of `AnimatePresence`, it can use `usePresence`
* to access information about whether it's still present in the React tree.
*
* ```jsx
* import { usePresence } from "framer-motion"
*
* export const Component = () => {
* const [isPresent, safeToRemove] = usePresence()
*
* useEffect(() => {
* !isPresent && setTimeout(safeToRemove, 1000)
* }, [isPresent])
*
* return <div />
* }
* ```
*
* If `isPresent` is `false`, it means that a component has been removed the tree, but
* `AnimatePresence` won't really remove it until `safeToRemove` has been called.
*
* @public
*/
function usePresence(subscribe = true) {
const context = useContext(PresenceContext);
if (context === null)
return [true, null];
const { isPresent, onExitComplete, register } = context;
// It's safe to call the following hooks conditionally (after an early return) because the context will always
// either be null or non-null for the lifespan of the component.
const id = useId();
useEffect(() => {
if (subscribe) {
return register(id);
}
}, [subscribe]);
const safeToRemove = useCallback(() => subscribe && onExitComplete && onExitComplete(id), [id, onExitComplete, subscribe]);
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
}
/**
* Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
* There is no `safeToRemove` function.
*
* ```jsx
* import { useIsPresent } from "framer-motion"
*
* export const Component = () => {
* const isPresent = useIsPresent()
*
* useEffect(() => {
* !isPresent && console.log("I've been removed!")
* }, [isPresent])
*
* return <div />
* }
* ```
*
* @public
*/
function useIsPresent() {
return isPresent(useContext(PresenceContext));
}
function isPresent(context) {
return context === null ? true : context.isPresent;
}
export { isPresent, useIsPresent, usePresence };

View File

@@ -0,0 +1,14 @@
import { Children, isValidElement } from 'react';
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 `.$`
Children.forEach(children, (child) => {
if (isValidElement(child))
filtered.push(child);
});
return filtered;
}
export { getChildKey, onlyElements };

View File

@@ -0,0 +1,16 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { invariant } from 'motion-utils';
import * as React from 'react';
import { useConstant } from '../utils/use-constant.mjs';
import { LayoutGroup } from './LayoutGroup/index.mjs';
let id = 0;
const AnimateSharedLayout = ({ children }) => {
React.useEffect(() => {
invariant(false, "AnimateSharedLayout is deprecated: https://www.framer.com/docs/guide-upgrade/##shared-layout-animations");
}, []);
return (jsx(LayoutGroup, { id: useConstant(() => `asl-${id++}`), children: children }));
};
export { AnimateSharedLayout };

View File

@@ -0,0 +1,32 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { useContext, useRef, useMemo } from 'react';
import { LayoutGroupContext } from '../../context/LayoutGroupContext.mjs';
import { DeprecatedLayoutGroupContext } from '../../context/DeprecatedLayoutGroupContext.mjs';
import { useForceUpdate } from '../../utils/use-force-update.mjs';
import { nodeGroup } from '../../projection/node/group.mjs';
const shouldInheritGroup = (inherit) => inherit === true;
const shouldInheritId = (inherit) => shouldInheritGroup(inherit === true) || inherit === "id";
const LayoutGroup = ({ children, id, inherit = true }) => {
const layoutGroupContext = useContext(LayoutGroupContext);
const deprecatedLayoutGroupContext = useContext(DeprecatedLayoutGroupContext);
const [forceRender, key] = useForceUpdate();
const context = 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 = useMemo(() => ({ ...context.current, forceRender }), [key]);
return (jsx(LayoutGroupContext.Provider, { value: memoizedContext, children: children }));
};
export { LayoutGroup };

View File

@@ -0,0 +1,68 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { useState, useRef, useEffect } from 'react';
import { LazyContext } from '../../context/LazyContext.mjs';
import { loadFeatures } from '../../motion/features/load-features.mjs';
/**
* 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] = useState(!isLazyBundle(features));
const loadedRenderer = useRef(undefined);
/**
* If this is a synchronous load, load features immediately
*/
if (!isLazyBundle(features)) {
const { renderer, ...loadedFeatures } = features;
loadedRenderer.current = renderer;
loadFeatures(loadedFeatures);
}
useEffect(() => {
if (isLazyBundle(features)) {
features().then(({ renderer, ...loadedFeatures }) => {
loadFeatures(loadedFeatures);
loadedRenderer.current = renderer;
setIsLoaded(true);
});
}
}, []);
return (jsx(LazyContext.Provider, { value: { renderer: loadedRenderer.current, strict }, children: children }));
}
function isLazyBundle(features) {
return typeof features === "function";
}
export { LazyMotion };

View File

@@ -0,0 +1,48 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { useContext, useMemo } from 'react';
import { MotionConfigContext } from '../../context/MotionConfigContext.mjs';
import { loadExternalIsValidProp } from '../../render/dom/utils/filter-props.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
/**
* `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 && loadExternalIsValidProp(isValidProp);
/**
* Inherit props from any parent MotionConfig components
*/
config = { ...useContext(MotionConfigContext), ...config };
/**
* Don't allow isStatic to change between renders as it affects how many hooks
* motion components fire.
*/
config.isStatic = 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 = useMemo(() => config, [
JSON.stringify(config.transition),
config.transformPagePoint,
config.reducedMotion,
]);
return (jsx(MotionConfigContext.Provider, { value: context, children: children }));
}
export { MotionConfig };

View File

@@ -0,0 +1,53 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { invariant } from 'motion-utils';
import { forwardRef, useRef, useEffect } from 'react';
import { ReorderContext } from '../../context/ReorderContext.mjs';
import { motion } from '../../render/components/motion/proxy.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
import { checkReorder } from './utils/check-reorder.mjs';
function ReorderGroupComponent({ children, as = "ul", axis = "y", onReorder, values, ...props }, externalRef) {
const Component = useConstant(() => motion[as]);
const order = [];
const isReordering = useRef(false);
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));
}
},
};
useEffect(() => {
isReordering.current = false;
});
return (jsx(Component, { ...props, ref: externalRef, ignoreStrict: true, children: jsx(ReorderContext.Provider, { value: context, children: children }) }));
}
const ReorderGroup = /*@__PURE__*/ forwardRef(ReorderGroupComponent);
function getValue(item) {
return item.value;
}
function compareMin(a, b) {
return a.layout.min - b.layout.min;
}
export { ReorderGroup, ReorderGroupComponent };

View File

@@ -0,0 +1,34 @@
"use client";
import { jsx } from 'react/jsx-runtime';
import { isMotionValue } from 'motion-dom';
import { invariant } from 'motion-utils';
import { forwardRef, useContext } from 'react';
import { ReorderContext } from '../../context/ReorderContext.mjs';
import { motion } from '../../render/components/motion/proxy.mjs';
import { useConstant } from '../../utils/use-constant.mjs';
import { useMotionValue } from '../../value/use-motion-value.mjs';
import { useTransform } from '../../value/use-transform.mjs';
function useDefaultMotionValue(value, defaultValue = 0) {
return isMotionValue(value) ? value : useMotionValue(defaultValue);
}
function ReorderItemComponent({ children, style = {}, value, as = "li", onDrag, layout = true, ...props }, externalRef) {
const Component = useConstant(() => motion[as]);
const context = 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");
invariant(Boolean(context), "Reorder.Item must be a child of Reorder.Group", "reorder-item-child");
const { axis, registerItem, updateOrder } = context;
return (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__*/ forwardRef(ReorderItemComponent);
export { ReorderItem, ReorderItemComponent };

View File

@@ -0,0 +1,2 @@
export { ReorderGroup as Group } from './Group.mjs';
export { ReorderItem as Item } from './Item.mjs';

View File

@@ -0,0 +1,24 @@
import { mixNumber } from 'motion-dom';
import { moveItem } from 'motion-utils';
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 = mixNumber(nextLayout.min, nextLayout.max, 0.5);
if ((nextOffset === 1 && item.layout.max + offset > nextItemCenter) ||
(nextOffset === -1 && item.layout.min + offset < nextItemCenter)) {
return moveItem(order, index, index + nextOffset);
}
return order;
}
export { checkReorder };