@svelte-atoms/core 1.0.0-alpha.30 → 1.0.0-alpha.31
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 +853 -852
- package/dist/components/accordion/accordion-root.svelte +7 -3
- package/dist/components/accordion/accordion.stories.svelte +7 -82
- package/dist/components/accordion/item/accordion-item-body.svelte +44 -42
- package/dist/components/accordion/item/accordion-item-header.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-indicator.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-root.svelte +66 -65
- package/dist/components/accordion/item/bond.svelte.d.ts +2 -0
- package/dist/components/accordion/item/index.d.ts +3 -0
- package/dist/components/accordion/item/index.js +3 -0
- package/dist/components/accordion/item/motion.svelte.d.ts +15 -0
- package/dist/components/accordion/item/motion.svelte.js +30 -0
- package/dist/components/accordion/item/types.d.ts +7 -24
- package/dist/components/alert/alert-close-button.svelte +66 -70
- package/dist/components/alert/alert-description.svelte +42 -42
- package/dist/components/alert/alert-description.svelte.d.ts +3 -6
- package/dist/components/alert/alert-root.svelte +68 -103
- package/dist/components/alert/alert-root.svelte.d.ts +2 -2
- package/dist/components/alert/alert.stories.svelte +400 -400
- package/dist/components/alert/bond.svelte.d.ts +0 -13
- package/dist/components/alert/bond.svelte.js +0 -32
- package/dist/components/alert/types.d.ts +8 -32
- package/dist/components/atom/html-atom.svelte +261 -261
- package/dist/components/avatar/avatar.stories.svelte +22 -22
- package/dist/components/badge/badge.stories.svelte +12 -12
- package/dist/components/badge/badge.svelte +19 -19
- package/dist/components/breadcrumb/breadcrumb.stories.svelte +5 -5
- package/dist/components/button/button.stories.svelte +27 -27
- package/dist/components/calendar/calendar-day.svelte +101 -96
- package/dist/components/calendar/calendar.stories.svelte +26 -26
- package/dist/components/card/card-body.svelte +39 -39
- package/dist/components/card/card-footer.svelte +41 -41
- package/dist/components/card/card-root.svelte +91 -91
- package/dist/components/card/card.stories.svelte +133 -133
- package/dist/components/checkbox/checkbox.stories.svelte +22 -22
- package/dist/components/checkbox/checkbox.svelte +6 -2
- package/dist/components/collapsible/collapsible.stories.svelte +172 -172
- package/dist/components/combobox/atoms.d.ts +1 -1
- package/dist/components/combobox/atoms.js +1 -1
- package/dist/components/combobox/combobox-root.svelte +65 -65
- package/dist/components/combobox/compobox.stories.svelte +51 -51
- package/dist/components/combobox/index.d.ts +1 -0
- package/dist/components/container/container.stories.svelte +20 -20
- package/dist/components/container/container.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid.stories.svelte +72 -72
- package/dist/components/datagrid/tr/bond.svelte.d.ts +4 -2
- package/dist/components/datagrid/tr/bond.svelte.js +9 -7
- package/dist/components/datagrid/tr/datagrid-tr.svelte +90 -88
- package/dist/components/date-picker/date-picker-calendar.svelte +67 -67
- package/dist/components/date-picker/date-picker-root.svelte +95 -95
- package/dist/components/date-picker/date-picker.stories.svelte +35 -35
- package/dist/components/dialog/bond.svelte.d.ts +13 -3
- package/dist/components/dialog/bond.svelte.js +66 -5
- package/dist/components/dialog/dialog-content.svelte +44 -62
- package/dist/components/dialog/dialog-root.svelte +91 -110
- package/dist/components/dialog/dialog.stories.svelte +64 -64
- package/dist/components/dialog/motion.svelte.d.ts +13 -0
- package/dist/components/dialog/motion.svelte.js +44 -0
- package/dist/components/drawer/attachments.svelte.d.ts +1 -1
- package/dist/components/drawer/attachments.svelte.js +1 -3
- package/dist/components/drawer/bond.svelte.d.ts +24 -5
- package/dist/components/drawer/bond.svelte.js +77 -11
- package/dist/components/drawer/drawer-content.svelte +6 -14
- package/dist/components/drawer/drawer.stories.svelte +27 -95
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +2 -0
- package/dist/components/drawer/motion.d.ts +15 -0
- package/dist/components/drawer/motion.js +28 -0
- package/dist/components/dropdown/atoms.d.ts +1 -1
- package/dist/components/dropdown/atoms.js +1 -1
- package/dist/components/dropdown/bond.svelte.d.ts +5 -1
- package/dist/components/dropdown/dropdown-root.svelte +59 -59
- package/dist/components/dropdown/dropdown.stories.svelte +80 -80
- package/dist/components/dropdown/index.d.ts +1 -0
- package/dist/components/form/form.stories.svelte +96 -96
- package/dist/components/image/image.stories.svelte +20 -20
- package/dist/components/input/input.stories.svelte +35 -35
- package/dist/components/label/label.stories.svelte +15 -15
- package/dist/components/lazy/lazy.stories.svelte +28 -28
- package/dist/components/link/link.stories.svelte +15 -15
- package/dist/components/list/list-item.svelte +20 -20
- package/dist/components/menu/atoms.d.ts +1 -0
- package/dist/components/menu/atoms.js +1 -0
- package/dist/components/menu/index.d.ts +2 -1
- package/dist/components/menu/index.js +1 -1
- package/dist/components/menu/menu-item.svelte +69 -51
- package/dist/components/menu/menu-item.svelte.d.ts +1 -0
- package/dist/components/menu/menu.stories.svelte +33 -33
- package/dist/components/popover/bond.svelte.d.ts +20 -7
- package/dist/components/popover/bond.svelte.js +80 -27
- package/dist/components/popover/motion.d.ts +6 -0
- package/dist/components/popover/motion.js +56 -0
- package/dist/components/popover/popover-arrow.svelte +111 -111
- package/dist/components/popover/popover-content.svelte +34 -72
- package/dist/components/popover/popover-indicator.svelte +44 -44
- package/dist/components/popover/popover-root.svelte +48 -48
- package/dist/components/popover/popover.stories.svelte +3 -3
- package/dist/components/popover/types.d.ts +9 -7
- package/dist/components/portal/active-portal.svelte +29 -22
- package/dist/components/portal/active-portal.svelte.d.ts +2 -9
- package/dist/components/portal/portal-root.svelte +76 -83
- package/dist/components/portal/portal-root.svelte.d.ts +4 -6
- package/dist/components/portal/teleport.svelte +49 -50
- package/dist/components/portal/teleport.svelte.d.ts +3 -4
- package/dist/components/qr-code/qr-code.stories.svelte +18 -18
- package/dist/components/radio/radio-group.stories.svelte +41 -41
- package/dist/components/radio/radio.stories.svelte +17 -17
- package/dist/components/radio/radio.svelte +1 -1
- package/dist/components/radio/types.d.ts +98 -0
- package/dist/components/radio/types.js +2 -0
- package/dist/components/root/root.svelte +13 -30
- package/dist/components/root/root.svelte.d.ts +1 -1
- package/dist/components/scrollable/scrollable-root.svelte.d.ts +2 -2
- package/dist/components/scrollable/scrollable.stories.svelte +116 -116
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/motion.svelte.d.ts +11 -0
- package/dist/components/sidebar/motion.svelte.js +16 -0
- package/dist/components/sidebar/sidebar-content.svelte +3 -13
- package/dist/components/sidebar/sidebar-root.svelte +39 -39
- package/dist/components/sidebar/sidebar.stories.svelte +43 -43
- package/dist/components/sidebar/types.d.ts +2 -12
- package/dist/components/tabs/tabs.stories.svelte +56 -56
- package/dist/components/textarea/atoms.d.ts +1 -0
- package/dist/components/textarea/atoms.js +1 -0
- package/dist/components/textarea/textarea-input.svelte +9 -6
- package/dist/components/textarea/textarea-root.svelte +9 -9
- package/dist/components/textarea/textarea-root.svelte.d.ts +2 -0
- package/dist/components/tooltip/tooltip-trigger.svelte +2 -2
- package/dist/components/tooltip/tooltip-trigger.svelte.d.ts +1 -0
- package/dist/components/tooltip/tooltip.stories.svelte +32 -32
- package/dist/components/tree/tree.stories.svelte +142 -142
- package/dist/icons/icon-copy.svelte +6 -0
- package/dist/{components/radio/types.svelte.d.ts → icons/icon-copy.svelte.d.ts} +3 -3
- package/dist/utils/markdown-to-llm.d.ts +28 -0
- package/dist/utils/markdown-to-llm.js +76 -0
- package/package.json +1 -2
- package/dist/components/radio/types.svelte +0 -0
- package/llm/composition.md +0 -395
- package/llm/crafting.md +0 -838
- package/llm/motion.md +0 -970
- package/llm/philosophy.md +0 -23
- package/llm/preset-variant-integration.md +0 -516
- package/llm/preset.md +0 -383
- package/llm/styling.md +0 -216
- package/llm/usage.md +0 -46
- package/llm/variants.md +0 -1259
package/llm/motion.md
DELETED
|
@@ -1,970 +0,0 @@
|
|
|
1
|
-
# Motion & Animation System
|
|
2
|
-
|
|
3
|
-
> **Audience**: LLMs and developers working with @svelte-atoms/core
|
|
4
|
-
|
|
5
|
-
This guide explains how to implement animations and transitions using the motion system in @svelte-atoms/core. The system is built around lifecycle hooks that provide precise control over element animation states.
|
|
6
|
-
|
|
7
|
-
## Core Concepts
|
|
8
|
-
|
|
9
|
-
@svelte-atoms/core provides a powerful motion system through `HtmlAtom` and `HtmlElement` components. Both components expose four key animation lifecycle hooks:
|
|
10
|
-
|
|
11
|
-
- **`initial`** - Set up the initial state before the element enters
|
|
12
|
-
- **`enter`** - Define the transition when element enters the DOM
|
|
13
|
-
- **`exit`** - Define the transition when element leaves the DOM
|
|
14
|
-
- **`animate`** - Execute animations after enter transition completes OR when reactive state changes
|
|
15
|
-
|
|
16
|
-
## Architecture
|
|
17
|
-
|
|
18
|
-
### HtmlElement vs HtmlAtom
|
|
19
|
-
|
|
20
|
-
Both components support the same motion API, but serve different purposes:
|
|
21
|
-
|
|
22
|
-
**`HtmlElement`** - Low-level primitive
|
|
23
|
-
|
|
24
|
-
- Direct control over transitions
|
|
25
|
-
- No preset/base styling system
|
|
26
|
-
- Minimal overhead
|
|
27
|
-
- Use for foundational components
|
|
28
|
-
|
|
29
|
-
**`HtmlAtom`** - High-level building block
|
|
30
|
-
|
|
31
|
-
- Extends HtmlElement with base styling
|
|
32
|
-
- Preset system integration
|
|
33
|
-
- Root context support
|
|
34
|
-
- Use for UI components
|
|
35
|
-
|
|
36
|
-
## Lifecycle Hook Signatures
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
type NodeFunction<T> = (node: ElementType<T>) => void | (() => void);
|
|
40
|
-
type TransitionFunction<T> = (node: ElementType<T>) => TransitionConfig | void;
|
|
41
|
-
|
|
42
|
-
interface TransitionConfig {
|
|
43
|
-
delay?: number;
|
|
44
|
-
duration?: number;
|
|
45
|
-
easing?: (t: number) => number;
|
|
46
|
-
css?: (t: number, u: number) => string;
|
|
47
|
-
tick?: (t: number, u: number) => void;
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Animation Lifecycle Order
|
|
52
|
-
|
|
53
|
-
1. **`initial(node)`** - Runs immediately when node is created (before enter)
|
|
54
|
-
2. **`enter(node)`** - Returns transition config, runs as element enters
|
|
55
|
-
3. **`animate(node)`** - Runs after enter completes (skipped on first mount if enter exists)
|
|
56
|
-
4. **`exit(node)`** - Returns transition config, runs as element leaves
|
|
57
|
-
|
|
58
|
-
### Setting Initial State
|
|
59
|
-
|
|
60
|
-
You can set the initial state in three ways:
|
|
61
|
-
|
|
62
|
-
**1. Using the `initial` hook:**
|
|
63
|
-
|
|
64
|
-
```svelte
|
|
65
|
-
<HtmlAtom
|
|
66
|
-
initial={(node) => {
|
|
67
|
-
gsap.set(node, { opacity: 0, y: 20 });
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**2. Using TailwindCSS classes:**
|
|
73
|
-
|
|
74
|
-
```svelte
|
|
75
|
-
<HtmlAtom class="opacity-0 translate-y-5">
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**3. Using inline styles:**
|
|
79
|
-
|
|
80
|
-
```svelte
|
|
81
|
-
<HtmlAtom style="opacity: 0; transform: translateY(20px);">
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Choose based on your needs:
|
|
85
|
-
|
|
86
|
-
- **`initial` hook**: Best for dynamic initial states or when using animation libraries
|
|
87
|
-
- **TailwindCSS**: Best for static initial states with utility classes
|
|
88
|
-
- **Inline styles**: Best for one-off custom initial states
|
|
89
|
-
|
|
90
|
-
### Important Behaviors
|
|
91
|
-
|
|
92
|
-
- `animate` is **skipped on first render** if `enter` is defined (prevents double animation)
|
|
93
|
-
- `animate` runs on **subsequent reactive updates** to animate state changes
|
|
94
|
-
- `onmount` returns cleanup function, runs before `ondestroy`
|
|
95
|
-
- `initial` sets up state before any transitions occur
|
|
96
|
-
|
|
97
|
-
## Pattern 1: Svelte Native Transitions
|
|
98
|
-
|
|
99
|
-
Use Svelte's built-in transition functions from `svelte/transition`.
|
|
100
|
-
|
|
101
|
-
```svelte
|
|
102
|
-
<script>
|
|
103
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
104
|
-
import { fade, slide } from 'svelte/transition';
|
|
105
|
-
|
|
106
|
-
let show = $state(true);
|
|
107
|
-
</script>
|
|
108
|
-
|
|
109
|
-
{#if show}
|
|
110
|
-
<HtmlAtom
|
|
111
|
-
enter={(node) => fade(node, { duration: 300 })}
|
|
112
|
-
exit={(node) => slide(node, { duration: 200 })}
|
|
113
|
-
>
|
|
114
|
-
Animated content
|
|
115
|
-
</HtmlAtom>
|
|
116
|
-
{/if}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Key Points
|
|
120
|
-
|
|
121
|
-
- Native Svelte transitions work out of the box
|
|
122
|
-
- Return value becomes the TransitionConfig
|
|
123
|
-
- Use `global={true}` (default) to respect `prefers-reduced-motion`
|
|
124
|
-
|
|
125
|
-
## Pattern 2: GSAP Animations
|
|
126
|
-
|
|
127
|
-
GSAP provides powerful animation capabilities with precise control.
|
|
128
|
-
|
|
129
|
-
### Basic GSAP Setup
|
|
130
|
-
|
|
131
|
-
**Using `initial` hook:**
|
|
132
|
-
|
|
133
|
-
```svelte
|
|
134
|
-
<script>
|
|
135
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
136
|
-
import gsap from 'gsap';
|
|
137
|
-
|
|
138
|
-
let isOpen = $state(false);
|
|
139
|
-
</script>
|
|
140
|
-
|
|
141
|
-
<HtmlAtom
|
|
142
|
-
initial={(node) => {
|
|
143
|
-
gsap.set(node, { opacity: 0, y: 20 });
|
|
144
|
-
}}
|
|
145
|
-
enter={(node) => {
|
|
146
|
-
const tween = gsap.to(node, {
|
|
147
|
-
opacity: 1,
|
|
148
|
-
y: 0,
|
|
149
|
-
duration: 0.3,
|
|
150
|
-
ease: 'power2.out'
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
duration: tween.duration() * 1000 // Convert to ms
|
|
155
|
-
};
|
|
156
|
-
}}
|
|
157
|
-
exit={(node) => {
|
|
158
|
-
const tween = gsap.to(node, {
|
|
159
|
-
opacity: 0,
|
|
160
|
-
y: -20,
|
|
161
|
-
duration: 0.2
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
duration: tween.duration() * 1000
|
|
166
|
-
};
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
Content
|
|
170
|
-
</HtmlAtom>
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**Or using TailwindCSS classes:**
|
|
174
|
-
|
|
175
|
-
```svelte
|
|
176
|
-
<script>
|
|
177
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
178
|
-
import gsap from 'gsap';
|
|
179
|
-
</script>
|
|
180
|
-
|
|
181
|
-
<HtmlAtom
|
|
182
|
-
class="translate-y-5 opacity-0"
|
|
183
|
-
enter={(node) => {
|
|
184
|
-
const tween = gsap.to(node, {
|
|
185
|
-
opacity: 1,
|
|
186
|
-
y: 0,
|
|
187
|
-
duration: 0.3,
|
|
188
|
-
ease: 'power2.out'
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
duration: tween.duration() * 1000
|
|
193
|
-
};
|
|
194
|
-
}}
|
|
195
|
-
>
|
|
196
|
-
Content
|
|
197
|
-
</HtmlAtom>
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
**Or using inline styles:**
|
|
201
|
-
|
|
202
|
-
```svelte
|
|
203
|
-
<HtmlAtom
|
|
204
|
-
style="opacity: 0; transform: translateY(20px);"
|
|
205
|
-
enter={(node) => {
|
|
206
|
-
const tween = gsap.to(node, { opacity: 1, y: 0, duration: 0.3 });
|
|
207
|
-
return { duration: tween.duration() * 1000 };
|
|
208
|
-
}}
|
|
209
|
-
>
|
|
210
|
-
Content
|
|
211
|
-
</HtmlAtom>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### GSAP with Reactive State
|
|
215
|
-
|
|
216
|
-
Common pattern for components like drawers, sidebars, accordions:
|
|
217
|
-
|
|
218
|
-
```svelte
|
|
219
|
-
<script>
|
|
220
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
221
|
-
import gsap from 'gsap';
|
|
222
|
-
import { cubicOut } from 'svelte/easing';
|
|
223
|
-
|
|
224
|
-
let isOpen = $state(false);
|
|
225
|
-
</script>
|
|
226
|
-
|
|
227
|
-
<HtmlAtom
|
|
228
|
-
initial={(node) => {
|
|
229
|
-
gsap.set(node, { opacity: +isOpen });
|
|
230
|
-
}}
|
|
231
|
-
animate={(node) => {
|
|
232
|
-
gsap.to(node, {
|
|
233
|
-
opacity: +isOpen,
|
|
234
|
-
duration: 0.3,
|
|
235
|
-
onComplete: () => !isOpen && node.close?.()
|
|
236
|
-
});
|
|
237
|
-
}}
|
|
238
|
-
>
|
|
239
|
-
Backdrop
|
|
240
|
-
</HtmlAtom>
|
|
241
|
-
|
|
242
|
-
<HtmlAtom
|
|
243
|
-
class="drawer-content"
|
|
244
|
-
initial={(node) => {
|
|
245
|
-
gsap.set(node, { xPercent: isOpen ? 0 : -100 });
|
|
246
|
-
}}
|
|
247
|
-
animate={(node) => {
|
|
248
|
-
gsap.to(node, {
|
|
249
|
-
xPercent: isOpen ? 0 : -100,
|
|
250
|
-
duration: 0.2,
|
|
251
|
-
ease: cubicOut
|
|
252
|
-
});
|
|
253
|
-
}}
|
|
254
|
-
>
|
|
255
|
-
Drawer content
|
|
256
|
-
</HtmlAtom>
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### GSAP Helper Utility
|
|
260
|
-
|
|
261
|
-
@svelte-atoms/core provides `toTransitionConfig` to convert GSAP tweens:
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
// $svelte-atoms/core/utils/gsap
|
|
265
|
-
export function toTransitionConfig(tween: gsap.core.Tween): TransitionConfig {
|
|
266
|
-
return {
|
|
267
|
-
delay: tween.delay() * 1000,
|
|
268
|
-
duration: tween.duration() * 1000,
|
|
269
|
-
easing: tween.vars.ease
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Usage:**
|
|
275
|
-
|
|
276
|
-
```svelte
|
|
277
|
-
<script>
|
|
278
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
279
|
-
import gsap from 'gsap';
|
|
280
|
-
import { toTransitionConfig } from '$svelte-atoms/core/utils/gsap';
|
|
281
|
-
import { linear } from 'svelte/easing';
|
|
282
|
-
</script>
|
|
283
|
-
|
|
284
|
-
<HtmlAtom
|
|
285
|
-
initial={(node) => {
|
|
286
|
-
gsap.set(node, { opacity: 0, height: 0 });
|
|
287
|
-
}}
|
|
288
|
-
enter={(node) => {
|
|
289
|
-
const tween = gsap.to(node, {
|
|
290
|
-
opacity: 1,
|
|
291
|
-
height: 'auto',
|
|
292
|
-
duration: 0.2,
|
|
293
|
-
ease: linear
|
|
294
|
-
});
|
|
295
|
-
return toTransitionConfig(tween);
|
|
296
|
-
}}
|
|
297
|
-
exit={(node) => {
|
|
298
|
-
const tween = gsap.to(node, {
|
|
299
|
-
opacity: 0,
|
|
300
|
-
height: 0,
|
|
301
|
-
duration: 0.2,
|
|
302
|
-
ease: linear
|
|
303
|
-
});
|
|
304
|
-
return toTransitionConfig(tween);
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
Collapsible content
|
|
308
|
-
</HtmlAtom>
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
## Pattern 3: Motion One Library
|
|
312
|
-
|
|
313
|
-
Motion One provides a modern, performant animation API.
|
|
314
|
-
|
|
315
|
-
### Basic Motion Setup
|
|
316
|
-
|
|
317
|
-
```svelte
|
|
318
|
-
<script>
|
|
319
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
320
|
-
import { animate } from 'motion';
|
|
321
|
-
|
|
322
|
-
let isOpen = $state(false);
|
|
323
|
-
</script>
|
|
324
|
-
|
|
325
|
-
<HtmlAtom
|
|
326
|
-
animate={(node) => {
|
|
327
|
-
animate(
|
|
328
|
-
node,
|
|
329
|
-
{
|
|
330
|
-
y: (isOpen ? 0 : -1) * 8,
|
|
331
|
-
opacity: +isOpen
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
duration: 0.1
|
|
335
|
-
}
|
|
336
|
-
);
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
Content
|
|
340
|
-
</HtmlAtom>
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Motion with Enter/Exit
|
|
344
|
-
|
|
345
|
-
```svelte
|
|
346
|
-
<script>
|
|
347
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
348
|
-
import { animate } from 'motion';
|
|
349
|
-
|
|
350
|
-
let isOpen = $state(false);
|
|
351
|
-
</script>
|
|
352
|
-
|
|
353
|
-
<HtmlAtom
|
|
354
|
-
class={['pointer-events-none h-0 opacity-0', isOpen && 'pointer-events-auto']}
|
|
355
|
-
enter={(node) => {
|
|
356
|
-
animate(
|
|
357
|
-
node,
|
|
358
|
-
{ opacity: +isOpen, height: isOpen ? 'auto' : 0 },
|
|
359
|
-
{ duration: 0.2, ease: 'linear' }
|
|
360
|
-
);
|
|
361
|
-
return { duration: 200 };
|
|
362
|
-
}}
|
|
363
|
-
exit={(node) => {
|
|
364
|
-
animate(node, { opacity: 0, height: 0 }, { duration: 0.2, ease: 'linear' });
|
|
365
|
-
return { duration: 200 };
|
|
366
|
-
}}
|
|
367
|
-
animate={(node) => {
|
|
368
|
-
animate(
|
|
369
|
-
node,
|
|
370
|
-
{ opacity: +isOpen, height: isOpen ? 'auto' : 0 },
|
|
371
|
-
{ duration: 0.2, ease: 'linear' }
|
|
372
|
-
);
|
|
373
|
-
}}
|
|
374
|
-
>
|
|
375
|
-
Collapsible body
|
|
376
|
-
</HtmlAtom>
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### Key Points
|
|
380
|
-
|
|
381
|
-
- Motion One animations are auto-canceled when new animations start
|
|
382
|
-
- Works seamlessly with Web Animations API
|
|
383
|
-
- Great performance with GPU acceleration
|
|
384
|
-
|
|
385
|
-
## Pattern 4: Context-Aware Animations
|
|
386
|
-
|
|
387
|
-
Access component state via `this` binding for dynamic animations.
|
|
388
|
-
|
|
389
|
-
```svelte
|
|
390
|
-
<script>
|
|
391
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
392
|
-
import gsap from 'gsap';
|
|
393
|
-
</script>
|
|
394
|
-
|
|
395
|
-
<HtmlAtom
|
|
396
|
-
enter={function (node) {
|
|
397
|
-
// 'this' refers to the component context/bond
|
|
398
|
-
const isOpen = this.isOpen;
|
|
399
|
-
|
|
400
|
-
const tween = gsap.to(node, {
|
|
401
|
-
opacity: +isOpen,
|
|
402
|
-
height: isOpen ? 'auto' : 0,
|
|
403
|
-
duration: 0.2
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
return toTransitionConfig(tween);
|
|
407
|
-
}}
|
|
408
|
-
>
|
|
409
|
-
Context-aware content
|
|
410
|
-
</HtmlAtom>
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### Binding to Component State
|
|
414
|
-
|
|
415
|
-
When building custom components, bind hooks to state:
|
|
416
|
-
|
|
417
|
-
```svelte
|
|
418
|
-
<script>
|
|
419
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
420
|
-
|
|
421
|
-
let bond = /* ... component bond ... */;
|
|
422
|
-
</script>
|
|
423
|
-
|
|
424
|
-
<HtmlAtom
|
|
425
|
-
enter={enter?.bind(bond.state)}
|
|
426
|
-
exit={exit?.bind(bond.state)}
|
|
427
|
-
animate={animate?.bind(bond.state)}
|
|
428
|
-
>
|
|
429
|
-
Content
|
|
430
|
-
</HtmlAtom>
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
## Pattern 5: Staggered Animations
|
|
434
|
-
|
|
435
|
-
Animate lists with sequential delays.
|
|
436
|
-
|
|
437
|
-
```svelte
|
|
438
|
-
<script>
|
|
439
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
440
|
-
import { fade, slide } from 'svelte/transition';
|
|
441
|
-
|
|
442
|
-
let items = $state([
|
|
443
|
-
{ id: 1, title: 'Item 1' },
|
|
444
|
-
{ id: 2, title: 'Item 2' },
|
|
445
|
-
{ id: 3, title: 'Item 3' }
|
|
446
|
-
]);
|
|
447
|
-
</script>
|
|
448
|
-
|
|
449
|
-
<HtmlAtom as="div" class="grid gap-4">
|
|
450
|
-
{#each items as item, i (item.id)}
|
|
451
|
-
<HtmlAtom
|
|
452
|
-
as="article"
|
|
453
|
-
enter={(node) => slide(node, { delay: i * 100, duration: 300 })}
|
|
454
|
-
exit={(node) => fade(node)}
|
|
455
|
-
>
|
|
456
|
-
<HtmlAtom as="h2">{item.title}</HtmlAtom>
|
|
457
|
-
</HtmlAtom>
|
|
458
|
-
{/each}
|
|
459
|
-
</HtmlAtom>
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
## Pattern 6: Combining with Lifecycle Hooks
|
|
463
|
-
|
|
464
|
-
`onmount` and `ondestroy` work alongside animation hooks.
|
|
465
|
-
|
|
466
|
-
```svelte
|
|
467
|
-
<script>
|
|
468
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
469
|
-
import { fade } from 'svelte/transition';
|
|
470
|
-
|
|
471
|
-
function handleMount(node) {
|
|
472
|
-
console.log('Mounted:', node);
|
|
473
|
-
|
|
474
|
-
// Setup intersection observer
|
|
475
|
-
const observer = new IntersectionObserver((entries) => {
|
|
476
|
-
entries.forEach((entry) => {
|
|
477
|
-
if (entry.isIntersecting) {
|
|
478
|
-
node.classList.add('visible');
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
observer.observe(node);
|
|
483
|
-
|
|
484
|
-
// Return cleanup function
|
|
485
|
-
return () => {
|
|
486
|
-
observer.disconnect();
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
</script>
|
|
490
|
-
|
|
491
|
-
<HtmlAtom
|
|
492
|
-
onmount={handleMount}
|
|
493
|
-
enter={(node) => fade(node, { duration: 600 })}
|
|
494
|
-
class="visible:opacity-100 opacity-0"
|
|
495
|
-
>
|
|
496
|
-
Lazy-loaded content
|
|
497
|
-
</HtmlAtom>
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
## Pattern 7: Complex Component Animations
|
|
501
|
-
|
|
502
|
-
Real-world example from Drawer component:
|
|
503
|
-
|
|
504
|
-
```svelte
|
|
505
|
-
<script>
|
|
506
|
-
import { HtmlElement } from '@svelte-atoms/core';
|
|
507
|
-
import gsap from 'gsap';
|
|
508
|
-
import { cubicOut } from 'svelte/easing';
|
|
509
|
-
|
|
510
|
-
let isOpen = $state(false);
|
|
511
|
-
</script>
|
|
512
|
-
|
|
513
|
-
<!-- Backdrop -->
|
|
514
|
-
<HtmlElement
|
|
515
|
-
as="dialog"
|
|
516
|
-
class="border backdrop-blur-md"
|
|
517
|
-
initial={(node) => {
|
|
518
|
-
gsap.set(node, { opacity: +isOpen });
|
|
519
|
-
}}
|
|
520
|
-
animate={(node) => {
|
|
521
|
-
gsap.to(node, {
|
|
522
|
-
opacity: +isOpen,
|
|
523
|
-
duration: 0.3,
|
|
524
|
-
onComplete: () => !isOpen && node.close?.()
|
|
525
|
-
});
|
|
526
|
-
}}
|
|
527
|
-
>
|
|
528
|
-
<!-- Drawer content -->
|
|
529
|
-
<HtmlElement
|
|
530
|
-
class="drawer-panel"
|
|
531
|
-
initial={(node) => {
|
|
532
|
-
gsap.set(node, { xPercent: isOpen ? 0 : -100, left: 0 });
|
|
533
|
-
}}
|
|
534
|
-
animate={(node) => {
|
|
535
|
-
gsap.to(node, {
|
|
536
|
-
xPercent: isOpen ? 0 : -100,
|
|
537
|
-
left: 0,
|
|
538
|
-
duration: 0.2,
|
|
539
|
-
ease: cubicOut
|
|
540
|
-
});
|
|
541
|
-
}}
|
|
542
|
-
>
|
|
543
|
-
Drawer content
|
|
544
|
-
</HtmlElement>
|
|
545
|
-
</HtmlElement>
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
## Pattern 8: Scrollbar Animations
|
|
549
|
-
|
|
550
|
-
From Scrollable component - show/hide on hover:
|
|
551
|
-
|
|
552
|
-
```svelte
|
|
553
|
-
<script>
|
|
554
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
555
|
-
import gsap from 'gsap';
|
|
556
|
-
</script>
|
|
557
|
-
|
|
558
|
-
<HtmlAtom
|
|
559
|
-
class="scrollbar-track"
|
|
560
|
-
initial={(node) => gsap.set(node, { opacity: 0, right: 0, top: 0, bottom: 0 })}
|
|
561
|
-
enter={(node) => {
|
|
562
|
-
const tween = gsap.to(node, {
|
|
563
|
-
opacity: 1,
|
|
564
|
-
right: 8,
|
|
565
|
-
top: 8,
|
|
566
|
-
bottom: 8,
|
|
567
|
-
duration: 0.3,
|
|
568
|
-
ease: 'power2.out'
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
return { duration: tween.duration() * 1000 };
|
|
572
|
-
}}
|
|
573
|
-
exit={(node) => {
|
|
574
|
-
const tween = gsap.to(node, {
|
|
575
|
-
opacity: 0,
|
|
576
|
-
right: 0,
|
|
577
|
-
top: 0,
|
|
578
|
-
bottom: 0,
|
|
579
|
-
duration: 0.3,
|
|
580
|
-
ease: 'power2.out'
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
return { duration: tween.duration() * 1000 };
|
|
584
|
-
}}
|
|
585
|
-
>
|
|
586
|
-
<HtmlAtom class="scrollbar-thumb" />
|
|
587
|
-
</HtmlAtom>
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
## Global vs Local Transitions
|
|
591
|
-
|
|
592
|
-
Control whether transitions respect user motion preferences:
|
|
593
|
-
|
|
594
|
-
```svelte
|
|
595
|
-
<script>
|
|
596
|
-
import { HtmlElement } from '@svelte-atoms/core';
|
|
597
|
-
import { fade } from 'svelte/transition';
|
|
598
|
-
</script>
|
|
599
|
-
|
|
600
|
-
<!-- Global transition (default) - respects prefers-reduced-motion -->
|
|
601
|
-
<HtmlElement global={true} enter={(node) => fade(node, { duration: 300 })}>
|
|
602
|
-
Global transition
|
|
603
|
-
</HtmlElement>
|
|
604
|
-
|
|
605
|
-
<!-- Local transition - always runs -->
|
|
606
|
-
<HtmlElement global={false} enter={(node) => fade(node, { duration: 300 })}>
|
|
607
|
-
Local transition
|
|
608
|
-
</HtmlElement>
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### Accessibility Note
|
|
612
|
-
|
|
613
|
-
- Always use `global={true}` (default) for UI components
|
|
614
|
-
- Users with motion sensitivity will see instant transitions
|
|
615
|
-
- Only use `global={false}` for essential animations
|
|
616
|
-
|
|
617
|
-
## Best Practices
|
|
618
|
-
|
|
619
|
-
### 1. Choose the Right Hook
|
|
620
|
-
|
|
621
|
-
- **`initial`** - Set starting state (opacity: 0, transform values)
|
|
622
|
-
- **`enter`** - One-time entrance animations
|
|
623
|
-
- **`exit`** - Exit animations when element unmounts
|
|
624
|
-
- **`animate`** - React to state changes (expanding/collapsing, etc.)
|
|
625
|
-
|
|
626
|
-
**Note**: You can also set initial state using TailwindCSS classes or inline styles instead of the `initial` hook:
|
|
627
|
-
|
|
628
|
-
```svelte
|
|
629
|
-
<!-- Using initial hook -->
|
|
630
|
-
<HtmlAtom
|
|
631
|
-
initial={(node) => gsap.set(node, { opacity: 0, x: -20 })}
|
|
632
|
-
enter={(node) => {
|
|
633
|
-
/* ... */
|
|
634
|
-
}}
|
|
635
|
-
/>
|
|
636
|
-
|
|
637
|
-
<!-- Using TailwindCSS -->
|
|
638
|
-
<HtmlAtom
|
|
639
|
-
class="-translate-x-5 opacity-0"
|
|
640
|
-
enter={(node) => {
|
|
641
|
-
/* ... */
|
|
642
|
-
}}
|
|
643
|
-
/>
|
|
644
|
-
|
|
645
|
-
<!-- Using inline styles -->
|
|
646
|
-
<HtmlAtom
|
|
647
|
-
style="opacity: 0; transform: translateX(-20px);"
|
|
648
|
-
enter={(node) => {
|
|
649
|
-
/* ... */
|
|
650
|
-
}}
|
|
651
|
-
/>
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### 2. GSAP Timing Conversion
|
|
655
|
-
|
|
656
|
-
Always convert GSAP durations from seconds to milliseconds:
|
|
657
|
-
|
|
658
|
-
```svelte
|
|
659
|
-
enter={(node) => {
|
|
660
|
-
const tween = gsap.to(node, { opacity: 1, duration: 0.3 });
|
|
661
|
-
return { duration: tween.duration() * 1000 }; // ✅ Convert to ms
|
|
662
|
-
}}
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### 3. Conditional Classes with Animations
|
|
666
|
-
|
|
667
|
-
Combine CSS classes with animation hooks for pointer events, layout shifts:
|
|
668
|
-
|
|
669
|
-
```svelte
|
|
670
|
-
<HtmlAtom
|
|
671
|
-
class={['pointer-events-none h-0 opacity-0', isOpen && 'pointer-events-auto']}
|
|
672
|
-
animate={(node) => {
|
|
673
|
-
animate(node, { opacity: +isOpen, height: isOpen ? 'auto' : 0 });
|
|
674
|
-
}}
|
|
675
|
-
>
|
|
676
|
-
Content
|
|
677
|
-
</HtmlAtom>
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
### 4. Cleanup in Animations
|
|
681
|
-
|
|
682
|
-
Use `onComplete` callbacks to clean up DOM state:
|
|
683
|
-
|
|
684
|
-
```svelte
|
|
685
|
-
animate={(node) => {
|
|
686
|
-
gsap.to(node, {
|
|
687
|
-
opacity: +isOpen,
|
|
688
|
-
onComplete: () => !isOpen && node.close?.() // Close dialog when hidden
|
|
689
|
-
});
|
|
690
|
-
}}
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
### 5. Performance Considerations
|
|
694
|
-
|
|
695
|
-
- Animate transforms and opacity (GPU-accelerated)
|
|
696
|
-
- Avoid animating layout properties when possible
|
|
697
|
-
- Use `will-change` CSS sparingly
|
|
698
|
-
- Batch multiple property changes in one animation
|
|
699
|
-
|
|
700
|
-
### 6. Type Safety
|
|
701
|
-
|
|
702
|
-
Use generics for element-specific types:
|
|
703
|
-
|
|
704
|
-
```svelte
|
|
705
|
-
<script lang="ts">
|
|
706
|
-
import { HtmlAtom, type HtmlAtomProps } from '@svelte-atoms/core';
|
|
707
|
-
|
|
708
|
-
type Props = HtmlAtomProps<'button'> & {
|
|
709
|
-
variant?: 'primary' | 'secondary';
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
let { animate, enter, exit, ...props }: Props = $props();
|
|
713
|
-
</script>
|
|
714
|
-
|
|
715
|
-
<HtmlAtom as="button" {animate} {enter} {exit} {...props}>
|
|
716
|
-
{@render children?.()}
|
|
717
|
-
</HtmlAtom>
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
## Common Animation Patterns
|
|
721
|
-
|
|
722
|
-
### Fade In/Out
|
|
723
|
-
|
|
724
|
-
```svelte
|
|
725
|
-
<HtmlAtom
|
|
726
|
-
enter={(node) => fade(node, { duration: 300 })}
|
|
727
|
-
exit={(node) => fade(node, { duration: 200 })}
|
|
728
|
-
>
|
|
729
|
-
Content
|
|
730
|
-
</HtmlAtom>
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
### Slide In/Out
|
|
734
|
-
|
|
735
|
-
```svelte
|
|
736
|
-
<HtmlAtom
|
|
737
|
-
initial={(node) => gsap.set(node, { x: -100, opacity: 0 })}
|
|
738
|
-
enter={(node) => {
|
|
739
|
-
const tween = gsap.to(node, { x: 0, opacity: 1, duration: 0.3 });
|
|
740
|
-
return { duration: tween.duration() * 1000 };
|
|
741
|
-
}}
|
|
742
|
-
>
|
|
743
|
-
Content
|
|
744
|
-
</HtmlAtom>
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### Scale In/Out
|
|
748
|
-
|
|
749
|
-
```svelte
|
|
750
|
-
<HtmlAtom
|
|
751
|
-
initial={(node) => gsap.set(node, { scale: 0.8, opacity: 0 })}
|
|
752
|
-
enter={(node) => {
|
|
753
|
-
const tween = gsap.to(node, { scale: 1, opacity: 1, duration: 0.2 });
|
|
754
|
-
return { duration: tween.duration() * 1000 };
|
|
755
|
-
}}
|
|
756
|
-
>
|
|
757
|
-
Content
|
|
758
|
-
</HtmlAtom>
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### Height Auto Animation (Accordion/Collapsible)
|
|
762
|
-
|
|
763
|
-
```svelte
|
|
764
|
-
<HtmlAtom
|
|
765
|
-
initial={(node) => gsap.set(node, { height: 0, opacity: 0 })}
|
|
766
|
-
enter={(node) => {
|
|
767
|
-
const tween = gsap.to(node, { height: 'auto', opacity: 1, duration: 0.2 });
|
|
768
|
-
return toTransitionConfig(tween);
|
|
769
|
-
}}
|
|
770
|
-
exit={(node) => {
|
|
771
|
-
const tween = gsap.to(node, { height: 0, opacity: 0, duration: 0.2 });
|
|
772
|
-
return toTransitionConfig(tween);
|
|
773
|
-
}}
|
|
774
|
-
>
|
|
775
|
-
Collapsible content
|
|
776
|
-
</HtmlAtom>
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### Width Animation (Sidebar)
|
|
780
|
-
|
|
781
|
-
```svelte
|
|
782
|
-
<script>
|
|
783
|
-
let isOpen = $state(false);
|
|
784
|
-
</script>
|
|
785
|
-
|
|
786
|
-
<HtmlAtom
|
|
787
|
-
initial={(node) => gsap.set(node, { width: isOpen ? 240 : 96 })}
|
|
788
|
-
animate={(node) => gsap.to(node, { width: isOpen ? 240 : 96, duration: 0.2, ease: cubicOut })}
|
|
789
|
-
>
|
|
790
|
-
Sidebar content
|
|
791
|
-
</HtmlAtom>
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
### Backdrop Blur with Fade
|
|
795
|
-
|
|
796
|
-
```svelte
|
|
797
|
-
<HtmlAtom
|
|
798
|
-
class="backdrop-blur-md"
|
|
799
|
-
initial={(node) => gsap.set(node, { opacity: +isOpen })}
|
|
800
|
-
animate={(node) => {
|
|
801
|
-
gsap.to(node, { opacity: +isOpen, duration: 0.3 });
|
|
802
|
-
}}
|
|
803
|
-
>
|
|
804
|
-
Backdrop
|
|
805
|
-
</HtmlAtom>
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
## Animation Libraries Support
|
|
809
|
-
|
|
810
|
-
@svelte-atoms/core is animation-library agnostic. Common integrations:
|
|
811
|
-
|
|
812
|
-
### Svelte Transitions
|
|
813
|
-
|
|
814
|
-
```typescript
|
|
815
|
-
import { fade, slide, scale, fly, blur } from 'svelte/transition';
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
### GSAP
|
|
819
|
-
|
|
820
|
-
```typescript
|
|
821
|
-
import gsap from 'gsap';
|
|
822
|
-
import { toTransitionConfig } from '$svelte-atoms/core/utils/gsap';
|
|
823
|
-
```
|
|
824
|
-
|
|
825
|
-
### Motion One
|
|
826
|
-
|
|
827
|
-
```typescript
|
|
828
|
-
import { animate, stagger, timeline } from 'motion';
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
### Popmotion
|
|
832
|
-
|
|
833
|
-
```typescript
|
|
834
|
-
import { animate, spring } from 'popmotion';
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
### Custom Animations
|
|
838
|
-
|
|
839
|
-
Implement custom transition configs:
|
|
840
|
-
|
|
841
|
-
```svelte
|
|
842
|
-
<HtmlAtom
|
|
843
|
-
enter={(node) => {
|
|
844
|
-
return {
|
|
845
|
-
duration: 500,
|
|
846
|
-
tick: (t) => {
|
|
847
|
-
node.style.opacity = String(t);
|
|
848
|
-
node.style.transform = `translateY(${(1 - t) * 50}px)`;
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
}}
|
|
852
|
-
>
|
|
853
|
-
Custom animation
|
|
854
|
-
</HtmlAtom>
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
## Debugging Animations
|
|
858
|
-
|
|
859
|
-
### Log Animation Lifecycle
|
|
860
|
-
|
|
861
|
-
```svelte
|
|
862
|
-
<HtmlAtom
|
|
863
|
-
initial={(node) => {
|
|
864
|
-
console.log('Initial:', node);
|
|
865
|
-
gsap.set(node, { opacity: 0 });
|
|
866
|
-
}}
|
|
867
|
-
enter={(node) => {
|
|
868
|
-
console.log('Enter:', node);
|
|
869
|
-
const tween = gsap.to(node, { opacity: 1, duration: 0.3 });
|
|
870
|
-
return { duration: tween.duration() * 1000 };
|
|
871
|
-
}}
|
|
872
|
-
animate={(node) => {
|
|
873
|
-
console.log('Animate:', node);
|
|
874
|
-
}}
|
|
875
|
-
onmount={(node) => {
|
|
876
|
-
console.log('Mount:', node);
|
|
877
|
-
}}
|
|
878
|
-
ondestroy={(node) => {
|
|
879
|
-
console.log('Destroy:', node);
|
|
880
|
-
}}
|
|
881
|
-
>
|
|
882
|
-
Debug content
|
|
883
|
-
</HtmlAtom>
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
### Check Skip Behavior
|
|
887
|
-
|
|
888
|
-
Remember: `animate` is skipped on first render when `enter` exists:
|
|
889
|
-
|
|
890
|
-
```svelte
|
|
891
|
-
<script>
|
|
892
|
-
let count = $state(0);
|
|
893
|
-
</script>
|
|
894
|
-
|
|
895
|
-
<HtmlAtom
|
|
896
|
-
enter={(node) => fade(node, { duration: 300 })}
|
|
897
|
-
animate={(node) => {
|
|
898
|
-
// This will NOT run on first mount (because enter exists)
|
|
899
|
-
// This WILL run when count changes
|
|
900
|
-
console.log('Animating count:', count);
|
|
901
|
-
}}
|
|
902
|
-
>
|
|
903
|
-
Count: {count}
|
|
904
|
-
</HtmlAtom>
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
## Migration from Other Systems
|
|
908
|
-
|
|
909
|
-
### From Svelte 4 to Svelte 5 + @svelte-atoms
|
|
910
|
-
|
|
911
|
-
**Before (Svelte 4):**
|
|
912
|
-
|
|
913
|
-
```svelte
|
|
914
|
-
<div in:fade={{ duration: 300 }} out:slide={{ duration: 200 }}>Content</div>
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
**After (@svelte-atoms/core):**
|
|
918
|
-
|
|
919
|
-
```svelte
|
|
920
|
-
<HtmlAtom
|
|
921
|
-
enter={(node) => fade(node, { duration: 300 })}
|
|
922
|
-
exit={(node) => slide(node, { duration: 200 })}
|
|
923
|
-
>
|
|
924
|
-
Content
|
|
925
|
-
</HtmlAtom>
|
|
926
|
-
```
|
|
927
|
-
|
|
928
|
-
### From Manual GSAP Setup
|
|
929
|
-
|
|
930
|
-
**Before:**
|
|
931
|
-
|
|
932
|
-
```svelte
|
|
933
|
-
<script>
|
|
934
|
-
let ref;
|
|
935
|
-
|
|
936
|
-
$effect(() => {
|
|
937
|
-
if (ref) {
|
|
938
|
-
gsap.to(ref, { opacity: +isOpen });
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
</script>
|
|
942
|
-
|
|
943
|
-
<div bind:this={ref}>Content</div>
|
|
944
|
-
```
|
|
945
|
-
|
|
946
|
-
**After:**
|
|
947
|
-
|
|
948
|
-
```svelte
|
|
949
|
-
<HtmlAtom
|
|
950
|
-
animate={(node) => {
|
|
951
|
-
gsap.to(node, { opacity: +isOpen });
|
|
952
|
-
}}
|
|
953
|
-
>
|
|
954
|
-
Content
|
|
955
|
-
</HtmlAtom>
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
## Summary
|
|
959
|
-
|
|
960
|
-
The @svelte-atoms/core motion system provides:
|
|
961
|
-
|
|
962
|
-
✅ **Four lifecycle hooks**: `initial`, `enter`, `exit`, `animate`
|
|
963
|
-
✅ **Library agnostic**: Works with Svelte, GSAP, Motion One, etc.
|
|
964
|
-
✅ **Context-aware**: Access component state via `this` binding
|
|
965
|
-
✅ **Performance**: Skip unnecessary animations, GPU acceleration
|
|
966
|
-
✅ **Accessibility**: Global transitions respect motion preferences
|
|
967
|
-
✅ **Type-safe**: Full TypeScript support with generics
|
|
968
|
-
✅ **Composable**: Nest atoms with independent animations
|
|
969
|
-
|
|
970
|
-
Use `HtmlElement` for low-level control, `HtmlAtom` for full-featured UI components. Both share the same motion API for consistency.
|