@humanspeak/svelte-motion 0.4.8 → 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/html/_MotionContainer.svelte +8 -8
- package/dist/index.d.ts +17 -11
- package/dist/index.js +9 -9
- package/dist/utils/attachable.js +14 -9
- package/dist/utils/augmentMotionValue.svelte.d.ts +156 -0
- package/dist/utils/augmentMotionValue.svelte.js +193 -0
- package/dist/utils/booleanSnapshot.svelte.d.ts +37 -0
- package/dist/utils/booleanSnapshot.svelte.js +48 -0
- package/dist/utils/dom.d.ts +13 -0
- package/dist/utils/dom.js +19 -0
- package/dist/utils/inView.svelte.d.ts +209 -0
- package/dist/utils/inView.svelte.js +323 -0
- package/dist/utils/motionTemplate.svelte.d.ts +53 -0
- package/dist/utils/motionTemplate.svelte.js +78 -0
- package/dist/utils/motionValue.svelte.d.ts +61 -0
- package/dist/utils/motionValue.svelte.js +49 -0
- package/dist/utils/reducedMotion.svelte.d.ts +43 -0
- package/dist/utils/reducedMotion.svelte.js +80 -0
- package/dist/utils/reducedMotionConfig.svelte.d.ts +74 -0
- package/dist/utils/reducedMotionConfig.svelte.js +144 -0
- package/dist/utils/scroll.svelte.d.ts +91 -0
- package/dist/utils/scroll.svelte.js +259 -0
- package/dist/utils/spring.svelte.d.ts +2 -6
- package/dist/utils/spring.svelte.js +6 -70
- package/dist/utils/time.svelte.d.ts +47 -0
- package/dist/utils/time.svelte.js +128 -0
- package/dist/utils/transform.svelte.d.ts +170 -0
- package/dist/utils/transform.svelte.js +189 -0
- package/dist/utils/velocity.svelte.d.ts +61 -0
- package/dist/utils/velocity.svelte.js +132 -0
- package/package.json +1 -1
- package/dist/utils/inView.d.ts +0 -136
- package/dist/utils/inView.js +0 -266
- package/dist/utils/motionTemplate.d.ts +0 -21
- package/dist/utils/motionTemplate.js +0 -33
- package/dist/utils/motionValue.d.ts +0 -6
- package/dist/utils/motionValue.js +0 -13
- package/dist/utils/reducedMotion.d.ts +0 -20
- package/dist/utils/reducedMotion.js +0 -42
- package/dist/utils/reducedMotionConfig.d.ts +0 -39
- package/dist/utils/reducedMotionConfig.js +0 -92
- package/dist/utils/scroll.d.ts +0 -63
- package/dist/utils/scroll.js +0 -79
- package/dist/utils/time.d.ts +0 -14
- package/dist/utils/time.js +0 -68
- package/dist/utils/transform.d.ts +0 -74
- package/dist/utils/transform.js +0 -211
- package/dist/utils/velocity.d.ts +0 -15
- package/dist/utils/velocity.js +0 -62
|
@@ -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 {
|
|
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
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
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,30 +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
|
-
export { useInView } from './utils/inView';
|
|
17
|
-
export type { UseInViewOptions } from './utils/inView';
|
|
18
|
-
export { useMotionTemplate } from './utils/motionTemplate';
|
|
19
|
-
export {
|
|
20
|
-
export
|
|
17
|
+
export { useInView } from './utils/inView.svelte';
|
|
18
|
+
export type { InViewState, UseInViewOptions } from './utils/inView.svelte';
|
|
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
|
-
export { useReducedMotion } from './utils/reducedMotion';
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
24
|
+
export { useReducedMotion } from './utils/reducedMotion.svelte';
|
|
25
|
+
export type { ReducedMotionState } from './utils/reducedMotion.svelte';
|
|
26
|
+
export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
|
|
27
|
+
export { useScroll } from './utils/scroll.svelte';
|
|
28
|
+
export type { UseScrollOptions, UseScrollReturn } from './utils/scroll.svelte';
|
|
25
29
|
export { useSpring } from './utils/spring.svelte';
|
|
26
30
|
export type { SpringMotionValue, UseSpringOptions } from './utils/spring.svelte';
|
|
27
31
|
export { useIsPresent, usePresence } from './utils/usePresence';
|
|
28
32
|
export type { UsePresenceState } from './utils/usePresence';
|
|
29
|
-
export { useVelocity } from './utils/velocity';
|
|
33
|
+
export { useVelocity } from './utils/velocity.svelte';
|
|
34
|
+
export type { VelocitySource } from './utils/velocity.svelte';
|
|
30
35
|
/**
|
|
31
36
|
* @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
|
|
32
37
|
*/
|
|
33
38
|
export { stringifyStyleObject } from './utils/styleObject';
|
|
34
39
|
export { styleString } from './utils/styleObject.svelte';
|
|
35
|
-
export { useTime } from './utils/time';
|
|
36
|
-
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';
|
|
37
43
|
export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
|
|
38
44
|
export { default as MotionA } from './html/A.svelte';
|
|
39
45
|
export { default as MotionAbbr } from './html/Abbr.svelte';
|
package/dist/index.js
CHANGED
|
@@ -13,23 +13,23 @@ 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 { useInView } from './utils/inView';
|
|
17
|
-
export { useMotionTemplate } from './utils/motionTemplate';
|
|
18
|
-
export { useMotionValue } from './utils/motionValue';
|
|
16
|
+
export { useInView } from './utils/inView.svelte';
|
|
17
|
+
export { useMotionTemplate } from './utils/motionTemplate.svelte';
|
|
18
|
+
export { useMotionValue } from './utils/motionValue.svelte';
|
|
19
19
|
export { useMotionValueEvent } from './utils/motionValueEvent';
|
|
20
|
-
export { useReducedMotion } from './utils/reducedMotion';
|
|
21
|
-
export { useReducedMotionConfig } from './utils/reducedMotionConfig';
|
|
22
|
-
export { useScroll } from './utils/scroll';
|
|
20
|
+
export { useReducedMotion } from './utils/reducedMotion.svelte';
|
|
21
|
+
export { useReducedMotionConfig } from './utils/reducedMotionConfig.svelte';
|
|
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';
|
package/dist/utils/attachable.js
CHANGED
|
@@ -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
|
|
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 (
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|
package/dist/utils/dom.d.ts
CHANGED
|
@@ -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
|
+
};
|