UNPKG

@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
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;