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

157 lines (156 loc) 7.01 kB
import { type MotionValue } from 'motion-dom'; import { type Readable } from 'svelte/store'; /** * A motion-dom `MotionValue<T>` augmented with the two affordances every * Tier-2 hook in this library exposes for Svelte 5 consumers: * * - `current` — a `$state`-backed reactive getter. Reads inside templates, * `$derived`, and `$effect` track changes automatically without going * through `.subscribe()`. * - `subscribe(run)` — Svelte readable store contract. Calls `run(value)` * once synchronously on subscribe, then on every change. Lets the same * value drive `$value` template syntax, `svelte/store`'s `get()`, and * anything else that expects a readable. * * Every Wave 3 hook (`useMotionValue`, `useTransform`, `useScroll`, * `useTime`, `useVelocity`, `useMotionTemplate`) and `useSpring` returns a * value of this shape. The point is to keep one shared surface across all * motion-value-producing hooks instead of repeating the wiring in each. */ export type AugmentedMotionValue<T> = Omit<MotionValue<T>, 'current'> & { /** Reactive read in Svelte 5 templates / `$derived` / `$effect`. */ readonly current: T; /** Svelte readable store compatibility. */ subscribe: (run: (value: T) => void) => () => void; }; /** * Detects a Svelte readable store, excluding motion-dom `MotionValue` * instances (which also expose `subscribe`-shaped APIs in some versions). * Used by hook factories that accept either a `MotionValue` or a readable * store as a source. * * @template T The value type the readable emits. * @param value Any value to test. * @returns Whether the value is a Svelte readable (and not a `MotionValue`). * * @example * ```ts * import { writable } from 'svelte/store' * import { motionValue } from 'motion-dom' * * isSvelteReadable(writable(0)) // true * isSvelteReadable(motionValue(0)) // false (a MotionValue, not a readable) * isSvelteReadable({ subscribe: 'nope' }) // false (subscribe isn't callable) * ``` */ export declare const isSvelteReadable: <T = unknown>(value: unknown) => value is Readable<T>; /** * Synchronously samples a source: returns `T` directly, calls `.get()` on * a `MotionValue`, or `svelte/store`'s `get()` on a readable. Used by hook * factories to seed an initial value before any subscription is established. * * @template T The value type. * @param source A plain value, a `MotionValue`, or a Svelte readable. * @returns The current value of the source. * * @example * ```ts * sampleSource(42) // 42 * sampleSource(motionValue(7)) // 7 * sampleSource(writable(11)) // 11 * ``` */ export declare const sampleSource: <T>(source: T | MotionValue<T> | Readable<T>) => T; /** * Layer Svelte 5 affordances onto a motion-dom `MotionValue`: * * 1. A `$state`-tracked `.current` accessor. motion-dom writes to its own * `current` field on every frame via an internal setter; we redirect that * setter through `$state` so templates and `$derived` / `$effect` re-run * automatically. Same-value writes are skipped (motion-dom can call the * setter at rest, and `$state` would itself dedupe, but the explicit * check avoids the extra accessor work). * * 2. A `.subscribe(run)` shim implementing the Svelte readable store * contract: synchronous initial emit, then re-emit on every change. * Forwarded to motion-dom's `.on('change', …)` event bus. * * 3. `.destroy()` is wrapped so a caller-supplied `dispose` runs once before * motion-dom's own teardown (and only once, guarded against re-entrant * or duplicate destroy calls). * * The returned reference is the same `MotionValue` passed in, only retyped — * so identity checks (`isMotionValue`, `===`) still work and motion-dom's * own machinery (animation, follow, composition) is untouched. * * **Call once per MotionValue.** This function mutates the value (rewrites * `current`, `subscribe`, `destroy`). Calling it twice would re-define the * accessors and the first call's `dispose` would be discarded. * * @template T The value type — typically `number` or `string`. * @param value The motion-dom `MotionValue` to augment. * @param dispose Optional cleanup that runs once when `.destroy()` is first called (before motion-dom's internal teardown). Defaults to a no-op. * @returns The same `MotionValue` typed as {@link AugmentedMotionValue}. * * @example * ```svelte * <script lang="ts"> * import { motionValue } from 'motion-dom' * import { augmentMotionValue } from './augmentMotionValue.svelte.js' * * const mv = motionValue(0) * const aug = augmentMotionValue(mv, () => console.log('disposed')) * * $effect(() => () => aug.destroy()) * </script> * * <div style="transform: translateX({aug.current}px)">{aug.current}</div> * ``` */ export declare const augmentMotionValue: <T>(value: MotionValue<T>, dispose?: VoidFunction) => AugmentedMotionValue<T>; /** * Bridges a Svelte `Readable<T>` into a motion-dom `MotionValue<T>` that * mirrors the readable's emissions, so motion-dom primitives (`mapValue`, * `transformValue`, `attachFollow`, `getVelocity`, etc.) that only accept * `MotionValue` can track readable-shaped sources. * * The bridge: * 1. Seeds via `get(source)` so the initial value is correct synchronously. * 2. Subscribes to the readable, skipping the *synchronous initial emit* * (Svelte readables always fire one on subscribe, but the seed already * has it — without the skip the bridge would double-write on attach). * 3. Optionally coerces each emit through `coerce` — useful for unit-string * sources (e.g. `"100px"``100`). * * Returns the bridge value and a `dispose` that tears down the subscription * and destroys the bridge MV. Callers register `dispose` with their lifecycle * ($effect cleanup or the augmented `destroy`'s `dispose` slot). * * @template TIn The readable's emit type (often `number | string`). * @template TOut The bridge MotionValue's value type (often `number`). * @param source A Svelte readable store. * @param coerce Optional transform applied to each emit (and the initial seed). Identity by default. * @returns A `MotionValue<TOut>` mirroring the readable + a dispose function. * * @example * ```ts * import { writable } from 'svelte/store' * * // Identity bridge — readable<number> → motionValue<number> * const w = writable(0) * const { value: mv, dispose } = bridgeReadableToMotionValue(w) * w.set(50); mv.get() === 50 * * // Coerce bridge — readable<string> → motionValue<number> * const w2 = writable('100px') * const bridge = bridgeReadableToMotionValue<string, number>(w2, parseFloat) * bridge.value.get() === 100 * * // Always pair with dispose() in your $effect cleanup. * $effect(() => () => dispose()) * ``` */ export declare const bridgeReadableToMotionValue: <TIn, TOut = TIn>(source: Readable<TIn>, coerce?: (v: TIn) => TOut) => { value: MotionValue<TOut>; dispose: VoidFunction; };