@humanspeak/svelte-motion 0.1.18 → 0.1.20

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
@@ -12,62 +12,97 @@
12
12
  [![Types](https://img.shields.io/npm/types/@humanspeak/svelte-motion.svg)](https://www.npmjs.com/package/@humanspeak/svelte-motion)
13
13
  [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/humanspeak/svelte-motion/graphs/commit-activity)
14
14
 
15
- ## Why are we here?
15
+ Svelte Motion brings a Framer Motion-style API to Svelte 5 with `motion.<tag>` components, gestures, variants, exit animations, layout animation, and utility hooks.
16
16
 
17
- Motion vibes, Svelte runes. This brings Motion’s declarative animation goodness to Svelte with `motion.<tag>` components, interaction props, and composable config. If you spot a cool React example, drop it in an issue—we’ll port it. 😍
17
+ For the latest documentation and examples, visit [motion.svelte.page](https://motion.svelte.page).
18
18
 
19
- Requests welcome: Have a feature/prop/example you want? Please open an issue (ideally include a working Motion/React snippet or example link) and we’ll prioritize it.
19
+ ## Install
20
20
 
21
- ## Supported Elements
21
+ ```bash
22
+ npm install @humanspeak/svelte-motion
23
+ ```
24
+
25
+ ```svelte
26
+ <script lang="ts">
27
+ import { motion } from '@humanspeak/svelte-motion'
28
+ </script>
29
+
30
+ <motion.button initial={{ opacity: 0 }} animate={{ opacity: 1 }} whileTap={{ scale: 0.97 }}>
31
+ Hello motion
32
+ </motion.button>
33
+ ```
22
34
 
23
- All standard HTML and SVG elements are supported as motion components (e.g., `motion.div`, `motion.button`, `motion.svg`, `motion.circle`). The full set is generated from canonical lists using `html-tags`, `html-void-elements`, and `svg-tags`, and exported from `src/lib/html/`.
35
+ ## API parity snapshot (current)
24
36
 
25
- - HTML components respect void elements for documentation and generation purposes.
26
- - SVG components are treated as non-void.
27
- - Dashed tag names are exported as PascalCase components (e.g., `color-profile` → `ColorProfile`).
37
+ Goal: Framer Motion API parity for Svelte where common React examples can be translated with minimal changes.
28
38
 
29
- ## Configuration
39
+ | Capability | Status |
40
+ | --------------------------------------------------------- | ------------------------------- |
41
+ | `initial` / `animate` / `transition` | Supported |
42
+ | `variants` (string keys + inheritance) | Supported |
43
+ | `whileHover` / `whileTap` / `whileFocus` / `whileInView` | Supported |
44
+ | Drag (`drag`, constraints, momentum, controls, callbacks) | Supported |
45
+ | `AnimatePresence` (`initial`, `mode`, `onExitComplete`) | Supported |
46
+ | Layout (`layout`, `layout="position"`) | Supported (single-element FLIP) |
47
+ | Shared layout (`layoutId`) | Not yet supported |
48
+ | Pan gesture API (`whilePan`, `onPan*`) | Not yet supported |
49
+ | `MotionConfig` parity beyond `transition` | Partial |
50
+ | `reducedMotion`, `features`, `transformPagePoint` | Not yet supported |
30
51
 
31
- ### MotionConfig
52
+ ## Supported elements
32
53
 
33
- This package includes support for `MotionConfig`, which allows you to set default motion settings for all child components. See the [React - Motion Config](https://motion.dev/docs/react-motion-config) for more details.
54
+ Motion components are generated from canonical HTML/SVG tag lists and exported from `src/lib/html/`.
55
+
56
+ - `motion.div`, `motion.button`, `motion.svg`, `motion.path`, etc.
57
+ - Most standard tags are included.
58
+ - Excluded by generation: `script`, `style`, `link`, `meta`, `title`, `head`, `html`, `body`.
59
+
60
+ ## Core components
61
+
62
+ ### `motion.<tag>`
63
+
64
+ Use motion components the same way you use regular elements, with animation props:
34
65
 
35
66
  ```svelte
36
- <MotionConfig transition={{ duration: 0.5 }}>
37
- <!-- All motion components inside will inherit these settings -->
38
- <motion.div animate={{ scale: 1.2 }}>Inherits 0.5s duration</motion.div>
39
- </MotionConfig>
67
+ <motion.div
68
+ initial={{ opacity: 0, y: 8 }}
69
+ animate={{ opacity: 1, y: 0 }}
70
+ transition={{ duration: 0.25 }}
71
+ />
40
72
  ```
41
73
 
42
- ### Layout Animations (FLIP)
74
+ ### `MotionConfig`
43
75
 
44
- Svelte Motion supports minimal layout animations via FLIP using the `layout` prop:
76
+ `MotionConfig` currently supports default `transition` values for descendants.
45
77
 
46
78
  ```svelte
47
- <motion.div layout transition={{ duration: 0.25 }} />
48
- ```
79
+ <script lang="ts">
80
+ import { MotionConfig, motion } from '@humanspeak/svelte-motion'
81
+ </script>
49
82
 
50
- - **`layout`**: smoothly animates translation and scale between layout changes (size and position).
51
- - **`layout="position"`**: only animates translation (no scale).
83
+ <MotionConfig transition={{ duration: 0.4 }}>
84
+ <motion.div animate={{ scale: 1.05 }} />
85
+ </MotionConfig>
86
+ ```
52
87
 
53
- ### Exit Animations (AnimatePresence)
88
+ ### `AnimatePresence`
54
89
 
55
- Animate elements as they leave the DOM using `AnimatePresence`. This mirrors Motion’s React API and docs for exit animations ([reference](https://motion.dev/docs/react)).
90
+ Exit animations on unmount with support for `mode="sync" | "wait" | "popLayout"` and `onExitComplete`.
56
91
 
57
92
  ```svelte
58
93
  <script lang="ts">
59
- import { motion, AnimatePresence } from '$lib'
94
+ import { AnimatePresence, motion } from '@humanspeak/svelte-motion'
95
+
60
96
  let show = $state(true)
61
97
  </script>
62
98
 
63
- <AnimatePresence>
99
+ <AnimatePresence mode="wait" onExitComplete={() => console.log('done')}>
64
100
  {#if show}
65
101
  <motion.div
102
+ key="card"
66
103
  initial={{ opacity: 0, scale: 0.9 }}
67
104
  animate={{ opacity: 1, scale: 1 }}
68
- exit={{ opacity: 0, scale: 0.9 }}
69
- transition={{ duration: 0.5 }}
70
- class="size-24 rounded-md bg-cyan-400"
105
+ exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.2 } }}
71
106
  />
72
107
  {/if}
73
108
  </AnimatePresence>
@@ -75,328 +110,161 @@ Animate elements as they leave the DOM using `AnimatePresence`. This mirrors Mot
75
110
  <motion.button whileTap={{ scale: 0.97 }} onclick={() => (show = !show)}>Toggle</motion.button>
76
111
  ```
77
112
 
78
- - The exit animation is driven by `exit` and will play when the element unmounts.
79
- - Transition precedence (merged before running exit):
80
- - `exit.transition` (highest precedence)
81
- - component `transition` (merged with `MotionConfig`)
82
- - fallback default `{ duration: 0.35 }`
83
-
84
- #### Current Limitations
85
-
86
- Some Motion features are not yet implemented:
87
-
88
- - `reducedMotion` settings
89
- - `features` configuration
90
- - Performance optimizations like `transformPagePoint`
91
- - Advanced transition controls
92
- - Shared layout / `layoutId` (planned)
93
-
94
- ## External Dependencies
95
-
96
- This package carefully selects its dependencies to provide a robust and maintainable solution:
97
-
98
- ### Core Dependencies
99
-
100
- - **motion**
101
- - High-performance animation library for the web
102
- - Provides smooth, hardware-accelerated animations
103
- - Supports spring physics and custom easing
104
- - Used for creating fluid motion and transitions
105
-
106
- ### Examples
107
-
108
- | Motion | Demo / Route | Live Demo |
109
- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------- |
110
- | [React - Enter Animation](https://examples.motion.dev/react/enter-animation) | `/tests/motion/enter-animation` | [View Example](https://svelte.dev/playground/7f60c347729f4ea48b1a4590c9dedc02?version=5.38.10) |
111
- | [HTML Content (0→100 counter)](https://examples.motion.dev/react/html-content) | `/tests/motion/html-content` | [View Example](https://motion.svelte.page/examples/html-content) |
112
- | [Aspect Ratio](https://examples.motion.dev/react/aspect-ratio) | `/tests/motion/aspect-ratio` | [View Example](https://svelte.dev/playground/1bf60e745fae44f5becb4c830fde9b6e?version=5.38.10) |
113
- | [Hover + Tap (whileHover + whileTap)](https://examples.motion.dev/react/gestures) | `/tests/motion/hover-and-tap` | [View Example](https://motion.svelte.page/examples/hover-and-tap) |
114
- | [Focus (whileFocus)](https://motion.dev/docs/react-motion-component#focus) | `/tests/motion/while-focus` | [View Example](https://motion.svelte.page/examples/while-focus) |
115
- | [Rotate](https://examples.motion.dev/react/rotate) | `/tests/motion/rotate` | [View Example](https://motion.svelte.page/examples/rotate) |
116
- | [Random - Shiny Button](https://www.youtube.com/watch?v=jcpLprT5F0I) by [@verse\_](https://x.com/verse_) | `/tests/random/shiny-button` | [View Example](https://svelte.dev/playground/96f9e0bf624f4396adaf06c519147450?version=5.38.10) |
117
- | [Fancy Like Button](https://github.com/DRlFTER/fancyLikeButton) | `/tests/random/fancy-like-button` | [View Example](https://svelte.dev/playground/c34b7e53d41c48b0ab1eaf21ca120c6e?version=5.38.10) |
118
- | [Keyframes (square → circle → square; scale 1→2→1)](https://motion.dev/docs/react-animation#keyframes) | `/tests/motion/keyframes` | [View Example](https://svelte.dev/playground/05595ce0db124c1cbbe4e74fda68d717?version=5.38.10) |
119
- | [Animated Border Gradient (conic-gradient rotate)](https://www.youtube.com/watch?v=OgQI1-9T6ZA) | `/tests/random/animated-border-gradient` | [View Example](https://svelte.dev/playground/6983a61b4c35441b8aa72a971de01a23?version=5.38.10) |
120
- | [Exit Animation](https://motion.dev/docs/react#exit-animations) | `/tests/animate-presence/basic` | [View Example](https://svelte.dev/playground/ef277e283d864653ace54e7453801601?version=5.38.10) |
113
+ Notes:
121
114
 
122
- ## Interactions
115
+ - Direct children of `AnimatePresence` require `key`.
116
+ - Exit transition precedence: base `{ duration: 0.35 }` < merged `transition` < `exit.transition`.
123
117
 
124
- ### Hover
118
+ ## Interaction props
125
119
 
126
- Svelte Motion now supports hover interactions via the `whileHover` prop, similar to React Motion/Framer Motion.
120
+ ### `whileHover`
127
121
 
128
122
  ```svelte
129
- <motion.div whileHover={{ scale: 1.05 }} />
123
+ <motion.button whileHover={{ scale: 1.05, transition: { duration: 0.12 } }} />
130
124
  ```
131
125
 
132
- - `whileHover` accepts a keyframes object. It also supports a nested `transition` to override the global transition for hover only:
126
+ - Uses true-hover gating (`(hover: hover)` and `(pointer: fine)`).
127
+ - Supports `onHoverStart` and `onHoverEnd`.
133
128
 
134
- ```svelte
135
- <motion.button
136
- whileHover={{ scale: 1.05, transition: { duration: 0.12 } }}
137
- transition={{ duration: 0.25 }}
138
- />
139
- ```
140
-
141
- - Baseline restoration: when the pointer leaves, changed values are restored to their pre-hover baseline. Baseline is computed from `animate` values if present, otherwise `initial`, otherwise sensible defaults (e.g., `scale: 1`, `x/y: 0`) or current computed style where applicable.
142
- - True-hover gating: hover behavior runs only on devices that support real hover and fine pointers (media queries `(hover: hover)` and `(pointer: fine)`), avoiding sticky hover states on touch devices.
143
-
144
- ### Tap
129
+ ### `whileTap`
145
130
 
146
131
  ```svelte
147
132
  <motion.button whileTap={{ scale: 0.95 }} />
148
133
  ```
149
134
 
150
- - Callbacks: `onTapStart`, `onTap`, `onTapCancel` are supported.
151
- - Accessibility: Elements with `whileTap` are keyboard-accessible (Enter and Space).
152
- - Enter or Space down → fires `onTapStart` and applies `whileTap` (Space prevents default scrolling)
153
- - Enter or Space up → fires `onTap`
154
- - Blur while key is held → fires `onTapCancel`
155
- - `MotionContainer` sets `tabindex="0"` automatically when `whileTap` is present and no `tabindex`/`tabIndex` is provided.
135
+ - Supports `onTapStart`, `onTap`, `onTapCancel`.
136
+ - Keyboard accessible (Enter/Space).
156
137
 
157
- ### Focus
138
+ ### `whileFocus`
158
139
 
159
140
  ```svelte
160
141
  <motion.button whileFocus={{ scale: 1.05, outline: '2px solid blue' }} />
161
142
  ```
162
143
 
163
- - Animates when the element receives keyboard focus and restores baseline on blur.
164
- - Callbacks: `onFocusStart`, `onFocusEnd` are supported.
165
- - Perfect for keyboard navigation and accessibility enhancements.
144
+ - Supports `onFocusStart` and `onFocusEnd`.
166
145
 
167
- ### Animation lifecycle
146
+ ### `whileInView`
168
147
 
169
148
  ```svelte
170
149
  <motion.div
171
- onAnimationStart={(def) => {
172
- /* ... */
173
- }}
174
- onAnimationComplete={(def) => {
175
- /* ... */
176
- }}
150
+ initial={{ opacity: 0, y: 40 }}
151
+ whileInView={{ opacity: 1, y: 0 }}
152
+ onInViewStart={() => console.log('entered')}
153
+ onInViewEnd={() => console.log('left')}
177
154
  />
178
155
  ```
179
156
 
180
- ## Variants
157
+ - Uses `IntersectionObserver`.
158
+ - Current implementation uses a fixed threshold behavior (no Framer-style `viewport` options yet).
181
159
 
182
- Variants allow you to define named animation states that can be referenced throughout your component tree. They're perfect for creating reusable animations and orchestrating complex sequences.
160
+ ## Drag
183
161
 
184
- ### Basic usage
162
+ Supported drag props:
185
163
 
186
- Instead of defining animation objects inline, create a `Variants` object with named states:
164
+ - `drag`: `true | 'x' | 'y'`
165
+ - `dragConstraints`: pixel object or element ref
166
+ - `dragElastic`, `dragMomentum`, `dragTransition`
167
+ - `dragDirectionLock`, `dragPropagation`, `dragSnapToOrigin`
168
+ - `dragListener`, `dragControls`
169
+ - `whileDrag`
170
+ - Callbacks: `onDragStart`, `onDrag`, `onDragEnd`, `onDirectionLock`, `onDragTransitionEnd`
187
171
 
188
172
  ```svelte
189
173
  <script lang="ts">
190
- import { motion, type Variants } from '@humanspeak/svelte-motion'
191
-
192
- let isOpen = $state(false)
174
+ import { createDragControls, motion } from '@humanspeak/svelte-motion'
193
175
 
194
- const variants: Variants = {
195
- open: { opacity: 1, scale: 1 },
196
- closed: { opacity: 0, scale: 0.8 }
197
- }
176
+ const controls = createDragControls()
198
177
  </script>
199
178
 
200
- <motion.div {variants} initial="closed" animate={isOpen ? 'open' : 'closed'}>Click me</motion.div>
201
- ```
202
-
203
- ### Variant propagation
204
-
205
- One of the most powerful features is **automatic propagation** through component trees. When a parent changes its animation state, all children with `variants` defined automatically inherit that state:
206
-
207
- ```svelte
208
- <script lang="ts">
209
- let isVisible = $state(false)
179
+ <button onpointerdown={(e) => controls.start(e)}>Start drag</button>
210
180
 
211
- const containerVariants: Variants = {
212
- visible: { opacity: 1 },
213
- hidden: { opacity: 0 }
214
- }
215
-
216
- const itemVariants: Variants = {
217
- visible: { opacity: 1, x: 0 },
218
- hidden: { opacity: 0, x: -20 }
219
- }
220
- </script>
221
-
222
- <motion.ul variants={containerVariants} initial="hidden" animate={isVisible ? 'visible' : 'hidden'}>
223
- <!-- Children automatically inherit parent's variant state -->
224
- <motion.li variants={itemVariants}>Item 1</motion.li>
225
- <motion.li variants={itemVariants}>Item 2</motion.li>
226
- <motion.li variants={itemVariants}>Item 3</motion.li>
227
- </motion.ul>
228
- ```
229
-
230
- **How it works:**
231
-
232
- - Parent sets `animate="visible"`
233
- - Children with `variants` automatically inherit `"visible"` state
234
- - Each child resolves its own variant definition
235
- - No need to pass `animate` props to children!
236
-
237
- ### Staggered animations
238
-
239
- Create staggered animations with transition delays:
240
-
241
- ```svelte
242
- {#each items as item, i}
243
- <motion.div variants={itemVariants} transition={{ delay: i * 0.1 }}>
244
- {item}
245
- </motion.div>
246
- {/each}
247
- ```
248
-
249
- See the [Variants documentation](https://motion.svelte.page/docs/variants) for complete details and examples.
250
-
251
- ## Server-side rendering
252
-
253
- Motion components render their initial state during SSR. The container merges inline `style` with the first values from `initial` (or the first keyframes from `animate` when `initial` is empty) so the server HTML matches the starting appearance. On hydration, components promote to a ready state and animate without flicker.
254
-
255
- ```svelte
256
181
  <motion.div
257
- initial={{ opacity: 0, borderRadius: '12px' }}
258
- animate={{ opacity: 1 }}
259
- style="width: 100px; height: 50px"
182
+ drag="x"
183
+ dragControls={controls}
184
+ dragListener={false}
185
+ dragConstraints={{ left: -120, right: 120 }}
186
+ whileDrag={{ scale: 1.03 }}
260
187
  />
261
188
  ```
262
189
 
263
- Notes:
264
-
265
- - Transform properties like `scale`/`rotate` are composed into a single `transform` style during SSR.
266
- - When `initial` is empty, the first keyframe from `animate` is used to seed SSR styles.
267
- - `initial` can be `false` to not run on initial
268
-
269
- ## Utilities
270
-
271
- ### useTime(id?)
272
-
273
- - Returns a Svelte readable store that updates once per animation frame with elapsed milliseconds since creation.
274
- - If you pass an `id`, calls with the same id return a shared timeline (kept in sync across components).
275
- - SSR-safe: Returns a static `0` store when `window` is not available.
276
-
277
- ```svelte
278
- <script lang="ts">
279
- import { motion, useTime } from '$lib'
280
- import { derived } from 'svelte/store'
281
-
282
- const time = useTime('global') // shared
283
- const rotate = derived(time, (t) => ((t % 4000) / 4000) * 360)
284
- </script>
285
-
286
- <motion.div style={`rotate: ${$rotate}deg`} />
287
- ```
288
-
289
- ### useAnimationFrame(callback)
290
-
291
- - Runs a callback on every animation frame with the current timestamp.
292
- - The callback receives a `DOMHighResTimeStamp` representing the time elapsed since the time origin.
293
- - Returns a cleanup function that stops the animation loop.
294
- - Best used inside a `$effect` to ensure proper cleanup when the component unmounts.
295
- - SSR-safe: Does nothing and returns a no-op cleanup function when `window` is unavailable.
190
+ ## Variants
296
191
 
297
192
  ```svelte
298
193
  <script lang="ts">
299
- import { useAnimationFrame } from '$lib'
194
+ import { motion, type Variants } from '@humanspeak/svelte-motion'
300
195
 
301
- let cubeRef: HTMLDivElement
196
+ let open = $state(false)
302
197
 
303
- $effect(() => {
304
- return useAnimationFrame((t) => {
305
- if (!cubeRef) return
198
+ const parent: Variants = {
199
+ open: { opacity: 1 },
200
+ closed: { opacity: 0 }
201
+ }
306
202
 
307
- const rotate = Math.sin(t / 10000) * 200
308
- const y = (1 + Math.sin(t / 1000)) * -50
309
- cubeRef.style.transform = `translateY(${y}px) rotateX(${rotate}deg) rotateY(${rotate}deg)`
310
- })
311
- })
203
+ const child: Variants = {
204
+ open: { x: 0, opacity: 1 },
205
+ closed: { x: -16, opacity: 0 }
206
+ }
312
207
  </script>
313
208
 
314
- <div bind:this={cubeRef}>Animated content</div>
209
+ <motion.ul variants={parent} initial="closed" animate={open ? 'open' : 'closed'}>
210
+ <motion.li variants={child}>Item A</motion.li>
211
+ <motion.li variants={child}>Item B</motion.li>
212
+ </motion.ul>
315
213
  ```
316
214
 
317
- - Reference: Motion useAnimationFrame docs [motion.dev](https://motion.dev/docs/react-use-animation-frame).
215
+ - String variant keys are resolved from `variants`.
216
+ - Variant state inherits through context.
318
217
 
319
- ### useSpring
218
+ ## Layout animation
320
219
 
321
- `useSpring` creates a readable store that animates to its latest target with a spring. You can either control it directly with `set`/`jump`, or have it follow another readable (like a time-derived value).
220
+ Single-element FLIP layout animation:
322
221
 
323
222
  ```svelte
324
- <script lang="ts">
325
- import { useTime, useTransform, useSpring } from '$lib'
326
-
327
- // Track another readable
328
- const time = useTime()
329
- const blurTarget = useTransform(() => {
330
- const phase = ($time % 2000) / 2000
331
- return 4 * (0.5 + 0.5 * Math.sin(phase * Math.PI * 2)) // 0..4
332
- }, [time])
333
- const blur = useSpring(blurTarget, { stiffness: 300 })
334
-
335
- // Or direct control
336
- const x = useSpring(0, { stiffness: 300 })
337
- // x.set(100) // animates to 100
338
- // x.jump(0) // jumps without animation
339
- </script>
340
-
341
- <div style={`filter: blur(${$blur}px)`} />
223
+ <motion.div layout />
224
+ <motion.div layout="position" />
342
225
  ```
343
226
 
344
- - Accepts number or unit string (e.g., `"100vh"`) or a readable source.
345
- - Returns a readable with `{ set, jump }` methods when used in the browser; SSR-safe on the server.
346
- - Reference: Motion useSpring docs [motion.dev](https://motion.dev/docs/react-use-spring?platform=react).
347
-
348
- ### useTransform
349
-
350
- `useTransform` creates a derived readable. It supports:
351
-
352
- - Range mapping: map a numeric source across input/output ranges with optional `{ clamp, ease, mixer }`.
353
- - Function form: compute from one or more dependencies.
227
+ - `layout`: translate + scale.
228
+ - `layout="position"`: translate only.
229
+ - Shared layout (`layoutId`) is not implemented yet.
354
230
 
355
- Range mapping example:
231
+ ## Utilities
356
232
 
357
- ```svelte
358
- <script lang="ts">
359
- import { useTime, useTransform } from '$lib'
360
- const time = useTime()
361
- // Map 0..4000ms to 0..360deg, unclamped to allow wrap-around
362
- const rotate = useTransform(time, [0, 4000], [0, 360], { clamp: false })
363
- </script>
233
+ - `useAnimationFrame`
234
+ - `useSpring`
235
+ - `useTime`
236
+ - `useTransform`
237
+ - `styleString`
238
+ - `stringifyStyleObject` (deprecated)
239
+ - `createDragControls`
364
240
 
365
- <div style={`rotate: ${$rotate}deg`} />
366
- ```
241
+ The package also re-exports core helpers from `motion` (for example `animate`, `stagger`, `transform`, easings, and utility functions).
367
242
 
368
- Function form example:
243
+ ## SSR behavior
369
244
 
370
- ```svelte
371
- <script lang="ts">
372
- import { useTransform } from '$lib'
373
- // Given stores a and b, compute their sum
374
- const add = (a: number, b: number) => a + b
375
- // deps are stores; body can access them via $ syntax
376
- const total = useTransform(() => add($a, $b), [a, b])
377
- </script>
245
+ - Initial visual state is rendered server-side from `initial` (or first `animate` keyframe when `initial` is empty).
246
+ - `initial={false}` skips initial enter animation.
247
+ - Hydration path is designed to avoid flicker.
378
248
 
379
- <span>{$total}</span>
380
- ```
249
+ ## Verification snapshot
381
250
 
382
- - Reference: Motion useTransform docs [motion.dev](https://motion.dev/docs/react-use-transform?platform=react).
251
+ Validated against current source and test suite (local run):
383
252
 
384
- ## Access the underlying element (bind:ref)
253
+ - Unit/component tests: `259 passed`
254
+ - E2E tests: `78 passed`, `1 skipped`
385
255
 
386
- You can bind a ref to access the underlying DOM element rendered by a motion component:
256
+ ## Known gaps vs Framer Motion
387
257
 
388
- ```svelte
389
- <script lang="ts">
390
- import { motion } from '$lib'
391
- let el: HTMLDivElement | null = null
392
- </script>
258
+ - No shared layout API (`layoutId`, `LayoutGroup`).
259
+ - No pan gesture API (`whilePan`, `onPan*`).
260
+ - `whileInView` does not yet expose Framer-style viewport options.
261
+ - `MotionConfig` currently only provides `transition` defaults.
262
+ - `reducedMotion`, `features`, and `transformPagePoint` are not implemented.
393
263
 
394
- <motion.div bind:ref={el} animate={{ scale: 1.1 }} />
264
+ ## External dependencies
395
265
 
396
- {#if el}
397
- <!-- use el for measurements, focus, etc. -->
398
- {/if}
399
- ```
266
+ - `motion`
267
+ - `motion-dom`
400
268
 
401
269
  ## License
402
270
 
@@ -137,6 +137,7 @@
137
137
 
138
138
  // Use the provided key for presence tracking
139
139
  // When not inside AnimatePresence, use a stable identifier based on component instance
140
+ // trunk-ignore(eslint/no-useless-assignment): false positive — presenceKey is used throughout the component
140
141
  const presenceKey = keyProp ?? `motion-${++keyCounter}`
141
142
 
142
143
  // Track previous key for key-change detection (simulates React's key-based remounting)
package/dist/index.d.ts CHANGED
@@ -2,7 +2,9 @@ import AnimatePresence from './components/AnimatePresence.svelte';
2
2
  import MotionConfig from './components/MotionConfig.svelte';
3
3
  import type { MotionComponents } from './html/index';
4
4
  export declare const motion: MotionComponents;
5
- export { animate, hover } from 'motion';
5
+ export { animate, delay, hover, inView, press, resize, scroll, stagger, transform } from 'motion';
6
+ export { anticipate, backIn, backInOut, backOut, circIn, circInOut, circOut, cubicBezier, easeIn, easeInOut, easeOut } from 'motion';
7
+ export { clamp, distance, distance2D, interpolate, mix, pipe, progress, wrap } from 'motion';
6
8
  export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition, MotionAnimate, MotionInitial, MotionOnDirectionLock, MotionOnDragTransitionEnd, MotionOnInViewEnd, MotionOnInViewStart, MotionTransition, MotionWhileDrag, MotionWhileFocus, MotionWhileHover, MotionWhileInView, MotionWhileTap, Variants } from './types';
7
9
  export { useAnimationFrame } from './utils/animationFrame';
8
10
  export { createDragControls } from './utils/dragControls';
package/dist/index.js CHANGED
@@ -3,8 +3,12 @@ import MotionConfig from './components/MotionConfig.svelte';
3
3
  import * as html from './html/index';
4
4
  // Create the motion object with all components
5
5
  export const motion = Object.fromEntries(Object.entries(html).map(([key, component]) => [key.toLowerCase(), component]));
6
- // Export all types
7
- export { animate, hover } from 'motion';
6
+ // Re-export core animation functions from motion
7
+ export { animate, delay, hover, inView, press, resize, scroll, stagger, transform } from 'motion';
8
+ // Re-export easing functions
9
+ export { anticipate, backIn, backInOut, backOut, circIn, circInOut, circOut, cubicBezier, easeIn, easeInOut, easeOut } from 'motion';
10
+ // Re-export utility functions
11
+ export { clamp, distance, distance2D, interpolate, mix, pipe, progress, wrap } from 'motion';
8
12
  export { useAnimationFrame } from './utils/animationFrame';
9
13
  export { createDragControls } from './utils/dragControls';
10
14
  export { useSpring } from './utils/spring';
@@ -1,5 +1,31 @@
1
1
  import type { DragControls } from '../types';
2
2
  /**
3
- * Create imperative controls to start/cancel/stop a drag on a target element.
3
+ * Creates imperative drag controls for programmatically starting and stopping drags.
4
+ *
5
+ * Use this factory to create a controls object that can trigger a drag from external
6
+ * events (e.g., a button press or keyboard shortcut). Pass the controls to a motion
7
+ * element via the `dragControls` prop, then call `controls.start(event)` to initiate
8
+ * the drag as if the user had pressed down on the element.
9
+ *
10
+ * @returns {DragControls} An object with `start`, `cancel`, `stop`, and `subscribe` methods.
11
+ * @see https://motion.dev/docs/react-motion-component#dragging
12
+ *
13
+ * @example
14
+ * ```svelte
15
+ * <script>
16
+ * import { motion, createDragControls } from '@humanspeak/svelte-motion'
17
+ *
18
+ * const controls = createDragControls()
19
+ *
20
+ * function startDrag(event) {
21
+ * controls.start(event, { snapToCursor: true })
22
+ * }
23
+ * </script>
24
+ *
25
+ * <button onpointerdown={startDrag}>Drag handle</button>
26
+ * <motion.div drag="x" dragControls={controls}>
27
+ * Draggable content
28
+ * </motion.div>
29
+ * ```
4
30
  */
5
- export declare function createDragControls(): DragControls;
31
+ export declare const createDragControls: () => DragControls;
@@ -1,7 +1,33 @@
1
1
  /**
2
- * Create imperative controls to start/cancel/stop a drag on a target element.
2
+ * Creates imperative drag controls for programmatically starting and stopping drags.
3
+ *
4
+ * Use this factory to create a controls object that can trigger a drag from external
5
+ * events (e.g., a button press or keyboard shortcut). Pass the controls to a motion
6
+ * element via the `dragControls` prop, then call `controls.start(event)` to initiate
7
+ * the drag as if the user had pressed down on the element.
8
+ *
9
+ * @returns {DragControls} An object with `start`, `cancel`, `stop`, and `subscribe` methods.
10
+ * @see https://motion.dev/docs/react-motion-component#dragging
11
+ *
12
+ * @example
13
+ * ```svelte
14
+ * <script>
15
+ * import { motion, createDragControls } from '@humanspeak/svelte-motion'
16
+ *
17
+ * const controls = createDragControls()
18
+ *
19
+ * function startDrag(event) {
20
+ * controls.start(event, { snapToCursor: true })
21
+ * }
22
+ * </script>
23
+ *
24
+ * <button onpointerdown={startDrag}>Drag handle</button>
25
+ * <motion.div drag="x" dragControls={controls}>
26
+ * Draggable content
27
+ * </motion.div>
28
+ * ```
3
29
  */
4
- export function createDragControls() {
30
+ export const createDragControls = () => {
5
31
  let target = null;
6
32
  let starter = null;
7
33
  let cancelInertia = null;
@@ -28,4 +54,4 @@ export function createDragControls() {
28
54
  cancelInertia = c ?? null;
29
55
  };
30
56
  return controls;
31
- }
57
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-motion",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
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,14 +53,14 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "motion": "^12.33.0",
57
- "motion-dom": "^12.33.0"
56
+ "motion": "^12.33.2",
57
+ "motion-dom": "^12.33.2"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@changesets/cli": "^2.29.8",
61
61
  "@eslint/compat": "^2.0.2",
62
- "@eslint/js": "^9.39.2",
63
- "@playwright/test": "^1.58.1",
62
+ "@eslint/js": "^10.0.1",
63
+ "@playwright/test": "^1.58.2",
64
64
  "@sveltejs/adapter-auto": "^7.0.0",
65
65
  "@sveltejs/kit": "^2.50.2",
66
66
  "@sveltejs/package": "^2.5.7",
@@ -72,14 +72,14 @@
72
72
  "@tailwindcss/typography": "^0.5.19",
73
73
  "@testing-library/jest-dom": "^6.9.1",
74
74
  "@testing-library/svelte": "^5.3.1",
75
- "@types/node": "^25.2.1",
75
+ "@types/node": "^25.2.2",
76
76
  "@vitest/coverage-v8": "^4.0.18",
77
77
  "concurrently": "^9.2.1",
78
78
  "eslint": "^9.39.2",
79
79
  "eslint-config-prettier": "10.1.8",
80
80
  "eslint-plugin-import": "2.32.0",
81
81
  "eslint-plugin-svelte": "3.14.0",
82
- "eslint-plugin-unused-imports": "4.3.0",
82
+ "eslint-plugin-unused-imports": "4.4.1",
83
83
  "esm-env": "^1.2.2",
84
84
  "globals": "^17.3.0",
85
85
  "html-tags": "^5.1.0",
@@ -93,7 +93,7 @@
93
93
  "prettier-plugin-tailwindcss": "^0.7.2",
94
94
  "publint": "^0.3.17",
95
95
  "runed": "0.37.1",
96
- "svelte": "^5.49.2",
96
+ "svelte": "^5.50.0",
97
97
  "svelte-check": "^4.3.6",
98
98
  "svg-tags": "^1.0.0",
99
99
  "tailwind-merge": "^3.4.0",
@@ -104,7 +104,7 @@
104
104
  "typescript": "^5.9.3",
105
105
  "typescript-eslint": "^8.54.0",
106
106
  "vite": "^7.3.1",
107
- "vite-tsconfig-paths": "^6.0.5",
107
+ "vite-tsconfig-paths": "^6.1.0",
108
108
  "vitest": "^4.0.18"
109
109
  },
110
110
  "peerDependencies": {
@@ -135,7 +135,7 @@
135
135
  "cs:publish": "pnpm run build && changeset publish",
136
136
  "cs:version": "changeset version && pnpm i --lockfile-only",
137
137
  "dev": "vite dev",
138
- "dev:all": "concurrently -k -n pkg,docs,sitemap -c green,cyan,magenta \"pnpm -w -r --filter @humanspeak/svelte-motion run dev\" \"pnpm --filter docs run dev\" \"pnpm --filter docs run sitemap:watch\"",
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\"",
139
139
  "dev:pkg": "svelte-kit sync && svelte-package --watch",
140
140
  "format": "prettier --write .",
141
141
  "generate": "tsx scripts/generate-html.ts",