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

99 lines (98 loc) 4.42 kB
/** Function returned by {@link useCycle} for advancing or jumping the index. */ export type Cycle = (next?: number) => void; /** * State returned by {@link useCycle}: an object with a reactive `.current` * getter and a `cycle` function. Both reads and writes flow through the * same object, so consumers don't need to destructure (which would * snapshot `.current` and lose reactivity under runes). */ export type CycleState<T> = { readonly current: T; cycle: Cycle; }; /** * Function that returns the current items list, used by the reactive * overload of {@link useCycle}. The function is re-invoked on every read * so changes to the underlying reactive source propagate automatically. */ export type CycleItemsGetter<T> = () => readonly T[]; /** * Cycles through a series of values. Mirrors framer-motion's `useCycle`. * * Two call forms: * * - **Varargs**`useCycle(...items)` — items are captured once and stay * fixed for the cycle's lifetime. Matches React framer-motion's signature. * - **Reactive getter**`useCycle(() => items)` — items are read on every * access, so passing a `$state`/`$derived` source lets the cycle pick up * list changes without recreating it. * * In both forms: * * - `state.current` is reactive — read it in templates / `$derived` / `$effect` * and it tracks both index changes and (in the getter form) item changes. * - `state.cycle()` advances to the next item (wrapping at the end). * - `state.cycle(i)` jumps to index `i`. The index is stored as-given; * `.current` then clamps on read so any out-of-range index — negative, * overflow, or items shrinking underneath the reactive-getter form — * resolves to the nearest valid edge (`items[0]` or `items[length - 1]`) * instead of `undefined`. This is a defensive divergence from React * framer-motion (which returns `items[i]`, possibly undefined) so the * reactive form stays safe and `.current` always honors its `T` type. * If the reactive getter ever returns an empty list, `.current` throws. * - Calls that resolve to the current index are no-ops, matching React * `useState`'s `Object.is` bail-out. * * Two deliberate divergences from React's `useCycle`: * * 1. Return shape — React's `[value, cycle]` tuple can't survive * destructuring under Svelte 5 runes (snapshots the value, loses * reactivity), so we return `{ current, cycle }`. * 2. Out-of-range reads always clamp (see above) instead of returning * `items[i]` undefined. * * Otherwise 1:1 with React, including same-index no-op bail-out and * the `wrap(0, length, index + 1)` advance semantics. * * Ambiguity: `useCycle(fn)` with a single function value is treated as the * reactive overload, not as a single-item cycle. To cycle through one * function value, use `useCycle(() => [fn])` or just call it directly — * a single-item cycle is a no-op anyway. * * @see https://motion.dev/docs/react-use-cycle * * @example Static varargs * ```svelte * <script lang="ts"> * import { motion, useCycle } from '@humanspeak/svelte-motion' * * const x = useCycle(0, 50, 100) * </script> * * <motion.div animate={{ x: x.current }} onclick={() => x.cycle()} /> * ``` * * @example Reactive items * ```svelte * <script lang="ts"> * let { labels }: { labels: string[] } = $props() * const variant = useCycle(() => labels) * </script> * * <motion.div animate={variant.current} onclick={() => variant.cycle()} /> * ``` * * @param itemsGetter Function returning the current items list; re-invoked * on every `.current` read so reactive sources propagate. Use this form * when items can change between mount and unmount. (Reactive overload.) * @param items One or more values to cycle through. Captured once at call * time and fixed for the cycle's lifetime. (Varargs overload.) * @returns A `CycleState<T>` with a reactive `.current` getter and a * `cycle(next?: number)` advance/jump function. `.current` always * honors its `T` type by clamping out-of-range indexes; it throws * if a reactive getter empties the items list mid-cycle. * `.cycle()` throws on non-integer (`NaN`, `1.5`, `Infinity`) * indexes and returns early as a no-op on empty items. */ export declare function useCycle<T>(itemsGetter: CycleItemsGetter<T>): CycleState<T>; export declare function useCycle<T>(...items: T[]): CycleState<T>;