@svelte-atoms/core 1.0.0-alpha.25 → 1.0.0-alpha.27

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.
Files changed (354) hide show
  1. package/README.md +645 -645
  2. package/dist/components/accordion/accordion-root.svelte +61 -79
  3. package/dist/components/accordion/accordion-root.svelte.d.ts +2 -15
  4. package/dist/components/accordion/index.d.ts +2 -1
  5. package/dist/components/accordion/index.js +2 -1
  6. package/dist/components/accordion/item/accordion-item-body.svelte +42 -52
  7. package/dist/components/accordion/item/accordion-item-body.svelte.d.ts +2 -8
  8. package/dist/components/accordion/item/accordion-item-header.svelte +50 -56
  9. package/dist/components/accordion/item/accordion-item-header.svelte.d.ts +3 -20
  10. package/dist/components/accordion/item/accordion-item-indicator.svelte +50 -59
  11. package/dist/components/accordion/item/accordion-item-indicator.svelte.d.ts +2 -8
  12. package/dist/components/accordion/item/accordion-item-root.svelte +65 -79
  13. package/dist/components/accordion/item/accordion-item-root.svelte.d.ts +2 -12
  14. package/dist/components/accordion/item/index.d.ts +1 -0
  15. package/dist/components/accordion/item/types.d.ts +52 -0
  16. package/dist/components/accordion/item/types.js +1 -0
  17. package/dist/components/accordion/types.d.ts +21 -0
  18. package/dist/components/accordion/types.js +1 -0
  19. package/dist/components/alert/alert-actions.svelte +42 -52
  20. package/dist/components/alert/alert-actions.svelte.d.ts +3 -30
  21. package/dist/components/alert/alert-close-button.svelte +72 -79
  22. package/dist/components/alert/alert-close-button.svelte.d.ts +8 -35
  23. package/dist/components/alert/alert-content.svelte +42 -52
  24. package/dist/components/alert/alert-content.svelte.d.ts +3 -30
  25. package/dist/components/alert/alert-description.svelte +41 -51
  26. package/dist/components/alert/alert-description.svelte.d.ts +7 -10
  27. package/dist/components/alert/alert-icon.svelte +46 -56
  28. package/dist/components/alert/alert-icon.svelte.d.ts +2 -8
  29. package/dist/components/alert/alert-root.svelte +102 -118
  30. package/dist/components/alert/alert-root.svelte.d.ts +2 -13
  31. package/dist/components/alert/alert-title.svelte +41 -51
  32. package/dist/components/alert/alert-title.svelte.d.ts +2 -8
  33. package/dist/components/alert/index.d.ts +1 -0
  34. package/dist/components/alert/index.js +1 -0
  35. package/dist/components/alert/types.d.ts +85 -0
  36. package/dist/components/alert/types.js +1 -0
  37. package/dist/components/atom/html-atom.svelte +201 -217
  38. package/dist/components/atom/html-atom.svelte.d.ts +2 -22
  39. package/dist/components/atom/types.d.ts +7 -2
  40. package/dist/components/avatar/types.d.ts +7 -2
  41. package/dist/components/badge/badge.svelte +1 -1
  42. package/dist/components/badge/types.d.ts +7 -2
  43. package/dist/components/breadcrumb/breadcrumb-item.svelte +1 -1
  44. package/dist/components/breadcrumb/breadcrumb-root.svelte +1 -1
  45. package/dist/components/breadcrumb/breadcrumb-separator.svelte +1 -1
  46. package/dist/components/button/button.stories.svelte +57 -57
  47. package/dist/components/button/button.svelte +1 -1
  48. package/dist/components/button/button.svelte.d.ts +4 -1
  49. package/dist/components/button/index.d.ts +1 -0
  50. package/dist/components/button/index.js +1 -0
  51. package/dist/components/button/types.d.ts +8 -3
  52. package/dist/components/card/card-body.svelte +39 -45
  53. package/dist/components/card/card-body.svelte.d.ts +7 -4
  54. package/dist/components/card/card-description.svelte +41 -48
  55. package/dist/components/card/card-description.svelte.d.ts +7 -7
  56. package/dist/components/card/card-footer.svelte +41 -48
  57. package/dist/components/card/card-footer.svelte.d.ts +7 -4
  58. package/dist/components/card/card-header.svelte +41 -48
  59. package/dist/components/card/card-header.svelte.d.ts +7 -4
  60. package/dist/components/card/card-media.svelte +41 -48
  61. package/dist/components/card/card-media.svelte.d.ts +7 -4
  62. package/dist/components/card/card-root.svelte +91 -91
  63. package/dist/components/card/card-root.svelte.d.ts +1 -1
  64. package/dist/components/card/card-subtitle.svelte +41 -48
  65. package/dist/components/card/card-subtitle.svelte.d.ts +12 -9
  66. package/dist/components/card/card-title.svelte +45 -48
  67. package/dist/components/card/card-title.svelte.d.ts +12 -9
  68. package/dist/components/card/index.d.ts +1 -0
  69. package/dist/components/card/index.js +1 -0
  70. package/dist/components/card/types.d.ts +57 -2
  71. package/dist/components/checkbox/checkbox.svelte +39 -28
  72. package/dist/components/checkbox/types.d.ts +7 -2
  73. package/dist/components/collapsible/collapsible-body.svelte +39 -52
  74. package/dist/components/collapsible/collapsible-body.svelte.d.ts +2 -9
  75. package/dist/components/collapsible/collapsible-header.svelte +39 -52
  76. package/dist/components/collapsible/collapsible-header.svelte.d.ts +2 -9
  77. package/dist/components/collapsible/collapsible-indicator.svelte +50 -65
  78. package/dist/components/collapsible/collapsible-indicator.svelte.d.ts +3 -10
  79. package/dist/components/collapsible/collapsible-root.svelte +66 -85
  80. package/dist/components/collapsible/collapsible-root.svelte.d.ts +2 -14
  81. package/dist/components/collapsible/index.d.ts +1 -0
  82. package/dist/components/collapsible/index.js +1 -0
  83. package/dist/components/collapsible/types.d.ts +54 -0
  84. package/dist/components/collapsible/types.js +1 -0
  85. package/dist/components/combobox/atoms.d.ts +5 -1
  86. package/dist/components/combobox/atoms.js +5 -1
  87. package/dist/components/combobox/{combobox-input.svelte → combobox-control.svelte} +3 -3
  88. package/dist/components/combobox/{combobox-input.svelte.d.ts → combobox-control.svelte.d.ts} +3 -3
  89. package/dist/components/combobox/combobox-root.svelte +65 -68
  90. package/dist/components/combobox/combobox-root.svelte.d.ts +5 -18
  91. package/dist/components/combobox/combobox-trigger.svelte +1 -1
  92. package/dist/components/combobox/compobox-item.svelte +1 -1
  93. package/dist/components/combobox/index.d.ts +1 -0
  94. package/dist/components/combobox/index.js +1 -0
  95. package/dist/components/combobox/types.d.ts +25 -0
  96. package/dist/components/combobox/types.js +1 -0
  97. package/dist/components/container/container.svelte +1 -1
  98. package/dist/components/container/types.d.ts +7 -2
  99. package/dist/components/contextmenu/types.d.ts +8 -0
  100. package/dist/components/contextmenu/types.js +1 -0
  101. package/dist/components/datagrid/datagrid-body.svelte +37 -44
  102. package/dist/components/datagrid/datagrid-body.svelte.d.ts +17 -20
  103. package/dist/components/datagrid/datagrid-checkbox.svelte +101 -108
  104. package/dist/components/datagrid/datagrid-checkbox.svelte.d.ts +4 -6
  105. package/dist/components/datagrid/datagrid-footer.svelte +34 -34
  106. package/dist/components/datagrid/datagrid-footer.svelte.d.ts +1 -1
  107. package/dist/components/datagrid/datagrid-header.svelte +49 -49
  108. package/dist/components/datagrid/datagrid-header.svelte.d.ts +1 -1
  109. package/dist/components/datagrid/datagrid-root.svelte +59 -59
  110. package/dist/components/datagrid/datagrid-root.svelte.d.ts +1 -1
  111. package/dist/components/datagrid/datagrid.stories.svelte +75 -75
  112. package/dist/components/datagrid/td/datagrid-td.svelte +66 -80
  113. package/dist/components/datagrid/td/datagrid-td.svelte.d.ts +7 -16
  114. package/dist/components/datagrid/th/datagrid-th-sort-icon.svelte +1 -1
  115. package/dist/components/datagrid/th/datagrid-th.svelte +106 -127
  116. package/dist/components/datagrid/th/datagrid-th.svelte.d.ts +2 -20
  117. package/dist/components/datagrid/tr/bond.svelte.d.ts +3 -1
  118. package/dist/components/datagrid/tr/bond.svelte.js +4 -2
  119. package/dist/components/datagrid/tr/datagrid-tr.svelte +88 -103
  120. package/dist/components/datagrid/tr/datagrid-tr.svelte.d.ts +2 -18
  121. package/dist/components/datagrid/types.d.ts +85 -37
  122. package/dist/components/dialog/dialog-body.svelte +39 -45
  123. package/dist/components/dialog/dialog-body.svelte.d.ts +2 -2
  124. package/dist/components/dialog/dialog-close-button.svelte +58 -61
  125. package/dist/components/dialog/dialog-close-button.svelte.d.ts +7 -7
  126. package/dist/components/dialog/dialog-content.svelte +62 -71
  127. package/dist/components/dialog/dialog-content.svelte.d.ts +2 -2
  128. package/dist/components/dialog/dialog-description.svelte +40 -46
  129. package/dist/components/dialog/dialog-description.svelte.d.ts +2 -2
  130. package/dist/components/dialog/dialog-footer.svelte +39 -45
  131. package/dist/components/dialog/dialog-footer.svelte.d.ts +2 -2
  132. package/dist/components/dialog/dialog-header.svelte +39 -45
  133. package/dist/components/dialog/dialog-header.svelte.d.ts +2 -2
  134. package/dist/components/dialog/dialog-root.svelte +110 -120
  135. package/dist/components/dialog/dialog-root.svelte.d.ts +2 -10
  136. package/dist/components/dialog/dialog-title.svelte +41 -47
  137. package/dist/components/dialog/dialog-title.svelte.d.ts +7 -7
  138. package/dist/components/dialog/index.d.ts +1 -0
  139. package/dist/components/dialog/index.js +1 -0
  140. package/dist/components/dialog/types.d.ts +67 -0
  141. package/dist/components/dialog/types.js +1 -0
  142. package/dist/components/divider/types.d.ts +10 -0
  143. package/dist/components/divider/types.js +1 -0
  144. package/dist/components/drawer/drawer-backdrop.svelte +38 -47
  145. package/dist/components/drawer/drawer-backdrop.svelte.d.ts +3 -26
  146. package/dist/components/drawer/drawer-body.svelte +42 -56
  147. package/dist/components/drawer/drawer-body.svelte.d.ts +3 -16
  148. package/dist/components/drawer/drawer-content.svelte +42 -55
  149. package/dist/components/drawer/drawer-content.svelte.d.ts +3 -14
  150. package/dist/components/drawer/drawer-description.svelte +44 -57
  151. package/dist/components/drawer/drawer-description.svelte.d.ts +3 -14
  152. package/dist/components/drawer/drawer-footer.svelte +41 -54
  153. package/dist/components/drawer/drawer-footer.svelte.d.ts +3 -14
  154. package/dist/components/drawer/drawer-header.svelte +43 -56
  155. package/dist/components/drawer/drawer-header.svelte.d.ts +3 -14
  156. package/dist/components/drawer/drawer-root.svelte +93 -113
  157. package/dist/components/drawer/drawer-root.svelte.d.ts +3 -31
  158. package/dist/components/drawer/drawer-title.svelte +44 -57
  159. package/dist/components/drawer/drawer-title.svelte.d.ts +3 -14
  160. package/dist/components/drawer/index.d.ts +1 -0
  161. package/dist/components/drawer/index.js +1 -0
  162. package/dist/components/drawer/types.d.ts +86 -0
  163. package/dist/components/drawer/types.js +1 -0
  164. package/dist/components/dropdown/dropdown-placeholder.svelte +1 -1
  165. package/dist/components/dropdown/dropdown-query.svelte +54 -53
  166. package/dist/components/dropdown/dropdown-query.svelte.d.ts +11 -10
  167. package/dist/components/dropdown/dropdown-root.svelte +59 -59
  168. package/dist/components/dropdown/dropdown-trigger.svelte +41 -52
  169. package/dist/components/dropdown/dropdown-trigger.svelte.d.ts +1 -8
  170. package/dist/components/dropdown/dropdown-value.svelte +60 -62
  171. package/dist/components/dropdown/index.d.ts +1 -0
  172. package/dist/components/dropdown/index.js +1 -0
  173. package/dist/components/dropdown/item/bond.svelte.d.ts +4 -0
  174. package/dist/components/dropdown/item/bond.svelte.js +9 -0
  175. package/dist/components/dropdown/item/dropdown-item.svelte +10 -6
  176. package/dist/components/dropdown/types.d.ts +37 -0
  177. package/dist/components/dropdown/types.js +1 -0
  178. package/dist/components/element/html-element.svelte.d.ts +2 -14
  179. package/dist/components/element/svg-element.svelte.d.ts +2 -14
  180. package/dist/components/element/types.d.ts +14 -7
  181. package/dist/components/form/field/bond.svelte.d.ts +8 -0
  182. package/dist/components/form/field/bond.svelte.js +13 -1
  183. package/dist/components/form/field/field-control.svelte +48 -58
  184. package/dist/components/form/field/field-control.svelte.d.ts +5 -19
  185. package/dist/components/form/field/field-label.svelte +24 -31
  186. package/dist/components/form/field/field-label.svelte.d.ts +1 -2
  187. package/dist/components/form/field/field-root.svelte +59 -88
  188. package/dist/components/form/field/field-root.svelte.d.ts +5 -20
  189. package/dist/components/form/form.stories.svelte +3 -3
  190. package/dist/components/form/index.d.ts +1 -0
  191. package/dist/components/form/index.js +1 -0
  192. package/dist/components/form/types.d.ts +76 -0
  193. package/dist/components/form/types.js +1 -0
  194. package/dist/components/icon/icon.svelte +44 -55
  195. package/dist/components/icon/icon.svelte.d.ts +4 -29
  196. package/dist/components/icon/types.d.ts +11 -7
  197. package/dist/components/input/atoms.d.ts +5 -1
  198. package/dist/components/input/atoms.js +5 -1
  199. package/dist/components/input/index.d.ts +1 -0
  200. package/dist/components/input/index.js +1 -0
  201. package/dist/components/input/{input-value.svelte → input-control.svelte} +14 -24
  202. package/dist/components/input/input-control.svelte.d.ts +26 -0
  203. package/dist/components/input/input-icon.svelte +1 -1
  204. package/dist/components/input/input-icon.svelte.d.ts +1 -1
  205. package/dist/components/input/input-placeholder.svelte +54 -56
  206. package/dist/components/input/input-placeholder.svelte.d.ts +2 -19
  207. package/dist/components/input/input-root.svelte +5 -12
  208. package/dist/components/input/input-root.svelte.d.ts +3 -20
  209. package/dist/components/input/input.stories.svelte +2 -2
  210. package/dist/components/input/types.d.ts +33 -0
  211. package/dist/components/input/types.js +1 -0
  212. package/dist/components/label/index.d.ts +1 -0
  213. package/dist/components/label/index.js +1 -0
  214. package/dist/components/label/label.svelte +25 -41
  215. package/dist/components/label/label.svelte.d.ts +3 -27
  216. package/dist/components/label/types.d.ts +11 -0
  217. package/dist/components/label/types.js +1 -0
  218. package/dist/components/layer/layer-inner.svelte.d.ts +2 -19
  219. package/dist/components/layer/layer-root.svelte.d.ts +2 -19
  220. package/dist/components/layer/types.d.ts +11 -0
  221. package/dist/components/layer/types.js +1 -0
  222. package/dist/components/link/types.d.ts +8 -0
  223. package/dist/components/link/types.js +1 -0
  224. package/dist/components/list/list-group.svelte +1 -1
  225. package/dist/components/list/list-item.svelte +1 -1
  226. package/dist/components/list/list-root.svelte +6 -1
  227. package/dist/components/list/list-title.svelte +1 -1
  228. package/dist/components/list/types.d.ts +8 -0
  229. package/dist/components/list/types.js +1 -0
  230. package/dist/components/menu/index.d.ts +1 -0
  231. package/dist/components/menu/index.js +1 -0
  232. package/dist/components/menu/menu-list.svelte +1 -1
  233. package/dist/components/menu/types.d.ts +15 -0
  234. package/dist/components/menu/types.js +1 -0
  235. package/dist/components/popover/bond.svelte.d.ts +2 -0
  236. package/dist/components/popover/bond.svelte.js +1 -1
  237. package/dist/components/popover/index.d.ts +1 -0
  238. package/dist/components/popover/index.js +1 -0
  239. package/dist/components/popover/popover-arrow.svelte +111 -117
  240. package/dist/components/popover/popover-arrow.svelte.d.ts +3 -20
  241. package/dist/components/popover/popover-content.svelte +139 -147
  242. package/dist/components/popover/popover-content.svelte.d.ts +3 -17
  243. package/dist/components/popover/popover-indicator.svelte +1 -1
  244. package/dist/components/popover/popover-root.svelte +4 -18
  245. package/dist/components/popover/popover-root.svelte.d.ts +1 -15
  246. package/dist/components/popover/popover-trigger.svelte +3 -12
  247. package/dist/components/popover/popover-trigger.svelte.d.ts +2 -8
  248. package/dist/components/popover/types.d.ts +61 -0
  249. package/dist/components/popover/types.js +1 -0
  250. package/dist/components/portal/active-portal.svelte +8 -2
  251. package/dist/components/portal/active-portal.svelte.d.ts +2 -2
  252. package/dist/components/portal/index.d.ts +1 -0
  253. package/dist/components/portal/index.js +1 -0
  254. package/dist/components/portal/portal-inner.svelte +1 -1
  255. package/dist/components/portal/portal-inner.svelte.d.ts +2 -19
  256. package/dist/components/portal/portal-root.svelte +83 -88
  257. package/dist/components/portal/portal-root.svelte.d.ts +2 -22
  258. package/dist/components/portal/teleport.svelte +50 -49
  259. package/dist/components/portal/teleport.svelte.d.ts +5 -23
  260. package/dist/components/portal/types.d.ts +39 -0
  261. package/dist/components/portal/types.js +1 -0
  262. package/dist/components/radio/radio-group.stories.svelte +4 -4
  263. package/dist/components/radio/radio.svelte +109 -109
  264. package/dist/components/radio/radio.svelte.d.ts +14 -36
  265. package/dist/components/root/root.css +24 -66
  266. package/dist/components/root/root.svelte +121 -121
  267. package/dist/components/root/types.d.ts +8 -0
  268. package/dist/components/root/types.js +1 -0
  269. package/dist/components/scrollable/index.d.ts +1 -0
  270. package/dist/components/scrollable/index.js +1 -0
  271. package/dist/components/scrollable/scrollable-container.svelte +82 -89
  272. package/dist/components/scrollable/scrollable-container.svelte.d.ts +2 -6
  273. package/dist/components/scrollable/scrollable-content.svelte +41 -51
  274. package/dist/components/scrollable/scrollable-content.svelte.d.ts +1 -6
  275. package/dist/components/scrollable/scrollable-root.svelte +100 -120
  276. package/dist/components/scrollable/scrollable-root.svelte.d.ts +3 -19
  277. package/dist/components/scrollable/scrollable-thumb.svelte +75 -86
  278. package/dist/components/scrollable/scrollable-thumb.svelte.d.ts +1 -7
  279. package/dist/components/scrollable/scrollable-track.svelte +59 -70
  280. package/dist/components/scrollable/scrollable-track.svelte.d.ts +1 -7
  281. package/dist/components/scrollable/types.d.ts +62 -0
  282. package/dist/components/scrollable/types.js +1 -0
  283. package/dist/components/sidebar/index.d.ts +1 -0
  284. package/dist/components/sidebar/index.js +1 -0
  285. package/dist/components/sidebar/sidebar-content.svelte +2 -16
  286. package/dist/components/sidebar/sidebar-content.svelte.d.ts +2 -9
  287. package/dist/components/sidebar/sidebar-root.svelte +4 -23
  288. package/dist/components/sidebar/sidebar-root.svelte.d.ts +2 -13
  289. package/dist/components/sidebar/types.d.ts +30 -0
  290. package/dist/components/sidebar/types.js +1 -0
  291. package/dist/components/stack/stack-item.svelte +5 -1
  292. package/dist/components/stack/stack-root.svelte +5 -1
  293. package/dist/components/stack/stack-root.svelte.d.ts +2 -19
  294. package/dist/components/stack/types.d.ts +12 -0
  295. package/dist/components/stack/types.js +1 -0
  296. package/dist/components/tabs/index.d.ts +1 -0
  297. package/dist/components/tabs/index.js +1 -0
  298. package/dist/components/tabs/tab/tab-body.svelte +52 -61
  299. package/dist/components/tabs/tab/tab-body.svelte.d.ts +2 -8
  300. package/dist/components/tabs/tab/tab-description.svelte +41 -50
  301. package/dist/components/tabs/tab/tab-description.svelte.d.ts +2 -8
  302. package/dist/components/tabs/tab/tab-header.svelte +71 -81
  303. package/dist/components/tabs/tab/tab-header.svelte.d.ts +2 -11
  304. package/dist/components/tabs/tab/tab-root.svelte +86 -81
  305. package/dist/components/tabs/tabs-body.svelte +1 -1
  306. package/dist/components/tabs/tabs-header.svelte +1 -1
  307. package/dist/components/tabs/tabs-root.svelte +1 -1
  308. package/dist/components/tabs/types.d.ts +55 -0
  309. package/dist/components/tabs/types.js +1 -0
  310. package/dist/components/textarea/index.d.ts +1 -0
  311. package/dist/components/textarea/index.js +1 -0
  312. package/dist/components/textarea/textarea-input.svelte +2 -1
  313. package/dist/components/textarea/types.d.ts +28 -0
  314. package/dist/components/textarea/types.js +1 -0
  315. package/dist/components/toast/index.d.ts +1 -0
  316. package/dist/components/toast/index.js +1 -0
  317. package/dist/components/toast/toast-description.svelte +38 -44
  318. package/dist/components/toast/toast-description.svelte.d.ts +8 -34
  319. package/dist/components/toast/toast-root.svelte +61 -74
  320. package/dist/components/toast/toast-root.svelte.d.ts +4 -43
  321. package/dist/components/toast/toast-title.svelte +35 -43
  322. package/dist/components/toast/toast-title.svelte.d.ts +2 -34
  323. package/dist/components/toast/types.d.ts +40 -0
  324. package/dist/components/toast/types.js +1 -0
  325. package/dist/components/tooltip/types.d.ts +13 -0
  326. package/dist/components/tooltip/types.js +1 -0
  327. package/dist/components/tree/index.d.ts +1 -0
  328. package/dist/components/tree/index.js +1 -0
  329. package/dist/components/tree/tree-body.svelte +39 -50
  330. package/dist/components/tree/tree-body.svelte.d.ts +2 -10
  331. package/dist/components/tree/tree-header.svelte +54 -66
  332. package/dist/components/tree/tree-header.svelte.d.ts +3 -29
  333. package/dist/components/tree/tree-indicator.svelte +40 -50
  334. package/dist/components/tree/tree-indicator.svelte.d.ts +3 -9
  335. package/dist/components/tree/tree-root.svelte +65 -80
  336. package/dist/components/tree/tree-root.svelte.d.ts +2 -12
  337. package/dist/components/tree/types.d.ts +59 -0
  338. package/dist/components/tree/types.js +1 -0
  339. package/dist/components/virtual/types.d.ts +23 -0
  340. package/dist/components/virtual/types.js +1 -0
  341. package/dist/components/virtual/virtual-root.svelte +239 -258
  342. package/dist/components/virtual/virtual-root.svelte.d.ts +1 -18
  343. package/dist/context/preset.svelte.d.ts +1 -1
  344. package/llm/composition.md +395 -395
  345. package/llm/crafting.md +838 -838
  346. package/llm/motion.md +970 -970
  347. package/llm/philosophy.md +23 -23
  348. package/llm/preset-variant-integration.md +516 -516
  349. package/llm/preset.md +383 -383
  350. package/llm/styling.md +216 -216
  351. package/llm/usage.md +46 -46
  352. package/llm/variants.md +712 -712
  353. package/package.json +437 -437
  354. package/dist/components/input/input-value.svelte.d.ts +0 -19
package/llm/motion.md CHANGED
@@ -1,970 +1,970 @@
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.
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.