@humanspeak/svelte-motion 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  import {
9
9
  filterReducedMotionKeyframes,
10
10
  useReducedMotionConfig
11
- } from '../utils/reducedMotionConfig'
11
+ } from '../utils/reducedMotionConfig.svelte'
12
12
  import type {
13
13
  MotionProps,
14
14
  MotionTransition,
@@ -29,7 +29,7 @@
29
29
  import { attachWhileTap } from '../utils/interaction'
30
30
  import { attachWhileHover } from '../utils/hover'
31
31
  import { attachWhileFocus } from '../utils/focus'
32
- import { attachWhileInView } from '../utils/inView'
32
+ import { attachWhileInView } from '../utils/inView.svelte'
33
33
  import {
34
34
  measureRect,
35
35
  computeFlipTransforms,
@@ -57,7 +57,7 @@
57
57
  setCustomContext,
58
58
  getCustomContext
59
59
  } from '../components/variantContext.context'
60
- import { get, writable } from 'svelte/store'
60
+ import { writable } from 'svelte/store'
61
61
  import {
62
62
  transformSVGPathProperties,
63
63
  computeNormalizedSVGInitialAttrs,
@@ -130,11 +130,11 @@
130
130
  let isLoaded = $state<'mounting' | 'initial' | 'ready' | 'animated'>('mounting')
131
131
  let dataPath = $state<number>(-1)
132
132
  const motionConfig = $derived(getMotionConfig())
133
- const reducedMotionStore = useReducedMotionConfig()
134
- // Seed synchronously so the first render filters keyframes correctly —
135
- // otherwise transforms could flash before the subscribe effect runs.
136
- let reducedMotion = $state(get(reducedMotionStore))
137
- $effect(() => reducedMotionStore.subscribe((value) => (reducedMotion = value)))
133
+ const reducedMotionState = useReducedMotionConfig()
134
+ // `.current` is $state-backed inside reducedMotionState; tracking it via
135
+ // $derived makes `reducedMotion` re-evaluate whenever the OS preference
136
+ // or `<MotionConfig reducedMotion>` policy changes.
137
+ const reducedMotion = $derived(reducedMotionState.current)
138
138
 
139
139
  // Get presence context to check if we're inside AnimatePresence
140
140
  const context = getAnimatePresenceContext()
package/dist/index.d.ts CHANGED
@@ -10,19 +10,21 @@ export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition,
10
10
  export { useAnimate } from './utils/animate.svelte';
11
11
  export type { AnimationScope } from './utils/animate.svelte';
12
12
  export { useAnimationFrame } from './utils/animationFrame';
13
- export { useCycle } from './utils/cycle';
14
- export type { Cycle, CycleState } from './utils/cycle';
13
+ export { useCycle } from './utils/cycle.svelte';
14
+ export type { Cycle, CycleState } from './utils/cycle.svelte';
15
15
  export { createDragControls } from './utils/dragControls';
16
- export { useInView } from './utils/inView';
17
- export type { UseInViewOptions } from './utils/inView';
16
+ export { useInView } from './utils/inView.svelte';
17
+ export type { InViewState, UseInViewOptions } from './utils/inView.svelte';
18
18
  export { useMotionTemplate } from './utils/motionTemplate';
19
19
  export { useMotionValue } from './utils/motionValue';
20
20
  export type { MotionValue } from './utils/motionValue';
21
21
  export { useMotionValueEvent } from './utils/motionValueEvent';
22
- export { useReducedMotion } from './utils/reducedMotion';
23
- export { useReducedMotionConfig } from './utils/reducedMotionConfig';
22
+ export { useReducedMotion } from './utils/reducedMotion.svelte';
23
+ export type { ReducedMotionState } from './utils/reducedMotion.svelte';
24
+ export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
24
25
  export { useScroll } from './utils/scroll';
25
- export { useSpring } from './utils/spring';
26
+ export { useSpring } from './utils/spring.svelte';
27
+ export type { SpringMotionValue, UseSpringOptions } from './utils/spring.svelte';
26
28
  export { useIsPresent, usePresence } from './utils/usePresence';
27
29
  export type { UsePresenceState } from './utils/usePresence';
28
30
  export { useVelocity } from './utils/velocity';
package/dist/index.js CHANGED
@@ -11,16 +11,16 @@ export { anticipate, backIn, backInOut, backOut, circIn, circInOut, circOut, cub
11
11
  export { clamp, distance, distance2D, interpolate, mix, pipe, progress, wrap } from 'motion';
12
12
  export { useAnimate } from './utils/animate.svelte';
13
13
  export { useAnimationFrame } from './utils/animationFrame';
14
- export { useCycle } from './utils/cycle';
14
+ export { useCycle } from './utils/cycle.svelte';
15
15
  export { createDragControls } from './utils/dragControls';
16
- export { useInView } from './utils/inView';
16
+ export { useInView } from './utils/inView.svelte';
17
17
  export { useMotionTemplate } from './utils/motionTemplate';
18
18
  export { useMotionValue } from './utils/motionValue';
19
19
  export { useMotionValueEvent } from './utils/motionValueEvent';
20
- export { useReducedMotion } from './utils/reducedMotion';
21
- export { useReducedMotionConfig } from './utils/reducedMotionConfig';
20
+ export { useReducedMotion } from './utils/reducedMotion.svelte';
21
+ export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
22
22
  export { useScroll } from './utils/scroll';
23
- export { useSpring } from './utils/spring';
23
+ export { useSpring } from './utils/spring.svelte';
24
24
  export { useIsPresent, usePresence } from './utils/usePresence';
25
25
  export { useVelocity } from './utils/velocity';
26
26
  /**
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared `{ current, subscribe }` shape returned by the Wave 2 boolean
3
+ * snapshot hooks — `useReducedMotion`, `useReducedMotionConfig`,
4
+ * `useInView`. `.current` is `$state`-backed; `.subscribe(run)` is the
5
+ * Svelte readable store contract preserved for legacy consumers during
6
+ * the Tier 2 migration.
7
+ */
8
+ export type BooleanSnapshot = {
9
+ /** Reactive read in Svelte 5 templates / `$derived` / `$effect`. */
10
+ readonly current: boolean;
11
+ /** Svelte readable store contract — emits synchronously on subscribe. */
12
+ subscribe: (run: (value: boolean) => void) => () => void;
13
+ };
14
+ /**
15
+ * Build a `{ current, subscribe }` snapshot + an internal `set`
16
+ * function. Centralises the dedupe + subscriber-fanout that all three
17
+ * boolean-snapshot hooks need.
18
+ *
19
+ * Returns a tuple so consumers can hand the snapshot to callers while
20
+ * keeping `set` internal (it's not on the returned state object).
21
+ *
22
+ * Same-value writes via `set` are no-ops — saves a fanout call and
23
+ * means callers don't need their own change-detection guard.
24
+ *
25
+ * @param initial Starting value for the `current` cell.
26
+ * @returns A `[state, set]` tuple where `state` is the publicly-shared
27
+ * `{ current, subscribe }` and `set` is the internal updater the hook
28
+ * uses to push values from its event source.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const [state, set] = createBooleanSnapshot(media.matches)
33
+ * media.addEventListener('change', (e) => set(e.matches))
34
+ * return state // { current, subscribe }
35
+ * ```
36
+ */
37
+ export declare const createBooleanSnapshot: (initial: boolean) => [BooleanSnapshot, (value: boolean) => void];
@@ -0,0 +1,48 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ /**
3
+ * Build a `{ current, subscribe }` snapshot + an internal `set`
4
+ * function. Centralises the dedupe + subscriber-fanout that all three
5
+ * boolean-snapshot hooks need.
6
+ *
7
+ * Returns a tuple so consumers can hand the snapshot to callers while
8
+ * keeping `set` internal (it's not on the returned state object).
9
+ *
10
+ * Same-value writes via `set` are no-ops — saves a fanout call and
11
+ * means callers don't need their own change-detection guard.
12
+ *
13
+ * @param initial Starting value for the `current` cell.
14
+ * @returns A `[state, set]` tuple where `state` is the publicly-shared
15
+ * `{ current, subscribe }` and `set` is the internal updater the hook
16
+ * uses to push values from its event source.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const [state, set] = createBooleanSnapshot(media.matches)
21
+ * media.addEventListener('change', (e) => set(e.matches))
22
+ * return state // { current, subscribe }
23
+ * ```
24
+ */
25
+ export const createBooleanSnapshot = (initial) => {
26
+ let current = $state(initial);
27
+ const subscribers = new SvelteSet();
28
+ const state = {
29
+ get current() {
30
+ return current;
31
+ },
32
+ subscribe(run) {
33
+ subscribers.add(run);
34
+ run(current);
35
+ return () => {
36
+ subscribers.delete(run);
37
+ };
38
+ }
39
+ };
40
+ const set = (value) => {
41
+ if (value === current)
42
+ return;
43
+ current = value;
44
+ for (const sub of subscribers)
45
+ sub(value);
46
+ };
47
+ return [state, set];
48
+ };
@@ -0,0 +1,98 @@
1
+ /** Function returned by {@link useCycle} for advancing or jumping the index. */
2
+ export type Cycle = (next?: number) => void;
3
+ /**
4
+ * State returned by {@link useCycle}: an object with a reactive `.current`
5
+ * getter and a `cycle` function. Both reads and writes flow through the
6
+ * same object, so consumers don't need to destructure (which would
7
+ * snapshot `.current` and lose reactivity under runes).
8
+ */
9
+ export type CycleState<T> = {
10
+ readonly current: T;
11
+ cycle: Cycle;
12
+ };
13
+ /**
14
+ * Function that returns the current items list, used by the reactive
15
+ * overload of {@link useCycle}. The function is re-invoked on every read
16
+ * so changes to the underlying reactive source propagate automatically.
17
+ */
18
+ export type CycleItemsGetter<T> = () => readonly T[];
19
+ /**
20
+ * Cycles through a series of values. Mirrors framer-motion's `useCycle`.
21
+ *
22
+ * Two call forms:
23
+ *
24
+ * - **Varargs** — `useCycle(...items)` — items are captured once and stay
25
+ * fixed for the cycle's lifetime. Matches React framer-motion's signature.
26
+ * - **Reactive getter** — `useCycle(() => items)` — items are read on every
27
+ * access, so passing a `$state`/`$derived` source lets the cycle pick up
28
+ * list changes without recreating it.
29
+ *
30
+ * In both forms:
31
+ *
32
+ * - `state.current` is reactive — read it in templates / `$derived` / `$effect`
33
+ * and it tracks both index changes and (in the getter form) item changes.
34
+ * - `state.cycle()` advances to the next item (wrapping at the end).
35
+ * - `state.cycle(i)` jumps to index `i`. The index is stored as-given;
36
+ * `.current` then clamps on read so any out-of-range index — negative,
37
+ * overflow, or items shrinking underneath the reactive-getter form —
38
+ * resolves to the nearest valid edge (`items[0]` or `items[length - 1]`)
39
+ * instead of `undefined`. This is a defensive divergence from React
40
+ * framer-motion (which returns `items[i]`, possibly undefined) so the
41
+ * reactive form stays safe and `.current` always honors its `T` type.
42
+ * If the reactive getter ever returns an empty list, `.current` throws.
43
+ * - Calls that resolve to the current index are no-ops, matching React
44
+ * `useState`'s `Object.is` bail-out.
45
+ *
46
+ * Two deliberate divergences from React's `useCycle`:
47
+ *
48
+ * 1. Return shape — React's `[value, cycle]` tuple can't survive
49
+ * destructuring under Svelte 5 runes (snapshots the value, loses
50
+ * reactivity), so we return `{ current, cycle }`.
51
+ * 2. Out-of-range reads always clamp (see above) instead of returning
52
+ * `items[i]` undefined.
53
+ *
54
+ * Otherwise 1:1 with React, including same-index no-op bail-out and
55
+ * the `wrap(0, length, index + 1)` advance semantics.
56
+ *
57
+ * Ambiguity: `useCycle(fn)` with a single function value is treated as the
58
+ * reactive overload, not as a single-item cycle. To cycle through one
59
+ * function value, use `useCycle(() => [fn])` or just call it directly —
60
+ * a single-item cycle is a no-op anyway.
61
+ *
62
+ * @see https://motion.dev/docs/react-use-cycle
63
+ *
64
+ * @example Static varargs
65
+ * ```svelte
66
+ * <script lang="ts">
67
+ * import { motion, useCycle } from '@humanspeak/svelte-motion'
68
+ *
69
+ * const x = useCycle(0, 50, 100)
70
+ * </script>
71
+ *
72
+ * <motion.div animate={{ x: x.current }} onclick={() => x.cycle()} />
73
+ * ```
74
+ *
75
+ * @example Reactive items
76
+ * ```svelte
77
+ * <script lang="ts">
78
+ * let { labels }: { labels: string[] } = $props()
79
+ * const variant = useCycle(() => labels)
80
+ * </script>
81
+ *
82
+ * <motion.div animate={variant.current} onclick={() => variant.cycle()} />
83
+ * ```
84
+ *
85
+ * @param itemsGetter Function returning the current items list; re-invoked
86
+ * on every `.current` read so reactive sources propagate. Use this form
87
+ * when items can change between mount and unmount. (Reactive overload.)
88
+ * @param items One or more values to cycle through. Captured once at call
89
+ * time and fixed for the cycle's lifetime. (Varargs overload.)
90
+ * @returns A `CycleState<T>` with a reactive `.current` getter and a
91
+ * `cycle(next?: number)` advance/jump function. `.current` always
92
+ * honors its `T` type by clamping out-of-range indexes; it throws
93
+ * if a reactive getter empties the items list mid-cycle.
94
+ * `.cycle()` throws on non-integer (`NaN`, `1.5`, `Infinity`)
95
+ * indexes and returns early as a no-op on empty items.
96
+ */
97
+ export declare function useCycle<T>(itemsGetter: CycleItemsGetter<T>): CycleState<T>;
98
+ export declare function useCycle<T>(...items: T[]): CycleState<T>;
@@ -0,0 +1,44 @@
1
+ import { wrap } from 'motion';
2
+ export function useCycle(...args) {
3
+ const getItems = args.length === 1 && typeof args[0] === 'function'
4
+ ? args[0]
5
+ : () => args;
6
+ if (getItems().length === 0) {
7
+ throw new Error('useCycle requires at least one item');
8
+ }
9
+ let index = $state(0);
10
+ return {
11
+ get current() {
12
+ const items = getItems();
13
+ // Reactive-getter form: if the consumer's source emptied
14
+ // mid-cycle the public type can no longer be honored. Throw
15
+ // loudly so the bug surfaces immediately rather than leaking
16
+ // `undefined` through a `T`-typed read.
17
+ if (items.length === 0) {
18
+ throw new Error('useCycle items getter returned an empty list');
19
+ }
20
+ // Clamp on read so out-of-range indexes (from `cycle(-5)` or
21
+ // `cycle(99)`, or items shrinking under us in the getter form)
22
+ // resolve to the nearest valid edge instead of `undefined`.
23
+ const safeIndex = index < 0 ? 0 : index >= items.length ? items.length - 1 : index;
24
+ return items[safeIndex];
25
+ },
26
+ cycle: (next) => {
27
+ const items = getItems();
28
+ if (items.length === 0)
29
+ return;
30
+ // Reject non-finite / non-integer indexes up-front: `NaN` slips
31
+ // past the read-time clamp (NaN comparisons return false for
32
+ // both `< 0` and `>= length`) and would silently make `.current`
33
+ // resolve to `undefined`, breaking the `T` contract. Throw
34
+ // loudly to surface the consumer bug at write-time.
35
+ if (typeof next === 'number' && !Number.isInteger(next)) {
36
+ throw new Error('useCycle index must be a finite integer');
37
+ }
38
+ const target = typeof next === 'number' ? next : wrap(0, items.length, index + 1);
39
+ if (target === index)
40
+ return;
41
+ index = target;
42
+ }
43
+ };
44
+ }
@@ -0,0 +1,209 @@
1
+ import { type AnimationOptions, type DOMKeyframesDefinition } from 'motion';
2
+ import type { MotionViewport } from '../types.js';
3
+ import { type BooleanSnapshot } from './booleanSnapshot.svelte.js';
4
+ import { type ElementOrGetter } from './dom.js';
5
+ /**
6
+ * Split a `whileInView` definition into the visual keyframes and an
7
+ * optional nested `transition`. Mirrors the shape framer-motion uses
8
+ * where a single object carries both the target values and their
9
+ * timing config.
10
+ *
11
+ * Defensive against `undefined` / `null` input: `def ?? {}` ensures
12
+ * destructuring never throws, and the returned `keyframes` is then an
13
+ * empty record.
14
+ *
15
+ * @param def `whileInView` record possibly carrying a nested
16
+ * `transition` config. May be `null` / `undefined` defensively (the
17
+ * spread normalises to `{}`).
18
+ * @returns Object with the keyframes (everything *except* `transition`)
19
+ * and the extracted `transition` (or `undefined` if none was nested).
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * splitInViewDefinition({ opacity: 1, y: 0, transition: { duration: 0.5 } })
24
+ * // → { keyframes: { opacity: 1, y: 0 }, transition: { duration: 0.5 } }
25
+ *
26
+ * splitInViewDefinition({ opacity: 1 })
27
+ * // → { keyframes: { opacity: 1 }, transition: undefined }
28
+ * ```
29
+ */
30
+ export declare const splitInViewDefinition: (def: Record<string, unknown>) => {
31
+ keyframes: Record<string, unknown>;
32
+ transition?: AnimationOptions;
33
+ };
34
+ /**
35
+ * Compute the baseline values to restore to when an element leaves the
36
+ * viewport — only for the keys named in `whileInView`. Any key the
37
+ * element is not animating into stays as it was.
38
+ *
39
+ * For each key in `whileInView`, resolve a baseline by walking sources
40
+ * in this preference order:
41
+ *
42
+ * 1. `animate[key]` — the user's declared resting state
43
+ * 2. `initial[key]` — the pre-animation state
44
+ * 3. Neutral transform defaults (e.g. `x: 0`, `scale: 1`, `opacity: 1`)
45
+ * when the key is a known transform property
46
+ * 4. Inline CSS function value (`var(...)`, `calc(...)`, `url(...)`)
47
+ * read off `style.getPropertyValue` — handles cases where nested
48
+ * semicolons (e.g. `url(data:...;base64,...)`) would break a
49
+ * string-scrape
50
+ * 5. `getComputedStyle(el)[key]` — last resort
51
+ *
52
+ * The walk is per-key, so different baseline keys may be sourced from
53
+ * different layers.
54
+ *
55
+ * @param el Element whose computed style is read as the final fallback.
56
+ * Must be a real DOM node (the function reads inline style and
57
+ * `getComputedStyle`).
58
+ * @param opts Layered animation definitions:
59
+ * @param opts.initial Optional `initial` record from the component.
60
+ * @param opts.animate Optional `animate` record from the component.
61
+ * @param opts.whileInView The `whileInView` record — its keys drive
62
+ * which baseline entries get computed. Nested `transition` is
63
+ * stripped before walking.
64
+ * @returns A new record containing one entry per key found in
65
+ * `opts.whileInView`. May be empty if `whileInView` is empty.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * computeInViewBaseline(element, {
70
+ * initial: { opacity: 0, y: 50 },
71
+ * animate: { opacity: 1, y: 0 },
72
+ * whileInView: { opacity: 1, scale: 1.1 }
73
+ * })
74
+ * // → { opacity: 1, scale: 1 }
75
+ * // opacity sourced from animate; scale falls to the neutral default.
76
+ * ```
77
+ */
78
+ export declare const computeInViewBaseline: (el: HTMLElement, opts: {
79
+ initial?: Record<string, unknown>;
80
+ animate?: Record<string, unknown>;
81
+ whileInView?: Record<string, unknown>;
82
+ }) => Record<string, unknown>;
83
+ /**
84
+ * Wire a `whileInView` interaction onto an element using motion's
85
+ * `inView` primitive. On viewport entry the element animates to the
86
+ * supplied keyframes; on exit it animates back to a baseline computed
87
+ * via {@link computeInViewBaseline}.
88
+ *
89
+ * Used internally by `motion.<tag>` components to power the
90
+ * `whileInView` prop, and exposed for callers that want the same
91
+ * declarative behavior without going through a motion component.
92
+ *
93
+ * When `viewport.once` is `true`, the element latches on first entry
94
+ * — no exit animation runs, and the IntersectionObserver is detached
95
+ * via a `queueMicrotask(stop)` after the entry handler returns.
96
+ *
97
+ * @param el Target element to observe and animate.
98
+ * @param whileInView Keyframes to apply on entry. May carry a nested
99
+ * `transition` config (extracted via {@link splitInViewDefinition}).
100
+ * If `undefined`, the function returns a no-op cleanup without
101
+ * creating an observer.
102
+ * @param mergedTransition Default transition used both when
103
+ * `whileInView` has no nested `transition` and for the exit
104
+ * animation back to baseline.
105
+ * @param callbacks Optional lifecycle hooks:
106
+ * - `onStart` — fires on viewport entry, before the entry animation.
107
+ * - `onEnd` — fires on viewport exit, after the baseline restore
108
+ * animation kicks off. Not called when `viewport.once` is `true`.
109
+ * - `onAnimationComplete` — fires when the entry animation
110
+ * resolves; passed the keyframes that ran.
111
+ * @param baselineSources Sources for {@link computeInViewBaseline}'s
112
+ * per-key walk:
113
+ * - `initial` — the component's `initial` record.
114
+ * - `animate` — the component's `animate` record.
115
+ * @param viewport IntersectionObserver options:
116
+ * - `root` — scroll container (default page).
117
+ * - `margin` — `rootMargin` string.
118
+ * - `amount` — fraction visible required (defaults to `0` here so
119
+ * any pixel counts).
120
+ * - `once` — latch on first entry; skip exit animation.
121
+ * @returns A cleanup function that detaches the IntersectionObserver
122
+ * on call. Safe to invoke after a `once` latch has already fired.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const cleanup = attachWhileInView(
127
+ * element,
128
+ * { opacity: 1, y: 0, transition: { duration: 0.5 } },
129
+ * { duration: 0.3 },
130
+ * {
131
+ * onStart: () => trackImpression(),
132
+ * onEnd: () => console.log('left viewport')
133
+ * },
134
+ * { initial: { opacity: 0, y: 50 } },
135
+ * { once: true, amount: 0.5 }
136
+ * )
137
+ * // Later — typically component teardown:
138
+ * cleanup()
139
+ * ```
140
+ */
141
+ export declare const attachWhileInView: (el: HTMLElement, whileInView: Record<string, unknown> | undefined, mergedTransition: AnimationOptions, callbacks?: {
142
+ onStart?: () => void;
143
+ onEnd?: () => void;
144
+ onAnimationComplete?: (definition: DOMKeyframesDefinition | undefined) => void;
145
+ }, baselineSources?: {
146
+ initial?: Record<string, unknown>;
147
+ animate?: Record<string, unknown>;
148
+ }, viewport?: MotionViewport) => (() => void);
149
+ /**
150
+ * Options accepted by `useInView`.
151
+ */
152
+ export type UseInViewOptions = {
153
+ /** Element to use as the IntersectionObserver root. Defaults to the viewport. */
154
+ root?: ElementOrGetter;
155
+ /** CSS margin string applied to the root bounding box (e.g. `"100px 0px"`). */
156
+ margin?: string;
157
+ /** Fraction (0-1) or `"some"` / `"all"` of the target that must be visible. */
158
+ amount?: 'some' | 'all' | number;
159
+ /** When `true`, the state latches to `true` on first entry and never flips back. */
160
+ once?: boolean;
161
+ /** Initial value emitted before the first IntersectionObserver callback. */
162
+ initial?: boolean;
163
+ };
164
+ /**
165
+ * State returned by {@link useInView}.
166
+ */
167
+ export type InViewState = BooleanSnapshot;
168
+ /**
169
+ * Returns an `InViewState` that tracks whether `target` is in the viewport.
170
+ * Mirrors framer-motion's `useInView` adapted for Svelte 5 runes.
171
+ *
172
+ * `target` (and `options.root`) accept either an `HTMLElement` directly or
173
+ * a getter `() => HTMLElement | undefined`. With Svelte's `bind:this` the
174
+ * element isn't available until after mount, so element resolution is
175
+ * deferred — if the element isn't ready, the hook polls on
176
+ * `requestAnimationFrame` until it is.
177
+ *
178
+ * Lifecycle: the IntersectionObserver is bound to the surrounding reactive
179
+ * scope via `$effect`. The observer attaches at mount and detaches at
180
+ * unmount, regardless of how many consumers are reading `.current` or
181
+ * `.subscribe()`. This is a deliberate divergence from the previous
182
+ * store-based impl, which attached lazily on first subscribe.
183
+ *
184
+ * SSR-safe: returns a static `{ current: options.initial ?? false }` when
185
+ * `window` or `IntersectionObserver` is unavailable.
186
+ *
187
+ * @param target - Element (or getter) to observe.
188
+ * @param options - Optional `UseInViewOptions` (`root`, `margin`, `amount`,
189
+ * `once`, `initial`).
190
+ * @returns A `InViewState` reflecting the target's viewport intersection.
191
+ * @see https://motion.dev/docs/react-use-in-view
192
+ *
193
+ * @example
194
+ * ```svelte
195
+ * <script>
196
+ * import { useInView } from '@humanspeak/svelte-motion'
197
+ *
198
+ * let ref
199
+ * const inView = useInView(() => ref, { once: true })
200
+ *
201
+ * $effect(() => {
202
+ * if (inView.current) trackImpression()
203
+ * })
204
+ * </script>
205
+ *
206
+ * <div bind:this={ref}>{inView.current ? 'visible' : 'hidden'}</div>
207
+ * ```
208
+ */
209
+ export declare const useInView: (target: ElementOrGetter, options?: UseInViewOptions) => InViewState;