@humanspeak/svelte-motion 0.4.9 → 0.5.0

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.
package/dist/index.d.ts CHANGED
@@ -10,31 +10,36 @@ 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';
16
17
  export { useInView } from './utils/inView.svelte';
17
18
  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';
19
+ export { useMotionTemplate } from './utils/motionTemplate.svelte';
20
+ export type { MotionTemplateInput } from './utils/motionTemplate.svelte';
21
+ export { useMotionValue } from './utils/motionValue.svelte';
22
+ export type { MotionValue, RawMotionValue } from './utils/motionValue.svelte';
21
23
  export { useMotionValueEvent } from './utils/motionValueEvent';
22
24
  export { useReducedMotion } from './utils/reducedMotion.svelte';
23
25
  export type { ReducedMotionState } from './utils/reducedMotion.svelte';
24
26
  export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
25
- export { useScroll } from './utils/scroll';
27
+ export { useScroll } from './utils/scroll.svelte';
28
+ export type { UseScrollOptions, UseScrollReturn } from './utils/scroll.svelte';
26
29
  export { useSpring } from './utils/spring.svelte';
27
30
  export type { SpringMotionValue, UseSpringOptions } from './utils/spring.svelte';
28
31
  export { useIsPresent, usePresence } from './utils/usePresence';
29
32
  export type { UsePresenceState } from './utils/usePresence';
30
- export { useVelocity } from './utils/velocity';
33
+ export { useVelocity } from './utils/velocity.svelte';
34
+ export type { VelocitySource } from './utils/velocity.svelte';
31
35
  /**
32
36
  * @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
33
37
  */
34
38
  export { stringifyStyleObject } from './utils/styleObject';
35
39
  export { styleString } from './utils/styleObject.svelte';
36
- export { useTime } from './utils/time';
37
- export { useTransform } from './utils/transform';
40
+ export { useTime } from './utils/time.svelte';
41
+ export { useTransform } from './utils/transform.svelte';
42
+ export type { MultiTransformer, SingleTransformer, TransformOptions, TransformOutputMap, TransformSource } from './utils/transform.svelte';
38
43
  export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
39
44
  export { default as MotionA } from './html/A.svelte';
40
45
  export { default as MotionAbbr } from './html/Abbr.svelte';
package/dist/index.js CHANGED
@@ -14,22 +14,22 @@ export { useAnimationFrame } from './utils/animationFrame';
14
14
  export { useCycle } from './utils/cycle.svelte';
15
15
  export { createDragControls } from './utils/dragControls';
16
16
  export { useInView } from './utils/inView.svelte';
17
- export { useMotionTemplate } from './utils/motionTemplate';
18
- export { useMotionValue } from './utils/motionValue';
17
+ export { useMotionTemplate } from './utils/motionTemplate.svelte';
18
+ export { useMotionValue } from './utils/motionValue.svelte';
19
19
  export { useMotionValueEvent } from './utils/motionValueEvent';
20
20
  export { useReducedMotion } from './utils/reducedMotion.svelte';
21
21
  export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
22
- export { useScroll } from './utils/scroll';
22
+ export { useScroll } from './utils/scroll.svelte';
23
23
  export { useSpring } from './utils/spring.svelte';
24
24
  export { useIsPresent, usePresence } from './utils/usePresence';
25
- export { useVelocity } from './utils/velocity';
25
+ export { useVelocity } from './utils/velocity.svelte';
26
26
  /**
27
27
  * @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
28
28
  */
29
29
  export { stringifyStyleObject } from './utils/styleObject';
30
30
  export { styleString } from './utils/styleObject.svelte';
31
- export { useTime } from './utils/time';
32
- export { useTransform } from './utils/transform';
31
+ export { useTime } from './utils/time.svelte';
32
+ export { useTransform } from './utils/transform.svelte';
33
33
  export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
34
34
  // Named component exports — tree-shakeable alternative to the `motion` object
35
35
  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,53 @@
1
+ import { type Readable } from 'svelte/store';
2
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
3
+ /**
4
+ * Input to {@link useMotionTemplate}'s interpolation slots: a motion-dom
5
+ * `MotionValue`, a Svelte readable, or a plain `number` / `string` literal.
6
+ * Mirrors framer-motion's `useMotionTemplate` signature.
7
+ */
8
+ export type MotionTemplateInput = AugmentedMotionValue<number | string> | Readable<number | string> | number | string;
9
+ /**
10
+ * Tagged template literal that builds an augmented `MotionValue<string>`
11
+ * from interpolated motion values, Svelte readables, and plain literals.
12
+ *
13
+ * Mirrors framer-motion 1:1: returns a real motion-dom `MotionValue<string>`
14
+ * (via `transformValue`) that auto-tracks every `MotionValue.get()` called
15
+ * during template composition. Whenever any tracked input emits, the
16
+ * composer reruns and writes the new string into the result value.
17
+ *
18
+ * Svelte-readable slots are sampled via `svelte/store`'s `get()` inside the
19
+ * composer so they re-emit when adjacent motion values trigger a recompute.
20
+ * Plain `number` / `string` slots are stringified inline.
21
+ *
22
+ * The result is augmented with a `$state`-backed `.current` getter and a
23
+ * Svelte readable `.subscribe` shim so it composes with the rest of the
24
+ * Tier 2 surface and reads reactively in Svelte 5 scopes.
25
+ *
26
+ * Lifecycle: must be called during component initialization. motion-dom
27
+ * cleans up the change-subscriptions when the result `MotionValue` is
28
+ * destroyed; we wire that destroy to the surrounding `$effect`.
29
+ *
30
+ * SSR-safe: motion-dom's `transformValue` works without DOM access (no
31
+ * timers, no listeners). On the server the result is a static augmented
32
+ * motion value with no `$effect` registered.
33
+ *
34
+ * @param strings Static template string parts (supplied by the tagged-template syntax).
35
+ * @param values Interpolated motion values, Svelte readables, or literals.
36
+ * @returns An `AugmentedMotionValue<string>` with the composed template.
37
+ *
38
+ * @example
39
+ * ```svelte
40
+ * <script>
41
+ * import { useSpring, useTransform, useMotionTemplate } from '@humanspeak/svelte-motion'
42
+ *
43
+ * const x = useSpring(0)
44
+ * const blur = useTransform(x, [-100, 0, 100], [10, 0, 10])
45
+ * const filter = useMotionTemplate`blur(${blur}px)`
46
+ * </script>
47
+ *
48
+ * <div style="filter: {filter.current}">Animated blur</div>
49
+ * ```
50
+ *
51
+ * @see https://motion.dev/docs/react-use-motion-template
52
+ */
53
+ export declare const useMotionTemplate: (strings: TemplateStringsArray, ...values: MotionTemplateInput[]) => AugmentedMotionValue<string>;
@@ -0,0 +1,78 @@
1
+ import { isMotionValue, transformValue } from 'motion-dom';
2
+ import {} from 'svelte/store';
3
+ import { augmentMotionValue, sampleSource } from './augmentMotionValue.svelte.js';
4
+ /**
5
+ * Tagged template literal that builds an augmented `MotionValue<string>`
6
+ * from interpolated motion values, Svelte readables, and plain literals.
7
+ *
8
+ * Mirrors framer-motion 1:1: returns a real motion-dom `MotionValue<string>`
9
+ * (via `transformValue`) that auto-tracks every `MotionValue.get()` called
10
+ * during template composition. Whenever any tracked input emits, the
11
+ * composer reruns and writes the new string into the result value.
12
+ *
13
+ * Svelte-readable slots are sampled via `svelte/store`'s `get()` inside the
14
+ * composer so they re-emit when adjacent motion values trigger a recompute.
15
+ * Plain `number` / `string` slots are stringified inline.
16
+ *
17
+ * The result is augmented with a `$state`-backed `.current` getter and a
18
+ * Svelte readable `.subscribe` shim so it composes with the rest of the
19
+ * Tier 2 surface and reads reactively in Svelte 5 scopes.
20
+ *
21
+ * Lifecycle: must be called during component initialization. motion-dom
22
+ * cleans up the change-subscriptions when the result `MotionValue` is
23
+ * destroyed; we wire that destroy to the surrounding `$effect`.
24
+ *
25
+ * SSR-safe: motion-dom's `transformValue` works without DOM access (no
26
+ * timers, no listeners). On the server the result is a static augmented
27
+ * motion value with no `$effect` registered.
28
+ *
29
+ * @param strings Static template string parts (supplied by the tagged-template syntax).
30
+ * @param values Interpolated motion values, Svelte readables, or literals.
31
+ * @returns An `AugmentedMotionValue<string>` with the composed template.
32
+ *
33
+ * @example
34
+ * ```svelte
35
+ * <script>
36
+ * import { useSpring, useTransform, useMotionTemplate } from '@humanspeak/svelte-motion'
37
+ *
38
+ * const x = useSpring(0)
39
+ * const blur = useTransform(x, [-100, 0, 100], [10, 0, 10])
40
+ * const filter = useMotionTemplate`blur(${blur}px)`
41
+ * </script>
42
+ *
43
+ * <div style="filter: {filter.current}">Animated blur</div>
44
+ * ```
45
+ *
46
+ * @see https://motion.dev/docs/react-use-motion-template
47
+ */
48
+ export const useMotionTemplate = (strings, ...values) => {
49
+ const numFragments = strings.length;
50
+ const buildValue = () => {
51
+ let output = '';
52
+ for (let i = 0; i < numFragments; i++) {
53
+ output += strings[i] ?? '';
54
+ if (i >= values.length)
55
+ continue;
56
+ const value = values[i];
57
+ // motion-dom's collectMotionValues session inside transformValue
58
+ // auto-discovers MotionValue deps via `.get()`. Readables don't
59
+ // participate — they're sampled inline via get(); they only
60
+ // re-emit if some adjacent motion value triggers a recompute.
61
+ if (isMotionValue(value)) {
62
+ output += String(value.get());
63
+ }
64
+ else if (value && typeof value === 'object' && 'subscribe' in value) {
65
+ output += String(sampleSource(value));
66
+ }
67
+ else {
68
+ output += String(value);
69
+ }
70
+ }
71
+ return output;
72
+ };
73
+ const result = transformValue(buildValue);
74
+ if (typeof window !== 'undefined') {
75
+ $effect(() => () => result.destroy());
76
+ }
77
+ return augmentMotionValue(result);
78
+ };