@humanspeak/svelte-motion
Version:
Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values
232 lines (231 loc) • 9.74 kB
TypeScript
import type { AnimatePresenceMode, MotionExit, MotionTransition } from '../types';
import { type DOMKeyframesDefinition } from 'motion';
/**
* A measured `popLayout` box in the same coordinate space used by Motion's
* upstream `PopChild`.
*/
export type PopLayoutSnapshot = {
/** Width of the exiting element in pixels. */
width: number;
/** Height of the exiting element in pixels. */
height: number;
/** Offset from the top of the element's offset parent. */
top: number;
/** Offset from the left of the element's offset parent. */
left: number;
/** Offset from the right of the element's offset parent. */
right: number;
/** Offset from the bottom of the element's offset parent. */
bottom: number;
/** Resolved text direction used by horizontal anchoring. */
direction: string;
};
/**
* Anchor used to preserve horizontal positioning in `mode="popLayout"`.
*/
export type PopLayoutAnchorX = 'left' | 'right';
/**
* Anchor used to preserve vertical positioning in `mode="popLayout"`.
*/
export type PopLayoutAnchorY = 'top' | 'bottom';
/**
* Measure an element for `mode="popLayout"` using upstream Motion's coordinate
* model: offsets are captured relative to `offsetParent`, not the viewport.
* Ancestor scroll is intentionally not subtracted here because upstream
* `PopChild` snapshots raw `offsetTop`/`offsetLeft`; any Svelte clone fallback
* that breaks that containing-block relationship must compensate at the clone
* application layer instead.
*
* @param element The exiting element to measure while it is still in layout.
* @param computedStyle The computed style for the exiting element.
* @returns A snapshot that can be reapplied to an absolutely positioned exit node.
*/
export declare const measurePopLayoutSnapshot: (element: HTMLElement, computedStyle?: CSSStyleDeclaration) => PopLayoutSnapshot;
/**
* Convert a `popLayout` snapshot to absolute-positioned clone styles.
*
* @param snapshot The snapshot captured before the child exited.
* @param anchorX The horizontal edge to preserve.
* @param anchorY The vertical edge to preserve.
* @returns Inline styles equivalent to upstream `PopChild`'s injected rule.
*/
export declare const resolvePopLayoutStyles: (snapshot: PopLayoutSnapshot, anchorX?: PopLayoutAnchorX, anchorY?: PopLayoutAnchorY) => Partial<CSSStyleDeclaration>;
/**
* Resolves an exiting child's keyframes with the nearest
* `<AnimatePresence custom>` value.
*
* @param custom Data supplied by `<AnimatePresence custom={...}>`.
* @returns The resolved exit keyframes, or `undefined` when no exit applies.
*/
export type PresenceExitResolver = (custom: unknown) => DOMKeyframesDefinition | undefined;
/**
* Presence context API used by `AnimatePresence` and motion elements.
* Consumers register/unregister children and provide size/style snapshots
* so we can clone and animate them out after removal.
*/
export type AnimatePresenceContext = {
/** When false, children skip their enter animation on initial mount. */
initial: boolean;
/** Animation coordination mode: 'sync', 'wait', or 'popLayout'. */
mode: AnimatePresenceMode;
/** Latest data passed via `<AnimatePresence custom>`. */
readonly custom: unknown;
/** Read the latest data passed via `<AnimatePresence custom>`. */
getCustom: () => unknown;
/**
* Update the latest data passed via `<AnimatePresence custom>`.
*
* @param custom Data supplied by the parent presence boundary.
*/
setCustom: (custom: unknown) => void;
/**
* Returns true if a child with the given key should animate its enter.
* Returns false only during first render when initial={false} AND the key has never been seen.
* Re-entries (after exit) always animate.
*/
shouldAnimateEnter: (key: string) => boolean;
/**
* For mode='wait': Returns true if enters should be blocked by an exiting
* or currently-present sibling.
* Motion elements should delay their enter animation until this returns false.
*/
isEnterBlocked: (key?: string) => boolean;
/**
* For mode='wait': Register a callback to be invoked when enters are unblocked.
* Returns an unsubscribe function.
*/
onEnterUnblocked: (callback: () => void) => () => void;
/** Called when all exit animations complete (optional). */
onExitComplete?: () => void;
/** Register a child element and its exit definition. */
registerChild: (key: string, element: HTMLElement, exit?: MotionExit, mergedTransition?: MotionTransition, resolveExit?: PresenceExitResolver) => void;
/** Update the last known rect/style snapshot for a registered child. */
updateChildState: (key: string, rect: DOMRect, computedStyle: CSSStyleDeclaration) => void;
/** Update the last captured mid-animation style values for a child. */
updateChildAnimatedStyle: (key: string, opacity: string, transform: string) => void;
/** Unregister a child. If it has an exit, clone and animate it out. */
unregisterChild: (key: string) => void;
/**
* @internal Used by `PresenceChild` to participate in the same exit
* accounting as the clone-based motion-element exit path. Increments the
* in-flight exit counter and applies mode='wait' enter blocking. Not
* intended for direct consumer use.
*/
notifyExitStart: () => void;
/**
* @internal Pairs with `notifyExitStart`. Decrements the in-flight exit
* counter, fires `onExitComplete` once it reaches zero, and unblocks
* pending enters in mode='wait'. Not intended for direct consumer use.
*/
notifyExitComplete: () => void;
};
/**
* Create a new `AnimatePresence` context instance.
*
* - Maintains a registry of children keyed by a unique string.
* - On unregister, if a child has an `exit` definition, a visual clone is
* created at its last known position and animated using Motion.
*
* @param context Optional callbacks, e.g. `onExitComplete`.
* @returns An object implementing the `AnimatePresenceContext` API.
*/
/**
* Create a new `AnimatePresence` context instance.
*
* Manages child registration and on unregistration performs exit animation by
* cloning the DOM node, freezing its last known rect/styles, and animating
* the clone using Motion. Calls `onExitComplete` once when all exits settle.
*
* @param context Optional callbacks, for example `onExitComplete`.
* @returns A presence context with register/update/unregister APIs.
*/
export declare const createAnimatePresenceContext: (context: {
initial?: boolean;
mode?: AnimatePresenceMode;
onExitComplete?: () => void;
custom?: unknown;
getCustom?: () => unknown;
}) => AnimatePresenceContext;
/**
* Get the current `AnimatePresence` context from Svelte component context.
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const getAnimatePresenceContext: () => AnimatePresenceContext | undefined;
/**
* Set the `AnimatePresence` context into Svelte component context.
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const setAnimatePresenceContext: (context: AnimatePresenceContext) => void;
/**
* Get the current presence depth from Svelte component context.
*
* Returns undefined if not inside an AnimatePresence, or the depth level
* where 0 means direct child of AnimatePresence.
*
* @returns The current depth level (0 for direct children), or undefined if outside AnimatePresence.
* @example
* ```ts
* const depth = getPresenceDepth()
* if (depth === 0) {
* // Direct child of AnimatePresence - key prop required
* }
* ```
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const getPresenceDepth: () => number | undefined;
/**
* Set the presence depth in Svelte component context.
*
* AnimatePresence sets this to 0, and each motion element increments it
* for its descendants so only direct children (depth 0) require keys.
*
* @param depth - The nesting depth to set (0 for direct children of AnimatePresence).
* @returns void
* @example
* ```ts
* // In AnimatePresence component
* setPresenceDepth(0)
*
* // In nested motion element
* const currentDepth = getPresenceDepth() ?? 0
* setPresenceDepth(currentDepth + 1)
* ```
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const setPresenceDepth: (depth: number) => void;
/**
* Per-`PresenceChild` Svelte context payload. Read by the `useIsPresent` and
* `usePresence` hooks (and consulted by motion elements so they can opt out of
* the outer `AnimatePresence` clone path when a `PresenceChild` is driving
* the exit themselves).
*
* `isPresent` is exposed as a getter so consumers see live updates as the
* wrapper toggles between mounted, exiting, and re-entered states.
*/
export type PresenceChildContext = {
/** Reactive flag — `true` while present, `false` once the exit hold begins. */
readonly isPresent: boolean;
/**
* Signal that the consumer's exit work is complete. Triggers actual
* unmount and decrements the parent `AnimatePresenceContext` exit count.
* Idempotent and versioned (calls from a canceled exit cycle are no-ops).
*/
safeToRemove: () => void;
};
/**
* Get the nearest `PresenceChild` context from Svelte component context, or
* `undefined` if the caller is not wrapped in one.
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const getPresenceChildContext: () => PresenceChildContext | undefined;
/**
* Install a `PresenceChild` context for descendants.
*
* Note: Trivial wrapper - ignored for coverage.
*/
export declare const setPresenceChildContext: (context: PresenceChildContext) => void;