@humanspeak/svelte-motion 0.4.9 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.d.ts +14 -7
  2. package/dist/index.js +7 -6
  3. package/dist/utils/attachable.js +14 -9
  4. package/dist/utils/augmentMotionValue.svelte.d.ts +156 -0
  5. package/dist/utils/augmentMotionValue.svelte.js +193 -0
  6. package/dist/utils/dom.d.ts +13 -0
  7. package/dist/utils/dom.js +19 -0
  8. package/dist/utils/followValue.svelte.d.ts +84 -0
  9. package/dist/utils/followValue.svelte.js +51 -0
  10. package/dist/utils/motionTemplate.svelte.d.ts +53 -0
  11. package/dist/utils/motionTemplate.svelte.js +78 -0
  12. package/dist/utils/motionValue.svelte.d.ts +61 -0
  13. package/dist/utils/motionValue.svelte.js +49 -0
  14. package/dist/utils/scroll.svelte.d.ts +91 -0
  15. package/dist/utils/scroll.svelte.js +259 -0
  16. package/dist/utils/spring.svelte.d.ts +26 -31
  17. package/dist/utils/spring.svelte.js +8 -116
  18. package/dist/utils/time.svelte.d.ts +47 -0
  19. package/dist/utils/time.svelte.js +128 -0
  20. package/dist/utils/transform.svelte.d.ts +170 -0
  21. package/dist/utils/transform.svelte.js +189 -0
  22. package/dist/utils/velocity.svelte.d.ts +61 -0
  23. package/dist/utils/velocity.svelte.js +132 -0
  24. package/package.json +1 -1
  25. package/dist/utils/motionTemplate.d.ts +0 -21
  26. package/dist/utils/motionTemplate.js +0 -33
  27. package/dist/utils/motionValue.d.ts +0 -6
  28. package/dist/utils/motionValue.js +0 -13
  29. package/dist/utils/scroll.d.ts +0 -63
  30. package/dist/utils/scroll.js +0 -79
  31. package/dist/utils/time.d.ts +0 -14
  32. package/dist/utils/time.js +0 -68
  33. package/dist/utils/transform.d.ts +0 -74
  34. package/dist/utils/transform.js +0 -211
  35. package/dist/utils/velocity.d.ts +0 -15
  36. package/dist/utils/velocity.js +0 -62
@@ -1,5 +1,6 @@
1
1
  import { type FollowValueOptions, type MotionValue, type SpringOptions } from 'motion-dom';
2
2
  import { type Readable } from 'svelte/store';
3
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
3
4
  /**
4
5
  * Spring + follow options for {@link useSpring}.
5
6
  *
@@ -8,6 +9,10 @@ import { type Readable } from 'svelte/store';
8
9
  * `velocity`, `restDelta`, `restSpeed`) plus `skipInitialAnimation` for
9
10
  * scroll-restoration scenarios.
10
11
  *
12
+ * `useSpring` is a thin wrapper over {@link useFollowValue} that hard-codes
13
+ * `type: 'spring'`. For other transition types (tween, inertia, keyframes)
14
+ * use `useFollowValue` directly.
15
+ *
11
16
  * @see https://motion.dev/docs/react-use-spring
12
17
  */
13
18
  export type UseSpringOptions = SpringOptions & Pick<FollowValueOptions, 'skipInitialAnimation'>;
@@ -21,46 +26,36 @@ export type UseSpringOptions = SpringOptions & Pick<FollowValueOptions, 'skipIni
21
26
  * - `current` — a Svelte-5 reactive read backed by `$state`. Use in templates
22
27
  * and `$derived` / `$effect` to track the spring value without subscribing.
23
28
  * - `subscribe` — Svelte readable store contract. Calls the run function once
24
- * synchronously with the current value, then on every change. Lets the
25
- * spring be used with `$spring` template syntax, `get(spring)`, and as a
26
- * dependency in `useTransform`'s function form.
29
+ * synchronously with the current value, then on every change.
27
30
  */
28
- export type SpringMotionValue<T extends number | string> = Omit<MotionValue<T>, 'current'> & {
29
- /** Reactive read in Svelte 5 templates / `$derived` / `$effect`. */
30
- readonly current: T;
31
- /** Svelte readable store compatibility. */
32
- subscribe: (run: (value: T) => void) => () => void;
33
- };
31
+ export type SpringMotionValue<T extends number | string> = AugmentedMotionValue<T>;
34
32
  /**
35
33
  * Creates a spring-animated `MotionValue`.
36
34
  *
37
35
  * Set a target with `.set(v)` to animate to it using spring physics, or
38
- * `.jump(v)` to skip the animation. Pass another `MotionValue` (or, for
39
- * backwards compatibility, a Svelte readable store like the ones from
40
- * `useScroll` / `useTime`) as `source` and the spring will animate towards
41
- * whatever that source emits.
42
- *
43
- * Returned object is a real motion-dom `MotionValue` — composes with
44
- * `animate()`, `useTransform`, `useVelocity`, and motion-dom's animation
45
- * engine. On top, it exposes:
36
+ * `.jump(v)` to skip the animation. Pass another `MotionValue` (or a Svelte
37
+ * readable store from `useScroll` / `useTime`) as `source` and the spring
38
+ * will animate toward whatever that source emits.
46
39
  *
47
- * - `.current` Svelte-5 reactive read for templates and `$derived` /
48
- * `$effect`.
49
- * - `.subscribe(run)` Svelte readable store contract so `$spring` template
50
- * syntax and `useTransform(() => …, [spring])` keep working during the
51
- * Tier 2 migration window.
40
+ * Returned object is a real motion-dom `MotionValue` augmented with a
41
+ * `$state`-backed `.current` getter and a Svelte readable `.subscribe` shim.
42
+ * Composes with `useTransform`, `useVelocity`, `animate()`, and the rest of
43
+ * the motion-value surface.
52
44
  *
53
- * Lifecycle: must be called during component initialization. Cleanup is
54
- * registered via `$effect`; the spring stops animating and unsubscribes from
55
- * its source when the surrounding component / effect tears down. Call
56
- * `.destroy()` to clean up early.
45
+ * Lifecycle: must be called during component initialization. The follow
46
+ * animation, source bridge (if any), and motion value teardown are bound to
47
+ * the surrounding `$effect` scope.
57
48
  *
58
49
  * SSR-safe: returns a static `MotionValue` with no animation on the server.
59
50
  *
60
- * @template T
61
- * @param {number|string|MotionValue<number>|MotionValue<string>|Readable<number|string>} source Initial value or a source to follow.
62
- * @param {UseSpringOptions} [options] Spring + follow configuration.
63
- * @returns {SpringMotionValue<T>} A `MotionValue` with `.current` and `.subscribe`.
51
+ * Implementation: thin wrapper over {@link useFollowValue} that hard-codes
52
+ * `type: 'spring'`. For tween / inertia / keyframes follows, call
53
+ * `useFollowValue` directly.
54
+ *
55
+ * @template T The value type — `number` or `string` (unit strings preserved).
56
+ * @param source Initial value, a `MotionValue` to follow, or a Svelte readable.
57
+ * @param options Spring + follow configuration.
58
+ * @returns A `MotionValue<T>` with `.current` and `.subscribe`.
64
59
  *
65
60
  * @example
66
61
  * ```svelte
@@ -71,7 +66,7 @@ export type SpringMotionValue<T extends number | string> = Omit<MotionValue<T>,
71
66
  * </script>
72
67
  *
73
68
  * <button onclick={() => x.set(100)}>Animate</button>
74
- * <div>{x.current}</div>
69
+ * <div style="transform: translateX({x.current}px)">{x.current}</div>
75
70
  * ```
76
71
  *
77
72
  * @see https://motion.dev/docs/react-use-spring
@@ -1,118 +1,10 @@
1
- import { attachFollow, isMotionValue, motionValue } from 'motion-dom';
2
- import { get } from 'svelte/store';
3
- /**
4
- * Detects a Svelte readable store. Excludes motion-dom `MotionValue` instances
5
- * (which also expose `subscribe`-shaped APIs in some versions) so the
6
- * MotionValue path is preferred.
7
- */
8
- const isSvelteReadable = (value) => {
9
- return (!!value &&
10
- typeof value === 'object' &&
11
- typeof value.subscribe === 'function' &&
12
- !isMotionValue(value));
13
- };
1
+ import {} from 'motion-dom';
2
+ import {} from 'svelte/store';
3
+ import {} from './augmentMotionValue.svelte.js';
4
+ import { useFollowValue } from './followValue.svelte.js';
14
5
  export function useSpring(source, options = {}) {
15
- // SSR: return a static MotionValue with no animation. Reads return the
16
- // best-effort initial value; .set / .jump become no-ops to avoid drifting
17
- // away from the server-rendered snapshot.
18
- if (typeof window === 'undefined') {
19
- const initial = readInitial(source);
20
- const ssrValue = motionValue(initial);
21
- ssrValue.set = () => undefined;
22
- ssrValue.jump = () => undefined;
23
- return augmentForSvelte(ssrValue, () => undefined);
24
- }
25
- // Resolve initial + follow source.
26
- let followSource;
27
- let cleanupReadableBridge;
28
- let svelteBridge;
29
- if (isMotionValue(source)) {
30
- followSource = source;
31
- }
32
- else if (isSvelteReadable(source)) {
33
- // Bridge a Svelte readable into a MotionValue so attachFollow can
34
- // track it. Synchronous initial sample comes from svelte/store's get().
35
- const initialFromReadable = get(source);
36
- svelteBridge = motionValue(initialFromReadable);
37
- cleanupReadableBridge = source.subscribe((v) => {
38
- // The Svelte readable contract calls the subscriber synchronously
39
- // with the current value on subscribe. Skip if it equals the
40
- // already-seeded bridge value so attachFollow doesn't fire a
41
- // spring on the initial emit. Subsequent emits go through set()
42
- // and trigger animation.
43
- if (svelteBridge.get() === v)
44
- return;
45
- svelteBridge.set(v);
46
- });
47
- followSource = svelteBridge;
48
- }
49
- else {
50
- followSource = source;
51
- }
52
- const initial = isMotionValue(followSource) ? followSource.get() : followSource;
53
- const value = motionValue(initial);
54
- const stopFollow = attachFollow(value, followSource, { type: 'spring', ...options });
55
- // Side-cleanup for our augmentations. Single-shot guard lives in the
56
- // augmented `value.destroy` (the only caller), so no flag here.
57
- const dispose = () => {
58
- stopFollow?.();
59
- cleanupReadableBridge?.();
60
- svelteBridge?.destroy();
61
- };
62
- $effect(() => () => value.destroy());
63
- return augmentForSvelte(value, dispose);
6
+ // Cast through `unknown` because TypeScript can't narrow the multi-form
7
+ // `source` against `useFollowValue`'s overload set; runtime behavior is
8
+ // identical `useFollowValue` dispatches on the same shapes.
9
+ return useFollowValue(source, { type: 'spring', ...options });
64
10
  }
65
- /**
66
- * Pull the synchronous initial value out of any accepted source form.
67
- */
68
- const readInitial = (source) => {
69
- if (typeof source === 'number' || typeof source === 'string')
70
- return source;
71
- if (isMotionValue(source))
72
- return source.get();
73
- if (isSvelteReadable(source))
74
- return get(source);
75
- return 0;
76
- };
77
- /**
78
- * Layer Svelte-friendly affordances onto a motion-dom MotionValue: a
79
- * `$state`-tracked `.current` accessor (routing motion-dom's internal
80
- * `this.current = v` writes through `$state` so templates and `$derived` /
81
- * `$effect` re-run) and a Svelte readable store `.subscribe(run)` shim.
82
- */
83
- const augmentForSvelte = (value, dispose) => {
84
- let current = $state(value.get());
85
- Object.defineProperty(value, 'current', {
86
- get: () => current,
87
- // Same-value writes are no-ops: motion-dom's `updateAndNotify` calls
88
- // `setCurrent(v)` before its own change check, so spring frames at
89
- // rest still hit this setter; skipping equal writes avoids gratuitous
90
- // accessor work even though $state would itself dedupe.
91
- set: (v) => {
92
- if (v !== current)
93
- current = v;
94
- },
95
- enumerable: true,
96
- configurable: true
97
- });
98
- const originalDestroy = value.destroy.bind(value);
99
- let destroyed = false;
100
- value.destroy = () => {
101
- if (destroyed)
102
- return;
103
- destroyed = true;
104
- dispose();
105
- originalDestroy();
106
- };
107
- const subscribe = (run) => {
108
- run(value.get());
109
- return value.on('change', run);
110
- };
111
- Object.defineProperty(value, 'subscribe', {
112
- value: subscribe,
113
- writable: false,
114
- enumerable: false,
115
- configurable: true
116
- });
117
- return value;
118
- };
@@ -0,0 +1,47 @@
1
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
2
+ /**
3
+ * Returns an augmented `MotionValue<number>` that ticks once per render
4
+ * frame with the milliseconds elapsed since the value was created.
5
+ *
6
+ * Mirrors React framer-motion's `useTime` 1:1: a `MotionValue<number>`
7
+ * driven by motion-dom's `frame.update(tick, true)` keep-alive callback.
8
+ * The frame loop dedupes per-frame work across all motion-dom consumers,
9
+ * so multiple `useTime()` calls share the same render schedule.
10
+ *
11
+ * Two modes:
12
+ *
13
+ * - **Unique timeline** — `useTime()` starts its own keep-alive callback.
14
+ * The motion value and the callback are torn down when the surrounding
15
+ * `$effect` scope unmounts.
16
+ * - **Shared timeline** — `useTime(id)` callers passing the same `id` all
17
+ * observe a single shared frame-loop callback. Each call still returns
18
+ * an independent motion value (so destroying one consumer's value
19
+ * doesn't ripple to others) but the values stay in lockstep. The
20
+ * shared callback cancels the moment the last consumer unmounts; the
21
+ * next `useTime(id)` call restarts it.
22
+ *
23
+ * The result is augmented with a `$state`-backed `.current` getter and a
24
+ * `.subscribe` shim — it composes with `useTransform`, `useSpring`, and
25
+ * the rest of the Tier 2 surface.
26
+ *
27
+ * Lifecycle: must be called during component initialization. SSR-safe:
28
+ * returns a static `motionValue(0)` with no frame loop on the server.
29
+ *
30
+ * @param id Optional timeline identifier for sharing across components.
31
+ * @returns A `MotionValue<number>` with `.current` and `.subscribe`.
32
+ *
33
+ * @example
34
+ * ```svelte
35
+ * <script>
36
+ * import { useTime, useTransform } from '@humanspeak/svelte-motion'
37
+ *
38
+ * const time = useTime()
39
+ * const rotate = useTransform(time, [0, 4000], [0, 360], { clamp: false })
40
+ * </script>
41
+ *
42
+ * <div style="transform: rotate({rotate.current}deg)">↻</div>
43
+ * ```
44
+ *
45
+ * @see https://motion.dev/docs/react-use-time
46
+ */
47
+ export declare const useTime: (id?: string) => AugmentedMotionValue<number>;
@@ -0,0 +1,128 @@
1
+ import { cancelFrame, frame, motionValue } from 'motion-dom';
2
+ import { SvelteMap } from 'svelte/reactivity';
3
+ import { augmentMotionValue } from './augmentMotionValue.svelte.js';
4
+ // `SvelteMap` (not plain Map) per `eslint/svelte/prefer-svelte-reactivity`
5
+ // inside `.svelte.ts` files. The contents aren't read in reactive scopes —
6
+ // this is a plain keyed cache — but the linter rule applies uniformly.
7
+ const sharedTimelines = new SvelteMap();
8
+ // Clear shared timelines on HMR dispose to avoid stale entries across hot
9
+ // reloads.
10
+ const hot = import.meta.hot;
11
+ if (hot) {
12
+ hot.dispose(() => {
13
+ for (const t of sharedTimelines.values()) {
14
+ t.cancel();
15
+ t.base.destroy();
16
+ }
17
+ sharedTimelines.clear();
18
+ });
19
+ }
20
+ /**
21
+ * Starts a keep-alive frame-loop callback that writes elapsed-milliseconds
22
+ * into a fresh `MotionValue<number>`. Returns the value and a cancel
23
+ * function that stops the loop. Caller owns the value's destroy lifecycle.
24
+ *
25
+ * Uses motion-dom's `frame.update(cb, true)` — `true` is the `keepAlive`
26
+ * flag, telling the frame loop to re-schedule the callback every frame
27
+ * automatically. Matches React framer-motion's `useAnimationFrame`.
28
+ */
29
+ const startTimeBase = () => {
30
+ const base = motionValue(0);
31
+ let start = 0;
32
+ const tick = ({ timestamp }) => {
33
+ if (!start)
34
+ start = timestamp;
35
+ base.set(timestamp - start);
36
+ };
37
+ frame.update(tick, true);
38
+ return {
39
+ base,
40
+ cancel: () => cancelFrame(tick)
41
+ };
42
+ };
43
+ /**
44
+ * Returns an augmented `MotionValue<number>` that ticks once per render
45
+ * frame with the milliseconds elapsed since the value was created.
46
+ *
47
+ * Mirrors React framer-motion's `useTime` 1:1: a `MotionValue<number>`
48
+ * driven by motion-dom's `frame.update(tick, true)` keep-alive callback.
49
+ * The frame loop dedupes per-frame work across all motion-dom consumers,
50
+ * so multiple `useTime()` calls share the same render schedule.
51
+ *
52
+ * Two modes:
53
+ *
54
+ * - **Unique timeline** — `useTime()` starts its own keep-alive callback.
55
+ * The motion value and the callback are torn down when the surrounding
56
+ * `$effect` scope unmounts.
57
+ * - **Shared timeline** — `useTime(id)` callers passing the same `id` all
58
+ * observe a single shared frame-loop callback. Each call still returns
59
+ * an independent motion value (so destroying one consumer's value
60
+ * doesn't ripple to others) but the values stay in lockstep. The
61
+ * shared callback cancels the moment the last consumer unmounts; the
62
+ * next `useTime(id)` call restarts it.
63
+ *
64
+ * The result is augmented with a `$state`-backed `.current` getter and a
65
+ * `.subscribe` shim — it composes with `useTransform`, `useSpring`, and
66
+ * the rest of the Tier 2 surface.
67
+ *
68
+ * Lifecycle: must be called during component initialization. SSR-safe:
69
+ * returns a static `motionValue(0)` with no frame loop on the server.
70
+ *
71
+ * @param id Optional timeline identifier for sharing across components.
72
+ * @returns A `MotionValue<number>` with `.current` and `.subscribe`.
73
+ *
74
+ * @example
75
+ * ```svelte
76
+ * <script>
77
+ * import { useTime, useTransform } from '@humanspeak/svelte-motion'
78
+ *
79
+ * const time = useTime()
80
+ * const rotate = useTransform(time, [0, 4000], [0, 360], { clamp: false })
81
+ * </script>
82
+ *
83
+ * <div style="transform: rotate({rotate.current}deg)">↻</div>
84
+ * ```
85
+ *
86
+ * @see https://motion.dev/docs/react-use-time
87
+ */
88
+ export const useTime = (id) => {
89
+ // SSR: return a static motion value with no frame loop. Matches
90
+ // useSpring / useScroll's SSR branch — no $effect is registered.
91
+ if (typeof window === 'undefined') {
92
+ return augmentMotionValue(motionValue(0));
93
+ }
94
+ // Unique timeline: own callback, own MV, own lifecycle.
95
+ if (!id) {
96
+ const { base, cancel } = startTimeBase();
97
+ $effect(() => () => {
98
+ cancel();
99
+ base.destroy();
100
+ });
101
+ return augmentMotionValue(base);
102
+ }
103
+ // Shared timeline: one frame callback per `id`, mirrored into per-
104
+ // consumer MVs.
105
+ let timeline = sharedTimelines.get(id);
106
+ if (!timeline) {
107
+ const { base, cancel } = startTimeBase();
108
+ timeline = { base, refcount: 0, cancel };
109
+ sharedTimelines.set(id, timeline);
110
+ }
111
+ timeline.refcount++;
112
+ const consumerMv = motionValue(timeline.base.get());
113
+ const unsubBase = timeline.base.on('change', (t) => consumerMv.set(t));
114
+ $effect(() => () => {
115
+ unsubBase();
116
+ consumerMv.destroy();
117
+ const t = sharedTimelines.get(id);
118
+ if (!t)
119
+ return;
120
+ t.refcount--;
121
+ if (t.refcount > 0)
122
+ return;
123
+ t.cancel();
124
+ t.base.destroy();
125
+ sharedTimelines.delete(id);
126
+ });
127
+ return augmentMotionValue(consumerMv);
128
+ };
@@ -0,0 +1,170 @@
1
+ import { type MotionValue, type TransformOptions } from 'motion-dom';
2
+ import { type Readable } from 'svelte/store';
3
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
4
+ export type TransformValues = Partial<{
5
+ x: number;
6
+ y: number;
7
+ scale: number;
8
+ scaleX: number;
9
+ scaleY: number;
10
+ rotate: number;
11
+ }>;
12
+ /**
13
+ * Build a CSS transform string from numeric values (no matrices).
14
+ *
15
+ * @param values Partial map of translate/scale/rotate values.
16
+ * @returns A space-separated CSS `transform` string, or `""` when all values are defaults.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * buildTransform({ x: 10, y: 20, scale: 1.5 })
21
+ * // => "translate(10px, 20px) scale(1.5)"
22
+ *
23
+ * buildTransform({ x: 0, y: 0, rotate: 0 }) // all defaults
24
+ * // => ""
25
+ * ```
26
+ */
27
+ export declare const buildTransform: (values: TransformValues) => string;
28
+ /**
29
+ * Lightweight safety check for transform magnitudes and NaN values.
30
+ *
31
+ * @param values Transform values to validate.
32
+ * @param opts Optional configuration; `maxScale` caps allowable absolute scale (default 8).
33
+ * @returns `true` if all scale values are finite and within bounds.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * isSafeTransform({ scale: 2 }) // true
38
+ * isSafeTransform({ scale: 100 }) // false (exceeds default maxScale=8)
39
+ * isSafeTransform({ scale: 100 }, { maxScale: 200 }) // true
40
+ * isSafeTransform({ scale: NaN }) // false
41
+ * ```
42
+ */
43
+ export declare const isSafeTransform: (values: TransformValues, opts?: {
44
+ maxScale?: number;
45
+ }) => boolean;
46
+ /**
47
+ * Extract the uniform scale factor from a CSS `matrix()` string.
48
+ *
49
+ * @param matrix A CSS `matrix(...)` value, `"none"`, `null`, or `undefined`.
50
+ * @returns The `a` component of the matrix (uniform scale), or `null` if unparseable.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * parseMatrixScale('matrix(1.5, 0, 0, 1.5, 0, 0)') // 1.5
55
+ * parseMatrixScale('none') // null
56
+ * parseMatrixScale(null) // null
57
+ * ```
58
+ */
59
+ export declare const parseMatrixScale: (matrix: string | null | undefined) => number | null;
60
+ export type { TransformOptions };
61
+ /**
62
+ * Any motion-value shape this library accepts as a transform input: the raw
63
+ * motion-dom `MotionValue<T>` and our augmented `AugmentedMotionValue<T>` are
64
+ * both runtime-identical (the augmentation is a Svelte 5 retype, not a
65
+ * subclass), but TypeScript's private-field nominal typing means the two
66
+ * aren't structurally assignable in public signatures. This alias accepts
67
+ * both so call sites compile cleanly.
68
+ */
69
+ export type AnyMotionValue<T> = MotionValue<T> | AugmentedMotionValue<T>;
70
+ /**
71
+ * A source for {@link useTransform}'s mapping form: any motion value or a
72
+ * Svelte readable store. Svelte readables are bridged into a `MotionValue`
73
+ * internally so motion-dom's `mapValue` / `transformValue` can track them.
74
+ */
75
+ export type TransformSource<T> = AnyMotionValue<T> | Readable<T>;
76
+ /**
77
+ * Single-input transformer signature: `(latest) => output`.
78
+ */
79
+ export type SingleTransformer<I, O> = (input: I) => O;
80
+ /**
81
+ * Multi-input transformer signature: `([latestA, latestB, ...]) => output`.
82
+ */
83
+ export type MultiTransformer<I, O> = (inputs: I[]) => O;
84
+ /**
85
+ * Output map for the multi-output mapping form: `{ key: [stop, …] }`. The
86
+ * keys are stable per call and each entry produces its own `MotionValue`.
87
+ */
88
+ export type TransformOutputMap<O> = {
89
+ [key: string]: O[];
90
+ };
91
+ /**
92
+ * Creates an augmented `MotionValue<O>` derived from one or more `MotionValue`s
93
+ * (or Svelte readables), composed via a range mapping or a compute function.
94
+ *
95
+ * Mirrors framer-motion's `useTransform` 1:1 with five forms:
96
+ *
97
+ * - **Mapping form** — `useTransform(source, input, output, options?)` maps a
98
+ * numeric source's value across `input → output` stops with clamp, easing,
99
+ * and a pluggable mixer for non-numeric outputs. Delegates to motion-dom's
100
+ * `mapValue` (which is itself `transformValue` over the curried mapper).
101
+ * - **Multi-output mapping form** — `useTransform(source, input, outputMap, options?)`
102
+ * produces an object of motion values, one per key in `outputMap`. Each
103
+ * value is the same mapping form applied to that key's output stops.
104
+ * - **Single-transformer form** — `useTransform(mv, (latest) => O)` recomputes
105
+ * on every change of `mv`. Auto-tracks via `transformValue`.
106
+ * - **Multi-transformer form** — `useTransform([mv1, mv2, …], ([latest, …]) => O)`
107
+ * recomputes on every change of any input. Auto-tracks via `transformValue`.
108
+ * - **Compute form** — `useTransform(() => compute)` recomputes whenever any
109
+ * `MotionValue` referenced inside `compute` (via `.get()` or `.current`)
110
+ * changes. Auto-tracks via `transformValue` — no explicit deps array; the
111
+ * `collectMotionValues` session inside `transformValue` discovers them.
112
+ *
113
+ * Returns an {@link AugmentedMotionValue<O>} — a real motion-dom `MotionValue`
114
+ * (composes with `useSpring`, `useVelocity`, `animate()`, etc.) plus a
115
+ * `$state`-backed `.current` getter and a Svelte readable `.subscribe` shim.
116
+ *
117
+ * Lifecycle: must be called during component initialization. Source
118
+ * subscriptions, the underlying motion value, and any Svelte-readable bridges
119
+ * are torn down when the surrounding `$effect` scope unmounts.
120
+ *
121
+ * @template O Output value type.
122
+ * @param sourceOrCompute A motion value / readable (mapping forms), a single
123
+ * `MotionValue` (single-transformer form), an array of `MotionValue`s
124
+ * (multi-transformer form), or a compute function (compute form).
125
+ * @param inputOrTransformer Input stops `number[]` (mapping forms) or a
126
+ * transformer function (`(latest) => O` / `(latest[]) => O`).
127
+ * @param outputOrOutputMap Output stops `O[]` (single-output mapping) or an
128
+ * output map `{ [key]: O[] }` (multi-output mapping).
129
+ * @param options Optional `TransformOptions` — `clamp`, `ease`, `mixer`.
130
+ * @returns An `AugmentedMotionValue<O>` for single-output forms, or an object
131
+ * of motion values keyed by `outputMap` for the multi-output form.
132
+ *
133
+ * @example
134
+ * ```svelte
135
+ * <script lang="ts">
136
+ * import { useMotionValue, useTransform } from '@humanspeak/svelte-motion'
137
+ *
138
+ * const x = useMotionValue(0)
139
+ *
140
+ * // Mapping form
141
+ * const opacity = useTransform(x, [0, 100], [0, 1])
142
+ *
143
+ * // Single-MV transformer
144
+ * const doubled = useTransform(x, (latest) => latest * 2)
145
+ *
146
+ * // Compute form — auto-tracks via collectMotionValues
147
+ * const y = useMotionValue(0)
148
+ * const sum = useTransform(() => x.get() + y.get())
149
+ *
150
+ * // Multi-output mapping
151
+ * const { rotate, scale } = useTransform(x, [0, 100], {
152
+ * rotate: [0, 360],
153
+ * scale: [1, 2]
154
+ * })
155
+ * </script>
156
+ *
157
+ * <div style="opacity: {opacity.current}; transform: rotate({rotate.current}deg) scale({scale.current})">
158
+ * {sum.current}
159
+ * </div>
160
+ * ```
161
+ *
162
+ * @see https://motion.dev/docs/react-use-transform
163
+ */
164
+ export declare function useTransform<O>(source: TransformSource<number>, input: number[], output: O[], options?: TransformOptions<O>): AugmentedMotionValue<O>;
165
+ export declare function useTransform<T extends TransformOutputMap<unknown>>(source: TransformSource<number>, input: number[], outputMap: T, options?: TransformOptions<T[keyof T][number]>): {
166
+ [K in keyof T]: AugmentedMotionValue<T[K][number]>;
167
+ };
168
+ export declare function useTransform<I, O>(source: AnyMotionValue<I>, transformer: SingleTransformer<I, O>): AugmentedMotionValue<O>;
169
+ export declare function useTransform<I, O>(sources: ReadonlyArray<AnyMotionValue<I>>, transformer: MultiTransformer<I, O>): AugmentedMotionValue<O>;
170
+ export declare function useTransform<O>(compute: () => O): AugmentedMotionValue<O>;