@humanspeak/svelte-motion 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -44,7 +44,7 @@ Goal: Framer Motion API parity for Svelte where common React examples can be tra
44
44
  | Drag (`drag`, constraints, momentum, controls, callbacks) | Supported |
45
45
  | `AnimatePresence` (`initial`, `mode`, `onExitComplete`) | Supported |
46
46
  | Layout (`layout`, `layout="position"`) | Supported (single-element FLIP) |
47
- | Shared layout (`layoutId`) | Not yet supported |
47
+ | Shared layout (`layoutId`) | Supported |
48
48
  | Pan gesture API (`whilePan`, `onPan*`) | Not yet supported |
49
49
  | `MotionConfig` parity beyond `transition` | Partial |
50
50
  | `reducedMotion`, `features`, `transformPagePoint` | Not yet supported |
@@ -1,3 +1,14 @@
1
1
  import type { MotionConfigProps } from '../types.js';
2
+ /**
3
+ * Retrieve the current motion configuration from Svelte component context.
4
+ *
5
+ * @returns The active `MotionConfigProps`, or `undefined` if none was set by a parent.
6
+ */
2
7
  export declare const getMotionConfig: () => MotionConfigProps | undefined;
8
+ /**
9
+ * Provide motion configuration to descendant components via Svelte context.
10
+ *
11
+ * @param motionConfig The configuration to propagate (e.g. `reducedMotion`, `transition`).
12
+ * @returns The same `MotionConfigProps` that was set.
13
+ */
3
14
  export declare const createMotionConfig: (motionConfig: MotionConfigProps) => MotionConfigProps;
@@ -1,8 +1,19 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  const key = Symbol('motionConfig');
3
+ /**
4
+ * Retrieve the current motion configuration from Svelte component context.
5
+ *
6
+ * @returns The active `MotionConfigProps`, or `undefined` if none was set by a parent.
7
+ */
3
8
  export const getMotionConfig = () => {
4
9
  return getContext(key);
5
10
  };
11
+ /**
12
+ * Provide motion configuration to descendant components via Svelte context.
13
+ *
14
+ * @param motionConfig The configuration to propagate (e.g. `reducedMotion`, `transition`).
15
+ * @returns The same `MotionConfigProps` that was set.
16
+ */
6
17
  export const createMotionConfig = (motionConfig) => {
7
18
  return setContext(key, motionConfig);
8
19
  };
@@ -2,18 +2,26 @@ import type { Writable } from 'svelte/store';
2
2
  /**
3
3
  * Provide a writable store for the current variant key so children can
4
4
  * react to changes over time (true inheritance like Framer Motion).
5
+ *
6
+ * @param store Writable store holding the current variant name.
5
7
  */
6
- export declare function setVariantContext(store: Writable<string | undefined>): void;
8
+ export declare const setVariantContext: (store: Writable<string | undefined>) => void;
7
9
  /**
8
10
  * Read the parent's variant store (if any). Children subscribe to this store
9
11
  * to inherit and react to parent `animate` changes.
12
+ *
13
+ * @returns The parent variant store, or `undefined` if none exists.
10
14
  */
11
- export declare function getVariantContext(): Writable<string | undefined> | undefined;
15
+ export declare const getVariantContext: () => Writable<string | undefined> | undefined;
12
16
  /**
13
- * Set initial={false} in context so children inherit it
17
+ * Set initial={false} in context so children inherit it.
18
+ *
19
+ * @param value Whether the parent has `initial={false}`.
14
20
  */
15
- export declare function setInitialFalseContext(value: boolean): void;
21
+ export declare const setInitialFalseContext: (value: boolean) => void;
16
22
  /**
17
- * Check if parent has initial={false}
23
+ * Check if parent has initial={false}.
24
+ *
25
+ * @returns `true` if a parent set `initial={false}`, otherwise `false`.
18
26
  */
19
- export declare function getInitialFalseContext(): boolean;
27
+ export declare const getInitialFalseContext: () => boolean;
@@ -4,26 +4,34 @@ const INITIAL_FALSE_CONTEXT_KEY = Symbol('initial-false-context');
4
4
  /**
5
5
  * Provide a writable store for the current variant key so children can
6
6
  * react to changes over time (true inheritance like Framer Motion).
7
+ *
8
+ * @param store Writable store holding the current variant name.
7
9
  */
8
- export function setVariantContext(store) {
10
+ export const setVariantContext = (store) => {
9
11
  setContext(VARIANT_CONTEXT_KEY, store);
10
- }
12
+ };
11
13
  /**
12
14
  * Read the parent's variant store (if any). Children subscribe to this store
13
15
  * to inherit and react to parent `animate` changes.
16
+ *
17
+ * @returns The parent variant store, or `undefined` if none exists.
14
18
  */
15
- export function getVariantContext() {
19
+ export const getVariantContext = () => {
16
20
  return getContext(VARIANT_CONTEXT_KEY);
17
- }
21
+ };
18
22
  /**
19
- * Set initial={false} in context so children inherit it
23
+ * Set initial={false} in context so children inherit it.
24
+ *
25
+ * @param value Whether the parent has `initial={false}`.
20
26
  */
21
- export function setInitialFalseContext(value) {
27
+ export const setInitialFalseContext = (value) => {
22
28
  setContext(INITIAL_FALSE_CONTEXT_KEY, value);
23
- }
29
+ };
24
30
  /**
25
- * Check if parent has initial={false}
31
+ * Check if parent has initial={false}.
32
+ *
33
+ * @returns `true` if a parent set `initial={false}`, otherwise `false`.
26
34
  */
27
- export function getInitialFalseContext() {
35
+ export const getInitialFalseContext = () => {
28
36
  return getContext(INITIAL_FALSE_CONTEXT_KEY) ?? false;
29
- }
37
+ };
@@ -57,6 +57,7 @@
57
57
  isSVGTag,
58
58
  SVG_NAMESPACE
59
59
  } from '../utils/svg'
60
+ import { getLayoutIdRegistry } from '../utils/layoutId'
60
61
 
61
62
  type Props = MotionProps & {
62
63
  children?: Snippet
@@ -107,6 +108,7 @@
107
108
  dragListener: dragListenerProp,
108
109
  dragControls: dragControlsProp,
109
110
  layout: layoutProp,
111
+ layoutId: layoutIdProp,
110
112
  ref: element = $bindable(null),
111
113
  ...rest
112
114
  }: Props = $props()
@@ -117,6 +119,9 @@
117
119
  // Get presence context to check if we're inside AnimatePresence
118
120
  const context = getAnimatePresenceContext()
119
121
 
122
+ // Get layoutId registry (provided by AnimatePresence or a parent LayoutGroup)
123
+ const layoutIdRegistry = getLayoutIdRegistry()
124
+
120
125
  // Get current presence depth (0 = direct child of AnimatePresence, undefined = not in AnimatePresence)
121
126
  const presenceDepth = getPresenceDepth()
122
127
 
@@ -163,6 +168,36 @@
163
168
  })
164
169
  }
165
170
 
171
+ // Keep a live snapshot of the layoutId element's rect so the next element can FLIP from it.
172
+ // We store the last-known-good rect and push it to the registry on cleanup,
173
+ // because onDestroy fires after the element is removed from DOM (rect would be zeros).
174
+ let layoutIdLastRect: DOMRect | null = null
175
+ $effect(() => {
176
+ if (!(element && layoutIdProp && layoutIdRegistry)) return
177
+
178
+ // Capture rect on every frame while mounted
179
+ let rafId: number
180
+ const captureRect = () => {
181
+ if (element) {
182
+ layoutIdLastRect = element.getBoundingClientRect()
183
+ }
184
+ rafId = requestAnimationFrame(captureRect)
185
+ }
186
+ rafId = requestAnimationFrame(captureRect)
187
+
188
+ // On cleanup (before DOM removal), push last-known rect to registry
189
+ return () => {
190
+ cancelAnimationFrame(rafId)
191
+ if (layoutIdLastRect && layoutIdProp) {
192
+ layoutIdRegistry.snapshot(
193
+ layoutIdProp,
194
+ layoutIdLastRect,
195
+ (mergedTransition ?? {}) as AnimationOptions
196
+ )
197
+ }
198
+ }
199
+ })
200
+
166
201
  // Reactively update registration when element/exit/transition props change
167
202
  $effect(() => {
168
203
  if (element && context && resolvedExit) {
@@ -611,6 +646,25 @@
611
646
  }
612
647
  })
613
648
 
649
+ // Shared layout animation via layoutId.
650
+ // On mount, consume the previous snapshot and FLIP from its position.
651
+ $effect(() => {
652
+ if (!(element && layoutIdProp && layoutIdRegistry && isLoaded === 'ready')) return
653
+
654
+ const prev = layoutIdRegistry.consume(layoutIdProp)
655
+ if (!prev) return // First appearance, no animation needed
656
+
657
+ const next = measureRect(element)
658
+ const transforms = computeFlipTransforms(prev.rect, next, true)
659
+
660
+ setCompositorHints(element, true)
661
+ runFlipAnimation(
662
+ element,
663
+ transforms,
664
+ (prev.transition ?? mergedTransition ?? {}) as AnimationOptions
665
+ )
666
+ })
667
+
614
668
  // whileTap handling via motion-dom's press()
615
669
  $effect(() => {
616
670
  if (!(element && isLoaded === 'ready' && isNotEmpty(whileTapProp))) return
package/dist/types.d.ts CHANGED
@@ -278,6 +278,8 @@ export type MotionProps = {
278
278
  class?: string;
279
279
  /** Enable FLIP layout animations; "position" limits to translation only */
280
280
  layout?: boolean | 'position';
281
+ /** Shared layout animation identifier. Elements with matching layoutId animate between positions. */
282
+ layoutId?: string;
281
283
  /** Ref to the element */
282
284
  ref?: HTMLElement | null;
283
285
  /** Enable drag gestures. true for both axes, or lock to 'x'/'y'. */
@@ -32,4 +32,4 @@
32
32
  * <div bind:this={ref}>Animated content</div>
33
33
  * ```
34
34
  */
35
- export declare function useAnimationFrame(callback: (time: DOMHighResTimeStamp) => void): () => void;
35
+ export declare const useAnimationFrame: (callback: (time: DOMHighResTimeStamp) => void) => (() => void);
@@ -32,7 +32,7 @@
32
32
  * <div bind:this={ref}>Animated content</div>
33
33
  * ```
34
34
  */
35
- export function useAnimationFrame(callback) {
35
+ export const useAnimationFrame = (callback) => {
36
36
  // SSR guard
37
37
  if (typeof window === 'undefined')
38
38
  return () => { };
@@ -51,4 +51,4 @@ export function useAnimationFrame(callback) {
51
51
  cancelAnimationFrame(rafId);
52
52
  }
53
53
  };
54
- }
54
+ };
@@ -3,10 +3,12 @@
3
3
  * Preserves float precision.
4
4
  */
5
5
  export declare const mixNumber: (from: number, to: number, t: number) => number;
6
+ /** Range with optional min/max boundaries for constraining a point. */
6
7
  export type ConstraintRange = {
7
8
  min?: number;
8
9
  max?: number;
9
10
  };
11
+ /** Per-side elastic factors, a uniform factor, or `undefined` for no elasticity. */
10
12
  export type ConstraintElastic = {
11
13
  min: number;
12
14
  max: number;
@@ -16,4 +18,4 @@ export type ConstraintElastic = {
16
18
  * Mirrors Framer Motion behavior: clamp via Math.min/Math.max with no rounding.
17
19
  * If `elastic` provided, blends toward the bound using its side-specific factor.
18
20
  */
19
- export declare function applyConstraints(point: number, range: ConstraintRange, elastic?: ConstraintElastic): number;
21
+ export declare const applyConstraints: (point: number, range: ConstraintRange, elastic?: ConstraintElastic) => number;
@@ -8,7 +8,7 @@ export const mixNumber = (from, to, t) => from + (to - from) * t;
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
10
  */
11
- export function applyConstraints(point, range, elastic) {
11
+ export const applyConstraints = (point, range, elastic) => {
12
12
  const hasMin = range.min !== undefined;
13
13
  const hasMax = range.max !== undefined;
14
14
  if (hasMin && point < range.min) {
@@ -26,4 +26,4 @@ export function applyConstraints(point, range, elastic) {
26
26
  return Math.min(point, range.max);
27
27
  }
28
28
  return point;
29
- }
29
+ };
@@ -27,10 +27,10 @@ export type BoundaryPhysics = {
27
27
  * - `timeConstant` is expressed in seconds and internally converted to milliseconds.
28
28
  * @returns {BoundaryPhysics} Fully resolved physics parameters for inertia and boundary spring.
29
29
  */
30
- export declare function deriveBoundaryPhysics(elastic: number | undefined, transition?: {
30
+ export declare const deriveBoundaryPhysics: (elastic: number | undefined, transition?: {
31
31
  bounceStiffness?: number;
32
32
  bounceDamping?: number;
33
33
  timeConstant?: number;
34
34
  restDelta?: number;
35
35
  restSpeed?: number;
36
- }): BoundaryPhysics;
36
+ }) => BoundaryPhysics;
@@ -13,7 +13,7 @@
13
13
  * - `timeConstant` is expressed in seconds and internally converted to milliseconds.
14
14
  * @returns {BoundaryPhysics} Fully resolved physics parameters for inertia and boundary spring.
15
15
  */
16
- export function deriveBoundaryPhysics(elastic, transition) {
16
+ export const deriveBoundaryPhysics = (elastic, transition) => {
17
17
  const truthyElastic = typeof elastic === 'number' ? elastic > 0 : !!elastic;
18
18
  let bounceStiffness = truthyElastic ? 200 : 1_000_000;
19
19
  let bounceDamping = truthyElastic ? 40 : 10_000_000;
@@ -26,4 +26,4 @@ export function deriveBoundaryPhysics(elastic, transition) {
26
26
  const restDelta = transition?.restDelta ?? 1;
27
27
  const restSpeed = transition?.restSpeed ?? 10;
28
28
  return { timeConstantMs, restDelta, restSpeed, bounceStiffness, bounceDamping };
29
- }
29
+ };
@@ -7,14 +7,21 @@
7
7
  *
8
8
  * This module is SSR-safe and holds no references to the DOM or timers.
9
9
  */
10
+ /** Current position and velocity on a single axis. */
10
11
  export type AxisState = {
11
12
  value: number;
12
13
  velocity: number;
13
14
  };
15
+ /** Min/max boundary pair for clamping an axis. */
14
16
  export type Bounds = {
15
17
  min: number;
16
18
  max: number;
17
19
  };
20
+ /**
21
+ * Parameters controlling the inertia decay and boundary spring phases.
22
+ *
23
+ * @see deriveBoundaryPhysics
24
+ */
18
25
  export type InertiaHandoffOptions = {
19
26
  timeConstantMs: number;
20
27
  restDelta: number;
@@ -22,6 +29,7 @@ export type InertiaHandoffOptions = {
22
29
  bounceStiffness: number;
23
30
  bounceDamping: number;
24
31
  };
32
+ /** Value returned by each step of the inertia/spring simulation. */
25
33
  export type StepResult = {
26
34
  value: number;
27
35
  done: boolean;
@@ -29,5 +37,10 @@ export type StepResult = {
29
37
  /**
30
38
  * Create a stepper that yields a value at each elapsed time. Handoff from
31
39
  * inertia to spring when crossing the bounds.
40
+ *
41
+ * @param initial Starting position and velocity on the axis.
42
+ * @param bounds Min/max boundaries for the axis.
43
+ * @param opts Physics parameters for decay and boundary spring.
44
+ * @returns A function that accepts elapsed time in ms and returns the current `StepResult`.
32
45
  */
33
- export declare function createInertiaToBoundary(initial: AxisState, bounds: Bounds, opts: InertiaHandoffOptions): (tMs: number) => StepResult;
46
+ export declare const createInertiaToBoundary: (initial: AxisState, bounds: Bounds, opts: InertiaHandoffOptions) => ((tMs: number) => StepResult);
@@ -51,8 +51,13 @@ const solveCrossTimeMs = (x0, v0, tauMs, boundary) => {
51
51
  /**
52
52
  * Create a stepper that yields a value at each elapsed time. Handoff from
53
53
  * inertia to spring when crossing the bounds.
54
+ *
55
+ * @param initial Starting position and velocity on the axis.
56
+ * @param bounds Min/max boundaries for the axis.
57
+ * @param opts Physics parameters for decay and boundary spring.
58
+ * @returns A function that accepts elapsed time in ms and returns the current `StepResult`.
54
59
  */
55
- export function createInertiaToBoundary(initial, bounds, opts) {
60
+ export const createInertiaToBoundary = (initial, bounds, opts) => {
56
61
  const min = bounds.min;
57
62
  const max = bounds.max;
58
63
  const tauMs = Math.max(1, opts.timeConstantMs);
@@ -156,4 +161,4 @@ export function createInertiaToBoundary(initial, bounds, opts) {
156
161
  const settled = boundaryTarget != null ? springX : x;
157
162
  return { value: Math.min(max, Math.max(min, settled)), done: true };
158
163
  };
159
- }
164
+ };
@@ -0,0 +1,26 @@
1
+ import type { AnimationOptions } from 'motion';
2
+ /**
3
+ * Snapshot stored for a layoutId when an element unmounts.
4
+ */
5
+ type LayoutIdEntry = {
6
+ rect: DOMRect;
7
+ transition?: AnimationOptions;
8
+ };
9
+ /**
10
+ * Registry that stores last-known rect + transition for each layoutId.
11
+ *
12
+ * - `snapshot(id, rect, transition)` — called when an element with a layoutId unmounts.
13
+ * - `consume(id)` — called by a newly mounted element. Returns and **deletes** the entry (one-shot).
14
+ */
15
+ export type LayoutIdRegistry = {
16
+ snapshot(id: string, rect: DOMRect, transition?: AnimationOptions): void;
17
+ consume(id: string): LayoutIdEntry | undefined;
18
+ };
19
+ export declare const layoutIdRegistry: LayoutIdRegistry;
20
+ /**
21
+ * Get the global layoutId registry.
22
+ *
23
+ * @returns The singleton `LayoutIdRegistry` instance.
24
+ */
25
+ export declare const getLayoutIdRegistry: () => LayoutIdRegistry;
26
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Module-level singleton registry shared across the entire component tree.
3
+ * This matches React Framer Motion's behavior where layoutId is shared globally
4
+ * (or within a LayoutGroup, which we can add later).
5
+ */
6
+ const entries = new Map();
7
+ export const layoutIdRegistry = {
8
+ snapshot(id, rect, transition) {
9
+ entries.set(id, { rect, transition });
10
+ },
11
+ consume(id) {
12
+ const entry = entries.get(id);
13
+ if (entry)
14
+ entries.delete(id);
15
+ return entry;
16
+ }
17
+ };
18
+ /**
19
+ * Get the global layoutId registry.
20
+ *
21
+ * @returns The singleton `LayoutIdRegistry` instance.
22
+ */
23
+ export const getLayoutIdRegistry = () => {
24
+ return layoutIdRegistry;
25
+ };
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Detect whether the current page is running inside a Playwright test.
3
+ *
4
+ * @returns `true` when the URL contains the `@isPlaywright=true` query param.
5
+ */
1
6
  export declare const isPlaywrightEnv: () => boolean;
7
+ /**
8
+ * Log to the console only in DEV mode inside a Playwright environment.
9
+ *
10
+ * @param args Values forwarded to `console.log`.
11
+ */
2
12
  export declare const pwLog: (...args: unknown[]) => void;
13
+ /**
14
+ * Warn to the console only in DEV mode inside a Playwright environment.
15
+ *
16
+ * @param args Values forwarded to `console.warn`.
17
+ */
3
18
  export declare const pwWarn: (...args: unknown[]) => void;
package/dist/utils/log.js CHANGED
@@ -5,11 +5,21 @@
5
5
  * In production builds (npm package), these are noops to reduce bundle size.
6
6
  */
7
7
  import { DEV } from 'esm-env';
8
+ /**
9
+ * Detect whether the current page is running inside a Playwright test.
10
+ *
11
+ * @returns `true` when the URL contains the `@isPlaywright=true` query param.
12
+ */
8
13
  export const isPlaywrightEnv = () => {
9
14
  if (typeof window === 'undefined')
10
15
  return false;
11
16
  return window.location.search.includes('@isPlaywright=true');
12
17
  };
18
+ /**
19
+ * Log to the console only in DEV mode inside a Playwright environment.
20
+ *
21
+ * @param args Values forwarded to `console.log`.
22
+ */
13
23
  export const pwLog = (...args) => {
14
24
  if (!DEV)
15
25
  return;
@@ -17,6 +27,11 @@ export const pwLog = (...args) => {
17
27
  return;
18
28
  console.log(...args);
19
29
  };
30
+ /**
31
+ * Warn to the console only in DEV mode inside a Playwright environment.
32
+ *
33
+ * @param args Values forwarded to `console.warn`.
34
+ */
20
35
  export const pwWarn = (...args) => {
21
36
  if (!DEV)
22
37
  return;
@@ -54,23 +54,23 @@ export type AnimatePresenceContext = {
54
54
  * @param context Optional callbacks, for example `onExitComplete`.
55
55
  * @returns A presence context with register/update/unregister APIs.
56
56
  */
57
- export declare function createAnimatePresenceContext(context: {
57
+ export declare const createAnimatePresenceContext: (context: {
58
58
  initial?: boolean;
59
59
  mode?: AnimatePresenceMode;
60
60
  onExitComplete?: () => void;
61
- }): AnimatePresenceContext;
61
+ }) => AnimatePresenceContext;
62
62
  /**
63
63
  * Get the current `AnimatePresence` context from Svelte component context.
64
64
  *
65
65
  * Note: Trivial wrapper - ignored for coverage.
66
66
  */
67
- export declare function getAnimatePresenceContext(): AnimatePresenceContext | undefined;
67
+ export declare const getAnimatePresenceContext: () => AnimatePresenceContext | undefined;
68
68
  /**
69
69
  * Set the `AnimatePresence` context into Svelte component context.
70
70
  *
71
71
  * Note: Trivial wrapper - ignored for coverage.
72
72
  */
73
- export declare function setAnimatePresenceContext(context: AnimatePresenceContext): void;
73
+ export declare const setAnimatePresenceContext: (context: AnimatePresenceContext) => void;
74
74
  /**
75
75
  * Get the current presence depth from Svelte component context.
76
76
  *
@@ -127,4 +127,4 @@ export declare const setPresenceDepth: (depth: number) => void;
127
127
  * @param exit The exit keyframes definition.
128
128
  * @param mergedTransition The element's merged transition for precedence.
129
129
  */
130
- export declare function usePresence(key: string, element: HTMLElement | null, exit: MotionExit, mergedTransition?: MotionTransition): void;
130
+ export declare const usePresence: (key: string, element: HTMLElement | null, exit: MotionExit, mergedTransition?: MotionTransition) => void;
@@ -51,7 +51,7 @@ const resetTransforms = (element) => {
51
51
  * @param context Optional callbacks, for example `onExitComplete`.
52
52
  * @returns A presence context with register/update/unregister APIs.
53
53
  */
54
- export function createAnimatePresenceContext(context) {
54
+ export const createAnimatePresenceContext = (context) => {
55
55
  // Default initial to true (animate on first mount) unless explicitly false
56
56
  const initial = context.initial !== false;
57
57
  // Default mode to 'sync' if not specified
@@ -466,25 +466,25 @@ export function createAnimatePresenceContext(context) {
466
466
  updateChildState,
467
467
  unregisterChild
468
468
  };
469
- }
469
+ };
470
470
  /**
471
471
  * Get the current `AnimatePresence` context from Svelte component context.
472
472
  *
473
473
  * Note: Trivial wrapper - ignored for coverage.
474
474
  */
475
475
  /* c8 ignore next 3 */
476
- export function getAnimatePresenceContext() {
476
+ export const getAnimatePresenceContext = () => {
477
477
  return getContext(ANIMATE_PRESENCE_CONTEXT);
478
- }
478
+ };
479
479
  /**
480
480
  * Set the `AnimatePresence` context into Svelte component context.
481
481
  *
482
482
  * Note: Trivial wrapper - ignored for coverage.
483
483
  */
484
484
  /* c8 ignore next 3 */
485
- export function setAnimatePresenceContext(context) {
485
+ export const setAnimatePresenceContext = (context) => {
486
486
  setContext(ANIMATE_PRESENCE_CONTEXT, context);
487
- }
487
+ };
488
488
  /**
489
489
  * Get the current presence depth from Svelte component context.
490
490
  *
@@ -546,7 +546,7 @@ export const setPresenceDepth = (depth) => {
546
546
  * @param exit The exit keyframes definition.
547
547
  * @param mergedTransition The element's merged transition for precedence.
548
548
  */
549
- export function usePresence(key, element, exit, mergedTransition) {
549
+ export const usePresence = (key, element, exit, mergedTransition) => {
550
550
  const context = getAnimatePresenceContext();
551
551
  pwLog('[presence] usePresence called', {
552
552
  key,
@@ -567,5 +567,5 @@ export function usePresence(key, element, exit, mergedTransition) {
567
567
  reason: !element ? 'no element' : !context ? 'no context' : 'no exit'
568
568
  });
569
569
  }
570
- }
570
+ };
571
571
  /* c8 ignore end */
@@ -1 +1,7 @@
1
+ /**
2
+ * Return a promise that resolves after the given number of milliseconds.
3
+ *
4
+ * @param ms Delay in milliseconds.
5
+ * @returns A promise that resolves after the delay.
6
+ */
1
7
  export declare const sleep: (ms: number) => Promise<unknown>;
@@ -1 +1,7 @@
1
+ /**
2
+ * Return a promise that resolves after the given number of milliseconds.
3
+ *
4
+ * @param ms Delay in milliseconds.
5
+ * @returns A promise that resolves after the delay.
6
+ */
1
7
  export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -6,13 +6,30 @@ export type TransformValues = Partial<{
6
6
  scaleY: number;
7
7
  rotate: number;
8
8
  }>;
9
- /** Build a CSS transform string from numeric values (no matrices). */
10
- export declare function buildTransform(values: TransformValues): string;
11
- /** Lightweight safety check for transform magnitudes and NaN values. */
12
- export declare function isSafeTransform(values: TransformValues, opts?: {
9
+ /**
10
+ * Build a CSS transform string from numeric values (no matrices).
11
+ *
12
+ * @param values Partial map of translate/scale/rotate values.
13
+ * @returns A space-separated CSS `transform` string, or `""` when all values are defaults.
14
+ */
15
+ export declare const buildTransform: (values: TransformValues) => string;
16
+ /**
17
+ * Lightweight safety check for transform magnitudes and NaN values.
18
+ *
19
+ * @param values Transform values to validate.
20
+ * @param opts Optional configuration; `maxScale` caps allowable absolute scale (default 8).
21
+ * @returns `true` if all scale values are finite and within bounds.
22
+ */
23
+ export declare const isSafeTransform: (values: TransformValues, opts?: {
13
24
  maxScale?: number;
14
- }): boolean;
15
- export declare function parseMatrixScale(matrix: string | null | undefined): number | null;
25
+ }) => boolean;
26
+ /**
27
+ * Extract the uniform scale factor from a CSS `matrix()` string.
28
+ *
29
+ * @param matrix A CSS `matrix(...)` value, `"none"`, `null`, or `undefined`.
30
+ * @returns The `a` component of the matrix (uniform scale), or `null` if unparseable.
31
+ */
32
+ export declare const parseMatrixScale: (matrix: string | null | undefined) => number | null;
16
33
  import { type Readable } from 'svelte/store';
17
34
  /**
18
35
  * Options for range-mapping transform.
@@ -7,8 +7,13 @@ const DEFAULTS = {
7
7
  scaleY: 1,
8
8
  rotate: 0
9
9
  };
10
- /** Build a CSS transform string from numeric values (no matrices). */
11
- export function buildTransform(values) {
10
+ /**
11
+ * Build a CSS transform string from numeric values (no matrices).
12
+ *
13
+ * @param values Partial map of translate/scale/rotate values.
14
+ * @returns A space-separated CSS `transform` string, or `""` when all values are defaults.
15
+ */
16
+ export const buildTransform = (values) => {
12
17
  const v = { ...DEFAULTS, ...values };
13
18
  // If explicit per-axis scales provided, use them; otherwise use uniform scale
14
19
  const useAxes = values.scaleX !== undefined || values.scaleY !== undefined;
@@ -26,9 +31,15 @@ export function buildTransform(values) {
26
31
  parts.push(`scale(${round(v.scale)})`);
27
32
  }
28
33
  return parts.join(' ').trim();
29
- }
30
- /** Lightweight safety check for transform magnitudes and NaN values. */
31
- export function isSafeTransform(values, opts) {
34
+ };
35
+ /**
36
+ * Lightweight safety check for transform magnitudes and NaN values.
37
+ *
38
+ * @param values Transform values to validate.
39
+ * @param opts Optional configuration; `maxScale` caps allowable absolute scale (default 8).
40
+ * @returns `true` if all scale values are finite and within bounds.
41
+ */
42
+ export const isSafeTransform = (values, opts) => {
32
43
  const maxScale = opts?.maxScale ?? 8;
33
44
  const entries = [
34
45
  ['scale', values.scale],
@@ -44,8 +55,14 @@ export function isSafeTransform(values, opts) {
44
55
  return false;
45
56
  }
46
57
  return true;
47
- }
48
- export function parseMatrixScale(matrix) {
58
+ };
59
+ /**
60
+ * Extract the uniform scale factor from a CSS `matrix()` string.
61
+ *
62
+ * @param matrix A CSS `matrix(...)` value, `"none"`, `null`, or `undefined`.
63
+ * @returns The `a` component of the matrix (uniform scale), or `null` if unparseable.
64
+ */
65
+ export const parseMatrixScale = (matrix) => {
49
66
  if (!matrix || matrix === 'none')
50
67
  return null;
51
68
  const m = matrix.match(/matrix\(([^)]+)\)/);
@@ -53,11 +70,16 @@ export function parseMatrixScale(matrix) {
53
70
  return null;
54
71
  const [a] = m[1].split(',').map((s) => parseFloat(s.trim()));
55
72
  return Number.isFinite(a) ? a : null;
56
- }
57
- function round(n) {
58
- // avoid excessive precision in strings
73
+ };
74
+ /**
75
+ * Round a number to six decimal places to avoid excessive precision in CSS strings.
76
+ *
77
+ * @param n The number to round.
78
+ * @returns The rounded value.
79
+ */
80
+ const round = (n) => {
59
81
  return Math.round(n * 1e6) / 1e6;
60
- }
82
+ };
61
83
  import { derived, readable } from 'svelte/store';
62
84
  /**
63
85
  * Creates a linear mixer function for numeric values.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
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",
@@ -53,8 +53,8 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "motion": "^12.34.2",
57
- "motion-dom": "^12.34.2"
56
+ "motion": "^12.34.3",
57
+ "motion-dom": "^12.34.3"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@changesets/cli": "^2.29.8",
@@ -62,7 +62,7 @@
62
62
  "@eslint/js": "^10.0.1",
63
63
  "@playwright/test": "^1.58.2",
64
64
  "@sveltejs/adapter-auto": "^7.0.1",
65
- "@sveltejs/kit": "^2.52.2",
65
+ "@sveltejs/kit": "^2.53.0",
66
66
  "@sveltejs/package": "^2.5.7",
67
67
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
68
68
  "@tailwindcss/aspect-ratio": "^0.4.2",
@@ -75,7 +75,7 @@
75
75
  "@types/node": "^25.3.0",
76
76
  "@vitest/coverage-v8": "^4.0.18",
77
77
  "concurrently": "^9.2.1",
78
- "eslint": "^10.0.0",
78
+ "eslint": "^10.0.1",
79
79
  "eslint-config-prettier": "10.1.8",
80
80
  "eslint-plugin-import": "2.32.0",
81
81
  "eslint-plugin-svelte": "3.15.0",
@@ -93,8 +93,8 @@
93
93
  "prettier-plugin-tailwindcss": "^0.7.2",
94
94
  "publint": "^0.3.17",
95
95
  "runed": "0.37.1",
96
- "svelte": "^5.53.0",
97
- "svelte-check": "^4.4.1",
96
+ "svelte": "^5.53.2",
97
+ "svelte-check": "^4.4.3",
98
98
  "svg-tags": "^1.0.0",
99
99
  "tailwind-merge": "^3.5.0",
100
100
  "tailwind-variants": "^3.2.2",