@humanspeak/svelte-motion 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AnimatePresence.svelte +10 -2
- package/dist/html/_MotionContainer.svelte +125 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/utils/presence.d.ts +39 -0
- package/dist/utils/presence.js +50 -0
- package/dist/utils/styleObject.d.ts +20 -1
- package/dist/utils/styleObject.js +52 -4
- package/dist/utils/styleObject.svelte.d.ts +27 -0
- package/dist/utils/styleObject.svelte.js +31 -0
- package/dist/utils/svg.d.ts +19 -0
- package/dist/utils/svg.js +83 -0
- package/package.json +8 -8
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createAnimatePresenceContext,
|
|
5
|
+
setAnimatePresenceContext,
|
|
6
|
+
setPresenceDepth
|
|
7
|
+
} from '../utils/presence'
|
|
4
8
|
import { pwLog } from '../utils/log'
|
|
5
9
|
|
|
6
10
|
/**
|
|
@@ -27,6 +31,10 @@
|
|
|
27
31
|
pwLog('[AnimatePresence] mounting', { initial, hasOnExitComplete: !!onExitComplete })
|
|
28
32
|
const context = createAnimatePresenceContext({ initial, onExitComplete })
|
|
29
33
|
setAnimatePresenceContext(context)
|
|
34
|
+
|
|
35
|
+
// Initialize presence depth to 0 for direct children
|
|
36
|
+
// Only direct children (depth 0) require explicit key props, matching Framer Motion behavior
|
|
37
|
+
setPresenceDepth(0)
|
|
30
38
|
</script>
|
|
31
39
|
|
|
32
40
|
<div class="animate-presence-container">
|
|
@@ -35,6 +43,6 @@
|
|
|
35
43
|
|
|
36
44
|
<style>
|
|
37
45
|
.animate-presence-container {
|
|
38
|
-
|
|
46
|
+
display: contents;
|
|
39
47
|
}
|
|
40
48
|
</style>
|
|
@@ -36,7 +36,12 @@
|
|
|
36
36
|
import type { SvelteHTMLElements } from 'svelte/elements'
|
|
37
37
|
import { mergeInlineStyles } from '../utils/style'
|
|
38
38
|
import { isNativelyFocusable } from '../utils/a11y'
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
usePresence,
|
|
41
|
+
getAnimatePresenceContext,
|
|
42
|
+
getPresenceDepth,
|
|
43
|
+
setPresenceDepth
|
|
44
|
+
} from '../utils/presence'
|
|
40
45
|
import { getInitialKeyframes } from '../utils/initial'
|
|
41
46
|
import { attachDrag } from '../utils/drag'
|
|
42
47
|
import { resolveInitial, resolveAnimate, resolveExit } from '../utils/variants'
|
|
@@ -47,7 +52,12 @@
|
|
|
47
52
|
getInitialFalseContext
|
|
48
53
|
} from '../components/variantContext.context'
|
|
49
54
|
import { writable } from 'svelte/store'
|
|
50
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
transformSVGPathProperties,
|
|
57
|
+
computeNormalizedSVGInitialAttrs,
|
|
58
|
+
isSVGTag,
|
|
59
|
+
SVG_NAMESPACE
|
|
60
|
+
} from '../utils/svg'
|
|
51
61
|
|
|
52
62
|
type Props = MotionProps & {
|
|
53
63
|
children?: Snippet
|
|
@@ -108,18 +118,33 @@
|
|
|
108
118
|
// Get presence context to check if we're inside AnimatePresence
|
|
109
119
|
const context = getAnimatePresenceContext()
|
|
110
120
|
|
|
111
|
-
//
|
|
112
|
-
|
|
121
|
+
// Get current presence depth (0 = direct child of AnimatePresence, undefined = not in AnimatePresence)
|
|
122
|
+
const presenceDepth = getPresenceDepth()
|
|
123
|
+
|
|
124
|
+
// Validate key prop only for direct children of AnimatePresence (depth 0)
|
|
125
|
+
// This matches Framer Motion behavior where only immediate children need keys
|
|
126
|
+
if (context && presenceDepth === 0 && !keyProp) {
|
|
113
127
|
throw new Error(
|
|
114
|
-
'motion elements
|
|
128
|
+
'motion elements that are direct children of AnimatePresence must have a `key` prop. ' +
|
|
115
129
|
'Example: <motion.div key="unique-id" />'
|
|
116
130
|
)
|
|
117
131
|
}
|
|
118
132
|
|
|
133
|
+
// Increment depth for descendants so nested motion elements don't require keys
|
|
134
|
+
if (presenceDepth !== undefined) {
|
|
135
|
+
setPresenceDepth(presenceDepth + 1)
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
// Use the provided key for presence tracking
|
|
120
139
|
// When not inside AnimatePresence, use a stable identifier based on component instance
|
|
121
140
|
const presenceKey = keyProp ?? `motion-${++keyCounter}`
|
|
122
141
|
|
|
142
|
+
// Track previous key for key-change detection (simulates React's key-based remounting)
|
|
143
|
+
// Using $state for idiomatic Svelte 5 reactivity
|
|
144
|
+
let keyTrackerPrev = $state(keyProp)
|
|
145
|
+
let keyTrackerIsTransitioning = $state(false)
|
|
146
|
+
let keyTransitionStopped = $state(false)
|
|
147
|
+
|
|
123
148
|
// Compute merged transition without mutating props to avoid effect write loops
|
|
124
149
|
const mergedTransition = $derived<AnimationOptions>(
|
|
125
150
|
mergeTransitions(
|
|
@@ -556,6 +581,92 @@
|
|
|
556
581
|
)
|
|
557
582
|
})
|
|
558
583
|
|
|
584
|
+
// Handle key prop changes inside AnimatePresence (simulates React's key-based remounting)
|
|
585
|
+
// When key changes, run exit → initial → animate sequence on the same element
|
|
586
|
+
$effect(() => {
|
|
587
|
+
// Access keyProp to create reactive dependency
|
|
588
|
+
const currentKey = keyProp
|
|
589
|
+
|
|
590
|
+
// Only handle key changes when:
|
|
591
|
+
// 1. We're inside AnimatePresence (context exists)
|
|
592
|
+
// 2. Element is ready (not during initial mount)
|
|
593
|
+
// 3. Key actually changed (not undefined → value on mount)
|
|
594
|
+
// 4. Not already transitioning
|
|
595
|
+
if (
|
|
596
|
+
!context ||
|
|
597
|
+
!element ||
|
|
598
|
+
isLoaded !== 'ready' ||
|
|
599
|
+
keyTrackerIsTransitioning ||
|
|
600
|
+
currentKey === keyTrackerPrev ||
|
|
601
|
+
keyTrackerPrev === undefined
|
|
602
|
+
) {
|
|
603
|
+
// Update prev for next comparison
|
|
604
|
+
if (currentKey !== keyTrackerPrev) {
|
|
605
|
+
keyTrackerPrev = currentKey
|
|
606
|
+
}
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
pwLog('[motion] key changed, running exit→initial→animate', {
|
|
611
|
+
prevKey: keyTrackerPrev,
|
|
612
|
+
newKey: currentKey
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
// Mark as transitioning to prevent re-entry
|
|
616
|
+
keyTrackerIsTransitioning = true
|
|
617
|
+
keyTrackerPrev = currentKey
|
|
618
|
+
|
|
619
|
+
// Run the key transition sequence
|
|
620
|
+
const runKeyTransition = async () => {
|
|
621
|
+
try {
|
|
622
|
+
// 1. Run exit animation if defined
|
|
623
|
+
if (resolvedExit && element && !keyTransitionStopped) {
|
|
624
|
+
const exitKeyframes = { ...(resolvedExit as Record<string, unknown>) }
|
|
625
|
+
// Remove transition from keyframes (it's passed separately)
|
|
626
|
+
delete exitKeyframes.transition
|
|
627
|
+
|
|
628
|
+
pwLog('[motion] key transition: running exit', { exitKeyframes })
|
|
629
|
+
await animate(
|
|
630
|
+
element,
|
|
631
|
+
exitKeyframes as DOMKeyframesDefinition,
|
|
632
|
+
mergedTransition
|
|
633
|
+
).finished
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Check if component was unmounted during exit animation
|
|
637
|
+
if (keyTransitionStopped || !element) return
|
|
638
|
+
|
|
639
|
+
// 2. Snap to initial state
|
|
640
|
+
if (initialKeyframes && element) {
|
|
641
|
+
const transformedInitial = transformSVGPathProperties(
|
|
642
|
+
element,
|
|
643
|
+
initialKeyframes as Record<string, unknown>
|
|
644
|
+
)
|
|
645
|
+
pwLog('[motion] key transition: snapping to initial', { transformedInitial })
|
|
646
|
+
animate(element, transformedInitial as DOMKeyframesDefinition, { duration: 0 })
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Check again before running enter animation
|
|
650
|
+
if (keyTransitionStopped || !element) return
|
|
651
|
+
|
|
652
|
+
// 3. Run enter animation
|
|
653
|
+
pwLog('[motion] key transition: running enter animation')
|
|
654
|
+
runAnimation()
|
|
655
|
+
} finally {
|
|
656
|
+
if (!keyTransitionStopped) {
|
|
657
|
+
keyTrackerIsTransitioning = false
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
runKeyTransition()
|
|
663
|
+
|
|
664
|
+
// Cleanup on unmount
|
|
665
|
+
return () => {
|
|
666
|
+
keyTransitionStopped = true
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
|
|
559
670
|
// Re-run animate when animateProp changes while ready
|
|
560
671
|
$effect(() => {
|
|
561
672
|
if (!(element && isLoaded === 'ready')) return
|
|
@@ -765,7 +876,15 @@
|
|
|
765
876
|
</script>
|
|
766
877
|
|
|
767
878
|
{#if isVoidTag}
|
|
768
|
-
|
|
879
|
+
{#if isSVGTag(String(tag))}
|
|
880
|
+
<svelte:element this={tag} bind:this={element} xmlns={SVG_NAMESPACE} {...derivedAttrs} />
|
|
881
|
+
{:else}
|
|
882
|
+
<svelte:element this={tag} bind:this={element} {...derivedAttrs} />
|
|
883
|
+
{/if}
|
|
884
|
+
{:else if isSVGTag(String(tag))}
|
|
885
|
+
<svelte:element this={tag} bind:this={element} xmlns={SVG_NAMESPACE} {...derivedAttrs}>
|
|
886
|
+
{@render children?.()}
|
|
887
|
+
</svelte:element>
|
|
769
888
|
{:else}
|
|
770
889
|
<svelte:element this={tag} bind:this={element} {...derivedAttrs}>
|
|
771
890
|
{@render children?.()}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ export type { DragAxis, DragConstraints, DragControls, DragInfo, DragTransition,
|
|
|
7
7
|
export { useAnimationFrame } from './utils/animationFrame';
|
|
8
8
|
export { createDragControls } from './utils/dragControls';
|
|
9
9
|
export { useSpring } from './utils/spring';
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
|
|
12
|
+
*/
|
|
10
13
|
export { stringifyStyleObject } from './utils/styleObject';
|
|
14
|
+
export { styleString } from './utils/styleObject.svelte';
|
|
11
15
|
export { useTime } from './utils/time';
|
|
12
16
|
export { useTransform } from './utils/transform';
|
|
13
17
|
export { AnimatePresence, MotionConfig };
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,11 @@ export { animate, hover } from 'motion';
|
|
|
8
8
|
export { useAnimationFrame } from './utils/animationFrame';
|
|
9
9
|
export { createDragControls } from './utils/dragControls';
|
|
10
10
|
export { useSpring } from './utils/spring';
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Use `styleString` instead for reactive styles with automatic unit handling.
|
|
13
|
+
*/
|
|
11
14
|
export { stringifyStyleObject } from './utils/styleObject';
|
|
15
|
+
export { styleString } from './utils/styleObject.svelte';
|
|
12
16
|
export { useTime } from './utils/time';
|
|
13
17
|
export { useTransform } from './utils/transform';
|
|
14
18
|
export { AnimatePresence, MotionConfig };
|
package/dist/utils/presence.d.ts
CHANGED
|
@@ -58,6 +58,45 @@ export declare function getAnimatePresenceContext(): AnimatePresenceContext | un
|
|
|
58
58
|
* Note: Trivial wrapper - ignored for coverage.
|
|
59
59
|
*/
|
|
60
60
|
export declare function setAnimatePresenceContext(context: AnimatePresenceContext): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get the current presence depth from Svelte component context.
|
|
63
|
+
*
|
|
64
|
+
* Returns undefined if not inside an AnimatePresence, or the depth level
|
|
65
|
+
* where 0 means direct child of AnimatePresence.
|
|
66
|
+
*
|
|
67
|
+
* @returns The current depth level (0 for direct children), or undefined if outside AnimatePresence.
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const depth = getPresenceDepth()
|
|
71
|
+
* if (depth === 0) {
|
|
72
|
+
* // Direct child of AnimatePresence - key prop required
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Note: Trivial wrapper - ignored for coverage.
|
|
77
|
+
*/
|
|
78
|
+
export declare const getPresenceDepth: () => number | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Set the presence depth in Svelte component context.
|
|
81
|
+
*
|
|
82
|
+
* AnimatePresence sets this to 0, and each motion element increments it
|
|
83
|
+
* for its descendants so only direct children (depth 0) require keys.
|
|
84
|
+
*
|
|
85
|
+
* @param depth - The nesting depth to set (0 for direct children of AnimatePresence).
|
|
86
|
+
* @returns void
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* // In AnimatePresence component
|
|
90
|
+
* setPresenceDepth(0)
|
|
91
|
+
*
|
|
92
|
+
* // In nested motion element
|
|
93
|
+
* const currentDepth = getPresenceDepth() ?? 0
|
|
94
|
+
* setPresenceDepth(currentDepth + 1)
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* Note: Trivial wrapper - ignored for coverage.
|
|
98
|
+
*/
|
|
99
|
+
export declare const setPresenceDepth: (depth: number) => void;
|
|
61
100
|
/**
|
|
62
101
|
* Hook used by motion elements to participate in presence.
|
|
63
102
|
* Registers the element and ensures its exit animation runs on teardown.
|
package/dist/utils/presence.js
CHANGED
|
@@ -8,6 +8,13 @@ import { getContext, onDestroy, setContext } from 'svelte';
|
|
|
8
8
|
* Used with Svelte's context API to provide/register presence management.
|
|
9
9
|
*/
|
|
10
10
|
const ANIMATE_PRESENCE_CONTEXT = Symbol('animate-presence-context');
|
|
11
|
+
/**
|
|
12
|
+
* Context key for tracking nesting depth within AnimatePresence.
|
|
13
|
+
*
|
|
14
|
+
* Used to enforce key requirements only on direct children (depth 0),
|
|
15
|
+
* matching Framer Motion behavior where only immediate children need keys.
|
|
16
|
+
*/
|
|
17
|
+
const PRESENCE_DEPTH_CONTEXT = Symbol('presence-depth-context');
|
|
11
18
|
/**
|
|
12
19
|
* Reset any CSS transforms on the element's inline style.
|
|
13
20
|
*
|
|
@@ -331,6 +338,49 @@ export function getAnimatePresenceContext() {
|
|
|
331
338
|
export function setAnimatePresenceContext(context) {
|
|
332
339
|
setContext(ANIMATE_PRESENCE_CONTEXT, context);
|
|
333
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Get the current presence depth from Svelte component context.
|
|
343
|
+
*
|
|
344
|
+
* Returns undefined if not inside an AnimatePresence, or the depth level
|
|
345
|
+
* where 0 means direct child of AnimatePresence.
|
|
346
|
+
*
|
|
347
|
+
* @returns The current depth level (0 for direct children), or undefined if outside AnimatePresence.
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* const depth = getPresenceDepth()
|
|
351
|
+
* if (depth === 0) {
|
|
352
|
+
* // Direct child of AnimatePresence - key prop required
|
|
353
|
+
* }
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* Note: Trivial wrapper - ignored for coverage.
|
|
357
|
+
*/
|
|
358
|
+
/* c8 ignore next */
|
|
359
|
+
export const getPresenceDepth = () => getContext(PRESENCE_DEPTH_CONTEXT);
|
|
360
|
+
/**
|
|
361
|
+
* Set the presence depth in Svelte component context.
|
|
362
|
+
*
|
|
363
|
+
* AnimatePresence sets this to 0, and each motion element increments it
|
|
364
|
+
* for its descendants so only direct children (depth 0) require keys.
|
|
365
|
+
*
|
|
366
|
+
* @param depth - The nesting depth to set (0 for direct children of AnimatePresence).
|
|
367
|
+
* @returns void
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* // In AnimatePresence component
|
|
371
|
+
* setPresenceDepth(0)
|
|
372
|
+
*
|
|
373
|
+
* // In nested motion element
|
|
374
|
+
* const currentDepth = getPresenceDepth() ?? 0
|
|
375
|
+
* setPresenceDepth(currentDepth + 1)
|
|
376
|
+
* ```
|
|
377
|
+
*
|
|
378
|
+
* Note: Trivial wrapper - ignored for coverage.
|
|
379
|
+
*/
|
|
380
|
+
/* c8 ignore next */
|
|
381
|
+
export const setPresenceDepth = (depth) => {
|
|
382
|
+
setContext(PRESENCE_DEPTH_CONTEXT, depth);
|
|
383
|
+
};
|
|
334
384
|
/**
|
|
335
385
|
* Hook used by motion elements to participate in presence.
|
|
336
386
|
* Registers the element and ensures its exit animation runs on teardown.
|
|
@@ -1 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Converts a style object to a CSS style string.
|
|
3
|
+
*
|
|
4
|
+
* @deprecated Use `styleString` from `@humanspeak/svelte-motion` instead for reactive styles.
|
|
5
|
+
* This function is non-reactive and will not update when values change.
|
|
6
|
+
*
|
|
7
|
+
* @param obj - Style object with camelCase keys and string/number values
|
|
8
|
+
* @returns CSS style string with kebab-case properties and appropriate units
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Old (deprecated, non-reactive):
|
|
12
|
+
* import { stringifyStyleObject } from '..'
|
|
13
|
+
* const style = stringifyStyleObject({ rotate: 45, opacity: 0.5 })
|
|
14
|
+
*
|
|
15
|
+
* // New (reactive):
|
|
16
|
+
* import { styleString } from '@humanspeak/svelte-motion'
|
|
17
|
+
* const style = styleString(() => ({ rotate, opacity }))
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare const stringifyStyleObject: (obj: Record<string, string | number>) => string;
|
|
@@ -10,15 +10,63 @@ const UNITLESS_PROPERTIES = new Set([
|
|
|
10
10
|
'order',
|
|
11
11
|
'grid-column',
|
|
12
12
|
'grid-row',
|
|
13
|
-
'column-count'
|
|
13
|
+
'column-count',
|
|
14
|
+
'scale',
|
|
15
|
+
'scale-x',
|
|
16
|
+
'scale-y',
|
|
17
|
+
'scale-z'
|
|
14
18
|
]);
|
|
15
|
-
|
|
19
|
+
// CSS properties that need 'deg' unit instead of 'px'
|
|
20
|
+
const DEGREE_PROPERTIES = new Set([
|
|
21
|
+
'rotate',
|
|
22
|
+
'rotate-x',
|
|
23
|
+
'rotate-y',
|
|
24
|
+
'rotate-z',
|
|
25
|
+
'skew',
|
|
26
|
+
'skew-x',
|
|
27
|
+
'skew-y'
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Converts a style object to a CSS style string.
|
|
31
|
+
*
|
|
32
|
+
* @deprecated Use `styleString` from `@humanspeak/svelte-motion` instead for reactive styles.
|
|
33
|
+
* This function is non-reactive and will not update when values change.
|
|
34
|
+
*
|
|
35
|
+
* @param obj - Style object with camelCase keys and string/number values
|
|
36
|
+
* @returns CSS style string with kebab-case properties and appropriate units
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* // Old (deprecated, non-reactive):
|
|
40
|
+
* import { stringifyStyleObject } from '..'
|
|
41
|
+
* const style = stringifyStyleObject({ rotate: 45, opacity: 0.5 })
|
|
42
|
+
*
|
|
43
|
+
* // New (reactive):
|
|
44
|
+
* import { styleString } from '@humanspeak/svelte-motion'
|
|
45
|
+
* const style = styleString(() => ({ rotate, opacity }))
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const stringifyStyleObject = (obj) => {
|
|
16
49
|
return Object.entries(obj)
|
|
17
50
|
.map(([key, value]) => {
|
|
18
51
|
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
19
52
|
const isUnitless = UNITLESS_PROPERTIES.has(cssKey);
|
|
20
|
-
const
|
|
53
|
+
const isDegree = DEGREE_PROPERTIES.has(cssKey);
|
|
54
|
+
let cssValue;
|
|
55
|
+
if (typeof value === 'number') {
|
|
56
|
+
if (isUnitless) {
|
|
57
|
+
cssValue = String(value);
|
|
58
|
+
}
|
|
59
|
+
else if (isDegree) {
|
|
60
|
+
cssValue = `${value}deg`;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
cssValue = `${value}px`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
cssValue = String(value);
|
|
68
|
+
}
|
|
21
69
|
return `${cssKey}: ${cssValue}`;
|
|
22
70
|
})
|
|
23
71
|
.join('; ');
|
|
24
|
-
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type StyleObject = Record<string, string | number>;
|
|
2
|
+
/**
|
|
3
|
+
* Creates a CSS style string from a style object or factory function.
|
|
4
|
+
*
|
|
5
|
+
* In Svelte 5, template expressions are reactive - when you use `$state` variables
|
|
6
|
+
* in a template, the expression re-evaluates when those values change. This means
|
|
7
|
+
* you can use `styleString` directly in templates and it will update reactively.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <script>
|
|
12
|
+
* import { styleString } from '@humanspeak/svelte-motion'
|
|
13
|
+
*
|
|
14
|
+
* let rotate = $state(0)
|
|
15
|
+
* let opacity = $state(1)
|
|
16
|
+
* </script>
|
|
17
|
+
*
|
|
18
|
+
* <!-- Both forms are reactive in Svelte 5 templates -->
|
|
19
|
+
* <div style={styleString({ rotate, opacity })}>Object form</div>
|
|
20
|
+
* <div style={styleString(() => ({ rotate, opacity }))}>Factory form</div>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param input - A style object or a function returning a style object
|
|
24
|
+
* @returns A CSS style string
|
|
25
|
+
*/
|
|
26
|
+
export declare const styleString: (input: StyleObject | (() => StyleObject)) => string;
|
|
27
|
+
export { stringifyStyleObject } from './styleObject.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { stringifyStyleObject } from './styleObject.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a CSS style string from a style object or factory function.
|
|
4
|
+
*
|
|
5
|
+
* In Svelte 5, template expressions are reactive - when you use `$state` variables
|
|
6
|
+
* in a template, the expression re-evaluates when those values change. This means
|
|
7
|
+
* you can use `styleString` directly in templates and it will update reactively.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <script>
|
|
12
|
+
* import { styleString } from '@humanspeak/svelte-motion'
|
|
13
|
+
*
|
|
14
|
+
* let rotate = $state(0)
|
|
15
|
+
* let opacity = $state(1)
|
|
16
|
+
* </script>
|
|
17
|
+
*
|
|
18
|
+
* <!-- Both forms are reactive in Svelte 5 templates -->
|
|
19
|
+
* <div style={styleString({ rotate, opacity })}>Object form</div>
|
|
20
|
+
* <div style={styleString(() => ({ rotate, opacity }))}>Factory form</div>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param input - A style object or a function returning a style object
|
|
24
|
+
* @returns A CSS style string
|
|
25
|
+
*/
|
|
26
|
+
export const styleString = (input) => {
|
|
27
|
+
const obj = typeof input === 'function' ? input() : input;
|
|
28
|
+
return stringifyStyleObject(obj);
|
|
29
|
+
};
|
|
30
|
+
// Re-export the pure function for backwards compatibility
|
|
31
|
+
export { stringifyStyleObject } from './styleObject.js';
|
package/dist/utils/svg.d.ts
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
* These properties are not standard CSS properties and need to be transformed.
|
|
4
4
|
*/
|
|
5
5
|
export declare const SVG_PATH_PROPERTIES: Set<string>;
|
|
6
|
+
/**
|
|
7
|
+
* The SVG namespace URI.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
10
|
+
/**
|
|
11
|
+
* Set of SVG tag names that should be created in the SVG namespace.
|
|
12
|
+
* This list covers all standard SVG elements.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SVG_TAGS: Set<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Determines whether the provided tag name is an SVG element tag.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} tag The tag name to test.
|
|
19
|
+
* @returns {boolean} True when the tag is an SVG element.
|
|
20
|
+
* @example
|
|
21
|
+
* isSVGTag('path') // true
|
|
22
|
+
* isSVGTag('div') // false
|
|
23
|
+
*/
|
|
24
|
+
export declare const isSVGTag: (tag: string) => boolean;
|
|
6
25
|
/**
|
|
7
26
|
* Check if an element is an SVG path element.
|
|
8
27
|
*/
|
package/dist/utils/svg.js
CHANGED
|
@@ -3,6 +3,89 @@
|
|
|
3
3
|
* These properties are not standard CSS properties and need to be transformed.
|
|
4
4
|
*/
|
|
5
5
|
export const SVG_PATH_PROPERTIES = new Set(['pathLength', 'pathOffset', 'pathSpacing']);
|
|
6
|
+
/**
|
|
7
|
+
* The SVG namespace URI.
|
|
8
|
+
*/
|
|
9
|
+
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
10
|
+
/**
|
|
11
|
+
* Set of SVG tag names that should be created in the SVG namespace.
|
|
12
|
+
* This list covers all standard SVG elements.
|
|
13
|
+
*/
|
|
14
|
+
export const SVG_TAGS = new Set([
|
|
15
|
+
'svg',
|
|
16
|
+
'animate',
|
|
17
|
+
'animatemotion',
|
|
18
|
+
'animatetransform',
|
|
19
|
+
'circle',
|
|
20
|
+
'clippath',
|
|
21
|
+
'defs',
|
|
22
|
+
'desc',
|
|
23
|
+
'ellipse',
|
|
24
|
+
'feblend',
|
|
25
|
+
'fecolormatrix',
|
|
26
|
+
'fecomponenttransfer',
|
|
27
|
+
'fecomposite',
|
|
28
|
+
'feconvolvematrix',
|
|
29
|
+
'fediffuselighting',
|
|
30
|
+
'fedisplacementmap',
|
|
31
|
+
'fedistantlight',
|
|
32
|
+
'fedropshadow',
|
|
33
|
+
'feflood',
|
|
34
|
+
'fefunca',
|
|
35
|
+
'fefuncb',
|
|
36
|
+
'fefuncg',
|
|
37
|
+
'fefuncr',
|
|
38
|
+
'fegaussianblur',
|
|
39
|
+
'feimage',
|
|
40
|
+
'femerge',
|
|
41
|
+
'femergenode',
|
|
42
|
+
'femorphology',
|
|
43
|
+
'feoffset',
|
|
44
|
+
'fepointlight',
|
|
45
|
+
'fespecularlighting',
|
|
46
|
+
'fespotlight',
|
|
47
|
+
'fetile',
|
|
48
|
+
'feturbulence',
|
|
49
|
+
'filter',
|
|
50
|
+
'foreignobject',
|
|
51
|
+
'g',
|
|
52
|
+
'image',
|
|
53
|
+
'line',
|
|
54
|
+
'lineargradient',
|
|
55
|
+
'marker',
|
|
56
|
+
'mask',
|
|
57
|
+
'metadata',
|
|
58
|
+
'mpath',
|
|
59
|
+
'path',
|
|
60
|
+
'pattern',
|
|
61
|
+
'polygon',
|
|
62
|
+
'polyline',
|
|
63
|
+
'radialgradient',
|
|
64
|
+
'rect',
|
|
65
|
+
'set',
|
|
66
|
+
'stop',
|
|
67
|
+
'switch',
|
|
68
|
+
'symbol',
|
|
69
|
+
'text',
|
|
70
|
+
'textpath',
|
|
71
|
+
'title',
|
|
72
|
+
'tref',
|
|
73
|
+
'tspan',
|
|
74
|
+
'use',
|
|
75
|
+
'view'
|
|
76
|
+
]);
|
|
77
|
+
/**
|
|
78
|
+
* Determines whether the provided tag name is an SVG element tag.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} tag The tag name to test.
|
|
81
|
+
* @returns {boolean} True when the tag is an SVG element.
|
|
82
|
+
* @example
|
|
83
|
+
* isSVGTag('path') // true
|
|
84
|
+
* isSVGTag('div') // false
|
|
85
|
+
*/
|
|
86
|
+
export const isSVGTag = (tag) => {
|
|
87
|
+
return SVG_TAGS.has(tag.toLowerCase());
|
|
88
|
+
};
|
|
6
89
|
/**
|
|
7
90
|
* Check if an element is an SVG path element.
|
|
8
91
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-motion",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "A lightweight animation library for Svelte 5 that provides smooth, hardware-accelerated animations. Features include spring physics, custom easing, and fluid transitions. Built on top of the motion library, it offers a simple API for creating complex animations with minimal code. Perfect for interactive UIs, micro-interactions, and engaging user experiences.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"motion": "^12.
|
|
57
|
-
"motion-dom": "^12.
|
|
56
|
+
"motion": "^12.31.0",
|
|
57
|
+
"motion-dom": "^12.30.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@changesets/cli": "^2.29.8",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@eslint/js": "^9.39.2",
|
|
63
63
|
"@playwright/test": "^1.58.1",
|
|
64
64
|
"@sveltejs/adapter-auto": "^7.0.0",
|
|
65
|
-
"@sveltejs/kit": "^2.50.
|
|
65
|
+
"@sveltejs/kit": "^2.50.2",
|
|
66
66
|
"@sveltejs/package": "^2.5.7",
|
|
67
67
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
68
68
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@tailwindcss/typography": "^0.5.19",
|
|
73
73
|
"@testing-library/jest-dom": "^6.9.1",
|
|
74
74
|
"@testing-library/svelte": "^5.3.1",
|
|
75
|
-
"@types/node": "^25.
|
|
75
|
+
"@types/node": "^25.2.0",
|
|
76
76
|
"@vitest/coverage-v8": "^4.0.18",
|
|
77
77
|
"concurrently": "^9.2.1",
|
|
78
78
|
"eslint": "^9.39.2",
|
|
@@ -81,11 +81,11 @@
|
|
|
81
81
|
"eslint-plugin-svelte": "3.14.0",
|
|
82
82
|
"eslint-plugin-unused-imports": "4.3.0",
|
|
83
83
|
"esm-env": "^1.2.2",
|
|
84
|
-
"globals": "^17.
|
|
84
|
+
"globals": "^17.3.0",
|
|
85
85
|
"html-tags": "^5.1.0",
|
|
86
86
|
"html-void-elements": "^3.0.0",
|
|
87
87
|
"husky": "^9.1.7",
|
|
88
|
-
"jsdom": "^
|
|
88
|
+
"jsdom": "^28.0.0",
|
|
89
89
|
"prettier": "^3.8.1",
|
|
90
90
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
91
91
|
"prettier-plugin-sort-json": "^4.2.0",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
94
94
|
"publint": "^0.3.17",
|
|
95
95
|
"runed": "0.37.1",
|
|
96
|
-
"svelte": "^5.49.
|
|
96
|
+
"svelte": "^5.49.2",
|
|
97
97
|
"svelte-check": "^4.3.6",
|
|
98
98
|
"svg-tags": "^1.0.0",
|
|
99
99
|
"tailwind-merge": "^3.4.0",
|