@humanspeak/svelte-motion 0.1.27 → 0.1.28

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
@@ -1,4 +1,4 @@
1
- # @humanspeak/svelte-motion
1
+ # Svelte Motion — Framer Motion API for Svelte 5
2
2
 
3
3
  [![NPM version](https://img.shields.io/npm/v/@humanspeak/svelte-motion.svg)](https://www.npmjs.com/package/@humanspeak/svelte-motion)
4
4
  [![Build Status](https://github.com/humanspeak/svelte-motion/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/humanspeak/svelte-motion/actions/workflows/npm-publish.yml)
@@ -32,7 +32,7 @@ npm install @humanspeak/svelte-motion
32
32
  </motion.button>
33
33
  ```
34
34
 
35
- ## API parity snapshot (current)
35
+ ## Framer Motion API Parity
36
36
 
37
37
  Goal: Framer Motion API parity for Svelte where common React examples can be translated with minimal changes.
38
38
 
@@ -168,6 +168,24 @@
168
168
  })
169
169
  }
170
170
 
171
+ // Capture mid-animation computed styles via rAF so exit clones can start
172
+ // from the correct visual state. Without this, interrupting an enter animation
173
+ // causes the exit to snap (the element is disconnected before onDestroy, so
174
+ // getAnimations()/commitStyles() can't work at clone time).
175
+ $effect(() => {
176
+ if (!(element && context)) return
177
+ let rafId: number
178
+ const capture = () => {
179
+ if (element && element.isConnected && element.getAnimations().length > 0) {
180
+ const cs = getComputedStyle(element)
181
+ context.updateChildAnimatedStyle(presenceKey, cs.opacity, cs.transform)
182
+ }
183
+ rafId = requestAnimationFrame(capture)
184
+ }
185
+ rafId = requestAnimationFrame(capture)
186
+ return () => cancelAnimationFrame(rafId)
187
+ })
188
+
171
189
  // Keep a live snapshot of the layoutId element's rect so the next element can FLIP from it.
172
190
  // We store the last-known-good rect and push it to the registry on cleanup,
173
191
  // because onDestroy fires after the element is removed from DOM (rect would be zeros).
@@ -985,6 +1003,9 @@
985
1003
 
986
1004
  // Mark that we're triggering the initial animation to prevent duplicate runs
987
1005
  initialAnimationTriggered = true
1006
+ if (animateProp && typeof animateProp !== 'string') {
1007
+ lastAnimatePropJson = JSON.stringify(animateProp)
1008
+ }
988
1009
 
989
1010
  // IMPORTANT: Start the animation BEFORE changing isLoaded.
990
1011
  // When isLoaded changes to 'ready', Svelte will reactively remove the
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 { useMotionValue } from './utils/motionValue';
13
+ export type { MotionValue } from './utils/motionValue';
12
14
  export { useMotionValueEvent } from './utils/motionValueEvent';
13
15
  export { useScroll } from './utils/scroll';
14
16
  export { useSpring } from './utils/spring';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ 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 { useMotionValue } from './utils/motionValue';
15
16
  export { useMotionValueEvent } from './utils/motionValueEvent';
16
17
  export { useScroll } from './utils/scroll';
17
18
  export { useSpring } from './utils/spring';
@@ -0,0 +1,6 @@
1
+ import { type Readable } from 'svelte/store';
2
+ export type MotionValue<T = number> = Readable<T> & {
3
+ set: (v: T) => void;
4
+ get: () => T;
5
+ };
6
+ export declare const useMotionValue: <T = number>(initial: T) => MotionValue<T>;
@@ -0,0 +1,13 @@
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
+ };
@@ -31,6 +31,8 @@ export type AnimatePresenceContext = {
31
31
  registerChild: (key: string, element: HTMLElement, exit?: MotionExit, mergedTransition?: MotionTransition) => void;
32
32
  /** Update the last known rect/style snapshot for a registered child. */
33
33
  updateChildState: (key: string, rect: DOMRect, computedStyle: CSSStyleDeclaration) => void;
34
+ /** Update the last captured mid-animation style values for a child. */
35
+ updateChildAnimatedStyle: (key: string, opacity: string, transform: string) => void;
34
36
  /** Unregister a child. If it has an exit, clone and animate it out. */
35
37
  unregisterChild: (key: string) => void;
36
38
  };
@@ -239,6 +239,17 @@ export const createAnimatePresenceContext = (context) => {
239
239
  child.lastComputedStyle = computedStyle;
240
240
  }
241
241
  };
242
+ /**
243
+ * Update the last captured mid-animation style values for a child.
244
+ * Called from a rAF loop while WAAPI animations are running.
245
+ */
246
+ const updateChildAnimatedStyle = (key, opacity, transform) => {
247
+ const child = children.get(key);
248
+ if (child) {
249
+ child.lastAnimatedOpacity = opacity;
250
+ child.lastAnimatedTransform = transform;
251
+ }
252
+ };
242
253
  /**
243
254
  * Unregister a child. If it has an `exit` definition, create a styled
244
255
  * clone and run the exit animation using Motion. Cleans up after finish.
@@ -316,6 +327,13 @@ export const createAnimatePresenceContext = (context) => {
316
327
  catch {
317
328
  // Ignore
318
329
  }
330
+ // Apply last captured mid-animation values (from rAF polling) so that
331
+ // exit clones start from the correct visual state when interrupting
332
+ // an enter animation. The element is disconnected by now so
333
+ // getComputedStyle/getAnimations won't reflect in-flight values.
334
+ if (child.lastAnimatedOpacity != null) {
335
+ clone.style.opacity = child.lastAnimatedOpacity;
336
+ }
319
337
  // Attach to original parent and position absolutely at the last known rect
320
338
  // Find the nearest positioned ancestor that isn't display: contents
321
339
  let parent = child.element.parentElement ?? document.body;
@@ -464,6 +482,7 @@ export const createAnimatePresenceContext = (context) => {
464
482
  onExitComplete: context.onExitComplete,
465
483
  registerChild,
466
484
  updateChildState,
485
+ updateChildAnimatedStyle,
467
486
  unregisterChild
468
487
  };
469
488
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.1.27",
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.",
3
+ "version": "0.1.28",
4
+ "description": "A Framer Motion-compatible 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",
7
7
  "animation",
@@ -12,7 +12,18 @@
12
12
  "svelte5",
13
13
  "hardware-accelerated",
14
14
  "micro-interactions",
15
- "performance"
15
+ "performance",
16
+ "framer-motion",
17
+ "framer-motion-svelte",
18
+ "gestures",
19
+ "exit-animation",
20
+ "animate-presence",
21
+ "layout-animation",
22
+ "drag",
23
+ "variants",
24
+ "sveltekit",
25
+ "typescript",
26
+ "svelte-animation"
16
27
  ],
17
28
  "homepage": "https://motion.svelte.page",
18
29
  "bugs": {
@@ -62,20 +73,19 @@
62
73
  "@eslint/js": "^10.0.1",
63
74
  "@playwright/test": "^1.58.2",
64
75
  "@sveltejs/adapter-auto": "^7.0.1",
65
- "@sveltejs/kit": "^2.53.0",
76
+ "@sveltejs/kit": "^2.53.3",
66
77
  "@sveltejs/package": "^2.5.7",
67
78
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
68
79
  "@tailwindcss/aspect-ratio": "^0.4.2",
69
80
  "@tailwindcss/container-queries": "^0.1.1",
70
81
  "@tailwindcss/forms": "^0.5.11",
71
- "@tailwindcss/postcss": "^4.2.0",
82
+ "@tailwindcss/postcss": "^4.2.1",
72
83
  "@tailwindcss/typography": "^0.5.19",
73
84
  "@testing-library/jest-dom": "^6.9.1",
74
85
  "@testing-library/svelte": "^5.3.1",
75
- "@types/node": "^25.3.0",
86
+ "@types/node": "^25.3.2",
76
87
  "@vitest/coverage-v8": "^4.0.18",
77
- "concurrently": "^9.2.1",
78
- "eslint": "^10.0.1",
88
+ "eslint": "^10.0.2",
79
89
  "eslint-config-prettier": "10.1.8",
80
90
  "eslint-plugin-import": "2.32.0",
81
91
  "eslint-plugin-svelte": "3.15.0",
@@ -86,6 +96,7 @@
86
96
  "html-void-elements": "^3.0.0",
87
97
  "husky": "^9.1.7",
88
98
  "jsdom": "^28.1.0",
99
+ "mprocs": "^0.8.3",
89
100
  "prettier": "^3.8.1",
90
101
  "prettier-plugin-organize-imports": "^4.3.0",
91
102
  "prettier-plugin-sort-json": "^4.2.0",
@@ -93,16 +104,16 @@
93
104
  "prettier-plugin-tailwindcss": "^0.7.2",
94
105
  "publint": "^0.3.17",
95
106
  "runed": "0.37.1",
96
- "svelte": "^5.53.2",
97
- "svelte-check": "^4.4.3",
107
+ "svelte": "^5.53.5",
108
+ "svelte-check": "^4.4.4",
98
109
  "svg-tags": "^1.0.0",
99
110
  "tailwind-merge": "^3.5.0",
100
111
  "tailwind-variants": "^3.2.2",
101
- "tailwindcss": "^4.2.0",
112
+ "tailwindcss": "^4.2.1",
102
113
  "tailwindcss-animate": "^1.0.7",
103
114
  "tsx": "^4.21.0",
104
115
  "typescript": "^5.9.3",
105
- "typescript-eslint": "^8.56.0",
116
+ "typescript-eslint": "^8.56.1",
106
117
  "vite": "^7.3.1",
107
118
  "vite-tsconfig-paths": "^6.1.1",
108
119
  "vitest": "^4.0.18"
@@ -135,7 +146,7 @@
135
146
  "cs:publish": "pnpm run build && changeset publish",
136
147
  "cs:version": "changeset version && pnpm i --lockfile-only",
137
148
  "dev": "vite dev",
138
- "dev:all": "concurrently -k -n pkg,docs,sitemap,registry -c green,cyan,magenta,yellow \"pnpm -w -r --filter @humanspeak/svelte-motion run dev\" \"pnpm --filter docs run dev\" \"pnpm --filter docs run sitemap:watch\" \"pnpm --filter docs run registry:watch\"",
149
+ "dev:all": "mprocs",
139
150
  "dev:pkg": "svelte-kit sync && svelte-package --watch",
140
151
  "format": "prettier --write .",
141
152
  "generate": "tsx scripts/generate-html.ts",