@humanspeak/svelte-motion 0.5.2 → 0.5.4
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/dist/components/LazyMotion.svelte +70 -0
- package/dist/components/LazyMotion.svelte.d.ts +16 -0
- package/dist/components/lazyMotion.context.d.ts +25 -0
- package/dist/components/lazyMotion.context.js +19 -0
- package/dist/components/projection.context.d.ts +22 -0
- package/dist/components/projection.context.js +56 -0
- package/dist/features/domAnimation.d.ts +6 -0
- package/dist/features/domAnimation.js +8 -0
- package/dist/features/domMax.d.ts +5 -0
- package/dist/features/domMax.js +9 -0
- package/dist/features/domMin.d.ts +5 -0
- package/dist/features/domMin.js +6 -0
- package/dist/features/index.d.ts +39 -0
- package/dist/features/index.js +18 -0
- package/dist/html/_MotionContainer.svelte +202 -31
- package/dist/index.d.ts +8 -2
- package/dist/index.js +6 -1
- package/dist/m.d.ts +9 -0
- package/dist/m.js +9 -0
- package/dist/types.d.ts +59 -0
- package/dist/utils/drag.d.ts +42 -11
- package/dist/utils/drag.js +103 -12
- package/dist/utils/layout.d.ts +26 -2
- package/dist/utils/layout.js +13 -2
- package/dist/utils/projection.d.ts +287 -0
- package/dist/utils/projection.js +392 -0
- package/dist/utils/style.d.ts +23 -0
- package/dist/utils/style.js +27 -0
- package/dist/utils/variants.d.ts +26 -0
- package/dist/utils/variants.js +42 -0
- package/package.json +2 -2
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
<script lang="ts">
|
|
7
7
|
import { getMotionConfig } from '../components/motionConfig.context'
|
|
8
|
+
import { getLazyMotionContext } from '../components/lazyMotion.context'
|
|
9
|
+
import { domMax } from '../features/domMax'
|
|
8
10
|
import {
|
|
9
11
|
filterReducedMotionKeyframes,
|
|
10
12
|
useReducedMotionConfig
|
|
@@ -39,10 +41,11 @@
|
|
|
39
41
|
computeFlipTransforms,
|
|
40
42
|
runFlipAnimation,
|
|
41
43
|
setCompositorHints,
|
|
42
|
-
observeLayoutChanges
|
|
44
|
+
observeLayoutChanges,
|
|
45
|
+
type RectLike
|
|
43
46
|
} from '../utils/layout'
|
|
44
47
|
import type { SvelteHTMLElements } from 'svelte/elements'
|
|
45
|
-
import { mergeInlineStyles } from '../utils/style'
|
|
48
|
+
import { mergeInlineStyles, extractTransform } from '../utils/style'
|
|
46
49
|
import { isNativelyFocusable } from '../utils/a11y'
|
|
47
50
|
import {
|
|
48
51
|
getAnimatePresenceContext,
|
|
@@ -53,7 +56,15 @@
|
|
|
53
56
|
import { getInitialKeyframes } from '../utils/initial'
|
|
54
57
|
import { attachDrag } from '../utils/drag'
|
|
55
58
|
import { attachPan, type AttachPanCleanup } from '../utils/pan'
|
|
56
|
-
import {
|
|
59
|
+
import { ProjectionNode } from '../utils/projection'
|
|
60
|
+
import { getProjectionParent, setProjectionParent } from '../components/projection.context'
|
|
61
|
+
import {
|
|
62
|
+
resolveInitial,
|
|
63
|
+
resolveAnimate,
|
|
64
|
+
resolveExit,
|
|
65
|
+
resolveWhile,
|
|
66
|
+
resolveRestingValues
|
|
67
|
+
} from '../utils/variants'
|
|
57
68
|
import {
|
|
58
69
|
setVariantContext,
|
|
59
70
|
getVariantContext,
|
|
@@ -134,12 +145,24 @@
|
|
|
134
145
|
layout: layoutProp,
|
|
135
146
|
layoutId: layoutIdProp,
|
|
136
147
|
layoutScroll: layoutScrollProp,
|
|
148
|
+
onProjectionUpdate: onProjectionUpdateProp,
|
|
137
149
|
ref: element = $bindable(null),
|
|
138
150
|
...rest
|
|
139
151
|
}: Props = $props()
|
|
140
152
|
let isLoaded = $state<'mounting' | 'initial' | 'ready' | 'animated'>('mounting')
|
|
153
|
+
// True once the enter/animate animation has COMPLETED. Until then the
|
|
154
|
+
// WAAPI animation owns the transform; flipping the inline baseline to
|
|
155
|
+
// the target mid-run causes a one-frame snap (the target shows through
|
|
156
|
+
// for the frame the inline changes). We therefore only apply the target
|
|
157
|
+
// as the inline style once settled — see the style derivation. (#377)
|
|
158
|
+
let enterAnimationSettled = $state(false)
|
|
141
159
|
let dataPath = $state<number>(-1)
|
|
142
160
|
const motionConfig = $derived(getMotionConfig())
|
|
161
|
+
const lazyMotion = getLazyMotionContext()
|
|
162
|
+
const activeFeatures = $derived(lazyMotion?.getFeatures() ?? domMax)
|
|
163
|
+
const hasGestureFeatures = $derived(!!activeFeatures.gestures)
|
|
164
|
+
const hasDragFeatures = $derived(!!activeFeatures.drag)
|
|
165
|
+
const hasLayoutFeatures = $derived(!!activeFeatures.layout)
|
|
143
166
|
const reducedMotionState = useReducedMotionConfig()
|
|
144
167
|
// `.current` is $state-backed inside reducedMotionState; tracking it via
|
|
145
168
|
// $derived makes `reducedMotion` re-evaluate whenever the OS preference
|
|
@@ -191,6 +214,46 @@
|
|
|
191
214
|
return refs.filter((el): el is HTMLElement => Boolean(el))
|
|
192
215
|
}
|
|
193
216
|
|
|
217
|
+
// Projection tree wiring (#379). Capture the parent node BEFORE
|
|
218
|
+
// publishing our own — same shadowing trap as layoutScroll above.
|
|
219
|
+
// The node measures through `resolveLayoutScrollAncestors` so its
|
|
220
|
+
// boxes share the FLIP coordinate space, and zeros ancestor
|
|
221
|
+
// transforms during measure so nested layout-animated parents don't
|
|
222
|
+
// corrupt a child's delta.
|
|
223
|
+
// The user-authored transform, sourced from the `style` prop rather
|
|
224
|
+
// than the live inline transform — the latter already carries any
|
|
225
|
+
// transform-type `initial`/`animate` keyframe by the time the node
|
|
226
|
+
// measures, which would be mistaken for the user's base.
|
|
227
|
+
const userBaseTransform = $derived(extractTransform(styleProp))
|
|
228
|
+
|
|
229
|
+
const projectionParent = getProjectionParent()
|
|
230
|
+
const projection = new ProjectionNode({
|
|
231
|
+
parent: projectionParent,
|
|
232
|
+
getScrollContainers: resolveLayoutScrollAncestors,
|
|
233
|
+
getBaseTransform: () => userBaseTransform
|
|
234
|
+
})
|
|
235
|
+
setProjectionParent(projection)
|
|
236
|
+
|
|
237
|
+
// Convert a projection `Box` (ancestor chain reset to base, self
|
|
238
|
+
// transform stripped, scroll containers compensated) to the
|
|
239
|
+
// `RectLike` shape `computeFlipTransforms` consumes.
|
|
240
|
+
const boxToRectLike = (box: {
|
|
241
|
+
x: { min: number; max: number }
|
|
242
|
+
y: { min: number; max: number }
|
|
243
|
+
}): RectLike => ({
|
|
244
|
+
left: box.x.min,
|
|
245
|
+
top: box.y.min,
|
|
246
|
+
width: box.x.max - box.x.min,
|
|
247
|
+
height: box.y.max - box.y.min
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Ancestor-transform-invariant layout measurement for seeding the
|
|
251
|
+
// FLIP effect's first rect.
|
|
252
|
+
const measureLayoutRect = (): RectLike | null => {
|
|
253
|
+
const box = projection.measure()
|
|
254
|
+
return box ? boxToRectLike(box) : null
|
|
255
|
+
}
|
|
256
|
+
|
|
194
257
|
// Get current presence depth (0 = direct child of AnimatePresence, undefined = not in AnimatePresence)
|
|
195
258
|
const presenceDepth = getPresenceDepth()
|
|
196
259
|
|
|
@@ -478,6 +541,17 @@
|
|
|
478
541
|
)
|
|
479
542
|
)
|
|
480
543
|
|
|
544
|
+
// Reduced-motion-filtered animate values used as the inline-style
|
|
545
|
+
// baseline so an animated value (transforms included) persists after
|
|
546
|
+
// the WAAPI animation completes (#377). Filtered exactly like
|
|
547
|
+
// `initialKeyframes` so transforms are stripped under reduced motion.
|
|
548
|
+
const animateKeyframes = $derived(
|
|
549
|
+
filterReducedMotionKeyframes(
|
|
550
|
+
resolvedAnimate as Record<string, unknown> | undefined,
|
|
551
|
+
reducedMotion
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
|
|
481
555
|
// Derived attributes to keep both branches in sync (focusability, data flags, style, class)
|
|
482
556
|
const derivedAttrs = $derived<Record<string, unknown>>({
|
|
483
557
|
...(rest as Record<string, unknown>),
|
|
@@ -486,7 +560,8 @@
|
|
|
486
560
|
// key, empty array) would otherwise add `tabindex=0` for an
|
|
487
561
|
// element that never actually receives a tap gesture — an
|
|
488
562
|
// unintended tab stop. (#349 CR feedback)
|
|
489
|
-
...(
|
|
563
|
+
...(hasGestureFeatures &&
|
|
564
|
+
isNotEmpty(resolvedWhileTap) &&
|
|
490
565
|
!isNativelyFocusable(tag, rest as Record<string, unknown>) &&
|
|
491
566
|
((rest as Record<string, unknown>)?.tabindex ??
|
|
492
567
|
(rest as Record<string, unknown>)?.tabIndex ??
|
|
@@ -516,20 +591,33 @@
|
|
|
516
591
|
initialKeyframes && 'pathLength' in initialKeyframes && isLoaded === 'mounting'
|
|
517
592
|
? `${styleProp || ''};visibility:hidden`
|
|
518
593
|
: styleProp,
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
594
|
+
// The "from" slot: apply initialKeyframes as inline styles during
|
|
595
|
+
// the mounting/initial phases (before the WAAPI animation locks
|
|
596
|
+
// its from-value and we promote to 'ready' — see the lifecycle
|
|
597
|
+
// around the enter rAF). mergeInlineStyles prefers this slot when
|
|
598
|
+
// non-empty, so it wins over the animate slot below in these phases.
|
|
523
599
|
isLoaded === 'mounting' || isLoaded === 'initial'
|
|
524
600
|
? (initialKeyframes as unknown as Record<string, unknown>)
|
|
525
601
|
: undefined,
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
602
|
+
// The "target" slot. Only AFTER the enter animation completes does
|
|
603
|
+
// the target become the inline baseline, so the element holds it
|
|
604
|
+
// once WAAPI surrenders the property (default fill:'none' would
|
|
605
|
+
// otherwise leave transform:none). It must NOT be applied during
|
|
606
|
+
// the run: flipping the inline value to the target mid-animation
|
|
607
|
+
// shows the target for the one frame the inline changes (a visible
|
|
608
|
+
// snap), since WAAPI's composite doesn't override that exact frame.
|
|
609
|
+
// While the animation runs we keep the original behavior — initial
|
|
610
|
+
// keyframes own the inline (via the slot above), or, with no
|
|
611
|
+
// initial, the animate values seed the inline as the from. Resting
|
|
612
|
+
// values collapse keyframe arrays to their last element
|
|
613
|
+
// (animate={{x:[0,100,50]}} rests at 50). (#377)
|
|
614
|
+
enterAnimationSettled
|
|
615
|
+
? (resolveRestingValues(
|
|
616
|
+
animateKeyframes as DOMKeyframesDefinition | undefined
|
|
617
|
+
) as unknown as Record<string, unknown>)
|
|
618
|
+
: isNotEmpty(initialKeyframes)
|
|
619
|
+
? undefined
|
|
620
|
+
: (animateKeyframes as unknown as Record<string, unknown>)
|
|
533
621
|
),
|
|
534
622
|
class: classProp
|
|
535
623
|
})
|
|
@@ -546,7 +634,7 @@
|
|
|
546
634
|
// after any non-zero duration settle animation.
|
|
547
635
|
let teardownDrag: (() => void) | null = null
|
|
548
636
|
$effect(() => {
|
|
549
|
-
if (!(element && isLoaded === 'ready')) return
|
|
637
|
+
if (!(element && isLoaded === 'ready' && hasDragFeatures)) return
|
|
550
638
|
// Only attach if drag enabled
|
|
551
639
|
if (!dragProp) return
|
|
552
640
|
// Clean up previous
|
|
@@ -707,7 +795,7 @@
|
|
|
707
795
|
isLoaded
|
|
708
796
|
})
|
|
709
797
|
}
|
|
710
|
-
if (!element) return
|
|
798
|
+
if (!element || !hasGestureFeatures) return
|
|
711
799
|
// Defer attachment until the element has settled out of the enter
|
|
712
800
|
// animation phase — matches the gate every other gesture effect
|
|
713
801
|
// in this file uses (drag, whileTap, whileHover, whileFocus,
|
|
@@ -807,12 +895,20 @@
|
|
|
807
895
|
transitionAnimate
|
|
808
896
|
})
|
|
809
897
|
|
|
898
|
+
// A fresh run owns the transform again until it completes.
|
|
899
|
+
enterAnimationSettled = false
|
|
810
900
|
animateWithLifecycle(
|
|
811
901
|
element,
|
|
812
902
|
payload as unknown as DOMKeyframesDefinition,
|
|
813
903
|
transitionAnimate as unknown as AnimationOptions,
|
|
814
904
|
(def) => onAnimationStartProp?.(def as unknown as DOMKeyframesDefinition | undefined),
|
|
815
|
-
(def) =>
|
|
905
|
+
(def) => {
|
|
906
|
+
// Now the target is the resting state — promote it to the
|
|
907
|
+
// inline baseline so it persists after WAAPI surrenders the
|
|
908
|
+
// property (default fill:'none'). (#377)
|
|
909
|
+
enterAnimationSettled = true
|
|
910
|
+
onAnimationCompleteProp?.(def as unknown as DOMKeyframesDefinition | undefined)
|
|
911
|
+
}
|
|
816
912
|
)
|
|
817
913
|
}
|
|
818
914
|
|
|
@@ -930,26 +1026,65 @@
|
|
|
930
1026
|
: undefined
|
|
931
1027
|
)
|
|
932
1028
|
|
|
1029
|
+
// Projection node lifecycle + the `onProjectionUpdate` listener.
|
|
1030
|
+
// Mount once the element binds; seed the baseline layout; unmount on
|
|
1031
|
+
// cleanup. Depends ONLY on `element` — the `onProjectionUpdate`
|
|
1032
|
+
// subscription lives in its own effect below so a change to the
|
|
1033
|
+
// callback's identity re-subscribes WITHOUT tearing the node down
|
|
1034
|
+
// (an unmount would clear latestLayout/children and re-seed the
|
|
1035
|
+
// first commit instead of emitting a real delta).
|
|
1036
|
+
$effect(() => {
|
|
1037
|
+
if (!element) return
|
|
1038
|
+
projection.mount(element)
|
|
1039
|
+
projection.measure() // seed latestLayout so the first commit can diff
|
|
1040
|
+
return () => {
|
|
1041
|
+
projection.unmount()
|
|
1042
|
+
}
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1045
|
+
// Subscribe the consumer's `onProjectionUpdate` callback. Separate
|
|
1046
|
+
// from the mount effect so re-subscribing on a callback-identity
|
|
1047
|
+
// change never unmounts the node.
|
|
1048
|
+
$effect(() => {
|
|
1049
|
+
if (!(element && onProjectionUpdateProp)) return
|
|
1050
|
+
const off = projection.addEventListener('didUpdate', (data) => onProjectionUpdateProp(data))
|
|
1051
|
+
return () => {
|
|
1052
|
+
off()
|
|
1053
|
+
}
|
|
1054
|
+
})
|
|
1055
|
+
|
|
933
1056
|
// Minimal layout animation using FLIP when `layout` is enabled.
|
|
934
1057
|
// When layout === 'position' we only translate.
|
|
935
1058
|
// When layout === true we also scale to smoothly interpolate size changes.
|
|
936
|
-
let lastRect:
|
|
1059
|
+
let lastRect: RectLike | null = null
|
|
937
1060
|
$effect(() => {
|
|
938
|
-
if (!(element && layoutProp && isLoaded === 'ready')) return
|
|
939
|
-
|
|
940
|
-
// Initialize last rect on first ready frame
|
|
941
|
-
|
|
1061
|
+
if (!(element && layoutProp && isLoaded === 'ready' && hasLayoutFeatures)) return
|
|
1062
|
+
|
|
1063
|
+
// Initialize last rect on first ready frame. We measure through the
|
|
1064
|
+
// projection node rather than `measureRect` directly so the rect is
|
|
1065
|
+
// ancestor-transform-invariant: the node resets the whole ancestor
|
|
1066
|
+
// chain to its mount-time base while reading, which strips any
|
|
1067
|
+
// motion-applied (FLIP/drag) transform up the tree. Without this a
|
|
1068
|
+
// child re-measures and FLIPs whenever an ancestor's transform
|
|
1069
|
+
// animates, even though the child's own layout never moved. (#379)
|
|
1070
|
+
lastRect = measureLayoutRect()
|
|
942
1071
|
// Hint compositor for smoother FLIP transforms
|
|
943
1072
|
setCompositorHints(element!, true)
|
|
944
1073
|
|
|
945
1074
|
let rafId: number | null = null
|
|
946
1075
|
const runFlip = () => {
|
|
947
|
-
|
|
1076
|
+
// commitLayoutChange does the ancestor-stripped measure, fans
|
|
1077
|
+
// the delta out to `onProjectionUpdate` listeners, AND returns
|
|
1078
|
+
// the freshly-measured box — so the FLIP reuses that single
|
|
1079
|
+
// measurement as `next` instead of walking + transform-resetting
|
|
1080
|
+
// the ancestor chain a second time per frame. (#379)
|
|
1081
|
+
const nextBox = projection.commitLayoutChange()
|
|
1082
|
+
if (!nextBox) return
|
|
1083
|
+
const next = boxToRectLike(nextBox)
|
|
948
1084
|
if (!lastRect) {
|
|
949
|
-
lastRect =
|
|
1085
|
+
lastRect = next
|
|
950
1086
|
return
|
|
951
1087
|
}
|
|
952
|
-
const next = measureRect(element!, scrollContainers)
|
|
953
1088
|
const transforms = computeFlipTransforms(lastRect, next, layoutProp ?? false)
|
|
954
1089
|
runFlipAnimation(element!, transforms, (mergedTransition ?? {}) as AnimationOptions)
|
|
955
1090
|
lastRect = next
|
|
@@ -978,7 +1113,16 @@
|
|
|
978
1113
|
// Shared layout animation via layoutId.
|
|
979
1114
|
// On mount, consume the previous snapshot and FLIP from its position.
|
|
980
1115
|
$effect(() => {
|
|
981
|
-
if (
|
|
1116
|
+
if (
|
|
1117
|
+
!(
|
|
1118
|
+
element &&
|
|
1119
|
+
scopedLayoutId &&
|
|
1120
|
+
layoutIdRegistry &&
|
|
1121
|
+
isLoaded === 'ready' &&
|
|
1122
|
+
hasLayoutFeatures
|
|
1123
|
+
)
|
|
1124
|
+
)
|
|
1125
|
+
return
|
|
982
1126
|
|
|
983
1127
|
const prev = layoutIdRegistry.consume(scopedLayoutId)
|
|
984
1128
|
if (!prev) return // First appearance, no animation needed
|
|
@@ -996,7 +1140,10 @@
|
|
|
996
1140
|
|
|
997
1141
|
// whileTap handling via motion-dom's press()
|
|
998
1142
|
$effect(() => {
|
|
999
|
-
if (
|
|
1143
|
+
if (
|
|
1144
|
+
!(element && isLoaded === 'ready' && hasGestureFeatures && isNotEmpty(resolvedWhileTap))
|
|
1145
|
+
)
|
|
1146
|
+
return
|
|
1000
1147
|
return attachWhileTap(
|
|
1001
1148
|
element!,
|
|
1002
1149
|
(resolvedWhileTap ?? {}) as Record<string, unknown>,
|
|
@@ -1016,7 +1163,15 @@
|
|
|
1016
1163
|
|
|
1017
1164
|
// whileHover handling, gated to true-hover devices to avoid sticky states on touch
|
|
1018
1165
|
$effect(() => {
|
|
1019
|
-
if (
|
|
1166
|
+
if (
|
|
1167
|
+
!(
|
|
1168
|
+
element &&
|
|
1169
|
+
isLoaded === 'ready' &&
|
|
1170
|
+
hasGestureFeatures &&
|
|
1171
|
+
isNotEmpty(resolvedWhileHover)
|
|
1172
|
+
)
|
|
1173
|
+
)
|
|
1174
|
+
return
|
|
1020
1175
|
return attachWhileHover(
|
|
1021
1176
|
element!,
|
|
1022
1177
|
(resolvedWhileHover ?? {}) as Record<string, unknown>,
|
|
@@ -1031,7 +1186,15 @@
|
|
|
1031
1186
|
|
|
1032
1187
|
// whileFocus handling for keyboard focus interactions
|
|
1033
1188
|
$effect(() => {
|
|
1034
|
-
if (
|
|
1189
|
+
if (
|
|
1190
|
+
!(
|
|
1191
|
+
element &&
|
|
1192
|
+
isLoaded === 'ready' &&
|
|
1193
|
+
hasGestureFeatures &&
|
|
1194
|
+
isNotEmpty(resolvedWhileFocus)
|
|
1195
|
+
)
|
|
1196
|
+
)
|
|
1197
|
+
return
|
|
1035
1198
|
return attachWhileFocus(
|
|
1036
1199
|
element!,
|
|
1037
1200
|
(resolvedWhileFocus ?? {}) as Record<string, unknown>,
|
|
@@ -1046,7 +1209,15 @@
|
|
|
1046
1209
|
|
|
1047
1210
|
// whileInView handling for viewport intersection
|
|
1048
1211
|
$effect(() => {
|
|
1049
|
-
if (
|
|
1212
|
+
if (
|
|
1213
|
+
!(
|
|
1214
|
+
element &&
|
|
1215
|
+
isLoaded === 'ready' &&
|
|
1216
|
+
hasGestureFeatures &&
|
|
1217
|
+
isNotEmpty(resolvedWhileInView)
|
|
1218
|
+
)
|
|
1219
|
+
)
|
|
1220
|
+
return
|
|
1050
1221
|
return attachWhileInView(
|
|
1051
1222
|
element!,
|
|
1052
1223
|
(resolvedWhileInView ?? {}) as Record<string, unknown>,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import AnimatePresence from './components/AnimatePresence.svelte';
|
|
2
2
|
import LayoutGroup from './components/LayoutGroup.svelte';
|
|
3
|
+
import LazyMotion from './components/LazyMotion.svelte';
|
|
3
4
|
import MotionConfig from './components/MotionConfig.svelte';
|
|
4
5
|
import PresenceChild from './components/PresenceChild.svelte';
|
|
6
|
+
export type { FeatureBundle, LazyFeatureBundle } from './features';
|
|
7
|
+
export { domAnimation } from './features/domAnimation';
|
|
8
|
+
export { domMax } from './features/domMax';
|
|
9
|
+
export { domMin } from './features/domMin';
|
|
10
|
+
export { m } from './m';
|
|
5
11
|
export { motion } from './motion';
|
|
6
12
|
export { animate, delay, hover, inView, press, resize, scroll, stagger, transform } from 'motion';
|
|
7
13
|
export { anticipate, backIn, backInOut, backOut, circIn, circInOut, circOut, cubicBezier, easeIn, easeInOut, easeOut } from 'motion';
|
|
8
14
|
export { clamp, distance, distance2D, interpolate, mix, pipe, progress, wrap } from 'motion';
|
|
9
|
-
export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition, MotionAnimate, MotionInitial, MotionOnDirectionLock, MotionOnDragTransitionEnd, MotionOnInViewEnd, MotionOnInViewStart, MotionTransition, MotionWhileDrag, MotionWhileFocus, MotionWhileHover, MotionWhileInView, MotionWhileTap, ReducedMotionConfig, Variants } from './types';
|
|
15
|
+
export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition, MotionAnimate, MotionInitial, MotionOnDirectionLock, MotionOnDragTransitionEnd, MotionOnInViewEnd, MotionOnInViewStart, MotionOnProjectionUpdate, MotionTransition, MotionWhileDrag, MotionWhileFocus, MotionWhileHover, MotionWhileInView, MotionWhileTap, ProjectionUpdatePayload, ReducedMotionConfig, Variants } from './types';
|
|
10
16
|
export { useAnimate } from './utils/animate.svelte';
|
|
11
17
|
export type { AnimationScope } from './utils/animate.svelte';
|
|
12
18
|
export { useAnimationFrame } from './utils/animationFrame';
|
|
@@ -42,7 +48,7 @@ export { styleString } from './utils/styleObject.svelte';
|
|
|
42
48
|
export { useTime } from './utils/time.svelte';
|
|
43
49
|
export { useTransform } from './utils/transform.svelte';
|
|
44
50
|
export type { MultiTransformer, SingleTransformer, TransformOptions, TransformOutputMap, TransformSource } from './utils/transform.svelte';
|
|
45
|
-
export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
|
|
51
|
+
export { AnimatePresence, LayoutGroup, LazyMotion, MotionConfig, PresenceChild };
|
|
46
52
|
export { default as MotionA } from './html/A.svelte';
|
|
47
53
|
export { default as MotionAbbr } from './html/Abbr.svelte';
|
|
48
54
|
export { default as MotionAddress } from './html/Address.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import AnimatePresence from './components/AnimatePresence.svelte';
|
|
2
2
|
import LayoutGroup from './components/LayoutGroup.svelte';
|
|
3
|
+
import LazyMotion from './components/LazyMotion.svelte';
|
|
3
4
|
import MotionConfig from './components/MotionConfig.svelte';
|
|
4
5
|
import PresenceChild from './components/PresenceChild.svelte';
|
|
6
|
+
export { domAnimation } from './features/domAnimation';
|
|
7
|
+
export { domMax } from './features/domMax';
|
|
8
|
+
export { domMin } from './features/domMin';
|
|
9
|
+
export { m } from './m';
|
|
5
10
|
export { motion } from './motion';
|
|
6
11
|
// Re-export core animation functions from motion
|
|
7
12
|
export { animate, delay, hover, inView, press, resize, scroll, stagger, transform } from 'motion';
|
|
@@ -31,7 +36,7 @@ export { stringifyStyleObject } from './utils/styleObject';
|
|
|
31
36
|
export { styleString } from './utils/styleObject.svelte';
|
|
32
37
|
export { useTime } from './utils/time.svelte';
|
|
33
38
|
export { useTransform } from './utils/transform.svelte';
|
|
34
|
-
export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
|
|
39
|
+
export { AnimatePresence, LayoutGroup, LazyMotion, MotionConfig, PresenceChild };
|
|
35
40
|
// Named component exports — tree-shakeable alternative to the `motion` object
|
|
36
41
|
export { default as MotionA } from './html/A.svelte';
|
|
37
42
|
export { default as MotionAbbr } from './html/Abbr.svelte';
|
package/dist/m.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MotionComponents } from './html/index';
|
|
2
|
+
/**
|
|
3
|
+
* Lazy motion component namespace used with `<LazyMotion>`.
|
|
4
|
+
*
|
|
5
|
+
* The namespace mirrors the default `motion` object API (`m.div`, `m.button`,
|
|
6
|
+
* `m.svg`, etc.) while reading feature availability from the nearest
|
|
7
|
+
* LazyMotion provider.
|
|
8
|
+
*/
|
|
9
|
+
export declare const m: MotionComponents;
|
package/dist/m.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as html from './html/index';
|
|
2
|
+
/**
|
|
3
|
+
* Lazy motion component namespace used with `<LazyMotion>`.
|
|
4
|
+
*
|
|
5
|
+
* The namespace mirrors the default `motion` object API (`m.div`, `m.button`,
|
|
6
|
+
* `m.svg`, etc.) while reading feature availability from the nearest
|
|
7
|
+
* LazyMotion provider.
|
|
8
|
+
*/
|
|
9
|
+
export const m = Object.fromEntries(Object.entries(html).map(([key, component]) => [key.toLowerCase(), component]));
|
package/dist/types.d.ts
CHANGED
|
@@ -255,6 +255,59 @@ export type MotionOnPanSessionStart = ((event: PointerEvent, info: DragInfo) =>
|
|
|
255
255
|
export type MotionOnPanStart = ((event: PointerEvent, info: DragInfo) => void) | undefined;
|
|
256
256
|
export type MotionOnPan = ((event: PointerEvent, info: DragInfo) => void) | undefined;
|
|
257
257
|
export type MotionOnPanEnd = ((event: PointerEvent, info: DragInfo) => void) | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* Payload delivered to `onProjectionUpdate`. Re-declared structurally
|
|
260
|
+
* here (rather than imported from `projection.ts`) so `types.ts`
|
|
261
|
+
* stays dependency-free at the type layer. `delta.x/y.translate` is the
|
|
262
|
+
* px shift the element's layout box moved between the pre-change
|
|
263
|
+
* snapshot and the post-change measurement; `hasLayoutChanged` is false
|
|
264
|
+
* only when the delta is within a tight float epsilon (±0.01px
|
|
265
|
+
* translate), so even a sub-pixel real move is reported as a change.
|
|
266
|
+
*/
|
|
267
|
+
export type ProjectionUpdatePayload = {
|
|
268
|
+
layout: {
|
|
269
|
+
x: {
|
|
270
|
+
min: number;
|
|
271
|
+
max: number;
|
|
272
|
+
};
|
|
273
|
+
y: {
|
|
274
|
+
min: number;
|
|
275
|
+
max: number;
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
snapshot: {
|
|
279
|
+
x: {
|
|
280
|
+
min: number;
|
|
281
|
+
max: number;
|
|
282
|
+
};
|
|
283
|
+
y: {
|
|
284
|
+
min: number;
|
|
285
|
+
max: number;
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
delta: {
|
|
289
|
+
x: {
|
|
290
|
+
translate: number;
|
|
291
|
+
scale: number;
|
|
292
|
+
origin: number;
|
|
293
|
+
originPoint: number;
|
|
294
|
+
};
|
|
295
|
+
y: {
|
|
296
|
+
translate: number;
|
|
297
|
+
scale: number;
|
|
298
|
+
origin: number;
|
|
299
|
+
originPoint: number;
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
hasLayoutChanged: boolean;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Fires after each layout change to a `motion.*` element that has
|
|
306
|
+
* `layout` enabled, with the FLIP delta between the pre- and
|
|
307
|
+
* post-change layout boxes. Mirrors framer-motion's `onLayoutMeasure`
|
|
308
|
+
* surface. Wired through the element's internal `ProjectionNode`.
|
|
309
|
+
*/
|
|
310
|
+
export type MotionOnProjectionUpdate = ((data: ProjectionUpdatePayload) => void) | undefined;
|
|
258
311
|
export type DragAxis = boolean | 'x' | 'y';
|
|
259
312
|
export type DragConstraints = {
|
|
260
313
|
top?: number;
|
|
@@ -380,6 +433,12 @@ export type MotionProps = {
|
|
|
380
433
|
class?: string;
|
|
381
434
|
/** Enable FLIP layout animations; "position" limits to translation only */
|
|
382
435
|
layout?: boolean | 'position';
|
|
436
|
+
/**
|
|
437
|
+
* Fires after each `layout`-driven change with the FLIP delta from
|
|
438
|
+
* the element's internal projection node. Mirrors framer-motion's
|
|
439
|
+
* `onLayoutMeasure`. Requires `layout` to be enabled.
|
|
440
|
+
*/
|
|
441
|
+
onProjectionUpdate?: MotionOnProjectionUpdate;
|
|
383
442
|
/** Shared layout animation identifier. Elements with matching layoutId animate between positions. */
|
|
384
443
|
layoutId?: string;
|
|
385
444
|
/**
|
package/dist/utils/drag.d.ts
CHANGED
|
@@ -66,24 +66,55 @@ export declare const resolveConstraints: (el: HTMLElement | null, constraints: D
|
|
|
66
66
|
*/
|
|
67
67
|
export declare const applyElastic: (value: number, min: number, max: number, elastic: number) => number;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Cleanup handle returned by {@link attachDrag}. Invoke it to detach the
|
|
70
|
+
* gesture's pointer/resize listeners. It does NOT cancel an in-flight
|
|
71
|
+
* momentum/settle animation — matching framer-motion, whose drag teardown
|
|
72
|
+
* removes listeners only and deliberately lets motion continue across an
|
|
73
|
+
* unmount/remount (e.g. reorder reconciliation); the animation is cleaned
|
|
74
|
+
* up by the element/motion-value lifecycle.
|
|
70
75
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
76
|
+
* The handle also carries `adjustOrigin(dx, dy)`, which shifts the LIVE
|
|
77
|
+
* gesture's origin + visual offset by a layout-shift delta mid-drag — used
|
|
78
|
+
* for projection-driven cursor pinning when a layout slot moves under the
|
|
79
|
+
* dragged element (#379 / #310). It is a no-op when not currently dragging
|
|
80
|
+
* and compensates on both axes (mirroring upstream's per-axis `eachAxis`
|
|
81
|
+
* compensation).
|
|
74
82
|
*/
|
|
83
|
+
export type AttachDragCleanup = (() => void) & {
|
|
84
|
+
adjustOrigin: (dx: number, dy: number) => void;
|
|
85
|
+
};
|
|
75
86
|
/**
|
|
76
|
-
* Attach a drag gesture to an
|
|
87
|
+
* Attach a drag gesture to an element.
|
|
88
|
+
*
|
|
89
|
+
* Captures the pointer and updates x/y transforms with axis and optional
|
|
90
|
+
* direction lock, applies elastic overflow against constraints, emits
|
|
91
|
+
* lifecycle callbacks with `DragInfo`, and runs a momentum animation on
|
|
92
|
+
* release when enabled.
|
|
77
93
|
*
|
|
78
94
|
* Lifecycle:
|
|
79
95
|
* - pointerdown → capture pointer, snapshot origin, start velocity history, enter whileDrag
|
|
80
96
|
* - pointermove → compute deltas, direction lock, apply constraints + elastic, write x/y
|
|
81
97
|
* - pointerup/cancel → either run momentum decay to a target or settle/clamp instantly
|
|
82
98
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
99
|
+
* Invariant: `applied` tracks the currently applied x/y transform — it
|
|
100
|
+
* must stay in sync when writing transforms or finishing animations, or a
|
|
101
|
+
* second drag "jumps" from a stale origin (commonly a missed update after
|
|
102
|
+
* a non-zero-duration settle animation).
|
|
103
|
+
*
|
|
104
|
+
* @param el The element to make draggable.
|
|
105
|
+
* @param opts Drag options — `axis`, `constraints`, `elastic`,
|
|
106
|
+
* `momentum`, `whileDrag`, and the `onDrag*` lifecycle callbacks.
|
|
107
|
+
* @returns A callable cleanup handle ({@link AttachDragCleanup}): call it
|
|
108
|
+
* to detach the gesture's listeners (in-flight momentum is not
|
|
109
|
+
* cancelled — see the type docs), or call its `adjustOrigin(dx, dy)` to
|
|
110
|
+
* reposition the live gesture mid-drag.
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const cleanup = attachDrag(el, { axis: 'x', momentum: true })
|
|
114
|
+
* // …when a layout swap shifts the slot under the cursor mid-drag:
|
|
115
|
+
* cleanup.adjustOrigin(10, -5)
|
|
116
|
+
* // on teardown:
|
|
117
|
+
* cleanup()
|
|
118
|
+
* ```
|
|
88
119
|
*/
|
|
89
|
-
export declare const attachDrag: (el: HTMLElement, opts: AttachDragOptions) =>
|
|
120
|
+
export declare const attachDrag: (el: HTMLElement, opts: AttachDragOptions) => AttachDragCleanup;
|