@humanspeak/svelte-motion 0.4.9 → 0.5.1

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.
Files changed (36) hide show
  1. package/dist/index.d.ts +14 -7
  2. package/dist/index.js +7 -6
  3. package/dist/utils/attachable.js +14 -9
  4. package/dist/utils/augmentMotionValue.svelte.d.ts +156 -0
  5. package/dist/utils/augmentMotionValue.svelte.js +193 -0
  6. package/dist/utils/dom.d.ts +13 -0
  7. package/dist/utils/dom.js +19 -0
  8. package/dist/utils/followValue.svelte.d.ts +84 -0
  9. package/dist/utils/followValue.svelte.js +51 -0
  10. package/dist/utils/motionTemplate.svelte.d.ts +53 -0
  11. package/dist/utils/motionTemplate.svelte.js +78 -0
  12. package/dist/utils/motionValue.svelte.d.ts +61 -0
  13. package/dist/utils/motionValue.svelte.js +49 -0
  14. package/dist/utils/scroll.svelte.d.ts +91 -0
  15. package/dist/utils/scroll.svelte.js +259 -0
  16. package/dist/utils/spring.svelte.d.ts +26 -31
  17. package/dist/utils/spring.svelte.js +8 -116
  18. package/dist/utils/time.svelte.d.ts +47 -0
  19. package/dist/utils/time.svelte.js +128 -0
  20. package/dist/utils/transform.svelte.d.ts +170 -0
  21. package/dist/utils/transform.svelte.js +189 -0
  22. package/dist/utils/velocity.svelte.d.ts +61 -0
  23. package/dist/utils/velocity.svelte.js +132 -0
  24. package/package.json +1 -1
  25. package/dist/utils/motionTemplate.d.ts +0 -21
  26. package/dist/utils/motionTemplate.js +0 -33
  27. package/dist/utils/motionValue.d.ts +0 -6
  28. package/dist/utils/motionValue.js +0 -13
  29. package/dist/utils/scroll.d.ts +0 -63
  30. package/dist/utils/scroll.js +0 -79
  31. package/dist/utils/time.d.ts +0 -14
  32. package/dist/utils/time.js +0 -68
  33. package/dist/utils/transform.d.ts +0 -74
  34. package/dist/utils/transform.js +0 -211
  35. package/dist/utils/velocity.d.ts +0 -15
  36. package/dist/utils/velocity.js +0 -62
package/dist/index.d.ts CHANGED
@@ -10,31 +10,38 @@ 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 type { AugmentedMotionValue } from './utils/augmentMotionValue.svelte';
13
14
  export { useCycle } from './utils/cycle.svelte';
14
15
  export type { Cycle, CycleState } from './utils/cycle.svelte';
15
16
  export { createDragControls } from './utils/dragControls';
17
+ export { useFollowValue } from './utils/followValue.svelte';
18
+ export type { FollowMotionValue, UseFollowValueOptions } from './utils/followValue.svelte';
16
19
  export { useInView } from './utils/inView.svelte';
17
20
  export type { InViewState, UseInViewOptions } from './utils/inView.svelte';
18
- export { useMotionTemplate } from './utils/motionTemplate';
19
- export { useMotionValue } from './utils/motionValue';
20
- export type { MotionValue } from './utils/motionValue';
21
+ export { useMotionTemplate } from './utils/motionTemplate.svelte';
22
+ export type { MotionTemplateInput } from './utils/motionTemplate.svelte';
23
+ export { useMotionValue } from './utils/motionValue.svelte';
24
+ export type { MotionValue, RawMotionValue } from './utils/motionValue.svelte';
21
25
  export { useMotionValueEvent } from './utils/motionValueEvent';
22
26
  export { useReducedMotion } from './utils/reducedMotion.svelte';
23
27
  export type { ReducedMotionState } from './utils/reducedMotion.svelte';
24
28
  export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
25
- export { useScroll } from './utils/scroll';
29
+ export { useScroll } from './utils/scroll.svelte';
30
+ export type { UseScrollOptions, UseScrollReturn } from './utils/scroll.svelte';
26
31
  export { useSpring } from './utils/spring.svelte';
27
32
  export type { SpringMotionValue, UseSpringOptions } from './utils/spring.svelte';
28
33
  export { useIsPresent, usePresence } from './utils/usePresence';
29
34
  export type { UsePresenceState } from './utils/usePresence';
30
- export { useVelocity } from './utils/velocity';
35
+ export { useVelocity } from './utils/velocity.svelte';
36
+ export type { VelocitySource } from './utils/velocity.svelte';
31
37
  /**
32
38
  * @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
33
39
  */
34
40
  export { stringifyStyleObject } from './utils/styleObject';
35
41
  export { styleString } from './utils/styleObject.svelte';
36
- export { useTime } from './utils/time';
37
- export { useTransform } from './utils/transform';
42
+ export { useTime } from './utils/time.svelte';
43
+ export { useTransform } from './utils/transform.svelte';
44
+ export type { MultiTransformer, SingleTransformer, TransformOptions, TransformOutputMap, TransformSource } from './utils/transform.svelte';
38
45
  export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
39
46
  export { default as MotionA } from './html/A.svelte';
40
47
  export { default as MotionAbbr } from './html/Abbr.svelte';
package/dist/index.js CHANGED
@@ -13,23 +13,24 @@ export { useAnimate } from './utils/animate.svelte';
13
13
  export { useAnimationFrame } from './utils/animationFrame';
14
14
  export { useCycle } from './utils/cycle.svelte';
15
15
  export { createDragControls } from './utils/dragControls';
16
+ export { useFollowValue } from './utils/followValue.svelte';
16
17
  export { useInView } from './utils/inView.svelte';
17
- export { useMotionTemplate } from './utils/motionTemplate';
18
- export { useMotionValue } from './utils/motionValue';
18
+ export { useMotionTemplate } from './utils/motionTemplate.svelte';
19
+ export { useMotionValue } from './utils/motionValue.svelte';
19
20
  export { useMotionValueEvent } from './utils/motionValueEvent';
20
21
  export { useReducedMotion } from './utils/reducedMotion.svelte';
21
22
  export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
22
- export { useScroll } from './utils/scroll';
23
+ export { useScroll } from './utils/scroll.svelte';
23
24
  export { useSpring } from './utils/spring.svelte';
24
25
  export { useIsPresent, usePresence } from './utils/usePresence';
25
- export { useVelocity } from './utils/velocity';
26
+ export { useVelocity } from './utils/velocity.svelte';
26
27
  /**
27
28
  * @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
28
29
  */
29
30
  export { stringifyStyleObject } from './utils/styleObject';
30
31
  export { styleString } from './utils/styleObject.svelte';
31
- export { useTime } from './utils/time';
32
- export { useTransform } from './utils/transform';
32
+ export { useTime } from './utils/time.svelte';
33
+ export { useTransform } from './utils/transform.svelte';
33
34
  export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
34
35
  // Named component exports — tree-shakeable alternative to the `motion` object
35
36
  export { default as MotionA } from './html/A.svelte';
@@ -1,3 +1,4 @@
1
+ import { cancelMicrotask, microtask } from 'motion-dom';
1
2
  import { resolveElement } from './dom.js';
2
3
  /**
3
4
  * Builds a subscriber-refcounted DOM-attachment primitive. Both `useScroll`
@@ -27,17 +28,21 @@ import { resolveElement } from './dom.js';
27
28
  */
28
29
  export const createAttachable = (config) => {
29
30
  let cleanup;
30
- let pollRaf = 0;
31
+ let pollScheduled = false;
31
32
  let subscriberCount = 0;
32
33
  // When stop() runs synchronously inside onAttach, cleanup hasn't been
33
34
  // assigned yet. Defer the teardown so the just-returned disposer still
34
35
  // gets invoked.
35
36
  let attaching = false;
36
37
  let stopRequestedDuringAttach = false;
38
+ const pollTick = () => {
39
+ pollScheduled = false;
40
+ tryAttach();
41
+ };
37
42
  const cancelPoll = () => {
38
- if (pollRaf) {
39
- cancelAnimationFrame(pollRaf);
40
- pollRaf = 0;
43
+ if (pollScheduled) {
44
+ cancelMicrotask(pollTick);
45
+ pollScheduled = false;
41
46
  }
42
47
  };
43
48
  const stop = () => {
@@ -65,11 +70,11 @@ export const createAttachable = (config) => {
65
70
  els[key] = el;
66
71
  }
67
72
  if (needsPoll) {
68
- if (!pollRaf) {
69
- pollRaf = requestAnimationFrame(() => {
70
- pollRaf = 0;
71
- tryAttach();
72
- });
73
+ // Microtask schedule (matches useScroll's pattern) — one frame
74
+ // faster than rAF for refs that hydrate the same tick.
75
+ if (!pollScheduled) {
76
+ pollScheduled = true;
77
+ microtask.read(pollTick);
73
78
  }
74
79
  return;
75
80
  }
@@ -0,0 +1,156 @@
1
+ import { type MotionValue } from 'motion-dom';
2
+ import { type Readable } from 'svelte/store';
3
+ /**
4
+ * A motion-dom `MotionValue<T>` augmented with the two affordances every
5
+ * Tier-2 hook in this library exposes for Svelte 5 consumers:
6
+ *
7
+ * - `current` — a `$state`-backed reactive getter. Reads inside templates,
8
+ * `$derived`, and `$effect` track changes automatically without going
9
+ * through `.subscribe()`.
10
+ * - `subscribe(run)` — Svelte readable store contract. Calls `run(value)`
11
+ * once synchronously on subscribe, then on every change. Lets the same
12
+ * value drive `$value` template syntax, `svelte/store`'s `get()`, and
13
+ * anything else that expects a readable.
14
+ *
15
+ * Every Wave 3 hook (`useMotionValue`, `useTransform`, `useScroll`,
16
+ * `useTime`, `useVelocity`, `useMotionTemplate`) and `useSpring` returns a
17
+ * value of this shape. The point is to keep one shared surface across all
18
+ * motion-value-producing hooks instead of repeating the wiring in each.
19
+ */
20
+ export type AugmentedMotionValue<T> = Omit<MotionValue<T>, 'current'> & {
21
+ /** Reactive read in Svelte 5 templates / `$derived` / `$effect`. */
22
+ readonly current: T;
23
+ /** Svelte readable store compatibility. */
24
+ subscribe: (run: (value: T) => void) => () => void;
25
+ };
26
+ /**
27
+ * Detects a Svelte readable store, excluding motion-dom `MotionValue`
28
+ * instances (which also expose `subscribe`-shaped APIs in some versions).
29
+ * Used by hook factories that accept either a `MotionValue` or a readable
30
+ * store as a source.
31
+ *
32
+ * @template T The value type the readable emits.
33
+ * @param value Any value to test.
34
+ * @returns Whether the value is a Svelte readable (and not a `MotionValue`).
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { writable } from 'svelte/store'
39
+ * import { motionValue } from 'motion-dom'
40
+ *
41
+ * isSvelteReadable(writable(0)) // true
42
+ * isSvelteReadable(motionValue(0)) // false (a MotionValue, not a readable)
43
+ * isSvelteReadable({ subscribe: 'nope' }) // false (subscribe isn't callable)
44
+ * ```
45
+ */
46
+ export declare const isSvelteReadable: <T = unknown>(value: unknown) => value is Readable<T>;
47
+ /**
48
+ * Synchronously samples a source: returns `T` directly, calls `.get()` on
49
+ * a `MotionValue`, or `svelte/store`'s `get()` on a readable. Used by hook
50
+ * factories to seed an initial value before any subscription is established.
51
+ *
52
+ * @template T The value type.
53
+ * @param source A plain value, a `MotionValue`, or a Svelte readable.
54
+ * @returns The current value of the source.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * sampleSource(42) // 42
59
+ * sampleSource(motionValue(7)) // 7
60
+ * sampleSource(writable(11)) // 11
61
+ * ```
62
+ */
63
+ export declare const sampleSource: <T>(source: T | MotionValue<T> | Readable<T>) => T;
64
+ /**
65
+ * Layer Svelte 5 affordances onto a motion-dom `MotionValue`:
66
+ *
67
+ * 1. A `$state`-tracked `.current` accessor. motion-dom writes to its own
68
+ * `current` field on every frame via an internal setter; we redirect that
69
+ * setter through `$state` so templates and `$derived` / `$effect` re-run
70
+ * automatically. Same-value writes are skipped (motion-dom can call the
71
+ * setter at rest, and `$state` would itself dedupe, but the explicit
72
+ * check avoids the extra accessor work).
73
+ *
74
+ * 2. A `.subscribe(run)` shim implementing the Svelte readable store
75
+ * contract: synchronous initial emit, then re-emit on every change.
76
+ * Forwarded to motion-dom's `.on('change', …)` event bus.
77
+ *
78
+ * 3. `.destroy()` is wrapped so a caller-supplied `dispose` runs once before
79
+ * motion-dom's own teardown (and only once, guarded against re-entrant
80
+ * or duplicate destroy calls).
81
+ *
82
+ * The returned reference is the same `MotionValue` passed in, only retyped —
83
+ * so identity checks (`isMotionValue`, `===`) still work and motion-dom's
84
+ * own machinery (animation, follow, composition) is untouched.
85
+ *
86
+ * **Call once per MotionValue.** This function mutates the value (rewrites
87
+ * `current`, `subscribe`, `destroy`). Calling it twice would re-define the
88
+ * accessors and the first call's `dispose` would be discarded.
89
+ *
90
+ * @template T The value type — typically `number` or `string`.
91
+ * @param value The motion-dom `MotionValue` to augment.
92
+ * @param dispose Optional cleanup that runs once when `.destroy()` is first called (before motion-dom's internal teardown). Defaults to a no-op.
93
+ * @returns The same `MotionValue` typed as {@link AugmentedMotionValue}.
94
+ *
95
+ * @example
96
+ * ```svelte
97
+ * <script lang="ts">
98
+ * import { motionValue } from 'motion-dom'
99
+ * import { augmentMotionValue } from './augmentMotionValue.svelte.js'
100
+ *
101
+ * const mv = motionValue(0)
102
+ * const aug = augmentMotionValue(mv, () => console.log('disposed'))
103
+ *
104
+ * $effect(() => () => aug.destroy())
105
+ * </script>
106
+ *
107
+ * <div style="transform: translateX({aug.current}px)">{aug.current}</div>
108
+ * ```
109
+ */
110
+ export declare const augmentMotionValue: <T>(value: MotionValue<T>, dispose?: VoidFunction) => AugmentedMotionValue<T>;
111
+ /**
112
+ * Bridges a Svelte `Readable<T>` into a motion-dom `MotionValue<T>` that
113
+ * mirrors the readable's emissions, so motion-dom primitives (`mapValue`,
114
+ * `transformValue`, `attachFollow`, `getVelocity`, etc.) that only accept
115
+ * `MotionValue` can track readable-shaped sources.
116
+ *
117
+ * The bridge:
118
+ * 1. Seeds via `get(source)` so the initial value is correct synchronously.
119
+ * 2. Subscribes to the readable, skipping the *synchronous initial emit*
120
+ * (Svelte readables always fire one on subscribe, but the seed already
121
+ * has it — without the skip the bridge would double-write on attach).
122
+ * 3. Optionally coerces each emit through `coerce` — useful for unit-string
123
+ * sources (e.g. `"100px"` → `100`).
124
+ *
125
+ * Returns the bridge value and a `dispose` that tears down the subscription
126
+ * and destroys the bridge MV. Callers register `dispose` with their lifecycle
127
+ * ($effect cleanup or the augmented `destroy`'s `dispose` slot).
128
+ *
129
+ * @template TIn The readable's emit type (often `number | string`).
130
+ * @template TOut The bridge MotionValue's value type (often `number`).
131
+ * @param source A Svelte readable store.
132
+ * @param coerce Optional transform applied to each emit (and the initial seed). Identity by default.
133
+ * @returns A `MotionValue<TOut>` mirroring the readable + a dispose function.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * import { writable } from 'svelte/store'
138
+ *
139
+ * // Identity bridge — readable<number> → motionValue<number>
140
+ * const w = writable(0)
141
+ * const { value: mv, dispose } = bridgeReadableToMotionValue(w)
142
+ * w.set(50); mv.get() === 50
143
+ *
144
+ * // Coerce bridge — readable<string> → motionValue<number>
145
+ * const w2 = writable('100px')
146
+ * const bridge = bridgeReadableToMotionValue<string, number>(w2, parseFloat)
147
+ * bridge.value.get() === 100
148
+ *
149
+ * // Always pair with dispose() in your $effect cleanup.
150
+ * $effect(() => () => dispose())
151
+ * ```
152
+ */
153
+ export declare const bridgeReadableToMotionValue: <TIn, TOut = TIn>(source: Readable<TIn>, coerce?: (v: TIn) => TOut) => {
154
+ value: MotionValue<TOut>;
155
+ dispose: VoidFunction;
156
+ };
@@ -0,0 +1,193 @@
1
+ import { isMotionValue, motionValue } from 'motion-dom';
2
+ import { get } from 'svelte/store';
3
+ /**
4
+ * Detects a Svelte readable store, excluding motion-dom `MotionValue`
5
+ * instances (which also expose `subscribe`-shaped APIs in some versions).
6
+ * Used by hook factories that accept either a `MotionValue` or a readable
7
+ * store as a source.
8
+ *
9
+ * @template T The value type the readable emits.
10
+ * @param value Any value to test.
11
+ * @returns Whether the value is a Svelte readable (and not a `MotionValue`).
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { writable } from 'svelte/store'
16
+ * import { motionValue } from 'motion-dom'
17
+ *
18
+ * isSvelteReadable(writable(0)) // true
19
+ * isSvelteReadable(motionValue(0)) // false (a MotionValue, not a readable)
20
+ * isSvelteReadable({ subscribe: 'nope' }) // false (subscribe isn't callable)
21
+ * ```
22
+ */
23
+ export const isSvelteReadable = (value) => {
24
+ return (!!value &&
25
+ typeof value === 'object' &&
26
+ typeof value.subscribe === 'function' &&
27
+ !isMotionValue(value));
28
+ };
29
+ /**
30
+ * Synchronously samples a source: returns `T` directly, calls `.get()` on
31
+ * a `MotionValue`, or `svelte/store`'s `get()` on a readable. Used by hook
32
+ * factories to seed an initial value before any subscription is established.
33
+ *
34
+ * @template T The value type.
35
+ * @param source A plain value, a `MotionValue`, or a Svelte readable.
36
+ * @returns The current value of the source.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * sampleSource(42) // 42
41
+ * sampleSource(motionValue(7)) // 7
42
+ * sampleSource(writable(11)) // 11
43
+ * ```
44
+ */
45
+ export const sampleSource = (source) => {
46
+ if (isMotionValue(source))
47
+ return source.get();
48
+ if (isSvelteReadable(source))
49
+ return get(source);
50
+ return source;
51
+ };
52
+ /**
53
+ * Layer Svelte 5 affordances onto a motion-dom `MotionValue`:
54
+ *
55
+ * 1. A `$state`-tracked `.current` accessor. motion-dom writes to its own
56
+ * `current` field on every frame via an internal setter; we redirect that
57
+ * setter through `$state` so templates and `$derived` / `$effect` re-run
58
+ * automatically. Same-value writes are skipped (motion-dom can call the
59
+ * setter at rest, and `$state` would itself dedupe, but the explicit
60
+ * check avoids the extra accessor work).
61
+ *
62
+ * 2. A `.subscribe(run)` shim implementing the Svelte readable store
63
+ * contract: synchronous initial emit, then re-emit on every change.
64
+ * Forwarded to motion-dom's `.on('change', …)` event bus.
65
+ *
66
+ * 3. `.destroy()` is wrapped so a caller-supplied `dispose` runs once before
67
+ * motion-dom's own teardown (and only once, guarded against re-entrant
68
+ * or duplicate destroy calls).
69
+ *
70
+ * The returned reference is the same `MotionValue` passed in, only retyped —
71
+ * so identity checks (`isMotionValue`, `===`) still work and motion-dom's
72
+ * own machinery (animation, follow, composition) is untouched.
73
+ *
74
+ * **Call once per MotionValue.** This function mutates the value (rewrites
75
+ * `current`, `subscribe`, `destroy`). Calling it twice would re-define the
76
+ * accessors and the first call's `dispose` would be discarded.
77
+ *
78
+ * @template T The value type — typically `number` or `string`.
79
+ * @param value The motion-dom `MotionValue` to augment.
80
+ * @param dispose Optional cleanup that runs once when `.destroy()` is first called (before motion-dom's internal teardown). Defaults to a no-op.
81
+ * @returns The same `MotionValue` typed as {@link AugmentedMotionValue}.
82
+ *
83
+ * @example
84
+ * ```svelte
85
+ * <script lang="ts">
86
+ * import { motionValue } from 'motion-dom'
87
+ * import { augmentMotionValue } from './augmentMotionValue.svelte.js'
88
+ *
89
+ * const mv = motionValue(0)
90
+ * const aug = augmentMotionValue(mv, () => console.log('disposed'))
91
+ *
92
+ * $effect(() => () => aug.destroy())
93
+ * </script>
94
+ *
95
+ * <div style="transform: translateX({aug.current}px)">{aug.current}</div>
96
+ * ```
97
+ */
98
+ export const augmentMotionValue = (value, dispose = () => undefined) => {
99
+ // motion-dom's `.get()` returns `NonNullable<V>`, which would otherwise
100
+ // narrow `$state` to that and reject nullable-T setter writes. Cast to T
101
+ // so the state slot matches the public augmented signature; motion-dom's
102
+ // own contract guarantees it never sets a null/undefined frame value.
103
+ let current = $state(value.get());
104
+ Object.defineProperty(value, 'current', {
105
+ get: () => current,
106
+ set: (v) => {
107
+ if (v !== current)
108
+ current = v;
109
+ },
110
+ enumerable: true,
111
+ configurable: true
112
+ });
113
+ const originalDestroy = value.destroy.bind(value);
114
+ let destroyed = false;
115
+ value.destroy = () => {
116
+ if (destroyed)
117
+ return;
118
+ destroyed = true;
119
+ dispose();
120
+ originalDestroy();
121
+ };
122
+ const subscribe = (run) => {
123
+ run(value.get());
124
+ return value.on('change', run);
125
+ };
126
+ Object.defineProperty(value, 'subscribe', {
127
+ value: subscribe,
128
+ writable: false,
129
+ enumerable: false,
130
+ configurable: true
131
+ });
132
+ return value;
133
+ };
134
+ /**
135
+ * Bridges a Svelte `Readable<T>` into a motion-dom `MotionValue<T>` that
136
+ * mirrors the readable's emissions, so motion-dom primitives (`mapValue`,
137
+ * `transformValue`, `attachFollow`, `getVelocity`, etc.) that only accept
138
+ * `MotionValue` can track readable-shaped sources.
139
+ *
140
+ * The bridge:
141
+ * 1. Seeds via `get(source)` so the initial value is correct synchronously.
142
+ * 2. Subscribes to the readable, skipping the *synchronous initial emit*
143
+ * (Svelte readables always fire one on subscribe, but the seed already
144
+ * has it — without the skip the bridge would double-write on attach).
145
+ * 3. Optionally coerces each emit through `coerce` — useful for unit-string
146
+ * sources (e.g. `"100px"` → `100`).
147
+ *
148
+ * Returns the bridge value and a `dispose` that tears down the subscription
149
+ * and destroys the bridge MV. Callers register `dispose` with their lifecycle
150
+ * ($effect cleanup or the augmented `destroy`'s `dispose` slot).
151
+ *
152
+ * @template TIn The readable's emit type (often `number | string`).
153
+ * @template TOut The bridge MotionValue's value type (often `number`).
154
+ * @param source A Svelte readable store.
155
+ * @param coerce Optional transform applied to each emit (and the initial seed). Identity by default.
156
+ * @returns A `MotionValue<TOut>` mirroring the readable + a dispose function.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * import { writable } from 'svelte/store'
161
+ *
162
+ * // Identity bridge — readable<number> → motionValue<number>
163
+ * const w = writable(0)
164
+ * const { value: mv, dispose } = bridgeReadableToMotionValue(w)
165
+ * w.set(50); mv.get() === 50
166
+ *
167
+ * // Coerce bridge — readable<string> → motionValue<number>
168
+ * const w2 = writable('100px')
169
+ * const bridge = bridgeReadableToMotionValue<string, number>(w2, parseFloat)
170
+ * bridge.value.get() === 100
171
+ *
172
+ * // Always pair with dispose() in your $effect cleanup.
173
+ * $effect(() => () => dispose())
174
+ * ```
175
+ */
176
+ export const bridgeReadableToMotionValue = (source, coerce = (v) => v) => {
177
+ const bridge = motionValue(coerce(get(source)));
178
+ let seenInitial = false;
179
+ const unsub = source.subscribe((v) => {
180
+ if (!seenInitial) {
181
+ seenInitial = true;
182
+ return;
183
+ }
184
+ bridge.set(coerce(v));
185
+ });
186
+ return {
187
+ value: bridge,
188
+ dispose: () => {
189
+ unsub();
190
+ bridge.destroy();
191
+ }
192
+ };
193
+ };
@@ -41,3 +41,16 @@ export type ElementOrGetter = HTMLElement | (() => HTMLElement | null | undefine
41
41
  * ```
42
42
  */
43
43
  export declare const resolveElement: (ref?: ElementOrGetter) => HTMLElement | undefined;
44
+ /**
45
+ * Tests whether an `ElementOrGetter` is currently unresolved — defined as a
46
+ * getter that returns falsy. Direct element refs are never "pending" (they
47
+ * were already resolved at call time) and absent refs are not "pending"
48
+ * either (they're just absent).
49
+ *
50
+ * Lets microtask-defer / rAF-defer loops wait for refs to hydrate after
51
+ * `bind:this` settles on mount.
52
+ *
53
+ * @param ref Element or getter to check.
54
+ * @returns `true` only when `ref` is a getter and the getter currently returns falsy.
55
+ */
56
+ export declare const isRefPending: (ref?: ElementOrGetter) => boolean;
package/dist/utils/dom.js CHANGED
@@ -39,3 +39,22 @@ export const resolveElement = (ref) => {
39
39
  const value = typeof ref === 'function' ? ref() : ref;
40
40
  return value ?? undefined;
41
41
  };
42
+ /**
43
+ * Tests whether an `ElementOrGetter` is currently unresolved — defined as a
44
+ * getter that returns falsy. Direct element refs are never "pending" (they
45
+ * were already resolved at call time) and absent refs are not "pending"
46
+ * either (they're just absent).
47
+ *
48
+ * Lets microtask-defer / rAF-defer loops wait for refs to hydrate after
49
+ * `bind:this` settles on mount.
50
+ *
51
+ * @param ref Element or getter to check.
52
+ * @returns `true` only when `ref` is a getter and the getter currently returns falsy.
53
+ */
54
+ export const isRefPending = (ref) => {
55
+ if (!ref)
56
+ return false;
57
+ if (typeof ref !== 'function')
58
+ return false;
59
+ return !ref();
60
+ };
@@ -0,0 +1,84 @@
1
+ import { type FollowValueOptions, type MotionValue } from 'motion-dom';
2
+ import { type Readable } from 'svelte/store';
3
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
4
+ /**
5
+ * Options accepted by {@link useFollowValue}. Mirrors framer-motion's
6
+ * `FollowValueOptions` 1:1 — any `ValueAnimationTransition` shape (spring /
7
+ * tween / inertia / keyframes) plus `skipInitialAnimation` for
8
+ * scroll-restoration scenarios.
9
+ *
10
+ * The shape is re-exported from motion-dom so consumers don't have to
11
+ * cross-import.
12
+ *
13
+ * @see https://motion.dev/docs/react-use-follow-value
14
+ */
15
+ export type UseFollowValueOptions = FollowValueOptions;
16
+ /**
17
+ * The augmented `MotionValue` returned by {@link useFollowValue} (and, since
18
+ * `useSpring` now delegates here, by `useSpring` too).
19
+ */
20
+ export type FollowMotionValue<T extends number | string> = AugmentedMotionValue<T>;
21
+ /**
22
+ * Creates an animated `MotionValue` that, when `.set(v)` is called, animates
23
+ * toward `v` using **any** transition type — spring, tween, inertia, or
24
+ * keyframes. Pass another `MotionValue` (or Svelte readable) as the source
25
+ * and the result follows it: every source emit kicks off a new animation
26
+ * toward the latest value.
27
+ *
28
+ * Mirrors React framer-motion's `useFollowValue` 1:1. `useSpring` in this
29
+ * library is now a thin wrapper that delegates here with the default
30
+ * `{ type: 'spring' }`.
31
+ *
32
+ * Returned object is a real motion-dom `MotionValue` (composes with
33
+ * `useTransform`, `useVelocity`, `animate()`, etc.) augmented with a
34
+ * `$state`-backed `.current` getter and a Svelte readable `.subscribe`
35
+ * shim.
36
+ *
37
+ * Lifecycle: must be called during component initialization. Cleanup is
38
+ * registered via `$effect`; the follow animation stops, the source bridge
39
+ * (if any) tears down, and `value.destroy()` runs when the surrounding
40
+ * `$effect` scope unmounts.
41
+ *
42
+ * SSR-safe: returns a static augmented `MotionValue` with no animation on
43
+ * the server. `.set` and `.jump` become no-ops to avoid drifting away from
44
+ * the server-rendered snapshot.
45
+ *
46
+ * @template T The value type — `number` or `string` (unit strings preserved).
47
+ * @param source Initial value, a `MotionValue` to follow, or a Svelte readable.
48
+ * @param options Transition + follow configuration (`type: 'spring' | 'tween' | 'inertia' | 'keyframes'`, plus the corresponding per-type options).
49
+ * @returns A `MotionValue<T>` with `.current` and `.subscribe`.
50
+ *
51
+ * @example
52
+ * ```svelte
53
+ * <script lang="ts">
54
+ * import { useFollowValue, useMotionValue } from '@humanspeak/svelte-motion'
55
+ *
56
+ * const target = useMotionValue(0)
57
+ *
58
+ * // Spring follow (default — same as `useSpring(target)`).
59
+ * const spring = useFollowValue(target, { stiffness: 300, damping: 30 })
60
+ *
61
+ * // Tween follow — eased linear interpolation.
62
+ * const eased = useFollowValue(target, {
63
+ * type: 'tween',
64
+ * duration: 0.4,
65
+ * ease: 'easeInOut'
66
+ * })
67
+ *
68
+ * // Inertia — decays from initial velocity.
69
+ * const drifting = useFollowValue(0, { type: 'inertia', velocity: 800, power: 0.6 })
70
+ * </script>
71
+ *
72
+ * <button onclick={() => target.set(target.get() === 0 ? 200 : 0)}>Toggle</button>
73
+ * <div style="transform: translateX({spring.current}px)">spring</div>
74
+ * <div style="transform: translateX({eased.current}px)">tween</div>
75
+ * <div style="transform: translateX({drifting.current}px)">inertia</div>
76
+ * ```
77
+ *
78
+ * @see https://motion.dev/docs/react-use-follow-value
79
+ */
80
+ export declare function useFollowValue(source: number, options?: UseFollowValueOptions): FollowMotionValue<number>;
81
+ export declare function useFollowValue(source: string, options?: UseFollowValueOptions): FollowMotionValue<string>;
82
+ export declare function useFollowValue(source: MotionValue<number>, options?: UseFollowValueOptions): FollowMotionValue<number>;
83
+ export declare function useFollowValue(source: MotionValue<string>, options?: UseFollowValueOptions): FollowMotionValue<string>;
84
+ export declare function useFollowValue<T extends number | string>(source: Readable<T>, options?: UseFollowValueOptions): FollowMotionValue<T>;
@@ -0,0 +1,51 @@
1
+ import { attachFollow, isMotionValue, motionValue } from 'motion-dom';
2
+ import {} from 'svelte/store';
3
+ import { augmentMotionValue, isSvelteReadable, sampleSource } from './augmentMotionValue.svelte.js';
4
+ export function useFollowValue(source, options = {}) {
5
+ // SSR: return a static MotionValue with no animation. Reads return the
6
+ // best-effort initial value; .set / .jump become no-ops to avoid drifting
7
+ // away from the server-rendered snapshot.
8
+ if (typeof window === 'undefined') {
9
+ const initial = sampleSource(source);
10
+ const ssrValue = motionValue(initial);
11
+ ssrValue.set = () => undefined;
12
+ ssrValue.jump = () => undefined;
13
+ return augmentMotionValue(ssrValue);
14
+ }
15
+ // Resolve initial + follow source. Svelte readables get bridged into a
16
+ // motion-dom MotionValue so `attachFollow` can subscribe to their emits.
17
+ let followSource;
18
+ let cleanupReadableBridge;
19
+ let svelteBridge;
20
+ if (isMotionValue(source)) {
21
+ followSource = source;
22
+ }
23
+ else if (isSvelteReadable(source)) {
24
+ const initialFromReadable = sampleSource(source);
25
+ svelteBridge = motionValue(initialFromReadable);
26
+ cleanupReadableBridge = source.subscribe((v) => {
27
+ // Svelte readable contract emits synchronously on subscribe. Skip
28
+ // the initial emit (already seeded above) so attachFollow doesn't
29
+ // fire an animation on attach.
30
+ if (svelteBridge.get() === v)
31
+ return;
32
+ svelteBridge.set(v);
33
+ });
34
+ followSource = svelteBridge;
35
+ }
36
+ else {
37
+ followSource = source;
38
+ }
39
+ const initial = isMotionValue(followSource) ? followSource.get() : followSource;
40
+ const value = motionValue(initial);
41
+ // Default transition is `spring` — matches React framer-motion's
42
+ // `useFollowValue` default. Caller's `type` (if set) overrides.
43
+ const stopFollow = attachFollow(value, followSource, { type: 'spring', ...options });
44
+ const dispose = () => {
45
+ stopFollow?.();
46
+ cleanupReadableBridge?.();
47
+ svelteBridge?.destroy();
48
+ };
49
+ $effect(() => () => value.destroy());
50
+ return augmentMotionValue(value, dispose);
51
+ }