@humanspeak/svelte-motion 0.1.26 → 0.1.27

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.
@@ -8,7 +8,7 @@ export declare const getMotionConfig: () => MotionConfigProps | undefined;
8
8
  /**
9
9
  * Provide motion configuration to descendant components via Svelte context.
10
10
  *
11
- * @param motionConfig The configuration to propagate (e.g. `reducedMotion`, `transition`).
11
+ * @param motionConfig The configuration to propagate (e.g. `transition`).
12
12
  * @returns The same `MotionConfigProps` that was set.
13
13
  */
14
14
  export declare const createMotionConfig: (motionConfig: MotionConfigProps) => MotionConfigProps;
@@ -11,7 +11,7 @@ export const getMotionConfig = () => {
11
11
  /**
12
12
  * Provide motion configuration to descendant components via Svelte context.
13
13
  *
14
- * @param motionConfig The configuration to propagate (e.g. `reducedMotion`, `transition`).
14
+ * @param motionConfig The configuration to propagate (e.g. `transition`).
15
15
  * @returns The same `MotionConfigProps` that was set.
16
16
  */
17
17
  export const createMotionConfig = (motionConfig) => {
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition,
9
9
  export { useAnimationFrame } from './utils/animationFrame';
10
10
  export { createDragControls } from './utils/dragControls';
11
11
  export { useMotionTemplate } from './utils/motionTemplate';
12
+ export { useMotionValueEvent } from './utils/motionValueEvent';
13
+ export { useScroll } from './utils/scroll';
12
14
  export { useSpring } from './utils/spring';
13
15
  export { useVelocity } from './utils/velocity';
14
16
  /**
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ export { clamp, distance, distance2D, interpolate, mix, pipe, progress, wrap } f
12
12
  export { useAnimationFrame } from './utils/animationFrame';
13
13
  export { createDragControls } from './utils/dragControls';
14
14
  export { useMotionTemplate } from './utils/motionTemplate';
15
+ export { useMotionValueEvent } from './utils/motionValueEvent';
16
+ export { useScroll } from './utils/scroll';
15
17
  export { useSpring } from './utils/spring';
16
18
  export { useVelocity } from './utils/velocity';
17
19
  /**
@@ -17,5 +17,20 @@ export type ConstraintElastic = {
17
17
  * Apply float-safe constraints with optional elastic mixing.
18
18
  * Mirrors Framer Motion behavior: clamp via Math.min/Math.max with no rounding.
19
19
  * If `elastic` provided, blends toward the bound using its side-specific factor.
20
+ *
21
+ * @param point The unconstrained value to clamp.
22
+ * @param range Min/max boundaries. Either or both may be omitted.
23
+ * @param elastic Optional per-side elastic factor(s) in [0,1]. When provided,
24
+ * the value is interpolated toward the boundary instead of hard-clamped.
25
+ * @returns The constrained (and optionally elastically blended) value.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Hard clamp to [0, 100]
30
+ * applyConstraints(120, { min: 0, max: 100 }) // 100
31
+ *
32
+ * // Elastic overshoot (50 % blend toward max)
33
+ * applyConstraints(120, { min: 0, max: 100 }, 0.5) // 110
34
+ * ```
20
35
  */
21
36
  export declare const applyConstraints: (point: number, range: ConstraintRange, elastic?: ConstraintElastic) => number;
@@ -7,6 +7,21 @@ export const mixNumber = (from, to, t) => from + (to - from) * t;
7
7
  * Apply float-safe constraints with optional elastic mixing.
8
8
  * Mirrors Framer Motion behavior: clamp via Math.min/Math.max with no rounding.
9
9
  * If `elastic` provided, blends toward the bound using its side-specific factor.
10
+ *
11
+ * @param point The unconstrained value to clamp.
12
+ * @param range Min/max boundaries. Either or both may be omitted.
13
+ * @param elastic Optional per-side elastic factor(s) in [0,1]. When provided,
14
+ * the value is interpolated toward the boundary instead of hard-clamped.
15
+ * @returns The constrained (and optionally elastically blended) value.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // Hard clamp to [0, 100]
20
+ * applyConstraints(120, { min: 0, max: 100 }) // 100
21
+ *
22
+ * // Elastic overshoot (50 % blend toward max)
23
+ * applyConstraints(120, { min: 0, max: 100 }, 0.5) // 110
24
+ * ```
10
25
  */
11
26
  export const applyConstraints = (point, range, elastic) => {
12
27
  const hasMin = range.min !== undefined;
@@ -42,5 +42,15 @@ export type StepResult = {
42
42
  * @param bounds Min/max boundaries for the axis.
43
43
  * @param opts Physics parameters for decay and boundary spring.
44
44
  * @returns A function that accepts elapsed time in ms and returns the current `StepResult`.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const step = createInertiaToBoundary(
49
+ * { value: 50, velocity: 200 },
50
+ * { min: 0, max: 300 },
51
+ * { timeConstantMs: 350, restDelta: 0.5, restSpeed: 10, bounceStiffness: 500, bounceDamping: 25 }
52
+ * )
53
+ * const { value, done } = step(16) // advance 16 ms
54
+ * ```
45
55
  */
46
56
  export declare const createInertiaToBoundary: (initial: AxisState, bounds: Bounds, opts: InertiaHandoffOptions) => ((tMs: number) => StepResult);
@@ -56,6 +56,16 @@ const solveCrossTimeMs = (x0, v0, tauMs, boundary) => {
56
56
  * @param bounds Min/max boundaries for the axis.
57
57
  * @param opts Physics parameters for decay and boundary spring.
58
58
  * @returns A function that accepts elapsed time in ms and returns the current `StepResult`.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const step = createInertiaToBoundary(
63
+ * { value: 50, velocity: 200 },
64
+ * { min: 0, max: 300 },
65
+ * { timeConstantMs: 350, restDelta: 0.5, restSpeed: 10, bounceStiffness: 500, bounceDamping: 25 }
66
+ * )
67
+ * const { value, done } = step(16) // advance 16 ms
68
+ * ```
59
69
  */
60
70
  export const createInertiaToBoundary = (initial, bounds, opts) => {
61
71
  const min = bounds.min;
@@ -21,6 +21,13 @@ export declare const layoutIdRegistry: LayoutIdRegistry;
21
21
  * Get the global layoutId registry.
22
22
  *
23
23
  * @returns The singleton `LayoutIdRegistry` instance.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const registry = getLayoutIdRegistry()
28
+ * registry.snapshot('hero', element.getBoundingClientRect())
29
+ * const entry = registry.consume('hero') // one-shot: returns and deletes
30
+ * ```
24
31
  */
25
32
  export declare const getLayoutIdRegistry: () => LayoutIdRegistry;
26
33
  export {};
@@ -19,6 +19,13 @@ export const layoutIdRegistry = {
19
19
  * Get the global layoutId registry.
20
20
  *
21
21
  * @returns The singleton `LayoutIdRegistry` instance.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const registry = getLayoutIdRegistry()
26
+ * registry.snapshot('hero', element.getBoundingClientRect())
27
+ * const entry = registry.consume('hero') // one-shot: returns and deletes
28
+ * ```
22
29
  */
23
30
  export const getLayoutIdRegistry = () => {
24
31
  return layoutIdRegistry;
@@ -0,0 +1,29 @@
1
+ import type { Readable } from 'svelte/store';
2
+ /**
3
+ * Subscribes to a Svelte store and fires a callback on every *change*,
4
+ * skipping the initial synchronous emission that Svelte stores produce
5
+ * on subscribe.
6
+ *
7
+ * Returns an unsubscribe function. Use inside `$effect` or `onDestroy`
8
+ * for automatic cleanup.
9
+ *
10
+ * @example
11
+ * ```svelte
12
+ * <script>
13
+ * import { useMotionValueEvent, useSpring } from '@humanspeak/svelte-motion'
14
+ * import { onDestroy } from 'svelte'
15
+ *
16
+ * const x = useSpring(0)
17
+ * const unsub = useMotionValueEvent(x, 'change', (latest) => {
18
+ * console.log('x changed to', latest)
19
+ * })
20
+ * onDestroy(unsub)
21
+ * </script>
22
+ * ```
23
+ *
24
+ * @param store A readable Svelte store to observe.
25
+ * @param event The event type — currently only `'change'` is supported.
26
+ * @param callback Invoked with the latest value on each change after the initial emission.
27
+ * @returns An unsubscribe function.
28
+ */
29
+ export declare const useMotionValueEvent: <T>(store: Readable<T>, event: "change", callback: (latest: T) => void) => (() => void);
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Subscribes to a Svelte store and fires a callback on every *change*,
3
+ * skipping the initial synchronous emission that Svelte stores produce
4
+ * on subscribe.
5
+ *
6
+ * Returns an unsubscribe function. Use inside `$effect` or `onDestroy`
7
+ * for automatic cleanup.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * import { useMotionValueEvent, useSpring } from '@humanspeak/svelte-motion'
13
+ * import { onDestroy } from 'svelte'
14
+ *
15
+ * const x = useSpring(0)
16
+ * const unsub = useMotionValueEvent(x, 'change', (latest) => {
17
+ * console.log('x changed to', latest)
18
+ * })
19
+ * onDestroy(unsub)
20
+ * </script>
21
+ * ```
22
+ *
23
+ * @param store A readable Svelte store to observe.
24
+ * @param event The event type — currently only `'change'` is supported.
25
+ * @param callback Invoked with the latest value on each change after the initial emission.
26
+ * @returns An unsubscribe function.
27
+ */
28
+ export const useMotionValueEvent = (store, event, callback) => {
29
+ let initialized = false;
30
+ const unsub = store.subscribe((value) => {
31
+ if (!initialized) {
32
+ initialized = true;
33
+ return;
34
+ }
35
+ callback(value);
36
+ });
37
+ return unsub;
38
+ };
@@ -0,0 +1,68 @@
1
+ import { type Readable } from 'svelte/store';
2
+ /**
3
+ * A scroll offset edge defined as a string (e.g. `"start"`, `"end"`, `"center"`)
4
+ * or a number (0–1 progress). Each offset entry is a pair of `[target, container]`.
5
+ */
6
+ type ScrollOffset = Array<[number | string, number | string]> | string[];
7
+ /**
8
+ * An element reference — either an element directly or a getter function
9
+ * that returns one (useful with Svelte's `bind:this` where the element
10
+ * isn't available until after mount).
11
+ */
12
+ type ElementOrGetter = HTMLElement | (() => HTMLElement | undefined);
13
+ /**
14
+ * Options accepted by `useScroll`.
15
+ */
16
+ type UseScrollOptions = {
17
+ /** Scrollable container to track. Defaults to the page. Accepts an element or a getter function. */
18
+ container?: ElementOrGetter;
19
+ /** Target element to track position of within the container. Accepts an element or a getter function. */
20
+ target?: ElementOrGetter;
21
+ /** Scroll offset configuration for element position tracking. */
22
+ offset?: ScrollOffset;
23
+ /** Which axis to use for the single-axis `progress` value supplied to `scroll()`. */
24
+ axis?: 'x' | 'y';
25
+ };
26
+ /**
27
+ * Return type of `useScroll` — four readable Svelte stores representing
28
+ * scroll position and normalised progress for both axes.
29
+ */
30
+ type UseScrollReturn = {
31
+ scrollX: Readable<number>;
32
+ scrollY: Readable<number>;
33
+ scrollXProgress: Readable<number>;
34
+ scrollYProgress: Readable<number>;
35
+ };
36
+ /**
37
+ * Creates scroll-linked Svelte stores for building scroll-driven animations
38
+ * such as progress indicators and parallax effects.
39
+ *
40
+ * When the returned stores are used with `opacity` / `transform` CSS properties
41
+ * the animations can be hardware accelerated by the browser.
42
+ *
43
+ * SSR-safe: returns static `readable(0)` stores on the server.
44
+ *
45
+ * `container` and `target` accept either an `HTMLElement` directly or a
46
+ * getter function `() => HTMLElement | undefined`. This is useful with
47
+ * Svelte's `bind:this` where the element isn't available until after mount.
48
+ * When a getter is provided, element resolution is deferred until the
49
+ * first subscriber arrives, and if the element isn't available yet the
50
+ * stores poll on each animation frame until it is.
51
+ *
52
+ * @example
53
+ * ```svelte
54
+ * <script>
55
+ * import { useScroll, useSpring } from '@humanspeak/svelte-motion'
56
+ *
57
+ * const { scrollYProgress } = useScroll()
58
+ * const scaleX = useSpring(scrollYProgress)
59
+ * </script>
60
+ *
61
+ * <div style="transform: scaleX({$scaleX}); transform-origin: left;" />
62
+ * ```
63
+ *
64
+ * @param options Optional scroll tracking configuration.
65
+ * @returns An object with `scrollX`, `scrollY`, `scrollXProgress`, and `scrollYProgress` stores.
66
+ */
67
+ export declare const useScroll: (options?: UseScrollOptions) => UseScrollReturn;
68
+ export {};
@@ -0,0 +1,119 @@
1
+ import { scroll } from 'motion';
2
+ import { readable, writable } from 'svelte/store';
3
+ /**
4
+ * Resolves an element-or-getter to an HTMLElement (or undefined).
5
+ */
6
+ const resolveElement = (ref) => {
7
+ if (!ref)
8
+ return undefined;
9
+ return typeof ref === 'function' ? ref() : ref;
10
+ };
11
+ /**
12
+ * Creates scroll-linked Svelte stores for building scroll-driven animations
13
+ * such as progress indicators and parallax effects.
14
+ *
15
+ * When the returned stores are used with `opacity` / `transform` CSS properties
16
+ * the animations can be hardware accelerated by the browser.
17
+ *
18
+ * SSR-safe: returns static `readable(0)` stores on the server.
19
+ *
20
+ * `container` and `target` accept either an `HTMLElement` directly or a
21
+ * getter function `() => HTMLElement | undefined`. This is useful with
22
+ * Svelte's `bind:this` where the element isn't available until after mount.
23
+ * When a getter is provided, element resolution is deferred until the
24
+ * first subscriber arrives, and if the element isn't available yet the
25
+ * stores poll on each animation frame until it is.
26
+ *
27
+ * @example
28
+ * ```svelte
29
+ * <script>
30
+ * import { useScroll, useSpring } from '@humanspeak/svelte-motion'
31
+ *
32
+ * const { scrollYProgress } = useScroll()
33
+ * const scaleX = useSpring(scrollYProgress)
34
+ * </script>
35
+ *
36
+ * <div style="transform: scaleX({$scaleX}); transform-origin: left;" />
37
+ * ```
38
+ *
39
+ * @param options Optional scroll tracking configuration.
40
+ * @returns An object with `scrollX`, `scrollY`, `scrollXProgress`, and `scrollYProgress` stores.
41
+ */
42
+ export const useScroll = (options) => {
43
+ if (typeof window === 'undefined') {
44
+ return {
45
+ scrollX: readable(0),
46
+ scrollY: readable(0),
47
+ scrollXProgress: readable(0),
48
+ scrollYProgress: readable(0)
49
+ };
50
+ }
51
+ const stores = {
52
+ scrollX: writable(0),
53
+ scrollY: writable(0),
54
+ scrollXProgress: writable(0),
55
+ scrollYProgress: writable(0)
56
+ };
57
+ let cleanup;
58
+ let pollRaf = 0;
59
+ let subscriberCount = 0;
60
+ const attach = () => {
61
+ if (cleanup)
62
+ return;
63
+ // Resolve elements — they may not be available yet when using getters
64
+ const container = resolveElement(options?.container);
65
+ const target = resolveElement(options?.target);
66
+ // If a getter was provided but returned undefined, the element isn't
67
+ // mounted yet. Poll on the next frame until it appears.
68
+ const needsContainer = options?.container && !container;
69
+ const needsTarget = options?.target && !target;
70
+ if (needsContainer || needsTarget) {
71
+ if (!pollRaf) {
72
+ pollRaf = requestAnimationFrame(() => {
73
+ pollRaf = 0;
74
+ attach();
75
+ });
76
+ }
77
+ return;
78
+ }
79
+ cleanup = scroll((_progress, info) => {
80
+ stores.scrollX.set(info.x.current);
81
+ stores.scrollY.set(info.y.current);
82
+ stores.scrollXProgress.set(info.x.progress);
83
+ stores.scrollYProgress.set(info.y.progress);
84
+ }, {
85
+ container,
86
+ target,
87
+ offset: options?.offset,
88
+ axis: options?.axis
89
+ });
90
+ };
91
+ const detach = () => {
92
+ if (subscriberCount <= 0) {
93
+ if (pollRaf) {
94
+ cancelAnimationFrame(pollRaf);
95
+ pollRaf = 0;
96
+ }
97
+ if (cleanup) {
98
+ cleanup();
99
+ cleanup = undefined;
100
+ }
101
+ }
102
+ };
103
+ const make = (key) => readable(0, (set) => {
104
+ subscriberCount++;
105
+ const unsub = stores[key].subscribe(set);
106
+ attach();
107
+ return () => {
108
+ unsub();
109
+ subscriberCount--;
110
+ detach();
111
+ };
112
+ });
113
+ return {
114
+ scrollX: make('scrollX'),
115
+ scrollY: make('scrollY'),
116
+ scrollXProgress: make('scrollXProgress'),
117
+ scrollYProgress: make('scrollYProgress')
118
+ };
119
+ };
@@ -3,5 +3,10 @@
3
3
  *
4
4
  * @param ms Delay in milliseconds.
5
5
  * @returns A promise that resolves after the delay.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * await sleep(100) // pause for 100 ms
10
+ * ```
6
11
  */
7
12
  export declare const sleep: (ms: number) => Promise<unknown>;
@@ -3,5 +3,10 @@
3
3
  *
4
4
  * @param ms Delay in milliseconds.
5
5
  * @returns A promise that resolves after the delay.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * await sleep(100) // pause for 100 ms
10
+ * ```
6
11
  */
7
12
  export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "A lightweight animation library for Svelte 5 that provides smooth, hardware-accelerated animations. Features include spring physics, custom easing, and fluid transitions. Built on top of the motion library, it offers a simple API for creating complex animations with minimal code. Perfect for interactive UIs, micro-interactions, and engaging user experiences.",
5
5
  "keywords": [
6
6
  "svelte",