@humanspeak/svelte-motion 0.4.5 → 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 -13
- package/dist/components/LayoutGroup.svelte +68 -0
- package/dist/components/LayoutGroup.svelte.d.ts +9 -0
- package/dist/components/layoutGroup.context.d.ts +37 -0
- package/dist/components/layoutGroup.context.js +41 -0
- package/dist/html/_MotionContainer.svelte +45 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- 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 +5 -5
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`)
|
|
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
|
|
|
@@ -280,7 +280,6 @@ Validated against current source and test suite (local run):
|
|
|
280
280
|
|
|
281
281
|
## Known gaps vs Framer Motion
|
|
282
282
|
|
|
283
|
-
- No shared layout API (`layoutId`, `LayoutGroup`).
|
|
284
283
|
- No pan gesture API (`whilePan`, `onPan*`).
|
|
285
284
|
- `whileInView` does not yet expose Framer-style viewport options.
|
|
286
285
|
- `MotionConfig` currently only provides `transition` defaults.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import {
|
|
4
|
+
chainLayoutGroupId,
|
|
5
|
+
getLayoutGroupContext,
|
|
6
|
+
setLayoutGroupContext
|
|
7
|
+
} from './layoutGroup.context'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Scope `layoutId` shared-layout animations to a subtree.
|
|
11
|
+
*
|
|
12
|
+
* Wrap a region in `<LayoutGroup id="…">` so descendants' `layoutId`
|
|
13
|
+
* snapshots / consumes are prefixed with the group's id. Two groups
|
|
14
|
+
* containing the same `layoutId` values won't cross-animate — useful
|
|
15
|
+
* for repeated UI patterns (multiple tab indicators, kanban columns,
|
|
16
|
+
* sibling carousels) where each instance should animate independently.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors framer-motion's `<LayoutGroup>` (`inherit` defaults to true
|
|
19
|
+
* — descendants chain onto the parent group's id, so nested groups
|
|
20
|
+
* yield `"parent-child"`).
|
|
21
|
+
*
|
|
22
|
+
* @prop id Stable identifier for this group's scope. When omitted,
|
|
23
|
+
* the LayoutGroup is a transparent grouping with no own id
|
|
24
|
+
* (still useful for `inherit={false}` to break out of an outer
|
|
25
|
+
* group's scope, e.g. an embedded widget).
|
|
26
|
+
* @prop inherit `true` (default) — chain onto the parent group's id.
|
|
27
|
+
* `'id'` — same as `true` in this implementation; accepted for
|
|
28
|
+
* drop-in compatibility with framer-motion examples. In
|
|
29
|
+
* framer-motion, `'id'` inherits the id but breaks the internal
|
|
30
|
+
* projection-tree group. We don't have a projection-tree group
|
|
31
|
+
* (our snapshot/consume registry doesn't need sibling
|
|
32
|
+
* coordination), so `'id'` and `true` behave identically.
|
|
33
|
+
* `false` — start a fresh scope, ignoring any outer LayoutGroup.
|
|
34
|
+
* @prop children Slot rendered inside the group context.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```svelte
|
|
38
|
+
* <LayoutGroup id="tabs-a">
|
|
39
|
+
* <Tabs />
|
|
40
|
+
* </LayoutGroup>
|
|
41
|
+
* <LayoutGroup id="tabs-b">
|
|
42
|
+
* <Tabs /> <!-- same layoutId values, independent animations -->
|
|
43
|
+
* </LayoutGroup>
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @see https://motion.dev/docs/react-layout-animations#scoped-layout-animations
|
|
47
|
+
*/
|
|
48
|
+
const {
|
|
49
|
+
id,
|
|
50
|
+
inherit = true,
|
|
51
|
+
children
|
|
52
|
+
}: {
|
|
53
|
+
id?: string
|
|
54
|
+
inherit?: boolean | 'id'
|
|
55
|
+
children?: Snippet
|
|
56
|
+
} = $props()
|
|
57
|
+
|
|
58
|
+
// setContext is one-shot at component init, so reading `id` and `inherit`
|
|
59
|
+
// here captures their initial values intentionally — the scope id is
|
|
60
|
+
// fixed for this subtree's lifetime. The warning would only matter if we
|
|
61
|
+
// wanted descendants to react to prop changes, which we explicitly don't.
|
|
62
|
+
// svelte-ignore state_referenced_locally
|
|
63
|
+
const shouldInheritId = inherit === true || inherit === 'id'
|
|
64
|
+
const effectiveId = shouldInheritId ? chainLayoutGroupId(getLayoutGroupContext(), id) : id
|
|
65
|
+
setLayoutGroupContext(effectiveId)
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
{@render children?.()}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
id?: string;
|
|
4
|
+
inherit?: boolean | 'id';
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const LayoutGroup: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type LayoutGroup = ReturnType<typeof LayoutGroup>;
|
|
9
|
+
export default LayoutGroup;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier for a `<LayoutGroup>` subtree. Descendants prefix their
|
|
3
|
+
* `layoutId` lookups with this so two `<LayoutGroup>`s containing the
|
|
4
|
+
* same `layoutId` values don't cross-animate.
|
|
5
|
+
*
|
|
6
|
+
* `undefined` means "no enclosing LayoutGroup" — the descendant uses
|
|
7
|
+
* `layoutId` verbatim against the global registry, preserving the
|
|
8
|
+
* existing un-grouped behaviour.
|
|
9
|
+
*/
|
|
10
|
+
export type LayoutGroupContext = string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Publish a LayoutGroup id for descendants. Called by `<LayoutGroup>`
|
|
13
|
+
* after computing its own (possibly inherited and chained) id.
|
|
14
|
+
*/
|
|
15
|
+
export declare const setLayoutGroupContext: (id: LayoutGroupContext) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Read the nearest LayoutGroup id, or `undefined` if not inside one.
|
|
18
|
+
*
|
|
19
|
+
* `_MotionContainer.svelte` reads this to prefix `layoutId` when
|
|
20
|
+
* snapshotting and consuming against the registry, so shared-layout
|
|
21
|
+
* animations stay scoped to the surrounding group.
|
|
22
|
+
*/
|
|
23
|
+
export declare const getLayoutGroupContext: () => LayoutGroupContext;
|
|
24
|
+
/**
|
|
25
|
+
* Combine a parent group's id with a descendant LayoutGroup's own id
|
|
26
|
+
* to produce the effective scope id. Mirrors framer-motion's chaining
|
|
27
|
+
* (`"parent-id"` + `"-"` + `"own-id"`).
|
|
28
|
+
*
|
|
29
|
+
* Either side can be `undefined`; the result is the other one, or
|
|
30
|
+
* `undefined` if both are absent.
|
|
31
|
+
*/
|
|
32
|
+
export declare const chainLayoutGroupId: (parent: LayoutGroupContext, own: string | undefined) => LayoutGroupContext;
|
|
33
|
+
/**
|
|
34
|
+
* Apply a LayoutGroup scope to a raw `layoutId` for registry lookups.
|
|
35
|
+
* Returns the un-prefixed id when no group is in scope.
|
|
36
|
+
*/
|
|
37
|
+
export declare const scopeLayoutId: (groupId: LayoutGroupContext, layoutId: string) => string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const LAYOUT_GROUP_CONTEXT_KEY = Symbol('layout-group');
|
|
3
|
+
/**
|
|
4
|
+
* Publish a LayoutGroup id for descendants. Called by `<LayoutGroup>`
|
|
5
|
+
* after computing its own (possibly inherited and chained) id.
|
|
6
|
+
*/
|
|
7
|
+
export const setLayoutGroupContext = (id) => {
|
|
8
|
+
setContext(LAYOUT_GROUP_CONTEXT_KEY, id);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Read the nearest LayoutGroup id, or `undefined` if not inside one.
|
|
12
|
+
*
|
|
13
|
+
* `_MotionContainer.svelte` reads this to prefix `layoutId` when
|
|
14
|
+
* snapshotting and consuming against the registry, so shared-layout
|
|
15
|
+
* animations stay scoped to the surrounding group.
|
|
16
|
+
*/
|
|
17
|
+
export const getLayoutGroupContext = () => {
|
|
18
|
+
return getContext(LAYOUT_GROUP_CONTEXT_KEY);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Combine a parent group's id with a descendant LayoutGroup's own id
|
|
22
|
+
* to produce the effective scope id. Mirrors framer-motion's chaining
|
|
23
|
+
* (`"parent-id"` + `"-"` + `"own-id"`).
|
|
24
|
+
*
|
|
25
|
+
* Either side can be `undefined`; the result is the other one, or
|
|
26
|
+
* `undefined` if both are absent.
|
|
27
|
+
*/
|
|
28
|
+
export const chainLayoutGroupId = (parent, own) => {
|
|
29
|
+
if (!parent)
|
|
30
|
+
return own;
|
|
31
|
+
if (!own)
|
|
32
|
+
return parent;
|
|
33
|
+
return `${parent}-${own}`;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Apply a LayoutGroup scope to a raw `layoutId` for registry lookups.
|
|
37
|
+
* Returns the un-prefixed id when no group is in scope.
|
|
38
|
+
*/
|
|
39
|
+
export const scopeLayoutId = (groupId, layoutId) => {
|
|
40
|
+
return groupId ? `${groupId}::${layoutId}` : layoutId;
|
|
41
|
+
};
|
|
@@ -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,
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
getLayoutScrollContainerRef,
|
|
70
70
|
setLayoutScrollContainer
|
|
71
71
|
} from '../components/layoutScroll.context'
|
|
72
|
+
import { getLayoutGroupContext, scopeLayoutId } from '../components/layoutGroup.context'
|
|
72
73
|
|
|
73
74
|
type Props = MotionProps & {
|
|
74
75
|
children?: Snippet
|
|
@@ -147,6 +148,15 @@
|
|
|
147
148
|
// Get layoutId registry (provided by AnimatePresence or a parent LayoutGroup)
|
|
148
149
|
const layoutIdRegistry = getLayoutIdRegistry()
|
|
149
150
|
|
|
151
|
+
// Scope layoutId by the surrounding <LayoutGroup>, so identical
|
|
152
|
+
// layoutId values in two sibling groups don't cross-animate (#311).
|
|
153
|
+
// Undefined when no group is in scope — descendants behave exactly
|
|
154
|
+
// as before relative to the global registry.
|
|
155
|
+
const layoutGroupId = getLayoutGroupContext()
|
|
156
|
+
const scopedLayoutId = $derived(
|
|
157
|
+
layoutIdProp ? scopeLayoutId(layoutGroupId, layoutIdProp) : undefined
|
|
158
|
+
)
|
|
159
|
+
|
|
150
160
|
// Capture the ancestor `layoutScroll` chain BEFORE we potentially shadow
|
|
151
161
|
// the context with ourselves below — this element's own FLIP measurements
|
|
152
162
|
// must resolve against the *ancestors*' scroll containers, not against
|
|
@@ -258,9 +268,9 @@
|
|
|
258
268
|
// On cleanup (before DOM removal), push last-known rect to registry
|
|
259
269
|
return () => {
|
|
260
270
|
cancelAnimationFrame(rafId)
|
|
261
|
-
if (layoutIdLastRect &&
|
|
271
|
+
if (layoutIdLastRect && scopedLayoutId) {
|
|
262
272
|
layoutIdRegistry.snapshot(
|
|
263
|
-
|
|
273
|
+
scopedLayoutId,
|
|
264
274
|
layoutIdLastRect,
|
|
265
275
|
(mergedTransition ?? {}) as AnimationOptions
|
|
266
276
|
)
|
|
@@ -436,6 +446,19 @@
|
|
|
436
446
|
)
|
|
437
447
|
const resolvedExit = $derived(resolveExit(exitProp, variantsProp, effectiveCustom))
|
|
438
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
|
+
|
|
439
462
|
// Extract keyframes from resolved initial, handling initial={false}
|
|
440
463
|
const initialKeyframes = $derived(
|
|
441
464
|
filterReducedMotionKeyframes(
|
|
@@ -447,7 +470,12 @@
|
|
|
447
470
|
// Derived attributes to keep both branches in sync (focusability, data flags, style, class)
|
|
448
471
|
const derivedAttrs = $derived<Record<string, unknown>>({
|
|
449
472
|
...(rest as Record<string, unknown>),
|
|
450
|
-
|
|
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) &&
|
|
451
479
|
!isNativelyFocusable(tag, rest as Record<string, unknown>) &&
|
|
452
480
|
((rest as Record<string, unknown>)?.tabindex ??
|
|
453
481
|
(rest as Record<string, unknown>)?.tabIndex ??
|
|
@@ -531,7 +559,7 @@
|
|
|
531
559
|
directionLock: !!dragDirectionLockProp,
|
|
532
560
|
listener: dragListenerProp !== false,
|
|
533
561
|
controls,
|
|
534
|
-
whileDrag:
|
|
562
|
+
whileDrag: resolvedWhileDrag as MotionWhileDrag,
|
|
535
563
|
mergedTransition: (mergedTransition ?? {}) as AnimationOptions,
|
|
536
564
|
callbacks: {
|
|
537
565
|
onStart: onDragStartProp as (e: PointerEvent, info: DragInfo) => void,
|
|
@@ -773,9 +801,9 @@
|
|
|
773
801
|
// Shared layout animation via layoutId.
|
|
774
802
|
// On mount, consume the previous snapshot and FLIP from its position.
|
|
775
803
|
$effect(() => {
|
|
776
|
-
if (!(element &&
|
|
804
|
+
if (!(element && scopedLayoutId && layoutIdRegistry && isLoaded === 'ready')) return
|
|
777
805
|
|
|
778
|
-
const prev = layoutIdRegistry.consume(
|
|
806
|
+
const prev = layoutIdRegistry.consume(scopedLayoutId)
|
|
779
807
|
if (!prev) return // First appearance, no animation needed
|
|
780
808
|
|
|
781
809
|
const next = measureRect(element, resolveLayoutScrollAncestors())
|
|
@@ -791,18 +819,18 @@
|
|
|
791
819
|
|
|
792
820
|
// whileTap handling via motion-dom's press()
|
|
793
821
|
$effect(() => {
|
|
794
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
822
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileTap))) return
|
|
795
823
|
return attachWhileTap(
|
|
796
824
|
element!,
|
|
797
|
-
(
|
|
825
|
+
(resolvedWhileTap ?? {}) as Record<string, unknown>,
|
|
798
826
|
(resolvedInitial ?? {}) as Record<string, unknown>,
|
|
799
827
|
(resolvedAnimate ?? {}) as Record<string, unknown>,
|
|
800
828
|
{
|
|
801
829
|
onTapStart: onTapStartProp,
|
|
802
830
|
onTap: onTapProp,
|
|
803
831
|
onTapCancel: onTapCancelProp,
|
|
804
|
-
hoverDef: isNotEmpty(
|
|
805
|
-
? ((
|
|
832
|
+
hoverDef: isNotEmpty(resolvedWhileHover ?? {})
|
|
833
|
+
? ((resolvedWhileHover ?? {}) as Record<string, unknown>)
|
|
806
834
|
: undefined,
|
|
807
835
|
hoverFallbackTransition: (mergedTransition ?? {}) as AnimationOptions
|
|
808
836
|
}
|
|
@@ -811,10 +839,10 @@
|
|
|
811
839
|
|
|
812
840
|
// whileHover handling, gated to true-hover devices to avoid sticky states on touch
|
|
813
841
|
$effect(() => {
|
|
814
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
842
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileHover))) return
|
|
815
843
|
return attachWhileHover(
|
|
816
844
|
element!,
|
|
817
|
-
(
|
|
845
|
+
(resolvedWhileHover ?? {}) as Record<string, unknown>,
|
|
818
846
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
819
847
|
{ onStart: onHoverStartProp, onEnd: onHoverEndProp },
|
|
820
848
|
{
|
|
@@ -826,10 +854,10 @@
|
|
|
826
854
|
|
|
827
855
|
// whileFocus handling for keyboard focus interactions
|
|
828
856
|
$effect(() => {
|
|
829
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
857
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileFocus))) return
|
|
830
858
|
return attachWhileFocus(
|
|
831
859
|
element!,
|
|
832
|
-
(
|
|
860
|
+
(resolvedWhileFocus ?? {}) as Record<string, unknown>,
|
|
833
861
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
834
862
|
{ onStart: onFocusStartProp, onEnd: onFocusEndProp },
|
|
835
863
|
{
|
|
@@ -841,10 +869,10 @@
|
|
|
841
869
|
|
|
842
870
|
// whileInView handling for viewport intersection
|
|
843
871
|
$effect(() => {
|
|
844
|
-
if (!(element && isLoaded === 'ready' && isNotEmpty(
|
|
872
|
+
if (!(element && isLoaded === 'ready' && isNotEmpty(resolvedWhileInView))) return
|
|
845
873
|
return attachWhileInView(
|
|
846
874
|
element!,
|
|
847
|
-
(
|
|
875
|
+
(resolvedWhileInView ?? {}) as Record<string, unknown>,
|
|
848
876
|
(mergedTransition ?? {}) as AnimationOptions,
|
|
849
877
|
{
|
|
850
878
|
onStart: onInViewStartProp,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AnimatePresence from './components/AnimatePresence.svelte';
|
|
2
|
+
import LayoutGroup from './components/LayoutGroup.svelte';
|
|
2
3
|
import MotionConfig from './components/MotionConfig.svelte';
|
|
3
4
|
import PresenceChild from './components/PresenceChild.svelte';
|
|
4
5
|
export { motion } from './motion';
|
|
@@ -32,7 +33,7 @@ export { stringifyStyleObject } from './utils/styleObject';
|
|
|
32
33
|
export { styleString } from './utils/styleObject.svelte';
|
|
33
34
|
export { useTime } from './utils/time';
|
|
34
35
|
export { useTransform } from './utils/transform';
|
|
35
|
-
export { AnimatePresence, MotionConfig, PresenceChild };
|
|
36
|
+
export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
|
|
36
37
|
export { default as MotionA } from './html/A.svelte';
|
|
37
38
|
export { default as MotionAbbr } from './html/Abbr.svelte';
|
|
38
39
|
export { default as MotionAddress } from './html/Address.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AnimatePresence from './components/AnimatePresence.svelte';
|
|
2
|
+
import LayoutGroup from './components/LayoutGroup.svelte';
|
|
2
3
|
import MotionConfig from './components/MotionConfig.svelte';
|
|
3
4
|
import PresenceChild from './components/PresenceChild.svelte';
|
|
4
5
|
export { motion } from './motion';
|
|
@@ -29,7 +30,7 @@ export { stringifyStyleObject } from './utils/styleObject';
|
|
|
29
30
|
export { styleString } from './utils/styleObject.svelte';
|
|
30
31
|
export { useTime } from './utils/time';
|
|
31
32
|
export { useTransform } from './utils/transform';
|
|
32
|
-
export { AnimatePresence, MotionConfig, PresenceChild };
|
|
33
|
+
export { AnimatePresence, LayoutGroup, MotionConfig, PresenceChild };
|
|
33
34
|
// Named component exports — tree-shakeable alternative to the `motion` object
|
|
34
35
|
export { default as MotionA } from './html/A.svelte';
|
|
35
36
|
export { default as MotionAbbr } from './html/Abbr.svelte';
|
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",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"@tailwindcss/vite": "^4.3.0",
|
|
115
115
|
"@testing-library/jest-dom": "^6.9.1",
|
|
116
116
|
"@testing-library/svelte": "^5.3.1",
|
|
117
|
-
"@types/node": "^25.9.
|
|
117
|
+
"@types/node": "^25.9.1",
|
|
118
118
|
"@vitest/coverage-v8": "^4.1.6",
|
|
119
119
|
"eslint": "^10.4.0",
|
|
120
120
|
"eslint-config-prettier": "10.1.8",
|
|
@@ -135,16 +135,16 @@
|
|
|
135
135
|
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
136
136
|
"publint": "^0.3.21",
|
|
137
137
|
"runed": "0.37.1",
|
|
138
|
-
"svelte": "^5.55.
|
|
138
|
+
"svelte": "^5.55.8",
|
|
139
139
|
"svelte-check": "^4.4.8",
|
|
140
140
|
"svg-tags": "^1.0.0",
|
|
141
141
|
"tailwind-merge": "^3.6.0",
|
|
142
142
|
"tailwind-variants": "^3.2.2",
|
|
143
143
|
"tailwindcss": "^4.3.0",
|
|
144
144
|
"tailwindcss-animate": "^1.0.7",
|
|
145
|
-
"tsx": "^4.22.
|
|
145
|
+
"tsx": "^4.22.3",
|
|
146
146
|
"typescript": "^6.0.3",
|
|
147
|
-
"typescript-eslint": "^8.59.
|
|
147
|
+
"typescript-eslint": "^8.59.4",
|
|
148
148
|
"vite": "^8.0.13",
|
|
149
149
|
"vite-tsconfig-paths": "^6.1.1",
|
|
150
150
|
"vitest": "^4.1.6"
|