@humanspeak/svelte-motion 0.4.2 → 0.4.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/README.md +23 -0
- package/dist/components/variantContext.context.d.ts +20 -0
- package/dist/components/variantContext.context.js +25 -0
- package/dist/html/_MotionContainer.svelte +62 -7
- package/dist/types.d.ts +56 -1
- package/dist/utils/inView.d.ts +5 -2
- package/dist/utils/inView.js +24 -4
- package/dist/utils/variants.d.ts +35 -41
- package/dist/utils/variants.js +42 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,6 +214,29 @@ Supported drag props:
|
|
|
214
214
|
|
|
215
215
|
- String variant keys are resolved from `variants`.
|
|
216
216
|
- Variant state inherits through context.
|
|
217
|
+
- A variant entry can be a `(custom) => keyframes` factory. The `custom` prop is forwarded — useful for staggered lists where each child needs its own offset or delay. Children without `custom` inherit the nearest motion ancestor's value.
|
|
218
|
+
|
|
219
|
+
```svelte
|
|
220
|
+
<script lang="ts">
|
|
221
|
+
import { motion, type Variants } from '@humanspeak/svelte-motion'
|
|
222
|
+
|
|
223
|
+
const variants: Variants = {
|
|
224
|
+
hidden: { opacity: 0, x: -100 },
|
|
225
|
+
visible: (i) => ({
|
|
226
|
+
opacity: 1,
|
|
227
|
+
x: 0,
|
|
228
|
+
transition: { delay: (i as number) * 0.1 }
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
const items = ['Alpha', 'Beta', 'Gamma', 'Delta']
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
{#each items as item, i}
|
|
235
|
+
<motion.li custom={i} {variants} initial="hidden" animate="visible">
|
|
236
|
+
{item}
|
|
237
|
+
</motion.li>
|
|
238
|
+
{/each}
|
|
239
|
+
```
|
|
217
240
|
|
|
218
241
|
## Layout animation
|
|
219
242
|
|
|
@@ -25,3 +25,23 @@ export declare const setInitialFalseContext: (value: boolean) => void;
|
|
|
25
25
|
* @returns `true` if a parent set `initial={false}`, otherwise `false`.
|
|
26
26
|
*/
|
|
27
27
|
export declare const getInitialFalseContext: () => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Provide a writable store carrying the current motion component's
|
|
30
|
+
* `custom` value so descendant motion components without their own
|
|
31
|
+
* `custom` prop can inherit it — and re-resolve their variants when the
|
|
32
|
+
* parent's `custom` changes.
|
|
33
|
+
*
|
|
34
|
+
* Mirrors framer-motion's variant-tree custom propagation. A store
|
|
35
|
+
* (rather than a snapshot) lets descendants react to changes the parent
|
|
36
|
+
* makes after mount.
|
|
37
|
+
*
|
|
38
|
+
* @param store Writable store holding the current component's effective `custom`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const setCustomContext: (store: Writable<unknown>) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Read the nearest ancestor's `custom` store (if any).
|
|
43
|
+
*
|
|
44
|
+
* @returns The ancestor's writable store, or `undefined` when no motion
|
|
45
|
+
* ancestor has set one.
|
|
46
|
+
*/
|
|
47
|
+
export declare const getCustomContext: () => Writable<unknown> | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
const VARIANT_CONTEXT_KEY = Symbol('variant-context');
|
|
3
3
|
const INITIAL_FALSE_CONTEXT_KEY = Symbol('initial-false-context');
|
|
4
|
+
const CUSTOM_CONTEXT_KEY = Symbol('custom-context');
|
|
4
5
|
/**
|
|
5
6
|
* Provide a writable store for the current variant key so children can
|
|
6
7
|
* react to changes over time (true inheritance like Framer Motion).
|
|
@@ -35,3 +36,27 @@ export const setInitialFalseContext = (value) => {
|
|
|
35
36
|
export const getInitialFalseContext = () => {
|
|
36
37
|
return getContext(INITIAL_FALSE_CONTEXT_KEY) ?? false;
|
|
37
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Provide a writable store carrying the current motion component's
|
|
41
|
+
* `custom` value so descendant motion components without their own
|
|
42
|
+
* `custom` prop can inherit it — and re-resolve their variants when the
|
|
43
|
+
* parent's `custom` changes.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors framer-motion's variant-tree custom propagation. A store
|
|
46
|
+
* (rather than a snapshot) lets descendants react to changes the parent
|
|
47
|
+
* makes after mount.
|
|
48
|
+
*
|
|
49
|
+
* @param store Writable store holding the current component's effective `custom`.
|
|
50
|
+
*/
|
|
51
|
+
export const setCustomContext = (store) => {
|
|
52
|
+
setContext(CUSTOM_CONTEXT_KEY, store);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Read the nearest ancestor's `custom` store (if any).
|
|
56
|
+
*
|
|
57
|
+
* @returns The ancestor's writable store, or `undefined` when no motion
|
|
58
|
+
* ancestor has set one.
|
|
59
|
+
*/
|
|
60
|
+
export const getCustomContext = () => {
|
|
61
|
+
return getContext(CUSTOM_CONTEXT_KEY);
|
|
62
|
+
};
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
setVariantContext,
|
|
54
54
|
getVariantContext,
|
|
55
55
|
setInitialFalseContext,
|
|
56
|
-
getInitialFalseContext
|
|
56
|
+
getInitialFalseContext,
|
|
57
|
+
setCustomContext,
|
|
58
|
+
getCustomContext
|
|
57
59
|
} from '../components/variantContext.context'
|
|
58
60
|
import { get, writable } from 'svelte/store'
|
|
59
61
|
import {
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
tag = 'div',
|
|
76
78
|
key: keyProp,
|
|
77
79
|
variants: variantsProp,
|
|
80
|
+
custom: customProp,
|
|
78
81
|
initial: initialProp,
|
|
79
82
|
animate: animateProp,
|
|
80
83
|
exit: exitProp,
|
|
@@ -87,6 +90,7 @@
|
|
|
87
90
|
whileHover: whileHoverProp,
|
|
88
91
|
whileFocus: whileFocusProp,
|
|
89
92
|
whileInView: whileInViewProp,
|
|
93
|
+
viewport: viewportProp,
|
|
90
94
|
whileDrag: whileDragProp,
|
|
91
95
|
onHoverStart: onHoverStartProp,
|
|
92
96
|
onHoverEnd: onHoverEndProp,
|
|
@@ -359,6 +363,32 @@
|
|
|
359
363
|
// Provide context immediately during initialization so children can inherit
|
|
360
364
|
setVariantContext(localVariantStore)
|
|
361
365
|
|
|
366
|
+
// Custom-value inheritance. Children with no `custom` prop adopt the
|
|
367
|
+
// nearest motion ancestor's value. Reactive via a writable store so a
|
|
368
|
+
// parent updating `custom` re-fires descendants' variant resolution.
|
|
369
|
+
const parentCustomStore = getCustomContext()
|
|
370
|
+
let inheritedCustom: unknown = undefined
|
|
371
|
+
if (parentCustomStore) {
|
|
372
|
+
parentCustomStore.subscribe((v) => (inheritedCustom = v))()
|
|
373
|
+
}
|
|
374
|
+
const initialCustomValue = customProp !== undefined ? customProp : inheritedCustom
|
|
375
|
+
const localCustomStore = writable<unknown>(initialCustomValue)
|
|
376
|
+
setCustomContext(localCustomStore)
|
|
377
|
+
|
|
378
|
+
let parentInheritedCustom = $state<unknown>(inheritedCustom)
|
|
379
|
+
$effect(() => {
|
|
380
|
+
if (!parentCustomStore) {
|
|
381
|
+
parentInheritedCustom = undefined
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
const unsubscribe = parentCustomStore.subscribe((v) => (parentInheritedCustom = v))
|
|
385
|
+
return () => unsubscribe()
|
|
386
|
+
})
|
|
387
|
+
const effectiveCustom = $derived(customProp !== undefined ? customProp : parentInheritedCustom)
|
|
388
|
+
$effect(() => {
|
|
389
|
+
localCustomStore.set(effectiveCustom)
|
|
390
|
+
})
|
|
391
|
+
|
|
362
392
|
$effect(() => {
|
|
363
393
|
if (!variantsProp) return localVariantStore.set(undefined)
|
|
364
394
|
if (typeof animateProp === 'string') return localVariantStore.set(animateProp)
|
|
@@ -366,9 +396,13 @@
|
|
|
366
396
|
localVariantStore.set(undefined)
|
|
367
397
|
})
|
|
368
398
|
|
|
369
|
-
const resolvedInitial = $derived(
|
|
370
|
-
|
|
371
|
-
|
|
399
|
+
const resolvedInitial = $derived(
|
|
400
|
+
resolveInitial(effectiveInitialProp, variantsProp, effectiveCustom)
|
|
401
|
+
)
|
|
402
|
+
const resolvedAnimate = $derived(
|
|
403
|
+
resolveAnimate(effectiveAnimate, variantsProp, effectiveCustom)
|
|
404
|
+
)
|
|
405
|
+
const resolvedExit = $derived(resolveExit(exitProp, variantsProp, effectiveCustom))
|
|
372
406
|
|
|
373
407
|
// Extract keyframes from resolved initial, handling initial={false}
|
|
374
408
|
const initialKeyframes = $derived(
|
|
@@ -638,6 +672,12 @@
|
|
|
638
672
|
|
|
639
673
|
// Track the last variant key we ran to avoid re-running on mount
|
|
640
674
|
let lastRanVariantKey = $state<string | undefined>(undefined)
|
|
675
|
+
// Companion to `lastRanVariantKey`: the JSON-serialized resolved
|
|
676
|
+
// keyframes for that variant. Lets us detect when a function-form
|
|
677
|
+
// variant produces new keyframes (because `custom` changed) while
|
|
678
|
+
// the variant key stayed the same — otherwise the animate effect
|
|
679
|
+
// would short-circuit and the element would never re-animate.
|
|
680
|
+
let lastRanResolvedJson = $state<string | undefined>(undefined)
|
|
641
681
|
let mountedWithInitialFalse = $state(false)
|
|
642
682
|
// Track if the initial->animate transition has already been triggered by main effect
|
|
643
683
|
let initialAnimationTriggered = $state(false)
|
|
@@ -781,7 +821,8 @@
|
|
|
781
821
|
{
|
|
782
822
|
initial: (resolvedInitial ?? {}) as Record<string, unknown>,
|
|
783
823
|
animate: (resolvedAnimate ?? {}) as Record<string, unknown>
|
|
784
|
-
}
|
|
824
|
+
},
|
|
825
|
+
viewportProp
|
|
785
826
|
)
|
|
786
827
|
})
|
|
787
828
|
|
|
@@ -914,8 +955,14 @@
|
|
|
914
955
|
return
|
|
915
956
|
}
|
|
916
957
|
if (typeof animateProp === 'string') {
|
|
917
|
-
|
|
958
|
+
// Compare BOTH the variant key and the resolved keyframes JSON.
|
|
959
|
+
// For static variants the JSON is constant per key; for
|
|
960
|
+
// function-form variants the JSON changes when `custom`
|
|
961
|
+
// changes, which we must treat as a new animation target.
|
|
962
|
+
const resolvedJson = resolvedAnimate ? JSON.stringify(resolvedAnimate) : undefined
|
|
963
|
+
if (lastRanVariantKey !== animateProp || lastRanResolvedJson !== resolvedJson) {
|
|
918
964
|
lastRanVariantKey = animateProp
|
|
965
|
+
lastRanResolvedJson = resolvedJson
|
|
919
966
|
runAnimation()
|
|
920
967
|
}
|
|
921
968
|
} else if (animateProp) {
|
|
@@ -954,8 +1001,10 @@
|
|
|
954
1001
|
mountedWithInitialFalse = false
|
|
955
1002
|
}
|
|
956
1003
|
if (typeof currentAnimateKey === 'string') {
|
|
957
|
-
|
|
1004
|
+
const resolvedJson = resolvedAnimate ? JSON.stringify(resolvedAnimate) : undefined
|
|
1005
|
+
if (lastRanVariantKey !== currentAnimateKey || lastRanResolvedJson !== resolvedJson) {
|
|
958
1006
|
lastRanVariantKey = currentAnimateKey
|
|
1007
|
+
lastRanResolvedJson = resolvedJson
|
|
959
1008
|
runAnimation()
|
|
960
1009
|
}
|
|
961
1010
|
} else {
|
|
@@ -987,6 +1036,9 @@
|
|
|
987
1036
|
mountedWithInitialFalse = true
|
|
988
1037
|
if (typeof currentAnimateKey === 'string') {
|
|
989
1038
|
lastRanVariantKey = currentAnimateKey
|
|
1039
|
+
lastRanResolvedJson = resolvedAnimate
|
|
1040
|
+
? JSON.stringify(resolvedAnimate)
|
|
1041
|
+
: undefined
|
|
990
1042
|
}
|
|
991
1043
|
dataPath = 5
|
|
992
1044
|
isLoaded = 'ready'
|
|
@@ -1079,6 +1131,9 @@
|
|
|
1079
1131
|
snapshot = transformSVGPathProperties(element!, snapshot)
|
|
1080
1132
|
animate(element!, snapshot as DOMKeyframesDefinition, { duration: 0 })
|
|
1081
1133
|
lastRanVariantKey = currentAnimateKey
|
|
1134
|
+
lastRanResolvedJson = resolvedAnimate
|
|
1135
|
+
? JSON.stringify(resolvedAnimate)
|
|
1136
|
+
: undefined
|
|
1082
1137
|
} else {
|
|
1083
1138
|
runAnimation()
|
|
1084
1139
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
import type { AnimationOptions, DOMKeyframesDefinition } from 'motion';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
/**
|
|
4
|
+
* A variant value: either a static keyframes object, or a factory function
|
|
5
|
+
* that receives the consumer-provided `custom` value and returns keyframes.
|
|
6
|
+
*
|
|
7
|
+
* Dynamic (function-form) variants let a single variants object emit
|
|
8
|
+
* per-instance keyframes — common for staggered lists where each child
|
|
9
|
+
* needs its own offset or delay.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <motion.div
|
|
14
|
+
* custom={index}
|
|
15
|
+
* variants={{
|
|
16
|
+
* visible: (i) => ({ opacity: 1, x: i * 50 }),
|
|
17
|
+
* hidden: { opacity: 0 }
|
|
18
|
+
* }}
|
|
19
|
+
* animate="visible"
|
|
20
|
+
* />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export type Variant = DOMKeyframesDefinition | ((custom: unknown) => DOMKeyframesDefinition) | undefined;
|
|
3
24
|
/**
|
|
4
25
|
* Variants define named animation states that can be referenced by string keys.
|
|
5
26
|
*
|
|
27
|
+
* Each entry can be a static keyframes object or a `(custom) => keyframes`
|
|
28
|
+
* factory function (see {@link Variant}).
|
|
29
|
+
*
|
|
6
30
|
* @example
|
|
7
31
|
* ```svelte
|
|
8
32
|
* <script>
|
|
@@ -15,7 +39,7 @@ import type { Snippet } from 'svelte';
|
|
|
15
39
|
* <motion.div variants={variants} animate="open" />
|
|
16
40
|
* ```
|
|
17
41
|
*/
|
|
18
|
-
export type Variants = Record<string,
|
|
42
|
+
export type Variants = Record<string, Variant>;
|
|
19
43
|
/**
|
|
20
44
|
* Initial animation properties for a motion component.
|
|
21
45
|
*
|
|
@@ -133,6 +157,29 @@ export type MotionWhileDrag = (Record<string, unknown> & {
|
|
|
133
157
|
export type MotionWhileInView = (Record<string, unknown> & {
|
|
134
158
|
transition?: AnimationOptions;
|
|
135
159
|
}) | DOMKeyframesDefinition | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* IntersectionObserver configuration for `whileInView`. Mirrors framer-motion's
|
|
162
|
+
* `viewport` prop. Same shape as `UseInViewOptions` minus `initial` (which is
|
|
163
|
+
* only meaningful for the hook's pre-mount return value).
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```svelte
|
|
167
|
+
* <motion.div
|
|
168
|
+
* whileInView={{ opacity: 1, y: 0 }}
|
|
169
|
+
* viewport={{ once: true, amount: 0.5 }}
|
|
170
|
+
* />
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export type MotionViewport = {
|
|
174
|
+
/** When `true`, fire only once on first entry. Subsequent re-entries no-op. */
|
|
175
|
+
once?: boolean;
|
|
176
|
+
/** Element to use as the IntersectionObserver root. Defaults to the viewport. */
|
|
177
|
+
root?: Element | Document;
|
|
178
|
+
/** CSS margin string applied to the root bounding box (e.g. `"100px 0px"`). */
|
|
179
|
+
margin?: string;
|
|
180
|
+
/** Fraction (0-1) or `"some"` / `"all"` of the target that must be visible. */
|
|
181
|
+
amount?: 'some' | 'all' | number;
|
|
182
|
+
};
|
|
136
183
|
/**
|
|
137
184
|
* Animation transition configuration for hover interactions.
|
|
138
185
|
* Overrides the global transition when provided.
|
|
@@ -222,6 +269,12 @@ export type MotionProps = {
|
|
|
222
269
|
key?: string;
|
|
223
270
|
/** Variants define named animation states */
|
|
224
271
|
variants?: Variants;
|
|
272
|
+
/**
|
|
273
|
+
* Value passed into function-form variants. Children without their own
|
|
274
|
+
* `custom` prop inherit this from the nearest motion ancestor — matching
|
|
275
|
+
* framer-motion's variant-tree custom propagation.
|
|
276
|
+
*/
|
|
277
|
+
custom?: unknown;
|
|
225
278
|
/** Initial state of the animation (object or variant key) */
|
|
226
279
|
initial?: MotionInitial;
|
|
227
280
|
/** Target state of the animation (object or variant key) */
|
|
@@ -240,6 +293,8 @@ export type MotionProps = {
|
|
|
240
293
|
whileDrag?: MotionWhileDrag;
|
|
241
294
|
/** In-view interaction animation - animates when element enters viewport */
|
|
242
295
|
whileInView?: MotionWhileInView;
|
|
296
|
+
/** IntersectionObserver options for `whileInView` (once / root / margin / amount) */
|
|
297
|
+
viewport?: MotionViewport;
|
|
243
298
|
/** Called right before a main animate transition starts */
|
|
244
299
|
onAnimationStart?: MotionAnimationStart;
|
|
245
300
|
/** Called after a main animate transition completes */
|
package/dist/utils/inView.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AnimationOptions, type DOMKeyframesDefinition } from 'motion';
|
|
2
2
|
import { type Readable } from 'svelte/store';
|
|
3
|
+
import type { MotionViewport } from '../types.js';
|
|
3
4
|
import { type ElementOrGetter } from './dom.js';
|
|
4
5
|
/**
|
|
5
6
|
* Split a whileInView definition into keyframes and an optional nested transition.
|
|
@@ -57,6 +58,7 @@ export declare const computeInViewBaseline: (el: HTMLElement, opts: {
|
|
|
57
58
|
* @param mergedTransition Root/component merged transition.
|
|
58
59
|
* @param callbacks Optional lifecycle callbacks for in-view start/end and animation completion.
|
|
59
60
|
* @param baselineSources Optional sources used to compute baseline.
|
|
61
|
+
* @param viewport Optional IntersectionObserver options. `amount` defaults to `0` (any pixel visible).
|
|
60
62
|
* @returns Cleanup function to stop observing.
|
|
61
63
|
* @example
|
|
62
64
|
* const cleanup = attachWhileInView(
|
|
@@ -67,7 +69,8 @@ export declare const computeInViewBaseline: (el: HTMLElement, opts: {
|
|
|
67
69
|
* onStart: () => console.log('Entered viewport'),
|
|
68
70
|
* onEnd: () => console.log('Left viewport')
|
|
69
71
|
* },
|
|
70
|
-
* { initial: { opacity: 0, y: 50 } }
|
|
72
|
+
* { initial: { opacity: 0, y: 50 } },
|
|
73
|
+
* { once: true, amount: 0.5 }
|
|
71
74
|
* )
|
|
72
75
|
* // Later: cleanup() to stop observing
|
|
73
76
|
*/
|
|
@@ -78,7 +81,7 @@ export declare const attachWhileInView: (el: HTMLElement, whileInView: Record<st
|
|
|
78
81
|
}, baselineSources?: {
|
|
79
82
|
initial?: Record<string, unknown>;
|
|
80
83
|
animate?: Record<string, unknown>;
|
|
81
|
-
}) => (() => void);
|
|
84
|
+
}, viewport?: MotionViewport) => (() => void);
|
|
82
85
|
/**
|
|
83
86
|
* Options accepted by `useInView`.
|
|
84
87
|
*/
|
package/dist/utils/inView.js
CHANGED
|
@@ -122,6 +122,7 @@ export const computeInViewBaseline = (el, opts) => {
|
|
|
122
122
|
* @param mergedTransition Root/component merged transition.
|
|
123
123
|
* @param callbacks Optional lifecycle callbacks for in-view start/end and animation completion.
|
|
124
124
|
* @param baselineSources Optional sources used to compute baseline.
|
|
125
|
+
* @param viewport Optional IntersectionObserver options. `amount` defaults to `0` (any pixel visible).
|
|
125
126
|
* @returns Cleanup function to stop observing.
|
|
126
127
|
* @example
|
|
127
128
|
* const cleanup = attachWhileInView(
|
|
@@ -132,14 +133,18 @@ export const computeInViewBaseline = (el, opts) => {
|
|
|
132
133
|
* onStart: () => console.log('Entered viewport'),
|
|
133
134
|
* onEnd: () => console.log('Left viewport')
|
|
134
135
|
* },
|
|
135
|
-
* { initial: { opacity: 0, y: 50 } }
|
|
136
|
+
* { initial: { opacity: 0, y: 50 } },
|
|
137
|
+
* { once: true, amount: 0.5 }
|
|
136
138
|
* )
|
|
137
139
|
* // Later: cleanup() to stop observing
|
|
138
140
|
*/
|
|
139
|
-
export const attachWhileInView = (el, whileInView, mergedTransition, callbacks, baselineSources) => {
|
|
141
|
+
export const attachWhileInView = (el, whileInView, mergedTransition, callbacks, baselineSources, viewport) => {
|
|
140
142
|
if (!whileInView)
|
|
141
143
|
return () => { };
|
|
142
|
-
|
|
144
|
+
let latched = false;
|
|
145
|
+
const stop = motionInView(el, () => {
|
|
146
|
+
if (latched)
|
|
147
|
+
return;
|
|
143
148
|
const inViewBaseline = computeInViewBaseline(el, {
|
|
144
149
|
initial: baselineSources?.initial,
|
|
145
150
|
animate: baselineSources?.animate,
|
|
@@ -155,13 +160,28 @@ export const attachWhileInView = (el, whileInView, mergedTransition, callbacks,
|
|
|
155
160
|
.catch(() => {
|
|
156
161
|
/* animation cancelled — skip completion callback */
|
|
157
162
|
});
|
|
163
|
+
if (viewport?.once) {
|
|
164
|
+
// Latch on first entry. Don't return an exit handler so the
|
|
165
|
+
// element holds its in-view state and we never animate back.
|
|
166
|
+
// Stop observing entirely after the entry handler returns.
|
|
167
|
+
latched = true;
|
|
168
|
+
queueMicrotask(stop);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
158
171
|
return () => {
|
|
159
172
|
if (Object.keys(inViewBaseline).length > 0) {
|
|
160
173
|
animate(el, inViewBaseline, mergedTransition);
|
|
161
174
|
}
|
|
162
175
|
callbacks?.onEnd?.();
|
|
163
176
|
};
|
|
164
|
-
}, {
|
|
177
|
+
}, {
|
|
178
|
+
root: viewport?.root,
|
|
179
|
+
// framer-motion types `margin` as a CSS-shorthand template literal;
|
|
180
|
+
// we expose plain `string` so consumers can pass any computed value.
|
|
181
|
+
margin: viewport?.margin,
|
|
182
|
+
amount: viewport?.amount ?? 0
|
|
183
|
+
});
|
|
184
|
+
return stop;
|
|
165
185
|
};
|
|
166
186
|
/**
|
|
167
187
|
* Returns a Svelte readable store that tracks whether `target` is in the
|
package/dist/utils/variants.d.ts
CHANGED
|
@@ -3,80 +3,74 @@ import type { DOMKeyframesDefinition } from 'motion';
|
|
|
3
3
|
/**
|
|
4
4
|
* Resolves a variant key to its keyframes definition.
|
|
5
5
|
*
|
|
6
|
-
* Looks up
|
|
7
|
-
*
|
|
6
|
+
* Looks up `key` in `variants`. When the entry is a function (dynamic
|
|
7
|
+
* variant), it's invoked with `custom` to produce keyframes — matching
|
|
8
|
+
* framer-motion's per-instance variant pattern.
|
|
8
9
|
*
|
|
9
10
|
* @param variants - The variants object containing named animation states.
|
|
10
11
|
* @param key - The variant key to look up.
|
|
11
|
-
* @
|
|
12
|
+
* @param custom - Value forwarded to function-form variants. Pass-through
|
|
13
|
+
* `undefined` when no `custom` is in scope; the dynamic variant itself
|
|
14
|
+
* decides how to handle the absent input.
|
|
15
|
+
* @returns The keyframes definition for the variant, or `undefined` if the
|
|
16
|
+
* key is missing.
|
|
12
17
|
*
|
|
13
18
|
* @example
|
|
14
|
-
* ```
|
|
19
|
+
* ```ts
|
|
15
20
|
* const variants = {
|
|
16
|
-
* visible: {
|
|
17
|
-
* hidden:
|
|
21
|
+
* visible: (i: number) => ({ x: i * 50 }),
|
|
22
|
+
* hidden: { opacity: 0 }
|
|
18
23
|
* }
|
|
19
|
-
* resolveVariant(variants, 'visible') // {
|
|
20
|
-
* resolveVariant(variants, '
|
|
21
|
-
* resolveVariant(undefined, 'visible')
|
|
24
|
+
* resolveVariant(variants, 'visible', 3) // { x: 150 }
|
|
25
|
+
* resolveVariant(variants, 'hidden') // { opacity: 0 }
|
|
26
|
+
* resolveVariant(undefined, 'visible') // undefined
|
|
22
27
|
* ```
|
|
23
28
|
*/
|
|
24
|
-
export declare const resolveVariant: (variants: Variants | undefined, key: string | undefined) => DOMKeyframesDefinition | undefined;
|
|
29
|
+
export declare const resolveVariant: (variants: Variants | undefined, key: string | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
25
30
|
/**
|
|
26
31
|
* Resolves the initial prop to keyframes, handling variant keys and `initial={false}`.
|
|
27
32
|
*
|
|
28
|
-
* When `initial` is a string, looks it up in the variants object
|
|
29
|
-
*
|
|
33
|
+
* When `initial` is a string, looks it up in the variants object (invoking
|
|
34
|
+
* dynamic variants with `custom`). When `initial={false}`, returns `false` to
|
|
35
|
+
* skip the initial animation. Otherwise returns the keyframes directly.
|
|
30
36
|
*
|
|
31
37
|
* @param initial - The initial prop value (keyframes, variant key, false, or undefined).
|
|
32
38
|
* @param variants - The variants object for resolving string keys.
|
|
39
|
+
* @param custom - Forwarded to function-form variants.
|
|
33
40
|
* @returns Keyframes definition, `false` to skip animation, or undefined.
|
|
34
41
|
*
|
|
35
42
|
* @example
|
|
36
|
-
* ```
|
|
37
|
-
* const variants = { hidden: {
|
|
38
|
-
* resolveInitial('hidden', variants)
|
|
39
|
-
* resolveInitial({ x: 0 }, variants)
|
|
40
|
-
* resolveInitial(false, variants)
|
|
41
|
-
* resolveInitial(undefined, variants)
|
|
43
|
+
* ```ts
|
|
44
|
+
* const variants = { hidden: (i: number) => ({ x: -i * 100 }) }
|
|
45
|
+
* resolveInitial('hidden', variants, 2) // { x: -200 }
|
|
46
|
+
* resolveInitial({ x: 0 }, variants) // { x: 0 }
|
|
47
|
+
* resolveInitial(false, variants) // false
|
|
48
|
+
* resolveInitial(undefined, variants) // undefined
|
|
42
49
|
* ```
|
|
43
50
|
*/
|
|
44
|
-
export declare const resolveInitial: (initial: MotionInitial, variants: Variants | undefined) => DOMKeyframesDefinition | false | undefined;
|
|
51
|
+
export declare const resolveInitial: (initial: MotionInitial, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | false | undefined;
|
|
45
52
|
/**
|
|
46
53
|
* Resolves the animate prop to keyframes, handling variant keys.
|
|
47
54
|
*
|
|
48
|
-
* When `animate` is a string, looks it up in the variants object
|
|
49
|
-
* Otherwise returns the keyframes directly.
|
|
55
|
+
* When `animate` is a string, looks it up in the variants object (invoking
|
|
56
|
+
* dynamic variants with `custom`). Otherwise returns the keyframes directly.
|
|
50
57
|
*
|
|
51
58
|
* @param animate - The animate prop value (keyframes, variant key, or undefined).
|
|
52
59
|
* @param variants - The variants object for resolving string keys.
|
|
60
|
+
* @param custom - Forwarded to function-form variants.
|
|
53
61
|
* @returns Keyframes definition or undefined.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```typescript
|
|
57
|
-
* const variants = { visible: { opacity: 1 } }
|
|
58
|
-
* resolveAnimate('visible', variants) // { opacity: 1 }
|
|
59
|
-
* resolveAnimate({ scale: 1.2 }, variants) // { scale: 1.2 }
|
|
60
|
-
* resolveAnimate(undefined, variants) // undefined
|
|
61
|
-
* ```
|
|
62
62
|
*/
|
|
63
|
-
export declare const resolveAnimate: (animate: MotionAnimate, variants: Variants | undefined) => DOMKeyframesDefinition | undefined;
|
|
63
|
+
export declare const resolveAnimate: (animate: MotionAnimate, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
|
64
64
|
/**
|
|
65
65
|
* Resolves the exit prop to keyframes, handling variant keys.
|
|
66
66
|
*
|
|
67
|
-
* When `exit` is a string, looks it up in the variants object
|
|
68
|
-
* Otherwise returns the keyframes directly.
|
|
67
|
+
* When `exit` is a string, looks it up in the variants object (invoking
|
|
68
|
+
* dynamic variants with `custom`). Otherwise returns the keyframes directly.
|
|
69
|
+
* Used by AnimatePresence for exit animations.
|
|
69
70
|
*
|
|
70
71
|
* @param exit - The exit prop value (keyframes, variant key, or undefined).
|
|
71
72
|
* @param variants - The variants object for resolving string keys.
|
|
73
|
+
* @param custom - Forwarded to function-form variants.
|
|
72
74
|
* @returns Keyframes definition or undefined.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* const variants = { hidden: { opacity: 0 } }
|
|
77
|
-
* resolveExit('hidden', variants) // { opacity: 0 }
|
|
78
|
-
* resolveExit({ y: -100 }, variants) // { y: -100 }
|
|
79
|
-
* resolveExit(undefined, variants) // undefined
|
|
80
|
-
* ```
|
|
81
75
|
*/
|
|
82
|
-
export declare const resolveExit: (exit: MotionExit, variants: Variants | undefined) => DOMKeyframesDefinition | undefined;
|
|
76
|
+
export declare const resolveExit: (exit: MotionExit, variants: Variants | undefined, custom?: unknown) => DOMKeyframesDefinition | undefined;
|
package/dist/utils/variants.js
CHANGED
|
@@ -1,104 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolves a variant key to its keyframes definition.
|
|
3
3
|
*
|
|
4
|
-
* Looks up
|
|
5
|
-
*
|
|
4
|
+
* Looks up `key` in `variants`. When the entry is a function (dynamic
|
|
5
|
+
* variant), it's invoked with `custom` to produce keyframes — matching
|
|
6
|
+
* framer-motion's per-instance variant pattern.
|
|
6
7
|
*
|
|
7
8
|
* @param variants - The variants object containing named animation states.
|
|
8
9
|
* @param key - The variant key to look up.
|
|
9
|
-
* @
|
|
10
|
+
* @param custom - Value forwarded to function-form variants. Pass-through
|
|
11
|
+
* `undefined` when no `custom` is in scope; the dynamic variant itself
|
|
12
|
+
* decides how to handle the absent input.
|
|
13
|
+
* @returns The keyframes definition for the variant, or `undefined` if the
|
|
14
|
+
* key is missing.
|
|
10
15
|
*
|
|
11
16
|
* @example
|
|
12
|
-
* ```
|
|
17
|
+
* ```ts
|
|
13
18
|
* const variants = {
|
|
14
|
-
* visible: {
|
|
15
|
-
* hidden:
|
|
19
|
+
* visible: (i: number) => ({ x: i * 50 }),
|
|
20
|
+
* hidden: { opacity: 0 }
|
|
16
21
|
* }
|
|
17
|
-
* resolveVariant(variants, 'visible') // {
|
|
18
|
-
* resolveVariant(variants, '
|
|
19
|
-
* resolveVariant(undefined, 'visible')
|
|
22
|
+
* resolveVariant(variants, 'visible', 3) // { x: 150 }
|
|
23
|
+
* resolveVariant(variants, 'hidden') // { opacity: 0 }
|
|
24
|
+
* resolveVariant(undefined, 'visible') // undefined
|
|
20
25
|
* ```
|
|
21
26
|
*/
|
|
22
|
-
export const resolveVariant = (variants, key) => {
|
|
27
|
+
export const resolveVariant = (variants, key, custom) => {
|
|
23
28
|
if (!variants || !key)
|
|
24
29
|
return undefined;
|
|
25
|
-
|
|
30
|
+
const entry = variants[key];
|
|
31
|
+
if (typeof entry === 'function')
|
|
32
|
+
return entry(custom);
|
|
33
|
+
return entry;
|
|
26
34
|
};
|
|
27
35
|
/**
|
|
28
36
|
* Resolves the initial prop to keyframes, handling variant keys and `initial={false}`.
|
|
29
37
|
*
|
|
30
|
-
* When `initial` is a string, looks it up in the variants object
|
|
31
|
-
*
|
|
38
|
+
* When `initial` is a string, looks it up in the variants object (invoking
|
|
39
|
+
* dynamic variants with `custom`). When `initial={false}`, returns `false` to
|
|
40
|
+
* skip the initial animation. Otherwise returns the keyframes directly.
|
|
32
41
|
*
|
|
33
42
|
* @param initial - The initial prop value (keyframes, variant key, false, or undefined).
|
|
34
43
|
* @param variants - The variants object for resolving string keys.
|
|
44
|
+
* @param custom - Forwarded to function-form variants.
|
|
35
45
|
* @returns Keyframes definition, `false` to skip animation, or undefined.
|
|
36
46
|
*
|
|
37
47
|
* @example
|
|
38
|
-
* ```
|
|
39
|
-
* const variants = { hidden: {
|
|
40
|
-
* resolveInitial('hidden', variants)
|
|
41
|
-
* resolveInitial({ x: 0 }, variants)
|
|
42
|
-
* resolveInitial(false, variants)
|
|
43
|
-
* resolveInitial(undefined, variants)
|
|
48
|
+
* ```ts
|
|
49
|
+
* const variants = { hidden: (i: number) => ({ x: -i * 100 }) }
|
|
50
|
+
* resolveInitial('hidden', variants, 2) // { x: -200 }
|
|
51
|
+
* resolveInitial({ x: 0 }, variants) // { x: 0 }
|
|
52
|
+
* resolveInitial(false, variants) // false
|
|
53
|
+
* resolveInitial(undefined, variants) // undefined
|
|
44
54
|
* ```
|
|
45
55
|
*/
|
|
46
|
-
export const resolveInitial = (initial, variants) => {
|
|
56
|
+
export const resolveInitial = (initial, variants, custom) => {
|
|
47
57
|
if (initial === false)
|
|
48
58
|
return false;
|
|
49
59
|
if (initial === undefined)
|
|
50
60
|
return undefined;
|
|
51
61
|
if (typeof initial === 'string')
|
|
52
|
-
return resolveVariant(variants, initial);
|
|
62
|
+
return resolveVariant(variants, initial, custom);
|
|
53
63
|
return initial;
|
|
54
64
|
};
|
|
55
65
|
/**
|
|
56
66
|
* Resolves the animate prop to keyframes, handling variant keys.
|
|
57
67
|
*
|
|
58
|
-
* When `animate` is a string, looks it up in the variants object
|
|
59
|
-
* Otherwise returns the keyframes directly.
|
|
68
|
+
* When `animate` is a string, looks it up in the variants object (invoking
|
|
69
|
+
* dynamic variants with `custom`). Otherwise returns the keyframes directly.
|
|
60
70
|
*
|
|
61
71
|
* @param animate - The animate prop value (keyframes, variant key, or undefined).
|
|
62
72
|
* @param variants - The variants object for resolving string keys.
|
|
73
|
+
* @param custom - Forwarded to function-form variants.
|
|
63
74
|
* @returns Keyframes definition or undefined.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* const variants = { visible: { opacity: 1 } }
|
|
68
|
-
* resolveAnimate('visible', variants) // { opacity: 1 }
|
|
69
|
-
* resolveAnimate({ scale: 1.2 }, variants) // { scale: 1.2 }
|
|
70
|
-
* resolveAnimate(undefined, variants) // undefined
|
|
71
|
-
* ```
|
|
72
75
|
*/
|
|
73
|
-
export const resolveAnimate = (animate, variants) => {
|
|
76
|
+
export const resolveAnimate = (animate, variants, custom) => {
|
|
74
77
|
if (animate === undefined)
|
|
75
78
|
return undefined;
|
|
76
79
|
if (typeof animate === 'string')
|
|
77
|
-
return resolveVariant(variants, animate);
|
|
80
|
+
return resolveVariant(variants, animate, custom);
|
|
78
81
|
return animate;
|
|
79
82
|
};
|
|
80
83
|
/**
|
|
81
84
|
* Resolves the exit prop to keyframes, handling variant keys.
|
|
82
85
|
*
|
|
83
|
-
* When `exit` is a string, looks it up in the variants object
|
|
84
|
-
* Otherwise returns the keyframes directly.
|
|
86
|
+
* When `exit` is a string, looks it up in the variants object (invoking
|
|
87
|
+
* dynamic variants with `custom`). Otherwise returns the keyframes directly.
|
|
88
|
+
* Used by AnimatePresence for exit animations.
|
|
85
89
|
*
|
|
86
90
|
* @param exit - The exit prop value (keyframes, variant key, or undefined).
|
|
87
91
|
* @param variants - The variants object for resolving string keys.
|
|
92
|
+
* @param custom - Forwarded to function-form variants.
|
|
88
93
|
* @returns Keyframes definition or undefined.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* const variants = { hidden: { opacity: 0 } }
|
|
93
|
-
* resolveExit('hidden', variants) // { opacity: 0 }
|
|
94
|
-
* resolveExit({ y: -100 }, variants) // { y: -100 }
|
|
95
|
-
* resolveExit(undefined, variants) // undefined
|
|
96
|
-
* ```
|
|
97
94
|
*/
|
|
98
|
-
export const resolveExit = (exit, variants) => {
|
|
95
|
+
export const resolveExit = (exit, variants, custom) => {
|
|
99
96
|
if (exit === undefined)
|
|
100
97
|
return undefined;
|
|
101
98
|
if (typeof exit === 'string')
|
|
102
|
-
return resolveVariant(variants, exit);
|
|
99
|
+
return resolveVariant(variants, exit, custom);
|
|
103
100
|
return exit;
|
|
104
101
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-motion",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|