@humanspeak/svelte-motion 0.4.6 → 0.4.7
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 +12 -12
- package/dist/html/_MotionContainer.svelte +31 -13
- package/dist/types.d.ts +36 -8
- package/dist/utils/variants.d.ts +82 -8
- package/dist/utils/variants.js +124 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,18 +36,18 @@ npm install @humanspeak/svelte-motion
|
|
|
36
36
|
|
|
37
37
|
Goal: Framer Motion API parity for Svelte where common React examples can be translated with minimal changes.
|
|
38
38
|
|
|
39
|
-
| Capability
|
|
40
|
-
|
|
|
41
|
-
| `initial` / `animate` / `transition`
|
|
42
|
-
| `variants` (string keys + inheritance)
|
|
43
|
-
| `whileHover` / `whileTap` / `whileFocus` / `whileInView`
|
|
44
|
-
| Drag (`drag`, constraints, momentum, controls, callbacks)
|
|
45
|
-
| `AnimatePresence` (`initial`, `mode`, `onExitComplete`)
|
|
46
|
-
| Layout (`layout`, `layout="position"`)
|
|
47
|
-
| Shared layout (`layoutId`, `LayoutGroup`, `layoutScroll`)
|
|
48
|
-
| Pan gesture API (`whilePan`, `onPan*`)
|
|
49
|
-
| `MotionConfig` parity beyond `transition`
|
|
50
|
-
| `reducedMotion`, `features`, `transformPagePoint`
|
|
39
|
+
| Capability | Status |
|
|
40
|
+
| ---------------------------------------------------------------------- | ------------------------------------------ |
|
|
41
|
+
| `initial` / `animate` / `transition` | Supported |
|
|
42
|
+
| `variants` (string keys + inheritance, function-form `custom`) | Supported |
|
|
43
|
+
| `whileHover` / `whileTap` / `whileFocus` / `whileDrag` / `whileInView` | Supported (inline + variant keys / arrays) |
|
|
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`, `LayoutGroup`, `layoutScroll`) | Supported |
|
|
48
|
+
| Pan gesture API (`whilePan`, `onPan*`) | Not yet supported |
|
|
49
|
+
| `MotionConfig` parity beyond `transition` | Partial |
|
|
50
|
+
| `reducedMotion`, `features`, `transformPagePoint` | Not yet supported |
|
|
51
51
|
|
|
52
52
|
## Supported elements
|
|
53
53
|
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
} from '../utils/presence'
|
|
49
49
|
import { getInitialKeyframes } from '../utils/initial'
|
|
50
50
|
import { attachDrag } from '../utils/drag'
|
|
51
|
-
import { resolveInitial, resolveAnimate, resolveExit } from '../utils/variants'
|
|
51
|
+
import { resolveInitial, resolveAnimate, resolveExit, resolveWhile } from '../utils/variants'
|
|
52
52
|
import {
|
|
53
53
|
setVariantContext,
|
|
54
54
|
getVariantContext,
|
|
@@ -446,6 +446,19 @@
|
|
|
446
446
|
)
|
|
447
447
|
const resolvedExit = $derived(resolveExit(exitProp, variantsProp, effectiveCustom))
|
|
448
448
|
|
|
449
|
+
// Resolve `whileX` props against `variants` so each gesture's attach
|
|
450
|
+
// helper receives a plain keyframes object regardless of whether the
|
|
451
|
+
// consumer wrote inline keyframes, a variant key, or an array of
|
|
452
|
+
// variant keys. Mirrors framer-motion's `whileHover` etc. surface
|
|
453
|
+
// (#349).
|
|
454
|
+
const resolvedWhileTap = $derived(resolveWhile(whileTapProp, variantsProp, effectiveCustom))
|
|
455
|
+
const resolvedWhileHover = $derived(resolveWhile(whileHoverProp, variantsProp, effectiveCustom))
|
|
456
|
+
const resolvedWhileFocus = $derived(resolveWhile(whileFocusProp, variantsProp, effectiveCustom))
|
|
457
|
+
const resolvedWhileDrag = $derived(resolveWhile(whileDragProp, variantsProp, effectiveCustom))
|
|
458
|
+
const resolvedWhileInView = $derived(
|
|
459
|
+
resolveWhile(whileInViewProp, variantsProp, effectiveCustom)
|
|
460
|
+
)
|
|
461
|
+
|
|
449
462
|
// Extract keyframes from resolved initial, handling initial={false}
|
|
450
463
|
const initialKeyframes = $derived(
|
|
451
464
|
filterReducedMotionKeyframes(
|
|
@@ -457,7 +470,12 @@
|
|
|
457
470
|
// Derived attributes to keep both branches in sync (focusability, data flags, style, class)
|
|
458
471
|
const derivedAttrs = $derived<Record<string, unknown>>({
|
|
459
472
|
...(rest as Record<string, unknown>),
|
|
460
|
-
|
|
473
|
+
// Gate on the *resolved* whileTap, not the raw prop. With
|
|
474
|
+
// variant-label support a truthy-but-unresolved value (unknown
|
|
475
|
+
// key, empty array) would otherwise add `tabindex=0` for an
|
|
476
|
+
// element that never actually receives a tap gesture — an
|
|
477
|
+
// unintended tab stop. (#349 CR feedback)
|
|
478
|
+
...(isNotEmpty(resolvedWhileTap) &&
|
|
461
479
|
!isNativelyFocusable(tag, rest as Record<string, unknown>) &&
|
|
462
480
|
((rest as Record<string, unknown>)?.tabindex ??
|
|
463
481
|
(rest as Record<string, unknown>)?.tabIndex ??
|
|
@@ -541,7 +559,7 @@
|
|
|
541
559
|
directionLock: !!dragDirectionLockProp,
|
|
542
560
|
listener: dragListenerProp !== false,
|
|
543
561
|
controls,
|
|
544
|
-
whileDrag:
|
|
562
|
+
whileDrag: resolvedWhileDrag as MotionWhileDrag,
|
|
545
563
|
mergedTransition: (mergedTransition ?? {}) as AnimationOptions,
|
|
546
564
|
callbacks: {
|
|
547
565
|
onStart: onDragStartProp as (e: PointerEvent, info: DragInfo) => void,
|
|
@@ -801,18 +819,18 @@
|
|
|
801
819
|
|
|
802
820
|
// whileTap handling via motion-dom's press()
|
|
803
821
|
$effect(() => {
|
|
804
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
822
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileTap))) return
|
|
805
823
|
return attachWhileTap(
|
|
806
824
|
element!,
|
|
807
|
-
(
|
|
825
|
+
(resolvedWhileTap ?? {}) as Record<string, unknown>,
|
|
808
826
|
(resolvedInitial ?? {}) as Record<string, unknown>,
|
|
809
827
|
(resolvedAnimate ?? {}) as Record<string, unknown>,
|
|
810
828
|
{
|
|
811
829
|
onTapStart: onTapStartProp,
|
|
812
830
|
onTap: onTapProp,
|
|
813
831
|
onTapCancel: onTapCancelProp,
|
|
814
|
-
hoverDef: isNotEmpty(
|
|
815
|
-
? ((
|
|
832
|
+
hoverDef: isNotEmpty(resolvedWhileHover ?? {})
|
|
833
|
+
? ((resolvedWhileHover ?? {}) as Record<string, unknown>)
|
|
816
834
|
: undefined,
|
|
817
835
|
hoverFallbackTransition: (mergedTransition ?? {}) as AnimationOptions
|
|
818
836
|
}
|
|
@@ -821,10 +839,10 @@
|
|
|
821
839
|
|
|
822
840
|
// whileHover handling, gated to true-hover devices to avoid sticky states on touch
|
|
823
841
|
$effect(() => {
|
|
824
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
842
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileHover))) return
|
|
825
843
|
return attachWhileHover(
|
|
826
844
|
element!,
|
|
827
|
-
(
|
|
845
|
+
(resolvedWhileHover ?? {}) as Record<string, unknown>,
|
|
828
846
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
829
847
|
{ onStart: onHoverStartProp, onEnd: onHoverEndProp },
|
|
830
848
|
{
|
|
@@ -836,10 +854,10 @@
|
|
|
836
854
|
|
|
837
855
|
// whileFocus handling for keyboard focus interactions
|
|
838
856
|
$effect(() => {
|
|
839
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
857
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileFocus))) return
|
|
840
858
|
return attachWhileFocus(
|
|
841
859
|
element!,
|
|
842
|
-
(
|
|
860
|
+
(resolvedWhileFocus ?? {}) as Record<string, unknown>,
|
|
843
861
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
844
862
|
{ onStart: onFocusStartProp, onEnd: onFocusEndProp },
|
|
845
863
|
{
|
|
@@ -851,10 +869,10 @@
|
|
|
851
869
|
|
|
852
870
|
// whileInView handling for viewport intersection
|
|
853
871
|
$effect(() => {
|
|
854
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
872
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileInView))) return
|
|
855
873
|
return attachWhileInView(
|
|
856
874
|
element!,
|
|
857
|
-
(
|
|
875
|
+
(resolvedWhileInView ?? {}) as Record<string, unknown>,
|
|
858
876
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
859
877
|
{
|
|
860
878
|
onStart: onInViewStartProp,
|
package/dist/types.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export type Variants = Record<string, Variant>;
|
|
|
59
59
|
* <motion.div variants={myVariants} initial="hidden" animate="visible" />
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
export type MotionInitial = DOMKeyframesDefinition | string | false | undefined;
|
|
62
|
+
export type MotionInitial = DOMKeyframesDefinition | string | string[] | false | undefined;
|
|
63
63
|
/**
|
|
64
64
|
* Target animation properties for a motion component.
|
|
65
65
|
*
|
|
@@ -74,7 +74,7 @@ export type MotionInitial = DOMKeyframesDefinition | string | false | undefined;
|
|
|
74
74
|
* <motion.div variants={myVariants} animate="visible" />
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
|
-
export type MotionAnimate = DOMKeyframesDefinition | string | undefined;
|
|
77
|
+
export type MotionAnimate = DOMKeyframesDefinition | string | string[] | undefined;
|
|
78
78
|
/**
|
|
79
79
|
* Exit animation properties for a motion component when unmounted.
|
|
80
80
|
*
|
|
@@ -91,7 +91,7 @@ export type MotionAnimate = DOMKeyframesDefinition | string | undefined;
|
|
|
91
91
|
*/
|
|
92
92
|
export type MotionExit = (Record<string, unknown> & {
|
|
93
93
|
transition?: AnimationOptions;
|
|
94
|
-
}) | DOMKeyframesDefinition | string | undefined;
|
|
94
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
95
95
|
/**
|
|
96
96
|
* Animation transition configuration.
|
|
97
97
|
* @example
|
|
@@ -111,52 +111,80 @@ export type MotionExit = (Record<string, unknown> & {
|
|
|
111
111
|
export type MotionTransition = AnimationOptions | undefined;
|
|
112
112
|
/**
|
|
113
113
|
* Animation properties for tap/click interactions.
|
|
114
|
+
*
|
|
115
|
+
* Accepts inline keyframes, a variant key, or an array of variant keys
|
|
116
|
+
* (later entries override earlier ones on key collisions).
|
|
117
|
+
*
|
|
114
118
|
* @example
|
|
115
119
|
* ```svelte
|
|
116
120
|
* <motion.button whileTap={{ scale: 0.95 }} />
|
|
121
|
+
*
|
|
122
|
+
* <!-- Variant key -->
|
|
123
|
+
* <motion.button variants={{ pressed: { scale: 0.95 } }} whileTap="pressed" />
|
|
124
|
+
*
|
|
125
|
+
* <!-- Array — later wins on conflicts -->
|
|
126
|
+
* <motion.button whileTap={["pressed", "muted"]} />
|
|
117
127
|
* ```
|
|
118
128
|
*/
|
|
119
|
-
export type MotionWhileTap =
|
|
129
|
+
export type MotionWhileTap = (Record<string, unknown> & {
|
|
130
|
+
transition?: AnimationOptions;
|
|
131
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
120
132
|
/**
|
|
121
133
|
* Animation properties for hover interactions.
|
|
134
|
+
*
|
|
135
|
+
* Accepts inline keyframes, a variant key, or an array of variant keys.
|
|
136
|
+
*
|
|
122
137
|
* @example
|
|
123
138
|
* ```svelte
|
|
124
139
|
* <motion.div whileHover={{ scale: 1.05 }} />
|
|
140
|
+
*
|
|
141
|
+
* <!-- Variant key -->
|
|
142
|
+
* <motion.div variants={{ hover: { scale: 1.05 } }} whileHover="hover" />
|
|
125
143
|
* ```
|
|
126
144
|
*/
|
|
127
145
|
export type MotionWhileHover = (Record<string, unknown> & {
|
|
128
146
|
transition?: AnimationOptions;
|
|
129
|
-
}) | DOMKeyframesDefinition | undefined;
|
|
147
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
130
148
|
/**
|
|
131
149
|
* Animation properties for focus interactions.
|
|
150
|
+
*
|
|
151
|
+
* Accepts inline keyframes, a variant key, or an array of variant keys.
|
|
152
|
+
*
|
|
132
153
|
* @example
|
|
133
154
|
* ```svelte
|
|
134
155
|
* <motion.button whileFocus={{ scale: 1.05 }} />
|
|
156
|
+
* <motion.button variants={{ active: { outline: '2px solid blue' } }} whileFocus="active" />
|
|
135
157
|
* ```
|
|
136
158
|
*/
|
|
137
159
|
export type MotionWhileFocus = (Record<string, unknown> & {
|
|
138
160
|
transition?: AnimationOptions;
|
|
139
|
-
}) | DOMKeyframesDefinition | undefined;
|
|
161
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
140
162
|
/**
|
|
141
163
|
* Animation properties for drag interactions.
|
|
142
164
|
* When a drag gesture starts, the element animates to this state; when it ends,
|
|
143
165
|
* it animates back to its baseline (from animate/initial), restoring only the changed keys.
|
|
166
|
+
*
|
|
167
|
+
* Accepts inline keyframes, a variant key, or an array of variant keys.
|
|
144
168
|
*/
|
|
145
169
|
export type MotionWhileDrag = (Record<string, unknown> & {
|
|
146
170
|
transition?: AnimationOptions;
|
|
147
|
-
}) | DOMKeyframesDefinition | undefined;
|
|
171
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
148
172
|
/**
|
|
149
173
|
* Animation properties for in-view interactions.
|
|
150
174
|
* When the element enters the viewport, it animates to this state; when it leaves,
|
|
151
175
|
* it animates back to its baseline (from animate/initial), restoring only the changed keys.
|
|
176
|
+
*
|
|
177
|
+
* Accepts inline keyframes, a variant key, or an array of variant keys.
|
|
178
|
+
*
|
|
152
179
|
* @example
|
|
153
180
|
* ```svelte
|
|
154
181
|
* <motion.div whileInView={{ opacity: 1, y: 0 }} />
|
|
182
|
+
* <motion.div variants={{ inView: { opacity: 1 } }} whileInView="inView" />
|
|
155
183
|
* ```
|
|
156
184
|
*/
|
|
157
185
|
export type MotionWhileInView = (Record<string, unknown> & {
|
|
158
186
|
transition?: AnimationOptions;
|
|
159
|
-
}) | DOMKeyframesDefinition | undefined;
|
|
187
|
+
}) | DOMKeyframesDefinition | string | string[] | undefined;
|
|
160
188
|
/**
|
|
161
189
|
* IntersectionObserver configuration for `whileInView`. Mirrors framer-motion's
|
|
162
190
|
* `viewport` prop. Same shape as `UseInViewOptions` minus `initial` (which is
|
package/dist/utils/variants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MotionAnimate, MotionExit, MotionInitial, Variants } from '../types';
|
|
1
|
+
import type { MotionAnimate, MotionExit, MotionInitial, MotionWhileDrag, MotionWhileFocus, MotionWhileHover, MotionWhileInView, MotionWhileTap, Variants } from '../types';
|
|
2
2
|
import type { DOMKeyframesDefinition } from 'motion';
|
|
3
3
|
/**
|
|
4
4
|
* Resolves a variant key to its keyframes definition.
|
|
@@ -27,6 +27,33 @@ import type { DOMKeyframesDefinition } from 'motion';
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
export declare const resolveVariant: (variants: Variants | undefined, key: string | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves a single variant key or an ordered list of keys to merged
|
|
32
|
+
* keyframes. Matches framer-motion's `VariantLabels = string | string[]`
|
|
33
|
+
* surface: later keys in the list override earlier ones on key
|
|
34
|
+
* collisions (`Object.assign` semantics).
|
|
35
|
+
*
|
|
36
|
+
* Missing keys are skipped. An empty list, an empty string, or an
|
|
37
|
+
* undefined argument all resolve to `undefined`.
|
|
38
|
+
*
|
|
39
|
+
* @param variants - The variants object containing named animation states.
|
|
40
|
+
* @param keys - A single variant key or an array of variant keys.
|
|
41
|
+
* @param custom - Forwarded to function-form variants (per-entry).
|
|
42
|
+
* @returns Merged keyframes definition, or `undefined` when nothing resolved.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const variants = {
|
|
47
|
+
* hover: { scale: 1.1 },
|
|
48
|
+
* active: { scale: 1.2, color: 'red' }
|
|
49
|
+
* }
|
|
50
|
+
* resolveVariantList(variants, 'hover') // { scale: 1.1 }
|
|
51
|
+
* resolveVariantList(variants, ['hover', 'active']) // { scale: 1.2, color: 'red' }
|
|
52
|
+
* resolveVariantList(variants, ['hover', 'missing']) // { scale: 1.1 }
|
|
53
|
+
* resolveVariantList(variants, []) // undefined
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const resolveVariantList: (variants: Variants | undefined, keys: string | string[] | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
30
57
|
/**
|
|
31
58
|
* Resolves the initial prop to keyframes, handling variant keys and `initial={false}`.
|
|
32
59
|
*
|
|
@@ -52,25 +79,72 @@ export declare const resolveInitial: (initial: MotionInitial, variants: Variants
|
|
|
52
79
|
/**
|
|
53
80
|
* Resolves the animate prop to keyframes, handling variant keys.
|
|
54
81
|
*
|
|
55
|
-
* When `animate` is a string, looks it up in the
|
|
56
|
-
* dynamic variants with `custom`). Otherwise
|
|
82
|
+
* When `animate` is a string (or array of strings), looks it up in the
|
|
83
|
+
* variants object (invoking dynamic variants with `custom`). Otherwise
|
|
84
|
+
* returns the keyframes directly.
|
|
57
85
|
*
|
|
58
|
-
* @param animate - The animate prop value (keyframes, variant key,
|
|
86
|
+
* @param animate - The animate prop value (keyframes, variant key, array
|
|
87
|
+
* of variant keys, or undefined).
|
|
59
88
|
* @param variants - The variants object for resolving string keys.
|
|
60
89
|
* @param custom - Forwarded to function-form variants.
|
|
61
90
|
* @returns Keyframes definition or undefined.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const variants = { visible: { opacity: 1 }, shifted: { x: 100 } }
|
|
95
|
+
* resolveAnimate('visible', variants) // { opacity: 1 }
|
|
96
|
+
* resolveAnimate(['visible', 'shifted'], variants) // { opacity: 1, x: 100 }
|
|
97
|
+
* resolveAnimate({ scale: 1.2 }, variants) // { scale: 1.2 } (pass-through)
|
|
98
|
+
* resolveAnimate(undefined, variants) // undefined
|
|
99
|
+
* ```
|
|
62
100
|
*/
|
|
63
101
|
export declare const resolveAnimate: (animate: MotionAnimate, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
64
102
|
/**
|
|
65
103
|
* Resolves the exit prop to keyframes, handling variant keys.
|
|
66
104
|
*
|
|
67
|
-
* When `exit` is a string, looks it up in the
|
|
68
|
-
* dynamic variants with `custom`). Otherwise
|
|
69
|
-
* Used by AnimatePresence for exit
|
|
105
|
+
* When `exit` is a string (or array of strings), looks it up in the
|
|
106
|
+
* variants object (invoking dynamic variants with `custom`). Otherwise
|
|
107
|
+
* returns the keyframes directly. Used by AnimatePresence for exit
|
|
108
|
+
* animations.
|
|
70
109
|
*
|
|
71
|
-
* @param exit - The exit prop value (keyframes, variant key,
|
|
110
|
+
* @param exit - The exit prop value (keyframes, variant key, array of
|
|
111
|
+
* variant keys, or undefined).
|
|
72
112
|
* @param variants - The variants object for resolving string keys.
|
|
73
113
|
* @param custom - Forwarded to function-form variants.
|
|
74
114
|
* @returns Keyframes definition or undefined.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* const variants = { hidden: { opacity: 0 }, small: { scale: 0.8 } }
|
|
119
|
+
* resolveExit('hidden', variants) // { opacity: 0 }
|
|
120
|
+
* resolveExit(['hidden', 'small'], variants) // { opacity: 0, scale: 0.8 }
|
|
121
|
+
* resolveExit({ y: -20 }, variants) // { y: -20 }
|
|
122
|
+
* resolveExit(undefined, variants) // undefined
|
|
123
|
+
* ```
|
|
75
124
|
*/
|
|
76
125
|
export declare const resolveExit: (exit: MotionExit, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Resolves a `whileX` prop (hover, tap, focus, drag, in-view) to
|
|
128
|
+
* keyframes. Mirrors `resolveAnimate` — pass-through for inline
|
|
129
|
+
* keyframes, look up variant keys via `resolveVariantList` (single
|
|
130
|
+
* string or array of strings, merged left-to-right).
|
|
131
|
+
*
|
|
132
|
+
* Used by `_MotionContainer.svelte` to feed the gesture attach helpers
|
|
133
|
+
* a consistent keyframes object regardless of whether the consumer
|
|
134
|
+
* wrote inline keyframes or a variant reference.
|
|
135
|
+
*
|
|
136
|
+
* @param value - The whileX prop value.
|
|
137
|
+
* @param variants - The variants object for resolving string keys.
|
|
138
|
+
* @param custom - Forwarded to function-form variants.
|
|
139
|
+
* @returns Keyframes definition or `undefined` when nothing applies.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const variants = { hover: { scale: 1.1 }, active: { color: 'red' } }
|
|
144
|
+
* resolveWhile('hover', variants) // { scale: 1.1 }
|
|
145
|
+
* resolveWhile(['hover', 'active'], variants) // { scale: 1.1, color: 'red' }
|
|
146
|
+
* resolveWhile({ scale: 1.2 }, variants) // { scale: 1.2 } (pass-through)
|
|
147
|
+
* resolveWhile(undefined, variants) // undefined
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare const resolveWhile: (value: MotionWhileTap | MotionWhileHover | MotionWhileFocus | MotionWhileDrag | MotionWhileInView, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
package/dist/utils/variants.js
CHANGED
|
@@ -27,11 +27,69 @@
|
|
|
27
27
|
export const resolveVariant = (variants, key, custom) => {
|
|
28
28
|
if (!variants || !key)
|
|
29
29
|
return undefined;
|
|
30
|
+
// Guard against built-in / inherited keys like 'toString' or
|
|
31
|
+
// 'constructor' — without this, `whileHover="toString"` would
|
|
32
|
+
// resolve to `Function.prototype.toString` and leak a function into
|
|
33
|
+
// the merge path.
|
|
34
|
+
if (!Object.prototype.hasOwnProperty.call(variants, key))
|
|
35
|
+
return undefined;
|
|
30
36
|
const entry = variants[key];
|
|
31
37
|
if (typeof entry === 'function')
|
|
32
38
|
return entry(custom);
|
|
33
39
|
return entry;
|
|
34
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolves a single variant key or an ordered list of keys to merged
|
|
43
|
+
* keyframes. Matches framer-motion's `VariantLabels = string | string[]`
|
|
44
|
+
* surface: later keys in the list override earlier ones on key
|
|
45
|
+
* collisions (`Object.assign` semantics).
|
|
46
|
+
*
|
|
47
|
+
* Missing keys are skipped. An empty list, an empty string, or an
|
|
48
|
+
* undefined argument all resolve to `undefined`.
|
|
49
|
+
*
|
|
50
|
+
* @param variants - The variants object containing named animation states.
|
|
51
|
+
* @param keys - A single variant key or an array of variant keys.
|
|
52
|
+
* @param custom - Forwarded to function-form variants (per-entry).
|
|
53
|
+
* @returns Merged keyframes definition, or `undefined` when nothing resolved.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const variants = {
|
|
58
|
+
* hover: { scale: 1.1 },
|
|
59
|
+
* active: { scale: 1.2, color: 'red' }
|
|
60
|
+
* }
|
|
61
|
+
* resolveVariantList(variants, 'hover') // { scale: 1.1 }
|
|
62
|
+
* resolveVariantList(variants, ['hover', 'active']) // { scale: 1.2, color: 'red' }
|
|
63
|
+
* resolveVariantList(variants, ['hover', 'missing']) // { scale: 1.1 }
|
|
64
|
+
* resolveVariantList(variants, []) // undefined
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export const resolveVariantList = (variants, keys, custom) => {
|
|
68
|
+
if (keys === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (typeof keys === 'string')
|
|
71
|
+
return resolveVariant(variants, keys, custom);
|
|
72
|
+
if (keys.length === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
let merged;
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const entry = resolveVariant(variants, key, custom);
|
|
77
|
+
// Defensive: only merge plain keyframe objects. A function-form
|
|
78
|
+
// variant could return something else (array, class instance,
|
|
79
|
+
// string) under a misuse, and spreading those would corrupt the
|
|
80
|
+
// merged result. Reject arrays explicitly and require the
|
|
81
|
+
// prototype to be `Object.prototype` (or `null` for objects
|
|
82
|
+
// created via `Object.create(null)`).
|
|
83
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
84
|
+
continue;
|
|
85
|
+
const proto = Object.getPrototypeOf(entry);
|
|
86
|
+
if (proto !== Object.prototype && proto !== null)
|
|
87
|
+
continue;
|
|
88
|
+
const obj = entry;
|
|
89
|
+
merged = merged ? { ...merged, ...obj } : { ...obj };
|
|
90
|
+
}
|
|
91
|
+
return merged;
|
|
92
|
+
};
|
|
35
93
|
/**
|
|
36
94
|
* Resolves the initial prop to keyframes, handling variant keys and `initial={false}`.
|
|
37
95
|
*
|
|
@@ -58,44 +116,97 @@ export const resolveInitial = (initial, variants, custom) => {
|
|
|
58
116
|
return false;
|
|
59
117
|
if (initial === undefined)
|
|
60
118
|
return undefined;
|
|
61
|
-
if (typeof initial === 'string')
|
|
62
|
-
return
|
|
119
|
+
if (typeof initial === 'string' || Array.isArray(initial))
|
|
120
|
+
return resolveVariantList(variants, initial, custom);
|
|
63
121
|
return initial;
|
|
64
122
|
};
|
|
65
123
|
/**
|
|
66
124
|
* Resolves the animate prop to keyframes, handling variant keys.
|
|
67
125
|
*
|
|
68
|
-
* When `animate` is a string, looks it up in the
|
|
69
|
-
* dynamic variants with `custom`). Otherwise
|
|
126
|
+
* When `animate` is a string (or array of strings), looks it up in the
|
|
127
|
+
* variants object (invoking dynamic variants with `custom`). Otherwise
|
|
128
|
+
* returns the keyframes directly.
|
|
70
129
|
*
|
|
71
|
-
* @param animate - The animate prop value (keyframes, variant key,
|
|
130
|
+
* @param animate - The animate prop value (keyframes, variant key, array
|
|
131
|
+
* of variant keys, or undefined).
|
|
72
132
|
* @param variants - The variants object for resolving string keys.
|
|
73
133
|
* @param custom - Forwarded to function-form variants.
|
|
74
134
|
* @returns Keyframes definition or undefined.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const variants = { visible: { opacity: 1 }, shifted: { x: 100 } }
|
|
139
|
+
* resolveAnimate('visible', variants) // { opacity: 1 }
|
|
140
|
+
* resolveAnimate(['visible', 'shifted'], variants) // { opacity: 1, x: 100 }
|
|
141
|
+
* resolveAnimate({ scale: 1.2 }, variants) // { scale: 1.2 } (pass-through)
|
|
142
|
+
* resolveAnimate(undefined, variants) // undefined
|
|
143
|
+
* ```
|
|
75
144
|
*/
|
|
76
145
|
export const resolveAnimate = (animate, variants, custom) => {
|
|
77
146
|
if (animate === undefined)
|
|
78
147
|
return undefined;
|
|
79
|
-
if (typeof animate === 'string')
|
|
80
|
-
return
|
|
148
|
+
if (typeof animate === 'string' || Array.isArray(animate))
|
|
149
|
+
return resolveVariantList(variants, animate, custom);
|
|
81
150
|
return animate;
|
|
82
151
|
};
|
|
83
152
|
/**
|
|
84
153
|
* Resolves the exit prop to keyframes, handling variant keys.
|
|
85
154
|
*
|
|
86
|
-
* When `exit` is a string, looks it up in the
|
|
87
|
-
* dynamic variants with `custom`). Otherwise
|
|
88
|
-
* Used by AnimatePresence for exit
|
|
155
|
+
* When `exit` is a string (or array of strings), looks it up in the
|
|
156
|
+
* variants object (invoking dynamic variants with `custom`). Otherwise
|
|
157
|
+
* returns the keyframes directly. Used by AnimatePresence for exit
|
|
158
|
+
* animations.
|
|
89
159
|
*
|
|
90
|
-
* @param exit - The exit prop value (keyframes, variant key,
|
|
160
|
+
* @param exit - The exit prop value (keyframes, variant key, array of
|
|
161
|
+
* variant keys, or undefined).
|
|
91
162
|
* @param variants - The variants object for resolving string keys.
|
|
92
163
|
* @param custom - Forwarded to function-form variants.
|
|
93
164
|
* @returns Keyframes definition or undefined.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const variants = { hidden: { opacity: 0 }, small: { scale: 0.8 } }
|
|
169
|
+
* resolveExit('hidden', variants) // { opacity: 0 }
|
|
170
|
+
* resolveExit(['hidden', 'small'], variants) // { opacity: 0, scale: 0.8 }
|
|
171
|
+
* resolveExit({ y: -20 }, variants) // { y: -20 }
|
|
172
|
+
* resolveExit(undefined, variants) // undefined
|
|
173
|
+
* ```
|
|
94
174
|
*/
|
|
95
175
|
export const resolveExit = (exit, variants, custom) => {
|
|
96
176
|
if (exit === undefined)
|
|
97
177
|
return undefined;
|
|
98
|
-
if (typeof exit === 'string')
|
|
99
|
-
return
|
|
178
|
+
if (typeof exit === 'string' || Array.isArray(exit))
|
|
179
|
+
return resolveVariantList(variants, exit, custom);
|
|
100
180
|
return exit;
|
|
101
181
|
};
|
|
182
|
+
/**
|
|
183
|
+
* Resolves a `whileX` prop (hover, tap, focus, drag, in-view) to
|
|
184
|
+
* keyframes. Mirrors `resolveAnimate` — pass-through for inline
|
|
185
|
+
* keyframes, look up variant keys via `resolveVariantList` (single
|
|
186
|
+
* string or array of strings, merged left-to-right).
|
|
187
|
+
*
|
|
188
|
+
* Used by `_MotionContainer.svelte` to feed the gesture attach helpers
|
|
189
|
+
* a consistent keyframes object regardless of whether the consumer
|
|
190
|
+
* wrote inline keyframes or a variant reference.
|
|
191
|
+
*
|
|
192
|
+
* @param value - The whileX prop value.
|
|
193
|
+
* @param variants - The variants object for resolving string keys.
|
|
194
|
+
* @param custom - Forwarded to function-form variants.
|
|
195
|
+
* @returns Keyframes definition or `undefined` when nothing applies.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* const variants = { hover: { scale: 1.1 }, active: { color: 'red' } }
|
|
200
|
+
* resolveWhile('hover', variants) // { scale: 1.1 }
|
|
201
|
+
* resolveWhile(['hover', 'active'], variants) // { scale: 1.1, color: 'red' }
|
|
202
|
+
* resolveWhile({ scale: 1.2 }, variants) // { scale: 1.2 } (pass-through)
|
|
203
|
+
* resolveWhile(undefined, variants) // undefined
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export const resolveWhile = (value, variants, custom) => {
|
|
207
|
+
if (value === undefined)
|
|
208
|
+
return undefined;
|
|
209
|
+
if (typeof value === 'string' || Array.isArray(value))
|
|
210
|
+
return resolveVariantList(variants, value, custom);
|
|
211
|
+
return value;
|
|
212
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-motion",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values. The drop-in Framer Motion alternative for Svelte and SvelteKit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|