@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 +150 -282
- package/dist/html/_MotionContainer.svelte +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +6 -2
- package/dist/utils/dragControls.d.ts +28 -2
- package/dist/utils/dragControls.js +29 -3
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -12,62 +12,97 @@
|
|
|
12
12
|
[](https://www.npmjs.com/package/@humanspeak/svelte-motion)
|
|
13
13
|
[](https://github.com/humanspeak/svelte-motion/graphs/commit-activity)
|
|
14
14
|
|
|
15
|
-
|
|
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
|
-
|
|
17
|
+
For the latest documentation and examples, visit [motion.svelte.page](https://motion.svelte.page).
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## Install
|
|
20
20
|
|
|
21
|
-
|
|
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
|
-
|
|
35
|
+
## API parity snapshot (current)
|
|
24
36
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
## Supported elements
|
|
32
53
|
|
|
33
|
-
|
|
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
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
###
|
|
74
|
+
### `MotionConfig`
|
|
43
75
|
|
|
44
|
-
|
|
76
|
+
`MotionConfig` currently supports default `transition` values for descendants.
|
|
45
77
|
|
|
46
78
|
```svelte
|
|
47
|
-
<
|
|
48
|
-
|
|
79
|
+
<script lang="ts">
|
|
80
|
+
import { MotionConfig, motion } from '@humanspeak/svelte-motion'
|
|
81
|
+
</script>
|
|
49
82
|
|
|
50
|
-
|
|
51
|
-
|
|
83
|
+
<MotionConfig transition={{ duration: 0.4 }}>
|
|
84
|
+
<motion.div animate={{ scale: 1.05 }} />
|
|
85
|
+
</MotionConfig>
|
|
86
|
+
```
|
|
52
87
|
|
|
53
|
-
###
|
|
88
|
+
### `AnimatePresence`
|
|
54
89
|
|
|
55
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
- Direct children of `AnimatePresence` require `key`.
|
|
116
|
+
- Exit transition precedence: base `{ duration: 0.35 }` < merged `transition` < `exit.transition`.
|
|
123
117
|
|
|
124
|
-
|
|
118
|
+
## Interaction props
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
### `whileHover`
|
|
127
121
|
|
|
128
122
|
```svelte
|
|
129
|
-
<motion.
|
|
123
|
+
<motion.button whileHover={{ scale: 1.05, transition: { duration: 0.12 } }} />
|
|
130
124
|
```
|
|
131
125
|
|
|
132
|
-
-
|
|
126
|
+
- Uses true-hover gating (`(hover: hover)` and `(pointer: fine)`).
|
|
127
|
+
- Supports `onHoverStart` and `onHoverEnd`.
|
|
133
128
|
|
|
134
|
-
|
|
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
|
-
-
|
|
151
|
-
-
|
|
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
|
-
###
|
|
138
|
+
### `whileFocus`
|
|
158
139
|
|
|
159
140
|
```svelte
|
|
160
141
|
<motion.button whileFocus={{ scale: 1.05, outline: '2px solid blue' }} />
|
|
161
142
|
```
|
|
162
143
|
|
|
163
|
-
-
|
|
164
|
-
- Callbacks: `onFocusStart`, `onFocusEnd` are supported.
|
|
165
|
-
- Perfect for keyboard navigation and accessibility enhancements.
|
|
144
|
+
- Supports `onFocusStart` and `onFocusEnd`.
|
|
166
145
|
|
|
167
|
-
###
|
|
146
|
+
### `whileInView`
|
|
168
147
|
|
|
169
148
|
```svelte
|
|
170
149
|
<motion.div
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
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
|
-
|
|
157
|
+
- Uses `IntersectionObserver`.
|
|
158
|
+
- Current implementation uses a fixed threshold behavior (no Framer-style `viewport` options yet).
|
|
181
159
|
|
|
182
|
-
|
|
160
|
+
## Drag
|
|
183
161
|
|
|
184
|
-
|
|
162
|
+
Supported drag props:
|
|
185
163
|
|
|
186
|
-
|
|
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 {
|
|
191
|
-
|
|
192
|
-
let isOpen = $state(false)
|
|
174
|
+
import { createDragControls, motion } from '@humanspeak/svelte-motion'
|
|
193
175
|
|
|
194
|
-
const
|
|
195
|
-
open: { opacity: 1, scale: 1 },
|
|
196
|
-
closed: { opacity: 0, scale: 0.8 }
|
|
197
|
-
}
|
|
176
|
+
const controls = createDragControls()
|
|
198
177
|
</script>
|
|
199
178
|
|
|
200
|
-
<
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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 {
|
|
194
|
+
import { motion, type Variants } from '@humanspeak/svelte-motion'
|
|
300
195
|
|
|
301
|
-
let
|
|
196
|
+
let open = $state(false)
|
|
302
197
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
198
|
+
const parent: Variants = {
|
|
199
|
+
open: { opacity: 1 },
|
|
200
|
+
closed: { opacity: 0 }
|
|
201
|
+
}
|
|
306
202
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
<
|
|
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
|
-
-
|
|
215
|
+
- String variant keys are resolved from `variants`.
|
|
216
|
+
- Variant state inherits through context.
|
|
318
217
|
|
|
319
|
-
|
|
218
|
+
## Layout animation
|
|
320
219
|
|
|
321
|
-
|
|
220
|
+
Single-element FLIP layout animation:
|
|
322
221
|
|
|
323
222
|
```svelte
|
|
324
|
-
<
|
|
325
|
-
|
|
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
|
-
-
|
|
345
|
-
-
|
|
346
|
-
-
|
|
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
|
-
|
|
231
|
+
## Utilities
|
|
356
232
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
233
|
+
- `useAnimationFrame`
|
|
234
|
+
- `useSpring`
|
|
235
|
+
- `useTime`
|
|
236
|
+
- `useTransform`
|
|
237
|
+
- `styleString`
|
|
238
|
+
- `stringifyStyleObject` (deprecated)
|
|
239
|
+
- `createDragControls`
|
|
364
240
|
|
|
365
|
-
|
|
366
|
-
```
|
|
241
|
+
The package also re-exports core helpers from `motion` (for example `animate`, `stagger`, `transform`, easings, and utility functions).
|
|
367
242
|
|
|
368
|
-
|
|
243
|
+
## SSR behavior
|
|
369
244
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
380
|
-
```
|
|
249
|
+
## Verification snapshot
|
|
381
250
|
|
|
382
|
-
|
|
251
|
+
Validated against current source and test suite (local run):
|
|
383
252
|
|
|
384
|
-
|
|
253
|
+
- Unit/component tests: `259 passed`
|
|
254
|
+
- E2E tests: `78 passed`, `1 skipped`
|
|
385
255
|
|
|
386
|
-
|
|
256
|
+
## Known gaps vs Framer Motion
|
|
387
257
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
264
|
+
## External dependencies
|
|
395
265
|
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
|
31
|
+
export declare const createDragControls: () => DragControls;
|
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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.
|
|
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.
|
|
57
|
-
"motion-dom": "^12.33.
|
|
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": "^
|
|
63
|
-
"@playwright/test": "^1.58.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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",
|