@humanspeak/svelte-motion 0.5.0 → 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 CHANGED
@@ -14,6 +14,8 @@ export type { AugmentedMotionValue } from './utils/augmentMotionValue.svelte';
14
14
  export { useCycle } from './utils/cycle.svelte';
15
15
  export type { Cycle, CycleState } from './utils/cycle.svelte';
16
16
  export { createDragControls } from './utils/dragControls';
17
+ export { useFollowValue } from './utils/followValue.svelte';
18
+ export type { FollowMotionValue, UseFollowValueOptions } from './utils/followValue.svelte';
17
19
  export { useInView } from './utils/inView.svelte';
18
20
  export type { InViewState, UseInViewOptions } from './utils/inView.svelte';
19
21
  export { useMotionTemplate } from './utils/motionTemplate.svelte';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { useAnimate } from './utils/animate.svelte';
13
13
  export { useAnimationFrame } from './utils/animationFrame';
14
14
  export { useCycle } from './utils/cycle.svelte';
15
15
  export { createDragControls } from './utils/dragControls';
16
+ export { useFollowValue } from './utils/followValue.svelte';
16
17
  export { useInView } from './utils/inView.svelte';
17
18
  export { useMotionTemplate } from './utils/motionTemplate.svelte';
18
19
  export { useMotionValue } from './utils/motionValue.svelte';
@@ -0,0 +1,84 @@
1
+ import { type FollowValueOptions, type MotionValue } from 'motion-dom';
2
+ import { type Readable } from 'svelte/store';
3
+ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
4
+ /**
5
+ * Options accepted by {@link useFollowValue}. Mirrors framer-motion's
6
+ * `FollowValueOptions` 1:1 — any `ValueAnimationTransition` shape (spring /
7
+ * tween / inertia / keyframes) plus `skipInitialAnimation` for
8
+ * scroll-restoration scenarios.
9
+ *
10
+ * The shape is re-exported from motion-dom so consumers don't have to
11
+ * cross-import.
12
+ *
13
+ * @see https://motion.dev/docs/react-use-follow-value
14
+ */
15
+ export type UseFollowValueOptions = FollowValueOptions;
16
+ /**
17
+ * The augmented `MotionValue` returned by {@link useFollowValue} (and, since
18
+ * `useSpring` now delegates here, by `useSpring` too).
19
+ */
20
+ export type FollowMotionValue<T extends number | string> = AugmentedMotionValue<T>;
21
+ /**
22
+ * Creates an animated `MotionValue` that, when `.set(v)` is called, animates
23
+ * toward `v` using **any** transition type — spring, tween, inertia, or
24
+ * keyframes. Pass another `MotionValue` (or Svelte readable) as the source
25
+ * and the result follows it: every source emit kicks off a new animation
26
+ * toward the latest value.
27
+ *
28
+ * Mirrors React framer-motion's `useFollowValue` 1:1. `useSpring` in this
29
+ * library is now a thin wrapper that delegates here with the default
30
+ * `{ type: 'spring' }`.
31
+ *
32
+ * Returned object is a real motion-dom `MotionValue` (composes with
33
+ * `useTransform`, `useVelocity`, `animate()`, etc.) augmented with a
34
+ * `$state`-backed `.current` getter and a Svelte readable `.subscribe`
35
+ * shim.
36
+ *
37
+ * Lifecycle: must be called during component initialization. Cleanup is
38
+ * registered via `$effect`; the follow animation stops, the source bridge
39
+ * (if any) tears down, and `value.destroy()` runs when the surrounding
40
+ * `$effect` scope unmounts.
41
+ *
42
+ * SSR-safe: returns a static augmented `MotionValue` with no animation on
43
+ * the server. `.set` and `.jump` become no-ops to avoid drifting away from
44
+ * the server-rendered snapshot.
45
+ *
46
+ * @template T The value type — `number` or `string` (unit strings preserved).
47
+ * @param source Initial value, a `MotionValue` to follow, or a Svelte readable.
48
+ * @param options Transition + follow configuration (`type: 'spring' | 'tween' | 'inertia' | 'keyframes'`, plus the corresponding per-type options).
49
+ * @returns A `MotionValue<T>` with `.current` and `.subscribe`.
50
+ *
51
+ * @example
52
+ * ```svelte
53
+ * <script lang="ts">
54
+ * import { useFollowValue, useMotionValue } from '@humanspeak/svelte-motion'
55
+ *
56
+ * const target = useMotionValue(0)
57
+ *
58
+ * // Spring follow (default — same as `useSpring(target)`).
59
+ * const spring = useFollowValue(target, { stiffness: 300, damping: 30 })
60
+ *
61
+ * // Tween follow — eased linear interpolation.
62
+ * const eased = useFollowValue(target, {
63
+ * type: 'tween',
64
+ * duration: 0.4,
65
+ * ease: 'easeInOut'
66
+ * })
67
+ *
68
+ * // Inertia — decays from initial velocity.
69
+ * const drifting = useFollowValue(0, { type: 'inertia', velocity: 800, power: 0.6 })
70
+ * </script>
71
+ *
72
+ * <button onclick={() => target.set(target.get() === 0 ? 200 : 0)}>Toggle</button>
73
+ * <div style="transform: translateX({spring.current}px)">spring</div>
74
+ * <div style="transform: translateX({eased.current}px)">tween</div>
75
+ * <div style="transform: translateX({drifting.current}px)">inertia</div>
76
+ * ```
77
+ *
78
+ * @see https://motion.dev/docs/react-use-follow-value
79
+ */
80
+ export declare function useFollowValue(source: number, options?: UseFollowValueOptions): FollowMotionValue<number>;
81
+ export declare function useFollowValue(source: string, options?: UseFollowValueOptions): FollowMotionValue<string>;
82
+ export declare function useFollowValue(source: MotionValue<number>, options?: UseFollowValueOptions): FollowMotionValue<number>;
83
+ export declare function useFollowValue(source: MotionValue<string>, options?: UseFollowValueOptions): FollowMotionValue<string>;
84
+ export declare function useFollowValue<T extends number | string>(source: Readable<T>, options?: UseFollowValueOptions): FollowMotionValue<T>;
@@ -0,0 +1,51 @@
1
+ import { attachFollow, isMotionValue, motionValue } from 'motion-dom';
2
+ import {} from 'svelte/store';
3
+ import { augmentMotionValue, isSvelteReadable, sampleSource } from './augmentMotionValue.svelte.js';
4
+ export function useFollowValue(source, options = {}) {
5
+ // SSR: return a static MotionValue with no animation. Reads return the
6
+ // best-effort initial value; .set / .jump become no-ops to avoid drifting
7
+ // away from the server-rendered snapshot.
8
+ if (typeof window === 'undefined') {
9
+ const initial = sampleSource(source);
10
+ const ssrValue = motionValue(initial);
11
+ ssrValue.set = () => undefined;
12
+ ssrValue.jump = () => undefined;
13
+ return augmentMotionValue(ssrValue);
14
+ }
15
+ // Resolve initial + follow source. Svelte readables get bridged into a
16
+ // motion-dom MotionValue so `attachFollow` can subscribe to their emits.
17
+ let followSource;
18
+ let cleanupReadableBridge;
19
+ let svelteBridge;
20
+ if (isMotionValue(source)) {
21
+ followSource = source;
22
+ }
23
+ else if (isSvelteReadable(source)) {
24
+ const initialFromReadable = sampleSource(source);
25
+ svelteBridge = motionValue(initialFromReadable);
26
+ cleanupReadableBridge = source.subscribe((v) => {
27
+ // Svelte readable contract emits synchronously on subscribe. Skip
28
+ // the initial emit (already seeded above) so attachFollow doesn't
29
+ // fire an animation on attach.
30
+ if (svelteBridge.get() === v)
31
+ return;
32
+ svelteBridge.set(v);
33
+ });
34
+ followSource = svelteBridge;
35
+ }
36
+ else {
37
+ followSource = source;
38
+ }
39
+ const initial = isMotionValue(followSource) ? followSource.get() : followSource;
40
+ const value = motionValue(initial);
41
+ // Default transition is `spring` — matches React framer-motion's
42
+ // `useFollowValue` default. Caller's `type` (if set) overrides.
43
+ const stopFollow = attachFollow(value, followSource, { type: 'spring', ...options });
44
+ const dispose = () => {
45
+ stopFollow?.();
46
+ cleanupReadableBridge?.();
47
+ svelteBridge?.destroy();
48
+ };
49
+ $effect(() => () => value.destroy());
50
+ return augmentMotionValue(value, dispose);
51
+ }
@@ -9,6 +9,10 @@ import { type AugmentedMotionValue } from './augmentMotionValue.svelte.js';
9
9
  * `velocity`, `restDelta`, `restSpeed`) plus `skipInitialAnimation` for
10
10
  * scroll-restoration scenarios.
11
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
+ *
12
16
  * @see https://motion.dev/docs/react-use-spring
13
17
  */
14
18
  export type UseSpringOptions = SpringOptions & Pick<FollowValueOptions, 'skipInitialAnimation'>;
@@ -22,41 +26,36 @@ export type UseSpringOptions = SpringOptions & Pick<FollowValueOptions, 'skipIni
22
26
  * - `current` — a Svelte-5 reactive read backed by `$state`. Use in templates
23
27
  * and `$derived` / `$effect` to track the spring value without subscribing.
24
28
  * - `subscribe` — Svelte readable store contract. Calls the run function once
25
- * synchronously with the current value, then on every change. Lets the
26
- * spring be used with `$spring` template syntax, `get(spring)`, and as a
27
- * dependency in `useTransform`'s function form.
29
+ * synchronously with the current value, then on every change.
28
30
  */
29
31
  export type SpringMotionValue<T extends number | string> = AugmentedMotionValue<T>;
30
32
  /**
31
33
  * Creates a spring-animated `MotionValue`.
32
34
  *
33
35
  * Set a target with `.set(v)` to animate to it using spring physics, or
34
- * `.jump(v)` to skip the animation. Pass another `MotionValue` (or, for
35
- * backwards compatibility, a Svelte readable store like the ones from
36
- * `useScroll` / `useTime`) as `source` and the spring will animate towards
37
- * whatever that source emits.
38
- *
39
- * Returned object is a real motion-dom `MotionValue` — composes with
40
- * `animate()`, `useTransform`, `useVelocity`, and motion-dom's animation
41
- * 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.
42
39
  *
43
- * - `.current` Svelte-5 reactive read for templates and `$derived` /
44
- * `$effect`.
45
- * - `.subscribe(run)` Svelte readable store contract so `$spring` template
46
- * syntax and `useTransform(() => …, [spring])` keep working during the
47
- * 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.
48
44
  *
49
- * Lifecycle: must be called during component initialization. Cleanup is
50
- * registered via `$effect`; the spring stops animating and unsubscribes from
51
- * its source when the surrounding component / effect tears down. Call
52
- * `.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.
53
48
  *
54
49
  * SSR-safe: returns a static `MotionValue` with no animation on the server.
55
50
  *
56
- * @template T
57
- * @param {number|string|MotionValue<number>|MotionValue<string>|Readable<number|string>} source Initial value or a source to follow.
58
- * @param {UseSpringOptions} [options] Spring + follow configuration.
59
- * @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`.
60
59
  *
61
60
  * @example
62
61
  * ```svelte
@@ -67,7 +66,7 @@ export type SpringMotionValue<T extends number | string> = AugmentedMotionValue<
67
66
  * </script>
68
67
  *
69
68
  * <button onclick={() => x.set(100)}>Animate</button>
70
- * <div>{x.current}</div>
69
+ * <div style="transform: translateX({x.current}px)">{x.current}</div>
71
70
  * ```
72
71
  *
73
72
  * @see https://motion.dev/docs/react-use-spring
@@ -1,54 +1,10 @@
1
- import { attachFollow, isMotionValue, motionValue } from 'motion-dom';
1
+ import {} from 'motion-dom';
2
2
  import {} from 'svelte/store';
3
- import { augmentMotionValue, isSvelteReadable, sampleSource } from './augmentMotionValue.svelte.js';
3
+ import {} from './augmentMotionValue.svelte.js';
4
+ import { useFollowValue } from './followValue.svelte.js';
4
5
  export function useSpring(source, options = {}) {
5
- // SSR: return a static MotionValue with no animation. Reads return the
6
- // best-effort initial value; .set / .jump become no-ops to avoid drifting
7
- // away from the server-rendered snapshot.
8
- if (typeof window === 'undefined') {
9
- const initial = sampleSource(source);
10
- const ssrValue = motionValue(initial);
11
- ssrValue.set = () => undefined;
12
- ssrValue.jump = () => undefined;
13
- return augmentMotionValue(ssrValue);
14
- }
15
- // Resolve initial + follow source.
16
- let followSource;
17
- let cleanupReadableBridge;
18
- let svelteBridge;
19
- if (isMotionValue(source)) {
20
- followSource = source;
21
- }
22
- else if (isSvelteReadable(source)) {
23
- // Bridge a Svelte readable into a MotionValue so attachFollow can
24
- // track it. Synchronous initial sample comes from svelte/store's get().
25
- const initialFromReadable = sampleSource(source);
26
- svelteBridge = motionValue(initialFromReadable);
27
- cleanupReadableBridge = source.subscribe((v) => {
28
- // The Svelte readable contract calls the subscriber synchronously
29
- // with the current value on subscribe. Skip if it equals the
30
- // already-seeded bridge value so attachFollow doesn't fire a
31
- // spring on the initial emit. Subsequent emits go through set()
32
- // and trigger animation.
33
- if (svelteBridge.get() === v)
34
- return;
35
- svelteBridge.set(v);
36
- });
37
- followSource = svelteBridge;
38
- }
39
- else {
40
- followSource = source;
41
- }
42
- const initial = isMotionValue(followSource) ? followSource.get() : followSource;
43
- const value = motionValue(initial);
44
- const stopFollow = attachFollow(value, followSource, { type: 'spring', ...options });
45
- // Side-cleanup for our augmentations. Single-shot guard lives in the
46
- // augmented `value.destroy` (the only caller), so no flag here.
47
- const dispose = () => {
48
- stopFollow?.();
49
- cleanupReadableBridge?.();
50
- svelteBridge?.destroy();
51
- };
52
- $effect(() => () => value.destroy());
53
- return augmentMotionValue(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 });
54
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.5.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",