@humanspeak/svelte-motion 0.1.24 → 0.1.25
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 +1 -1
- package/dist/html/_MotionContainer.svelte +54 -0
- package/dist/types.d.ts +2 -0
- package/dist/utils/layoutId.d.ts +24 -0
- package/dist/utils/layoutId.js +23 -0
- package/package.json +1 -1
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`) |
|
|
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 |
|
|
@@ -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'. */
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
export declare function getLayoutIdRegistry(): LayoutIdRegistry;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
export function getLayoutIdRegistry() {
|
|
22
|
+
return layoutIdRegistry;
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-motion",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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",
|