@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.
- package/dist/index.d.ts +14 -7
- package/dist/index.js +7 -6
- 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/dom.d.ts +13 -0
- package/dist/utils/dom.js +19 -0
- package/dist/utils/followValue.svelte.d.ts +84 -0
- package/dist/utils/followValue.svelte.js +51 -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/scroll.svelte.d.ts +91 -0
- package/dist/utils/scroll.svelte.js +259 -0
- package/dist/utils/spring.svelte.d.ts +26 -31
- package/dist/utils/spring.svelte.js +8 -116
- 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/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/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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Utilities for building and validating transform strings
|
|
2
|
+
import { isMotionValue, mapValue, transformValue } from 'motion-dom';
|
|
3
|
+
import {} from 'svelte/store';
|
|
4
|
+
import { augmentMotionValue, bridgeReadableToMotionValue } from './augmentMotionValue.svelte.js';
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
x: 0,
|
|
7
|
+
y: 0,
|
|
8
|
+
scale: 1,
|
|
9
|
+
scaleX: 1,
|
|
10
|
+
scaleY: 1,
|
|
11
|
+
rotate: 0
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Build a CSS transform string from numeric values (no matrices).
|
|
15
|
+
*
|
|
16
|
+
* @param values Partial map of translate/scale/rotate values.
|
|
17
|
+
* @returns A space-separated CSS `transform` string, or `""` when all values are defaults.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* buildTransform({ x: 10, y: 20, scale: 1.5 })
|
|
22
|
+
* // => "translate(10px, 20px) scale(1.5)"
|
|
23
|
+
*
|
|
24
|
+
* buildTransform({ x: 0, y: 0, rotate: 0 }) // all defaults
|
|
25
|
+
* // => ""
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const buildTransform = (values) => {
|
|
29
|
+
const v = { ...DEFAULTS, ...values };
|
|
30
|
+
const useAxes = values.scaleX !== undefined || values.scaleY !== undefined;
|
|
31
|
+
const parts = [];
|
|
32
|
+
if (v.x !== 0 || v.y !== 0)
|
|
33
|
+
parts.push(`translate(${round(v.x)}px, ${round(v.y)}px)`);
|
|
34
|
+
if (v.rotate !== 0)
|
|
35
|
+
parts.push(`rotate(${round(v.rotate)}deg)`);
|
|
36
|
+
if (useAxes) {
|
|
37
|
+
parts.push(`scaleX(${round(v.scaleX)})`);
|
|
38
|
+
parts.push(`scaleY(${round(v.scaleY)})`);
|
|
39
|
+
}
|
|
40
|
+
else if (v.scale !== 1) {
|
|
41
|
+
parts.push(`scale(${round(v.scale)})`);
|
|
42
|
+
}
|
|
43
|
+
return parts.join(' ').trim();
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Lightweight safety check for transform magnitudes and NaN values.
|
|
47
|
+
*
|
|
48
|
+
* @param values Transform values to validate.
|
|
49
|
+
* @param opts Optional configuration; `maxScale` caps allowable absolute scale (default 8).
|
|
50
|
+
* @returns `true` if all scale values are finite and within bounds.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* isSafeTransform({ scale: 2 }) // true
|
|
55
|
+
* isSafeTransform({ scale: 100 }) // false (exceeds default maxScale=8)
|
|
56
|
+
* isSafeTransform({ scale: 100 }, { maxScale: 200 }) // true
|
|
57
|
+
* isSafeTransform({ scale: NaN }) // false
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const isSafeTransform = (values, opts) => {
|
|
61
|
+
const maxScale = opts?.maxScale ?? 8;
|
|
62
|
+
const entries = [
|
|
63
|
+
['scale', values.scale],
|
|
64
|
+
['scaleX', values.scaleX],
|
|
65
|
+
['scaleY', values.scaleY]
|
|
66
|
+
];
|
|
67
|
+
for (const [, val] of entries) {
|
|
68
|
+
if (val === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
if (!Number.isFinite(val))
|
|
71
|
+
return false;
|
|
72
|
+
if (Math.abs(val) > maxScale)
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Extract the uniform scale factor from a CSS `matrix()` string.
|
|
79
|
+
*
|
|
80
|
+
* @param matrix A CSS `matrix(...)` value, `"none"`, `null`, or `undefined`.
|
|
81
|
+
* @returns The `a` component of the matrix (uniform scale), or `null` if unparseable.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* parseMatrixScale('matrix(1.5, 0, 0, 1.5, 0, 0)') // 1.5
|
|
86
|
+
* parseMatrixScale('none') // null
|
|
87
|
+
* parseMatrixScale(null) // null
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export const parseMatrixScale = (matrix) => {
|
|
91
|
+
if (!matrix || matrix === 'none')
|
|
92
|
+
return null;
|
|
93
|
+
const m = matrix.match(/matrix\(([^)]+)\)/);
|
|
94
|
+
if (!m)
|
|
95
|
+
return null;
|
|
96
|
+
const [a] = m[1].split(',').map((s) => parseFloat(s.trim()));
|
|
97
|
+
return Number.isFinite(a) ? a : null;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Round a number to six decimal places to avoid excessive precision in CSS strings.
|
|
101
|
+
*
|
|
102
|
+
* @param n The number to round.
|
|
103
|
+
* @returns The rounded value.
|
|
104
|
+
*/
|
|
105
|
+
const round = (n) => {
|
|
106
|
+
return Math.round(n * 1e6) / 1e6;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Normalizes a `TransformSource<T>` into a `MotionValue<T>`. Returns the
|
|
110
|
+
* source as-is for any motion-value input (no bridge), or a bridge
|
|
111
|
+
* `MotionValue` + cleanup for `Readable` inputs. The cast at the
|
|
112
|
+
* motion-value branch is safe — both `MotionValue` and `AugmentedMotionValue`
|
|
113
|
+
* are the same instance at runtime.
|
|
114
|
+
*/
|
|
115
|
+
const toMotionValue = (source) => {
|
|
116
|
+
if (isMotionValue(source)) {
|
|
117
|
+
return { value: source, dispose: () => undefined };
|
|
118
|
+
}
|
|
119
|
+
return bridgeReadableToMotionValue(source);
|
|
120
|
+
};
|
|
121
|
+
export function useTransform(sourceOrCompute, inputOrTransformer, outputOrOutputMap, options) {
|
|
122
|
+
// Compute form: useTransform(() => compute).
|
|
123
|
+
if (typeof sourceOrCompute === 'function') {
|
|
124
|
+
const compute = sourceOrCompute;
|
|
125
|
+
const value = transformValue(compute);
|
|
126
|
+
$effect(() => () => value.destroy());
|
|
127
|
+
return augmentMotionValue(value);
|
|
128
|
+
}
|
|
129
|
+
// Multi-input list form: useTransform([mv, mv, …], ([a, b, …]) => O).
|
|
130
|
+
if (Array.isArray(sourceOrCompute) && typeof inputOrTransformer === 'function') {
|
|
131
|
+
const sources = sourceOrCompute;
|
|
132
|
+
const transformer = inputOrTransformer;
|
|
133
|
+
const value = transformValue(() => {
|
|
134
|
+
const latest = [];
|
|
135
|
+
for (let i = 0; i < sources.length; i++) {
|
|
136
|
+
latest.push(sources[i].get());
|
|
137
|
+
}
|
|
138
|
+
return transformer(latest);
|
|
139
|
+
});
|
|
140
|
+
$effect(() => () => value.destroy());
|
|
141
|
+
return augmentMotionValue(value);
|
|
142
|
+
}
|
|
143
|
+
// Single-MV transformer form: useTransform(mv, (latest) => O).
|
|
144
|
+
if (typeof inputOrTransformer === 'function') {
|
|
145
|
+
const source = sourceOrCompute;
|
|
146
|
+
const transformer = inputOrTransformer;
|
|
147
|
+
const value = transformValue(() => transformer(source.get()));
|
|
148
|
+
$effect(() => () => value.destroy());
|
|
149
|
+
return augmentMotionValue(value);
|
|
150
|
+
}
|
|
151
|
+
// Mapping forms (single output array or output map).
|
|
152
|
+
const source = sourceOrCompute;
|
|
153
|
+
const input = inputOrTransformer ?? [];
|
|
154
|
+
const { value: numericSource, dispose: disposeBridge } = toMotionValue(source);
|
|
155
|
+
// Multi-output mapping form: useTransform(source, [range], { key: [out], … }, options).
|
|
156
|
+
// The `outputOrOutputMap !== null` check is load-bearing — `typeof null`
|
|
157
|
+
// is `'object'`, so a `null` argument would otherwise enter this branch
|
|
158
|
+
// and crash at `Object.keys(null)`.
|
|
159
|
+
if (outputOrOutputMap !== undefined &&
|
|
160
|
+
outputOrOutputMap !== null &&
|
|
161
|
+
!Array.isArray(outputOrOutputMap) &&
|
|
162
|
+
typeof outputOrOutputMap === 'object') {
|
|
163
|
+
const outputMap = outputOrOutputMap;
|
|
164
|
+
const keys = Object.keys(outputMap);
|
|
165
|
+
const result = {};
|
|
166
|
+
const inners = [];
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
const inner = mapValue(numericSource, input, outputMap[key], options);
|
|
169
|
+
inners.push(inner);
|
|
170
|
+
result[key] = augmentMotionValue(inner);
|
|
171
|
+
}
|
|
172
|
+
// One cleanup effect for all per-key MVs plus the shared bridge —
|
|
173
|
+
// saves N effect nodes vs. registering one per key.
|
|
174
|
+
$effect(() => () => {
|
|
175
|
+
for (const inner of inners)
|
|
176
|
+
inner.destroy();
|
|
177
|
+
disposeBridge();
|
|
178
|
+
});
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
// Single-output mapping form: useTransform(source, [range], [out], options).
|
|
182
|
+
const output = outputOrOutputMap ?? [];
|
|
183
|
+
const value = mapValue(numericSource, input, output, options);
|
|
184
|
+
$effect(() => () => {
|
|
185
|
+
value.destroy();
|
|
186
|
+
disposeBridge();
|
|
187
|
+
});
|
|
188
|
+
return augmentMotionValue(value);
|
|
189
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Readable } from 'svelte/store';
|
|
2
|
+
import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
|
|
3
|
+
/**
|
|
4
|
+
* Source for {@link useVelocity}: a motion-dom `MotionValue` or a Svelte
|
|
5
|
+
* readable. Svelte readables are bridged into a `MotionValue` so motion-dom's
|
|
6
|
+
* native velocity tracking can drive the result.
|
|
7
|
+
*/
|
|
8
|
+
export type VelocitySource = AugmentedMotionValue<number | string> | Readable<number | string>;
|
|
9
|
+
/**
|
|
10
|
+
* Creates an augmented `MotionValue<number>` whose value tracks the velocity
|
|
11
|
+
* of `source` in units/second.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors React framer-motion 1:1: on every `source` change, schedules an
|
|
14
|
+
* `updateVelocity` callback in motion-dom's frame loop via
|
|
15
|
+
* `frame.update(updateVelocity, false, true)`. `updateVelocity` reads
|
|
16
|
+
* `source.getVelocity()` (motion-dom tracks per-frame deltas + timestamps
|
|
17
|
+
* for free) and writes it to the result. If velocity is still non-zero,
|
|
18
|
+
* `updateVelocity` re-schedules itself for the next frame — so the loop
|
|
19
|
+
* only runs while there's actual motion and snaps to `0` the moment things
|
|
20
|
+
* settle. Idle CPU is zero.
|
|
21
|
+
*
|
|
22
|
+
* Returned value is a real motion-dom `MotionValue` (composes with
|
|
23
|
+
* `useTransform`, `useSpring`, `useMotionTemplate`, etc.) plus a
|
|
24
|
+
* `$state`-backed `.current` getter and a `.subscribe` shim.
|
|
25
|
+
*
|
|
26
|
+
* Lifecycle: must be called during component initialization. The change
|
|
27
|
+
* subscription, the frame-loop callback, and any Svelte-readable bridge are
|
|
28
|
+
* all torn down when the surrounding `$effect` unmounts.
|
|
29
|
+
*
|
|
30
|
+
* SSR-safe: returns a static augmented motion value with no subscriptions
|
|
31
|
+
* and no frame loop on the server.
|
|
32
|
+
*
|
|
33
|
+
* @param source A motion value or readable store of numeric or unit-string values.
|
|
34
|
+
* @returns A `MotionValue<number>` with `.current` and `.subscribe`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```svelte
|
|
38
|
+
* <script lang="ts">
|
|
39
|
+
* import {
|
|
40
|
+
* useMotionValue,
|
|
41
|
+
* useTransform,
|
|
42
|
+
* useVelocity
|
|
43
|
+
* } from '@humanspeak/svelte-motion'
|
|
44
|
+
*
|
|
45
|
+
* const x = useMotionValue(0)
|
|
46
|
+
* const xVelocity = useVelocity(x)
|
|
47
|
+
* // Map velocity to a momentum-driven skew. Skew goes positive when x is
|
|
48
|
+
* // accelerating right, negative when accelerating left, and snaps to 0
|
|
49
|
+
* // when motion settles.
|
|
50
|
+
* const skew = useTransform(xVelocity, [-1000, 0, 1000], [-15, 0, 15])
|
|
51
|
+
* </script>
|
|
52
|
+
*
|
|
53
|
+
* <div
|
|
54
|
+
* style="transform: translateX({x.current}px) skewX({skew.current}deg)"
|
|
55
|
+
* onpointermove={(e) => x.set(e.clientX)}
|
|
56
|
+
* />
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @see https://motion.dev/docs/react-use-velocity
|
|
60
|
+
*/
|
|
61
|
+
export declare const useVelocity: (source: VelocitySource) => AugmentedMotionValue<number>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { cancelFrame, frame, isMotionValue, motionValue } from 'motion-dom';
|
|
2
|
+
import {} from 'svelte/store';
|
|
3
|
+
import { augmentMotionValue, bridgeReadableToMotionValue } from './augmentMotionValue.svelte.js';
|
|
4
|
+
/**
|
|
5
|
+
* Parses a numeric value from a number or unit string (e.g. `"100px"` → `100`).
|
|
6
|
+
*/
|
|
7
|
+
const parseNumeric = (v) => {
|
|
8
|
+
if (typeof v === 'number')
|
|
9
|
+
return v;
|
|
10
|
+
const parsed = Number.parseFloat(String(v));
|
|
11
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Creates an augmented `MotionValue<number>` whose value tracks the velocity
|
|
15
|
+
* of `source` in units/second.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors React framer-motion 1:1: on every `source` change, schedules an
|
|
18
|
+
* `updateVelocity` callback in motion-dom's frame loop via
|
|
19
|
+
* `frame.update(updateVelocity, false, true)`. `updateVelocity` reads
|
|
20
|
+
* `source.getVelocity()` (motion-dom tracks per-frame deltas + timestamps
|
|
21
|
+
* for free) and writes it to the result. If velocity is still non-zero,
|
|
22
|
+
* `updateVelocity` re-schedules itself for the next frame — so the loop
|
|
23
|
+
* only runs while there's actual motion and snaps to `0` the moment things
|
|
24
|
+
* settle. Idle CPU is zero.
|
|
25
|
+
*
|
|
26
|
+
* Returned value is a real motion-dom `MotionValue` (composes with
|
|
27
|
+
* `useTransform`, `useSpring`, `useMotionTemplate`, etc.) plus a
|
|
28
|
+
* `$state`-backed `.current` getter and a `.subscribe` shim.
|
|
29
|
+
*
|
|
30
|
+
* Lifecycle: must be called during component initialization. The change
|
|
31
|
+
* subscription, the frame-loop callback, and any Svelte-readable bridge are
|
|
32
|
+
* all torn down when the surrounding `$effect` unmounts.
|
|
33
|
+
*
|
|
34
|
+
* SSR-safe: returns a static augmented motion value with no subscriptions
|
|
35
|
+
* and no frame loop on the server.
|
|
36
|
+
*
|
|
37
|
+
* @param source A motion value or readable store of numeric or unit-string values.
|
|
38
|
+
* @returns A `MotionValue<number>` with `.current` and `.subscribe`.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```svelte
|
|
42
|
+
* <script lang="ts">
|
|
43
|
+
* import {
|
|
44
|
+
* useMotionValue,
|
|
45
|
+
* useTransform,
|
|
46
|
+
* useVelocity
|
|
47
|
+
* } from '@humanspeak/svelte-motion'
|
|
48
|
+
*
|
|
49
|
+
* const x = useMotionValue(0)
|
|
50
|
+
* const xVelocity = useVelocity(x)
|
|
51
|
+
* // Map velocity to a momentum-driven skew. Skew goes positive when x is
|
|
52
|
+
* // accelerating right, negative when accelerating left, and snaps to 0
|
|
53
|
+
* // when motion settles.
|
|
54
|
+
* const skew = useTransform(xVelocity, [-1000, 0, 1000], [-15, 0, 15])
|
|
55
|
+
* </script>
|
|
56
|
+
*
|
|
57
|
+
* <div
|
|
58
|
+
* style="transform: translateX({x.current}px) skewX({skew.current}deg)"
|
|
59
|
+
* onpointermove={(e) => x.set(e.clientX)}
|
|
60
|
+
* />
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @see https://motion.dev/docs/react-use-velocity
|
|
64
|
+
*/
|
|
65
|
+
export const useVelocity = (source) => {
|
|
66
|
+
// Bridge non-numeric sources into a MotionValue<number> so motion-dom's
|
|
67
|
+
// getVelocity() tracks deltas correctly. Two paths feed this:
|
|
68
|
+
// 1. Svelte readables — always bridged (motion-dom doesn't know how to
|
|
69
|
+
// read them).
|
|
70
|
+
// 2. MotionValue<string> — bridged too, because motion-dom samples
|
|
71
|
+
// `canTrackVelocity` ONCE from the initial value via
|
|
72
|
+
// `!isNaN(parseFloat(value))`. A string MV that starts non-numeric
|
|
73
|
+
// (e.g. `""`) gets stuck at velocity = 0 forever, even if it later
|
|
74
|
+
// becomes a unit string like `"100px"`. The bridge runs every emit
|
|
75
|
+
// through `parseNumeric` so the tracker MV is always numeric.
|
|
76
|
+
let tracker;
|
|
77
|
+
let disposeBridge;
|
|
78
|
+
if (isMotionValue(source)) {
|
|
79
|
+
const initial = source.get();
|
|
80
|
+
if (typeof initial === 'number') {
|
|
81
|
+
tracker = source;
|
|
82
|
+
}
|
|
83
|
+
else if (typeof window !== 'undefined') {
|
|
84
|
+
const bridge = motionValue(parseNumeric(initial));
|
|
85
|
+
const unsub = source.on('change', (v) => {
|
|
86
|
+
bridge.set(parseNumeric(v));
|
|
87
|
+
});
|
|
88
|
+
tracker = bridge;
|
|
89
|
+
disposeBridge = () => {
|
|
90
|
+
unsub();
|
|
91
|
+
bridge.destroy();
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// SSR: parse the initial value but don't subscribe — the change
|
|
96
|
+
// listener would leak past the early SSR return below (no
|
|
97
|
+
// $effect runs to call dispose).
|
|
98
|
+
tracker = motionValue(parseNumeric(initial));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (typeof window !== 'undefined') {
|
|
102
|
+
const bridge = bridgeReadableToMotionValue(source, parseNumeric);
|
|
103
|
+
tracker = bridge.value;
|
|
104
|
+
disposeBridge = bridge.dispose;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
tracker = motionValue(0);
|
|
108
|
+
}
|
|
109
|
+
const result = motionValue(tracker.getVelocity());
|
|
110
|
+
// SSR: skip the frame-loop wiring entirely and return a static MV.
|
|
111
|
+
if (typeof window === 'undefined') {
|
|
112
|
+
return augmentMotionValue(result);
|
|
113
|
+
}
|
|
114
|
+
const updateVelocity = () => {
|
|
115
|
+
const latest = tracker.getVelocity();
|
|
116
|
+
result.set(latest);
|
|
117
|
+
if (latest)
|
|
118
|
+
frame.update(updateVelocity);
|
|
119
|
+
};
|
|
120
|
+
const unsubChange = tracker.on('change', () => {
|
|
121
|
+
// keepAlive: false, immediate: true — run at end of current frame if
|
|
122
|
+
// we're already in one. Matches React framer-motion's useVelocity.
|
|
123
|
+
frame.update(updateVelocity, false, true);
|
|
124
|
+
});
|
|
125
|
+
$effect(() => () => {
|
|
126
|
+
unsubChange();
|
|
127
|
+
cancelFrame(updateVelocity);
|
|
128
|
+
disposeBridge?.();
|
|
129
|
+
result.destroy();
|
|
130
|
+
});
|
|
131
|
+
return augmentMotionValue(result);
|
|
132
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-motion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values. The drop-in Framer Motion alternative for Svelte and SvelteKit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type Readable } from 'svelte/store';
|
|
2
|
-
/**
|
|
3
|
-
* Tagged template literal that creates a derived store from interpolated
|
|
4
|
-
* readable stores. When any input store changes, the resulting store
|
|
5
|
-
* recomputes the template string.
|
|
6
|
-
*
|
|
7
|
-
* SSR-safe: `derived` from svelte/store works on the server.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const blur = useSpring(0)
|
|
12
|
-
* const filter = useMotionTemplate`blur(${blur}px)`
|
|
13
|
-
* // $filter → "blur(0px)"
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @param strings Static template string parts.
|
|
17
|
-
* @param values Readable stores to interpolate.
|
|
18
|
-
* @returns A readable store of the composed string.
|
|
19
|
-
* @see https://motion.dev/docs/react-use-motion-template
|
|
20
|
-
*/
|
|
21
|
-
export declare const useMotionTemplate: (strings: TemplateStringsArray, ...values: Readable<number | string>[]) => Readable<string>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { derived } from 'svelte/store';
|
|
2
|
-
/**
|
|
3
|
-
* Tagged template literal that creates a derived store from interpolated
|
|
4
|
-
* readable stores. When any input store changes, the resulting store
|
|
5
|
-
* recomputes the template string.
|
|
6
|
-
*
|
|
7
|
-
* SSR-safe: `derived` from svelte/store works on the server.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const blur = useSpring(0)
|
|
12
|
-
* const filter = useMotionTemplate`blur(${blur}px)`
|
|
13
|
-
* // $filter → "blur(0px)"
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @param strings Static template string parts.
|
|
17
|
-
* @param values Readable stores to interpolate.
|
|
18
|
-
* @returns A readable store of the composed string.
|
|
19
|
-
* @see https://motion.dev/docs/react-use-motion-template
|
|
20
|
-
*/
|
|
21
|
-
export const useMotionTemplate = (strings, ...values) => {
|
|
22
|
-
if (values.length === 0)
|
|
23
|
-
return derived([], () => strings[0] ?? '');
|
|
24
|
-
return derived(values, (current) => {
|
|
25
|
-
let result = '';
|
|
26
|
-
for (let i = 0; i < strings.length; i++) {
|
|
27
|
-
result += strings[i] ?? '';
|
|
28
|
-
if (i < current.length)
|
|
29
|
-
result += String(current[i]);
|
|
30
|
-
}
|
|
31
|
-
return result;
|
|
32
|
-
});
|
|
33
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { writable } from 'svelte/store';
|
|
2
|
-
export const useMotionValue = (initial) => {
|
|
3
|
-
let current = initial;
|
|
4
|
-
const store = writable(initial);
|
|
5
|
-
return {
|
|
6
|
-
subscribe: store.subscribe,
|
|
7
|
-
set: (v) => {
|
|
8
|
-
current = v;
|
|
9
|
-
store.set(v);
|
|
10
|
-
},
|
|
11
|
-
get: () => current
|
|
12
|
-
};
|
|
13
|
-
};
|
package/dist/utils/scroll.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { type Readable } from 'svelte/store';
|
|
2
|
-
import { type ElementOrGetter } from './dom.js';
|
|
3
|
-
/**
|
|
4
|
-
* A scroll offset edge defined as a string (e.g. `"start"`, `"end"`, `"center"`)
|
|
5
|
-
* or a number (0–1 progress). Each offset entry is a pair of `[target, container]`.
|
|
6
|
-
*/
|
|
7
|
-
type ScrollOffset = Array<[number | string, number | string]> | string[];
|
|
8
|
-
/**
|
|
9
|
-
* Options accepted by `useScroll`.
|
|
10
|
-
*/
|
|
11
|
-
type UseScrollOptions = {
|
|
12
|
-
/** Scrollable container to track. Defaults to the page. Accepts an element or a getter function. */
|
|
13
|
-
container?: ElementOrGetter;
|
|
14
|
-
/** Target element to track position of within the container. Accepts an element or a getter function. */
|
|
15
|
-
target?: ElementOrGetter;
|
|
16
|
-
/** Scroll offset configuration for element position tracking. */
|
|
17
|
-
offset?: ScrollOffset;
|
|
18
|
-
/** Which axis to use for the single-axis `progress` value supplied to `scroll()`. */
|
|
19
|
-
axis?: 'x' | 'y';
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Return type of `useScroll` — four readable Svelte stores representing
|
|
23
|
-
* scroll position and normalised progress for both axes.
|
|
24
|
-
*/
|
|
25
|
-
type UseScrollReturn = {
|
|
26
|
-
scrollX: Readable<number>;
|
|
27
|
-
scrollY: Readable<number>;
|
|
28
|
-
scrollXProgress: Readable<number>;
|
|
29
|
-
scrollYProgress: Readable<number>;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Creates scroll-linked Svelte stores for building scroll-driven animations
|
|
33
|
-
* such as progress indicators and parallax effects.
|
|
34
|
-
*
|
|
35
|
-
* When the returned stores are used with `opacity` / `transform` CSS properties
|
|
36
|
-
* the animations can be hardware accelerated by the browser.
|
|
37
|
-
*
|
|
38
|
-
* SSR-safe: returns static `readable(0)` stores on the server.
|
|
39
|
-
*
|
|
40
|
-
* `container` and `target` accept either an `HTMLElement` directly or a
|
|
41
|
-
* getter function `() => HTMLElement | undefined`. This is useful with
|
|
42
|
-
* Svelte's `bind:this` where the element isn't available until after mount.
|
|
43
|
-
* When a getter is provided, element resolution is deferred until the
|
|
44
|
-
* first subscriber arrives, and if the element isn't available yet the
|
|
45
|
-
* stores poll on each animation frame until it is.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```svelte
|
|
49
|
-
* <script>
|
|
50
|
-
* import { useScroll, useSpring } from '@humanspeak/svelte-motion'
|
|
51
|
-
*
|
|
52
|
-
* const { scrollYProgress } = useScroll()
|
|
53
|
-
* const scaleX = useSpring(scrollYProgress)
|
|
54
|
-
* </script>
|
|
55
|
-
*
|
|
56
|
-
* <div style="transform: scaleX({$scaleX}); transform-origin: left;" />
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @param options Optional scroll tracking configuration.
|
|
60
|
-
* @returns An object with `scrollX`, `scrollY`, `scrollXProgress`, and `scrollYProgress` stores.
|
|
61
|
-
*/
|
|
62
|
-
export declare const useScroll: (options?: UseScrollOptions) => UseScrollReturn;
|
|
63
|
-
export {};
|
package/dist/utils/scroll.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { scroll } from 'motion';
|
|
2
|
-
import { readable, writable } from 'svelte/store';
|
|
3
|
-
import { createAttachable } from './attachable.js';
|
|
4
|
-
import {} from './dom.js';
|
|
5
|
-
/**
|
|
6
|
-
* Creates scroll-linked Svelte stores for building scroll-driven animations
|
|
7
|
-
* such as progress indicators and parallax effects.
|
|
8
|
-
*
|
|
9
|
-
* When the returned stores are used with `opacity` / `transform` CSS properties
|
|
10
|
-
* the animations can be hardware accelerated by the browser.
|
|
11
|
-
*
|
|
12
|
-
* SSR-safe: returns static `readable(0)` stores on the server.
|
|
13
|
-
*
|
|
14
|
-
* `container` and `target` accept either an `HTMLElement` directly or a
|
|
15
|
-
* getter function `() => HTMLElement | undefined`. This is useful with
|
|
16
|
-
* Svelte's `bind:this` where the element isn't available until after mount.
|
|
17
|
-
* When a getter is provided, element resolution is deferred until the
|
|
18
|
-
* first subscriber arrives, and if the element isn't available yet the
|
|
19
|
-
* stores poll on each animation frame until it is.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```svelte
|
|
23
|
-
* <script>
|
|
24
|
-
* import { useScroll, useSpring } from '@humanspeak/svelte-motion'
|
|
25
|
-
*
|
|
26
|
-
* const { scrollYProgress } = useScroll()
|
|
27
|
-
* const scaleX = useSpring(scrollYProgress)
|
|
28
|
-
* </script>
|
|
29
|
-
*
|
|
30
|
-
* <div style="transform: scaleX({$scaleX}); transform-origin: left;" />
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* @param options Optional scroll tracking configuration.
|
|
34
|
-
* @returns An object with `scrollX`, `scrollY`, `scrollXProgress`, and `scrollYProgress` stores.
|
|
35
|
-
*/
|
|
36
|
-
export const useScroll = (options) => {
|
|
37
|
-
if (typeof window === 'undefined') {
|
|
38
|
-
return {
|
|
39
|
-
scrollX: readable(0),
|
|
40
|
-
scrollY: readable(0),
|
|
41
|
-
scrollXProgress: readable(0),
|
|
42
|
-
scrollYProgress: readable(0)
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const stores = {
|
|
46
|
-
scrollX: writable(0),
|
|
47
|
-
scrollY: writable(0),
|
|
48
|
-
scrollXProgress: writable(0),
|
|
49
|
-
scrollYProgress: writable(0)
|
|
50
|
-
};
|
|
51
|
-
const attachable = createAttachable({
|
|
52
|
-
refs: { container: options?.container, target: options?.target },
|
|
53
|
-
onAttach: ({ container, target }) => scroll((_progress, info) => {
|
|
54
|
-
stores.scrollX.set(info.x.current);
|
|
55
|
-
stores.scrollY.set(info.y.current);
|
|
56
|
-
stores.scrollXProgress.set(info.x.progress);
|
|
57
|
-
stores.scrollYProgress.set(info.y.progress);
|
|
58
|
-
}, {
|
|
59
|
-
container,
|
|
60
|
-
target,
|
|
61
|
-
offset: options?.offset,
|
|
62
|
-
axis: options?.axis
|
|
63
|
-
})
|
|
64
|
-
});
|
|
65
|
-
const make = (key) => readable(0, (set) => {
|
|
66
|
-
const release = attachable.subscribe();
|
|
67
|
-
const unsub = stores[key].subscribe(set);
|
|
68
|
-
return () => {
|
|
69
|
-
unsub();
|
|
70
|
-
release();
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
return {
|
|
74
|
-
scrollX: make('scrollX'),
|
|
75
|
-
scrollY: make('scrollY'),
|
|
76
|
-
scrollXProgress: make('scrollXProgress'),
|
|
77
|
-
scrollYProgress: make('scrollYProgress')
|
|
78
|
-
};
|
|
79
|
-
};
|
package/dist/utils/time.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type Readable } from 'svelte/store';
|
|
2
|
-
/**
|
|
3
|
-
* Returns a time store that ticks once per animation frame.
|
|
4
|
-
*
|
|
5
|
-
* - Without an `id`, returns a fresh timeline per call.
|
|
6
|
-
* - With an `id`, callers sharing the same id receive the same store/timeline,
|
|
7
|
-
* ensuring synchronized reads across components.
|
|
8
|
-
* - SSR-safe: Returns a static 0-valued store when `window` is unavailable.
|
|
9
|
-
*
|
|
10
|
-
* @param {string=} id Optional timeline identifier for sharing across calls.
|
|
11
|
-
* @returns {Readable<number>} A readable store of elapsed milliseconds.
|
|
12
|
-
* @see https://motion.dev/docs/react-use-time?platform=react
|
|
13
|
-
*/
|
|
14
|
-
export declare const useTime: (id?: string) => Readable<number>;
|