@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/crafting.md CHANGED
@@ -1,838 +1,838 @@
1
- # Crafting Components from Scratch in @svelte-atoms/core
2
-
3
- ## Overview
4
-
5
- Components in @svelte-atoms/core follow a **Bond-based architecture** where state, DOM element references, and component methods are centralized in Bond/BondState classes. This guide shows how to build components from scratch using the Bond pattern.
6
-
7
- ## Component Architecture
8
-
9
- Every component consists of three layers:
10
-
11
- 1. **Bond Layer** (`bond.svelte.ts`) - State management and DOM coordination
12
- 2. **Root Component** (`component-root.svelte`) - Parent container that creates and shares the bond
13
- 3. **Child Components** (`component-title.svelte`, etc.) - Access shared bond via context
14
-
15
- ## Core Building Blocks
16
-
17
- ### 1. Base Classes
18
-
19
- ```typescript
20
- import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
21
-
22
- // BondStateProps: Base props type with optional id
23
- export type BondStateProps = Record<string, unknown> & { id?: string };
24
-
25
- // BondState: Manages component props and generates unique IDs
26
- export abstract class BondState<S extends BondStateProps = BondStateProps> {
27
- constructor(props: () => S, id?: string);
28
- get id(): string;
29
- get props(): S;
30
- }
31
-
32
- // Bond: Manages component state, DOM elements, and context sharing
33
- export abstract class Bond<
34
- Props extends BondStateProps = BondStateProps,
35
- State extends BondState<Props> = BondState<Props>,
36
- Elements extends BondElements = BondElements
37
- > {
38
- constructor(state: State);
39
- get state(): State;
40
- get elements(): Elements;
41
- abstract share(): this;
42
- static get(): unknown | undefined;
43
- static set(bond: unknown): unknown;
44
- }
45
- ```
46
-
47
- ### 2. Utility Functions
48
-
49
- ```typescript
50
- import { defineState, defineProperty } from '$svelte-atoms/core/utils';
51
-
52
- // defineState: Create reactive props object
53
- const bondProps = defineState<MyBondProps>(
54
- [
55
- defineProperty('open', () => open, (v) => { open = v; }),
56
- defineProperty('disabled', () => disabled)
57
- ],
58
- () => ({ extend: {} })
59
- );
60
-
61
- // defineProperty: Create reactive property with getter/setter
62
- defineProperty<T, R>(
63
- property: keyof T,
64
- get: () => R,
65
- set?: (value: R) => void
66
- )
67
- ```
68
-
69
- ## Step-by-Step: Building a Component
70
-
71
- ### Step 1: Define Bond Types and Classes
72
-
73
- Create `my-component/bond.svelte.ts`:
74
-
75
- ```typescript
76
- import { createAttachmentKey } from 'svelte/attachments';
77
- import { getContext, setContext } from 'svelte';
78
- import { getElementId } from '$svelte-atoms/core/utils/dom.svelte';
79
- import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
80
-
81
- // 1. Define Props Type
82
- export type MyComponentBondProps = BondStateProps & {
83
- open: boolean;
84
- disabled: boolean;
85
- variant?: 'default' | 'primary' | 'danger';
86
- extend?: Record<string, unknown>;
87
- };
88
-
89
- // 2. Define Elements Type (all DOM elements your component manages)
90
- export type MyComponentBondElements = {
91
- root: HTMLElement;
92
- header: HTMLElement;
93
- title: HTMLElement;
94
- content: HTMLElement;
95
- footer: HTMLElement;
96
- };
97
-
98
- // 3. Define Element Kinds (for data-kind attributes and ID generation)
99
- const MY_COMPONENT_ELEMENTS_KIND = {
100
- root: 'my-component-root',
101
- header: 'my-component-header',
102
- title: 'my-component-title',
103
- content: 'my-component-content',
104
- footer: 'my-component-footer'
105
- };
106
-
107
- // 4. Create BondState Class
108
- export class MyComponentBondState<
109
- Props extends MyComponentBondProps = MyComponentBondProps
110
- > extends BondState<Props> {
111
- constructor(props: () => Props) {
112
- super(props);
113
- }
114
-
115
- // Add component-specific methods
116
- open() {
117
- this.props.open = true;
118
- }
119
-
120
- close() {
121
- this.props.open = false;
122
- }
123
-
124
- toggle() {
125
- this.props.open = !this.props.open;
126
- }
127
- }
128
-
129
- // 5. Create Bond Class
130
- export class MyComponentBond<
131
- Props extends MyComponentBondProps = MyComponentBondProps,
132
- State extends MyComponentBondState<Props> = MyComponentBondState<Props>
133
- > extends Bond<Props, State, MyComponentBondElements> {
134
- static CONTEXT_KEY = '@atoms/context/my-component';
135
-
136
- constructor(state: State) {
137
- super(state);
138
- }
139
-
140
- // 6. Create element prop generators (return props + attachment for each element)
141
- root(props: Record<string, unknown> = {}) {
142
- const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.root);
143
- const titleId = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
144
-
145
- const isOpen = this.state.props.open ?? false;
146
- const isDisabled = this.state.props.disabled ?? false;
147
-
148
- return {
149
- id,
150
- role: 'dialog',
151
- 'aria-labelledby': titleId,
152
- 'aria-expanded': isOpen,
153
- 'aria-disabled': isDisabled,
154
- 'data-kind': MY_COMPONENT_ELEMENTS_KIND.root,
155
- 'data-variant': this.state.props.variant,
156
- ...props,
157
- [createAttachmentKey()]: (node: HTMLElement) => {
158
- this.elements.root = node;
159
- }
160
- };
161
- }
162
-
163
- header(props: Record<string, unknown> = {}) {
164
- const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.header);
165
- return {
166
- id,
167
- 'data-kind': MY_COMPONENT_ELEMENTS_KIND.header,
168
- ...props,
169
- [createAttachmentKey()]: (node: HTMLElement) => {
170
- this.elements.header = node;
171
- }
172
- };
173
- }
174
-
175
- title(props: Record<string, unknown> = {}) {
176
- const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
177
- return {
178
- id,
179
- 'data-kind': MY_COMPONENT_ELEMENTS_KIND.title,
180
- ...props,
181
- [createAttachmentKey()]: (node: HTMLElement) => {
182
- this.elements.title = node;
183
- }
184
- };
185
- }
186
-
187
- content(props: Record<string, unknown> = {}) {
188
- const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.content);
189
- return {
190
- id,
191
- role: 'region',
192
- 'data-kind': MY_COMPONENT_ELEMENTS_KIND.content,
193
- ...props,
194
- [createAttachmentKey()]: (node: HTMLElement) => {
195
- this.elements.content = node;
196
- }
197
- };
198
- }
199
-
200
- footer(props: Record<string, unknown> = {}) {
201
- const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.footer);
202
- return {
203
- id,
204
- 'data-kind': MY_COMPONENT_ELEMENTS_KIND.footer,
205
- ...props,
206
- [createAttachmentKey()]: (node: HTMLElement) => {
207
- this.elements.footer = node;
208
- }
209
- };
210
- }
211
-
212
- // 7. Implement share() to set bond in context
213
- share(): this {
214
- return MyComponentBond.set(this) as this;
215
- }
216
-
217
- // 8. Implement static get/set for context management
218
- static get(): MyComponentBond | undefined {
219
- return getContext(MyComponentBond.CONTEXT_KEY);
220
- }
221
-
222
- static set(bond: MyComponentBond): MyComponentBond {
223
- return setContext(MyComponentBond.CONTEXT_KEY, bond);
224
- }
225
- }
226
- ```
227
-
228
- ### Step 2: Create Root Component
229
-
230
- Create `my-component/my-component-root.svelte`:
231
-
232
- ```svelte
233
- <script module lang="ts">
234
- import type { Factory } from '$svelte-atoms/core/types';
235
-
236
- export type MyComponentRootProps<
237
- E extends keyof HTMLElementTagNameMap = 'div',
238
- B extends Base = Base
239
- > = Override<
240
- HtmlAtomProps<E, B>,
241
- {
242
- children?: Snippet<[{ myComponent: MyComponentBond }]>;
243
- }
244
- > & {
245
- open?: boolean;
246
- disabled?: boolean;
247
- variant?: 'default' | 'primary' | 'danger';
248
- onclose?: (event: Event, bond: MyComponentBond) => void;
249
- factory?: Factory<MyComponentBond>;
250
- };
251
- </script>
252
-
253
- <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
254
- import type { Snippet } from 'svelte';
255
- import { defineProperty, defineState } from '$svelte-atoms/core/utils';
256
- import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
257
- import type { Override } from '$svelte-atoms/core/types';
258
- import { MyComponentBond, MyComponentBondState, type MyComponentBondProps } from './bond.svelte';
259
-
260
- // 1. Define props with defaults and $bindable for two-way binding
261
- let {
262
- open = $bindable(false),
263
- disabled = false,
264
- variant = 'default',
265
- children = undefined,
266
- class: klass = '',
267
- onclose = undefined,
268
- onmount = undefined,
269
- ondestroy = undefined,
270
- animate = undefined,
271
- enter = undefined,
272
- exit = undefined,
273
- initial = undefined,
274
- factory = _factory,
275
- ...restProps
276
- }: MyComponentRootProps<E, B> = $props();
277
-
278
- // 2. Create reactive bond props using defineState and defineProperty
279
- const bondProps = defineState<MyComponentBondProps>(
280
- [
281
- defineProperty(
282
- 'open',
283
- () => open,
284
- (v) => {
285
- open = v;
286
- }
287
- ),
288
- defineProperty('disabled', () => disabled),
289
- defineProperty('variant', () => variant)
290
- ],
291
- () => ({})
292
- );
293
-
294
- // 3. Create and share bond
295
- const bond = factory(bondProps).share();
296
-
297
- // 4. Merge bond props with user props
298
- const rootProps = $derived({
299
- ...bond.root(),
300
- ...restProps
301
- });
302
-
303
- // 5. Factory function to create bond
304
- function _factory(props: typeof bondProps) {
305
- const state = new MyComponentBondState(() => props);
306
- return new MyComponentBond(state);
307
- }
308
-
309
- // 6. Optional: Export function to access bond from parent
310
- export function getBond() {
311
- return bond;
312
- }
313
-
314
- // 7. Optional: Handle side effects
315
- $effect(() => {
316
- if (open && !disabled) {
317
- // Do something when opened
318
- console.log('Component opened');
319
- }
320
- });
321
- </script>
322
-
323
- <HtmlAtom
324
- preset="my-component"
325
- class={[
326
- 'my-component bg-card border-border rounded-lg border shadow-sm',
327
- disabled && 'cursor-not-allowed opacity-50',
328
- '$preset',
329
- klass
330
- ]}
331
- {bond}
332
- enter={enter?.bind(bond.state)}
333
- exit={exit?.bind(bond.state)}
334
- initial={initial?.bind(bond.state)}
335
- animate={animate?.bind(bond.state)}
336
- onmount={onmount?.bind(bond.state)}
337
- ondestroy={ondestroy?.bind(bond.state)}
338
- {...rootProps}
339
- >
340
- {@render children?.({ myComponent: bond })}
341
- </HtmlAtom>
342
- ```
343
-
344
- ### Step 3: Create Child Components
345
-
346
- Create `my-component/my-component-title.svelte`:
347
-
348
- ```svelte
349
- <script module lang="ts">
350
- export type MyComponentTitleProps<
351
- E extends keyof HTMLElementTagNameMap = 'h3',
352
- B extends Base = Base
353
- > = HtmlAtomProps<E, B>;
354
- </script>
355
-
356
- <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'h3', B extends Base = Base">
357
- import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
358
- import { MyComponentBond } from './bond.svelte';
359
-
360
- // 1. Get bond from context (shared by root component)
361
- const bond = MyComponentBond.get();
362
-
363
- // 2. Define props
364
- let {
365
- class: klass = '',
366
- as = 'h3' as E,
367
- children = undefined,
368
- onmount = undefined,
369
- ondestroy = undefined,
370
- animate = undefined,
371
- enter = undefined,
372
- exit = undefined,
373
- initial = undefined,
374
- ...restProps
375
- }: MyComponentTitleProps<E, B> = $props();
376
-
377
- // 3. Merge bond props with user props
378
- const titleProps = $derived({
379
- ...bond?.title(),
380
- ...restProps
381
- });
382
- </script>
383
-
384
- <HtmlAtom
385
- {as}
386
- {bond}
387
- preset="my-component.title"
388
- class={['my-component-title text-lg font-semibold', '$preset', klass]}
389
- enter={enter?.bind(bond.state)}
390
- exit={exit?.bind(bond.state)}
391
- initial={initial?.bind(bond.state)}
392
- animate={animate?.bind(bond.state)}
393
- onmount={onmount?.bind(bond.state)}
394
- ondestroy={ondestroy?.bind(bond.state)}
395
- {...titleProps}
396
- >
397
- {@render children?.()}
398
- </HtmlAtom>
399
- ```
400
-
401
- Create `my-component/my-component-content.svelte`:
402
-
403
- ```svelte
404
- <script module lang="ts">
405
- export type MyComponentContentProps<
406
- E extends keyof HTMLElementTagNameMap = 'div',
407
- B extends Base = Base
408
- > = Override<
409
- HtmlAtomProps<E, B>,
410
- {
411
- children?: Snippet<[{ myComponent?: MyComponentBond }]>;
412
- }
413
- >;
414
- </script>
415
-
416
- <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
417
- import type { Snippet } from 'svelte';
418
- import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
419
- import type { Override } from '$svelte-atoms/core/types';
420
- import { MyComponentBond } from './bond.svelte';
421
-
422
- const bond = MyComponentBond.get();
423
-
424
- let {
425
- class: klass = '',
426
- children = undefined,
427
- onmount = undefined,
428
- ondestroy = undefined,
429
- animate = undefined,
430
- enter = undefined,
431
- exit = undefined,
432
- initial = undefined,
433
- ...restProps
434
- }: MyComponentContentProps<E, B> = $props();
435
-
436
- const contentProps = $derived({
437
- ...bond?.content(),
438
- ...restProps
439
- });
440
- </script>
441
-
442
- <HtmlAtom
443
- preset="my-component.content"
444
- class={['my-component-content p-4', '$preset', klass]}
445
- {bond}
446
- onmount={onmount?.bind(bond.state)}
447
- ondestroy={ondestroy?.bind(bond.state)}
448
- enter={enter?.bind(bond.state)}
449
- exit={exit?.bind(bond.state)}
450
- initial={initial?.bind(bond.state)}
451
- animate={animate?.bind(bond.state)}
452
- {...contentProps}
453
- >
454
- {@render children?.({ myComponent: bond })}
455
- </HtmlAtom>
456
- ```
457
-
458
- ### Step 4: Create Index Exports
459
-
460
- Create `my-component/atoms.ts`:
461
-
462
- ```typescript
463
- export { default as Root } from './my-component-root.svelte';
464
- export { default as Header } from './my-component-header.svelte';
465
- export { default as Title } from './my-component-title.svelte';
466
- export { default as Content } from './my-component-content.svelte';
467
- export { default as Footer } from './my-component-footer.svelte';
468
- ```
469
-
470
- Create `my-component/index.ts`:
471
-
472
- ```typescript
473
- export * as MyComponent from './atoms';
474
- export {
475
- MyComponentBond,
476
- MyComponentBondState,
477
- type MyComponentBondProps,
478
- type MyComponentBondElements
479
- } from './bond.svelte';
480
- ```
481
-
482
- ### Step 5: Usage
483
-
484
- ```svelte
485
- <script>
486
- import { MyComponent } from '@svelte-atoms/core/components/my-component';
487
- import { Button } from '@svelte-atoms/core/components/button';
488
-
489
- let open = $state(false);
490
- </script>
491
-
492
- <MyComponent.Root bind:open>
493
- {#snippet children({ myComponent })}
494
- <MyComponent.Header>
495
- <MyComponent.Title>My Component Title</MyComponent.Title>
496
- </MyComponent.Header>
497
-
498
- <MyComponent.Content>
499
- <p>This is the content</p>
500
-
501
- <!-- Access bond state -->
502
- <p>Is open: {myComponent.state.isOpen}</p>
503
-
504
- <!-- Control from inside -->
505
- <Button onclick={() => myComponent.state.close()}>Close from inside</Button>
506
- </MyComponent.Content>
507
-
508
- <MyComponent.Footer>
509
- <Button>Action</Button>
510
- </MyComponent.Footer>
511
- {/snippet}
512
- </MyComponent.Root>
513
- ```
514
-
515
- ## Key Patterns
516
-
517
- ### 1. Reactive Props with defineState
518
-
519
- Use `defineState` and `defineProperty` to create reactive props that sync with component props:
520
-
521
- ```typescript
522
- const bondProps = defineState<MyBondProps>(
523
- [
524
- // Two-way binding: changes in component prop update bond, vice versa
525
- defineProperty(
526
- 'open',
527
- () => open,
528
- (v) => {
529
- open = v;
530
- }
531
- ),
532
-
533
- // Read-only: only reads from component prop
534
- defineProperty('disabled', () => disabled),
535
-
536
- // Computed: derived from other values
537
- defineProperty('isActive', () => open && !disabled)
538
- ],
539
- () => ({ extend: {} }) // Base props
540
- );
541
- ```
542
-
543
- ### 2. Element Prop Generators
544
-
545
- Each Bond method returns props object with:
546
-
547
- - **Unique ID**: `getElementId(this.id, kind)`
548
- - **ARIA attributes**: Accessibility attributes based on state
549
- - **Data attributes**: `data-kind`, `data-variant`, etc.
550
- - **Attachment key**: Captures DOM element reference
551
-
552
- ```typescript
553
- content(props: Record<string, unknown> = {}) {
554
- const id = getElementId(this.id, ELEMENT_KIND.content);
555
- const isOpen = this.state.props.open ?? false;
556
-
557
- return {
558
- id,
559
- role: 'region',
560
- 'aria-expanded': isOpen,
561
- 'data-kind': ELEMENT_KIND.content,
562
- ...props, // Merge user props
563
- [createAttachmentKey()]: (node: HTMLElement) => {
564
- this.elements.content = node; // Capture element
565
- }
566
- };
567
- }
568
- ```
569
-
570
- ### 3. Context Sharing
571
-
572
- Bond is shared via Svelte context:
573
-
574
- ```typescript
575
- // Root component: Create and share
576
- const bond = factory(bondProps).share();
577
-
578
- // Bond.share() implementation
579
- share(): this {
580
- return MyComponentBond.set(this) as this;
581
- }
582
-
583
- static set(bond: MyComponentBond): MyComponentBond {
584
- return setContext(MyComponentBond.CONTEXT_KEY, bond);
585
- }
586
-
587
- // Child components: Access from context
588
- const bond = MyComponentBond.get();
589
-
590
- static get(): MyComponentBond | undefined {
591
- return getContext(MyComponentBond.CONTEXT_KEY);
592
- }
593
- ```
594
-
595
- ### 4. State Methods
596
-
597
- Add component-specific methods to BondState:
598
-
599
- ```typescript
600
- export class MyComponentBondState extends BondState<MyComponentBondProps> {
601
- open() {
602
- this.props.open = true;
603
- }
604
-
605
- close() {
606
- this.props.open = false;
607
- }
608
-
609
- toggle() {
610
- this.props.open = !this.props.open;
611
- }
612
-
613
- // Complex state logic
614
- validateAndClose() {
615
- if (this.isValid()) {
616
- this.close();
617
- }
618
- }
619
-
620
- isValid() {
621
- return this.props.value !== undefined;
622
- }
623
- }
624
- ```
625
-
626
- ### 5. Binding Lifecycle Hooks
627
-
628
- Bind animation/lifecycle hooks to bond.state to pass state to callbacks:
629
-
630
- ```svelte
631
- <HtmlAtom
632
- {bond}
633
- enter={enter?.bind(bond.state)}
634
- exit={exit?.bind(bond.state)}
635
- initial={initial?.bind(bond.state)}
636
- animate={animate?.bind(bond.state)}
637
- onmount={onmount?.bind(bond.state)}
638
- ondestroy={ondestroy?.bind(bond.state)}
639
- {...props}
640
- >
641
- ```
642
-
643
- This allows hooks to receive bond state as `this`:
644
-
645
- ```svelte
646
- <MyComponent.Root
647
- enter={function(node) {
648
- // `this` is MyComponentBondState
649
- console.log('Opening:', this.props.open);
650
- return animate(node, { opacity: [0, 1] });
651
- }}
652
- >
653
- ```
654
-
655
- ### 6. Factory Pattern
656
-
657
- Allow custom bond creation via factory prop:
658
-
659
- ```typescript
660
- type MyComponentRootProps = {
661
- factory?: Factory<MyComponentBond>;
662
- };
663
-
664
- function _factory(props: typeof bondProps) {
665
- const state = new MyComponentBondState(() => props);
666
- return new MyComponentBond(state);
667
- }
668
-
669
- // User can extend
670
- <MyComponent.Root
671
- factory={(props) => {
672
- const state = new CustomBondState(() => props);
673
- return new CustomBond(state);
674
- }}
675
- >
676
- ```
677
-
678
- ### 7. Attachment Key Pattern
679
-
680
- Use `createAttachmentKey()` to capture DOM elements in bond:
681
-
682
- ```typescript
683
- [createAttachmentKey()]: (node: HTMLElement) => {
684
- this.elements.content = node;
685
-
686
- // Optional: Do setup
687
- node.addEventListener('scroll', handleScroll);
688
-
689
- // Optional: Return cleanup
690
- return () => {
691
- node.removeEventListener('scroll', handleScroll);
692
- };
693
- }
694
- ```
695
-
696
- ## Advanced Patterns
697
-
698
- ### Extending Existing Bonds
699
-
700
- Create component that extends another:
701
-
702
- ```typescript
703
- // Dropdown extends Popover
704
- export class DropdownBond<
705
- Props extends DropdownBondProps = DropdownBondProps,
706
- State extends DropdownBondState<Props> = DropdownBondState<Props>,
707
- Elements extends DropdownBondElements = DropdownBondElements
708
- > extends PopoverBond<Props, State, Elements> {
709
- // Add dropdown-specific functionality
710
- selectItem(value: string) {
711
- this.state.props.value = value;
712
- this.state.close();
713
- }
714
- }
715
-
716
- export class DropdownBondState<
717
- Props extends DropdownBondProps = DropdownBondProps
718
- > extends PopoverState<Props> {
719
- // Inherit open/close/toggle from PopoverState
720
- // Add dropdown-specific state
721
- }
722
- ```
723
-
724
- ### Multiple Element References
725
-
726
- Track multiple instances of same element type:
727
-
728
- ```typescript
729
- export type ListBondElements = {
730
- root: HTMLElement;
731
- items: HTMLElement[]; // Array of items
732
- };
733
-
734
- export class ListBond extends Bond<ListBondProps, ListBondState, ListBondElements> {
735
- constructor(state: ListBondState) {
736
- super(state);
737
- this.elements.items = []; // Initialize array
738
- }
739
-
740
- item(index: number, props: Record<string, unknown> = {}) {
741
- return {
742
- id: getElementId(this.id, `item-${index}`),
743
- 'data-index': index,
744
- ...props,
745
- [createAttachmentKey()]: (node: HTMLElement) => {
746
- this.elements.items[index] = node;
747
- }
748
- };
749
- }
750
- }
751
- ```
752
-
753
- ### Validation Integration
754
-
755
- Add validation to component state:
756
-
757
- ```typescript
758
- export class FieldBondState extends BondState<FieldBondProps> {
759
- #errors = $state<ValidationError[]>([]);
760
-
761
- get errors() {
762
- return this.#errors;
763
- }
764
-
765
- validate(): ValidationResult {
766
- const { schema, validator, value } = this.props;
767
-
768
- if (!schema || !validator) {
769
- return { success: true, errors: [] };
770
- }
771
-
772
- const result = validator.validate(schema, value);
773
- this.#errors = result.errors;
774
-
775
- this.props.onvalidation?.(result.errors);
776
-
777
- return result;
778
- }
779
-
780
- async validateAsync(): Promise<ValidationResult> {
781
- const { schema, validator, value } = this.props;
782
-
783
- if (!schema || !validator?.validateAsync) {
784
- return this.validate();
785
- }
786
-
787
- const result = await validator.validateAsync(schema, value);
788
- this.#errors = result.errors;
789
-
790
- this.props.onvalidation?.(result.errors);
791
-
792
- return result;
793
- }
794
- }
795
- ```
796
-
797
- ## Component Checklist
798
-
799
- When creating a new component:
800
-
801
- - [ ] Define `MyComponentBondProps` type with all component props
802
- - [ ] Define `MyComponentBondElements` type with all managed DOM elements
803
- - [ ] Create element kinds object for consistent `data-kind` attributes
804
- - [ ] Create `MyComponentBondState` class extending `BondState`
805
- - [ ] Add state methods (open, close, validate, etc.)
806
- - [ ] Create `MyComponentBond` class extending `Bond`
807
- - [ ] Implement element prop generators (root, title, content, etc.)
808
- - [ ] Implement `share()` method
809
- - [ ] Implement static `get()` and `set()` methods with unique `CONTEXT_KEY`
810
- - [ ] Create root component with `defineState`/`defineProperty` for reactive props
811
- - [ ] Create factory function to instantiate bond
812
- - [ ] Call `bond.share()` to set in context
813
- - [ ] Create child components that access bond via `MyComponentBond.get()`
814
- - [ ] Merge bond props with user props using `$derived`
815
- - [ ] Bind lifecycle hooks to `bond.state`
816
- - [ ] Export all components in `atoms.ts`
817
- - [ ] Export bond classes and types in `index.ts`
818
- - [ ] Add ARIA attributes for accessibility
819
- - [ ] Add `data-kind` attributes for styling hooks
820
- - [ ] Generate unique IDs using `getElementId()`
821
-
822
- ## Summary
823
-
824
- The Bond pattern centralizes component state and DOM coordination:
825
-
826
- 1. **BondState**: Manages props and state methods
827
- 2. **Bond**: Manages DOM elements, generates props, shares via context
828
- 3. **Root Component**: Creates bond, shares via context, renders with children snippet
829
- 4. **Child Components**: Access bond from context, merge with own props
830
-
831
- Benefits:
832
-
833
- - **Centralized state**: All state and methods in one place
834
- - **Type-safe**: Full TypeScript support across all components
835
- - **Context-based**: Child components automatically access shared state
836
- - **Composable**: Components can extend existing bonds
837
- - **Accessible**: ARIA attributes automatically generated
838
- - **Flexible**: User props override bond defaults
1
+ # Crafting Components from Scratch in @svelte-atoms/core
2
+
3
+ ## Overview
4
+
5
+ Components in @svelte-atoms/core follow a **Bond-based architecture** where state, DOM element references, and component methods are centralized in Bond/BondState classes. This guide shows how to build components from scratch using the Bond pattern.
6
+
7
+ ## Component Architecture
8
+
9
+ Every component consists of three layers:
10
+
11
+ 1. **Bond Layer** (`bond.svelte.ts`) - State management and DOM coordination
12
+ 2. **Root Component** (`component-root.svelte`) - Parent container that creates and shares the bond
13
+ 3. **Child Components** (`component-title.svelte`, etc.) - Access shared bond via context
14
+
15
+ ## Core Building Blocks
16
+
17
+ ### 1. Base Classes
18
+
19
+ ```typescript
20
+ import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
21
+
22
+ // BondStateProps: Base props type with optional id
23
+ export type BondStateProps = Record<string, unknown> & { id?: string };
24
+
25
+ // BondState: Manages component props and generates unique IDs
26
+ export abstract class BondState<S extends BondStateProps = BondStateProps> {
27
+ constructor(props: () => S, id?: string);
28
+ get id(): string;
29
+ get props(): S;
30
+ }
31
+
32
+ // Bond: Manages component state, DOM elements, and context sharing
33
+ export abstract class Bond<
34
+ Props extends BondStateProps = BondStateProps,
35
+ State extends BondState<Props> = BondState<Props>,
36
+ Elements extends BondElements = BondElements
37
+ > {
38
+ constructor(state: State);
39
+ get state(): State;
40
+ get elements(): Elements;
41
+ abstract share(): this;
42
+ static get(): unknown | undefined;
43
+ static set(bond: unknown): unknown;
44
+ }
45
+ ```
46
+
47
+ ### 2. Utility Functions
48
+
49
+ ```typescript
50
+ import { defineState, defineProperty } from '$svelte-atoms/core/utils';
51
+
52
+ // defineState: Create reactive props object
53
+ const bondProps = defineState<MyBondProps>(
54
+ [
55
+ defineProperty('open', () => open, (v) => { open = v; }),
56
+ defineProperty('disabled', () => disabled)
57
+ ],
58
+ () => ({ extend: {} })
59
+ );
60
+
61
+ // defineProperty: Create reactive property with getter/setter
62
+ defineProperty<T, R>(
63
+ property: keyof T,
64
+ get: () => R,
65
+ set?: (value: R) => void
66
+ )
67
+ ```
68
+
69
+ ## Step-by-Step: Building a Component
70
+
71
+ ### Step 1: Define Bond Types and Classes
72
+
73
+ Create `my-component/bond.svelte.ts`:
74
+
75
+ ```typescript
76
+ import { createAttachmentKey } from 'svelte/attachments';
77
+ import { getContext, setContext } from 'svelte';
78
+ import { getElementId } from '$svelte-atoms/core/utils/dom.svelte';
79
+ import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
80
+
81
+ // 1. Define Props Type
82
+ export type MyComponentBondProps = BondStateProps & {
83
+ open: boolean;
84
+ disabled: boolean;
85
+ variant?: 'default' | 'primary' | 'danger';
86
+ extend?: Record<string, unknown>;
87
+ };
88
+
89
+ // 2. Define Elements Type (all DOM elements your component manages)
90
+ export type MyComponentBondElements = {
91
+ root: HTMLElement;
92
+ header: HTMLElement;
93
+ title: HTMLElement;
94
+ content: HTMLElement;
95
+ footer: HTMLElement;
96
+ };
97
+
98
+ // 3. Define Element Kinds (for data-kind attributes and ID generation)
99
+ const MY_COMPONENT_ELEMENTS_KIND = {
100
+ root: 'my-component-root',
101
+ header: 'my-component-header',
102
+ title: 'my-component-title',
103
+ content: 'my-component-content',
104
+ footer: 'my-component-footer'
105
+ };
106
+
107
+ // 4. Create BondState Class
108
+ export class MyComponentBondState<
109
+ Props extends MyComponentBondProps = MyComponentBondProps
110
+ > extends BondState<Props> {
111
+ constructor(props: () => Props) {
112
+ super(props);
113
+ }
114
+
115
+ // Add component-specific methods
116
+ open() {
117
+ this.props.open = true;
118
+ }
119
+
120
+ close() {
121
+ this.props.open = false;
122
+ }
123
+
124
+ toggle() {
125
+ this.props.open = !this.props.open;
126
+ }
127
+ }
128
+
129
+ // 5. Create Bond Class
130
+ export class MyComponentBond<
131
+ Props extends MyComponentBondProps = MyComponentBondProps,
132
+ State extends MyComponentBondState<Props> = MyComponentBondState<Props>
133
+ > extends Bond<Props, State, MyComponentBondElements> {
134
+ static CONTEXT_KEY = '@atoms/context/my-component';
135
+
136
+ constructor(state: State) {
137
+ super(state);
138
+ }
139
+
140
+ // 6. Create element prop generators (return props + attachment for each element)
141
+ root(props: Record<string, unknown> = {}) {
142
+ const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.root);
143
+ const titleId = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
144
+
145
+ const isOpen = this.state.props.open ?? false;
146
+ const isDisabled = this.state.props.disabled ?? false;
147
+
148
+ return {
149
+ id,
150
+ role: 'dialog',
151
+ 'aria-labelledby': titleId,
152
+ 'aria-expanded': isOpen,
153
+ 'aria-disabled': isDisabled,
154
+ 'data-kind': MY_COMPONENT_ELEMENTS_KIND.root,
155
+ 'data-variant': this.state.props.variant,
156
+ ...props,
157
+ [createAttachmentKey()]: (node: HTMLElement) => {
158
+ this.elements.root = node;
159
+ }
160
+ };
161
+ }
162
+
163
+ header(props: Record<string, unknown> = {}) {
164
+ const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.header);
165
+ return {
166
+ id,
167
+ 'data-kind': MY_COMPONENT_ELEMENTS_KIND.header,
168
+ ...props,
169
+ [createAttachmentKey()]: (node: HTMLElement) => {
170
+ this.elements.header = node;
171
+ }
172
+ };
173
+ }
174
+
175
+ title(props: Record<string, unknown> = {}) {
176
+ const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
177
+ return {
178
+ id,
179
+ 'data-kind': MY_COMPONENT_ELEMENTS_KIND.title,
180
+ ...props,
181
+ [createAttachmentKey()]: (node: HTMLElement) => {
182
+ this.elements.title = node;
183
+ }
184
+ };
185
+ }
186
+
187
+ content(props: Record<string, unknown> = {}) {
188
+ const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.content);
189
+ return {
190
+ id,
191
+ role: 'region',
192
+ 'data-kind': MY_COMPONENT_ELEMENTS_KIND.content,
193
+ ...props,
194
+ [createAttachmentKey()]: (node: HTMLElement) => {
195
+ this.elements.content = node;
196
+ }
197
+ };
198
+ }
199
+
200
+ footer(props: Record<string, unknown> = {}) {
201
+ const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.footer);
202
+ return {
203
+ id,
204
+ 'data-kind': MY_COMPONENT_ELEMENTS_KIND.footer,
205
+ ...props,
206
+ [createAttachmentKey()]: (node: HTMLElement) => {
207
+ this.elements.footer = node;
208
+ }
209
+ };
210
+ }
211
+
212
+ // 7. Implement share() to set bond in context
213
+ share(): this {
214
+ return MyComponentBond.set(this) as this;
215
+ }
216
+
217
+ // 8. Implement static get/set for context management
218
+ static get(): MyComponentBond | undefined {
219
+ return getContext(MyComponentBond.CONTEXT_KEY);
220
+ }
221
+
222
+ static set(bond: MyComponentBond): MyComponentBond {
223
+ return setContext(MyComponentBond.CONTEXT_KEY, bond);
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### Step 2: Create Root Component
229
+
230
+ Create `my-component/my-component-root.svelte`:
231
+
232
+ ```svelte
233
+ <script module lang="ts">
234
+ import type { Factory } from '$svelte-atoms/core/types';
235
+
236
+ export type MyComponentRootProps<
237
+ E extends keyof HTMLElementTagNameMap = 'div',
238
+ B extends Base = Base
239
+ > = Override<
240
+ HtmlAtomProps<E, B>,
241
+ {
242
+ children?: Snippet<[{ myComponent: MyComponentBond }]>;
243
+ }
244
+ > & {
245
+ open?: boolean;
246
+ disabled?: boolean;
247
+ variant?: 'default' | 'primary' | 'danger';
248
+ onclose?: (event: Event, bond: MyComponentBond) => void;
249
+ factory?: Factory<MyComponentBond>;
250
+ };
251
+ </script>
252
+
253
+ <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
254
+ import type { Snippet } from 'svelte';
255
+ import { defineProperty, defineState } from '$svelte-atoms/core/utils';
256
+ import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
257
+ import type { Override } from '$svelte-atoms/core/types';
258
+ import { MyComponentBond, MyComponentBondState, type MyComponentBondProps } from './bond.svelte';
259
+
260
+ // 1. Define props with defaults and $bindable for two-way binding
261
+ let {
262
+ open = $bindable(false),
263
+ disabled = false,
264
+ variant = 'default',
265
+ children = undefined,
266
+ class: klass = '',
267
+ onclose = undefined,
268
+ onmount = undefined,
269
+ ondestroy = undefined,
270
+ animate = undefined,
271
+ enter = undefined,
272
+ exit = undefined,
273
+ initial = undefined,
274
+ factory = _factory,
275
+ ...restProps
276
+ }: MyComponentRootProps<E, B> = $props();
277
+
278
+ // 2. Create reactive bond props using defineState and defineProperty
279
+ const bondProps = defineState<MyComponentBondProps>(
280
+ [
281
+ defineProperty(
282
+ 'open',
283
+ () => open,
284
+ (v) => {
285
+ open = v;
286
+ }
287
+ ),
288
+ defineProperty('disabled', () => disabled),
289
+ defineProperty('variant', () => variant)
290
+ ],
291
+ () => ({})
292
+ );
293
+
294
+ // 3. Create and share bond
295
+ const bond = factory(bondProps).share();
296
+
297
+ // 4. Merge bond props with user props
298
+ const rootProps = $derived({
299
+ ...bond.root(),
300
+ ...restProps
301
+ });
302
+
303
+ // 5. Factory function to create bond
304
+ function _factory(props: typeof bondProps) {
305
+ const state = new MyComponentBondState(() => props);
306
+ return new MyComponentBond(state);
307
+ }
308
+
309
+ // 6. Optional: Export function to access bond from parent
310
+ export function getBond() {
311
+ return bond;
312
+ }
313
+
314
+ // 7. Optional: Handle side effects
315
+ $effect(() => {
316
+ if (open && !disabled) {
317
+ // Do something when opened
318
+ console.log('Component opened');
319
+ }
320
+ });
321
+ </script>
322
+
323
+ <HtmlAtom
324
+ preset="my-component"
325
+ class={[
326
+ 'my-component bg-card border-border rounded-lg border shadow-sm',
327
+ disabled && 'cursor-not-allowed opacity-50',
328
+ '$preset',
329
+ klass
330
+ ]}
331
+ {bond}
332
+ enter={enter?.bind(bond.state)}
333
+ exit={exit?.bind(bond.state)}
334
+ initial={initial?.bind(bond.state)}
335
+ animate={animate?.bind(bond.state)}
336
+ onmount={onmount?.bind(bond.state)}
337
+ ondestroy={ondestroy?.bind(bond.state)}
338
+ {...rootProps}
339
+ >
340
+ {@render children?.({ myComponent: bond })}
341
+ </HtmlAtom>
342
+ ```
343
+
344
+ ### Step 3: Create Child Components
345
+
346
+ Create `my-component/my-component-title.svelte`:
347
+
348
+ ```svelte
349
+ <script module lang="ts">
350
+ export type MyComponentTitleProps<
351
+ E extends keyof HTMLElementTagNameMap = 'h3',
352
+ B extends Base = Base
353
+ > = HtmlAtomProps<E, B>;
354
+ </script>
355
+
356
+ <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'h3', B extends Base = Base">
357
+ import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
358
+ import { MyComponentBond } from './bond.svelte';
359
+
360
+ // 1. Get bond from context (shared by root component)
361
+ const bond = MyComponentBond.get();
362
+
363
+ // 2. Define props
364
+ let {
365
+ class: klass = '',
366
+ as = 'h3' as E,
367
+ children = undefined,
368
+ onmount = undefined,
369
+ ondestroy = undefined,
370
+ animate = undefined,
371
+ enter = undefined,
372
+ exit = undefined,
373
+ initial = undefined,
374
+ ...restProps
375
+ }: MyComponentTitleProps<E, B> = $props();
376
+
377
+ // 3. Merge bond props with user props
378
+ const titleProps = $derived({
379
+ ...bond?.title(),
380
+ ...restProps
381
+ });
382
+ </script>
383
+
384
+ <HtmlAtom
385
+ {as}
386
+ {bond}
387
+ preset="my-component.title"
388
+ class={['my-component-title text-lg font-semibold', '$preset', klass]}
389
+ enter={enter?.bind(bond.state)}
390
+ exit={exit?.bind(bond.state)}
391
+ initial={initial?.bind(bond.state)}
392
+ animate={animate?.bind(bond.state)}
393
+ onmount={onmount?.bind(bond.state)}
394
+ ondestroy={ondestroy?.bind(bond.state)}
395
+ {...titleProps}
396
+ >
397
+ {@render children?.()}
398
+ </HtmlAtom>
399
+ ```
400
+
401
+ Create `my-component/my-component-content.svelte`:
402
+
403
+ ```svelte
404
+ <script module lang="ts">
405
+ export type MyComponentContentProps<
406
+ E extends keyof HTMLElementTagNameMap = 'div',
407
+ B extends Base = Base
408
+ > = Override<
409
+ HtmlAtomProps<E, B>,
410
+ {
411
+ children?: Snippet<[{ myComponent?: MyComponentBond }]>;
412
+ }
413
+ >;
414
+ </script>
415
+
416
+ <script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
417
+ import type { Snippet } from 'svelte';
418
+ import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
419
+ import type { Override } from '$svelte-atoms/core/types';
420
+ import { MyComponentBond } from './bond.svelte';
421
+
422
+ const bond = MyComponentBond.get();
423
+
424
+ let {
425
+ class: klass = '',
426
+ children = undefined,
427
+ onmount = undefined,
428
+ ondestroy = undefined,
429
+ animate = undefined,
430
+ enter = undefined,
431
+ exit = undefined,
432
+ initial = undefined,
433
+ ...restProps
434
+ }: MyComponentContentProps<E, B> = $props();
435
+
436
+ const contentProps = $derived({
437
+ ...bond?.content(),
438
+ ...restProps
439
+ });
440
+ </script>
441
+
442
+ <HtmlAtom
443
+ preset="my-component.content"
444
+ class={['my-component-content p-4', '$preset', klass]}
445
+ {bond}
446
+ onmount={onmount?.bind(bond.state)}
447
+ ondestroy={ondestroy?.bind(bond.state)}
448
+ enter={enter?.bind(bond.state)}
449
+ exit={exit?.bind(bond.state)}
450
+ initial={initial?.bind(bond.state)}
451
+ animate={animate?.bind(bond.state)}
452
+ {...contentProps}
453
+ >
454
+ {@render children?.({ myComponent: bond })}
455
+ </HtmlAtom>
456
+ ```
457
+
458
+ ### Step 4: Create Index Exports
459
+
460
+ Create `my-component/atoms.ts`:
461
+
462
+ ```typescript
463
+ export { default as Root } from './my-component-root.svelte';
464
+ export { default as Header } from './my-component-header.svelte';
465
+ export { default as Title } from './my-component-title.svelte';
466
+ export { default as Content } from './my-component-content.svelte';
467
+ export { default as Footer } from './my-component-footer.svelte';
468
+ ```
469
+
470
+ Create `my-component/index.ts`:
471
+
472
+ ```typescript
473
+ export * as MyComponent from './atoms';
474
+ export {
475
+ MyComponentBond,
476
+ MyComponentBondState,
477
+ type MyComponentBondProps,
478
+ type MyComponentBondElements
479
+ } from './bond.svelte';
480
+ ```
481
+
482
+ ### Step 5: Usage
483
+
484
+ ```svelte
485
+ <script>
486
+ import { MyComponent } from '@svelte-atoms/core/components/my-component';
487
+ import { Button } from '@svelte-atoms/core/components/button';
488
+
489
+ let open = $state(false);
490
+ </script>
491
+
492
+ <MyComponent.Root bind:open>
493
+ {#snippet children({ myComponent })}
494
+ <MyComponent.Header>
495
+ <MyComponent.Title>My Component Title</MyComponent.Title>
496
+ </MyComponent.Header>
497
+
498
+ <MyComponent.Content>
499
+ <p>This is the content</p>
500
+
501
+ <!-- Access bond state -->
502
+ <p>Is open: {myComponent.state.isOpen}</p>
503
+
504
+ <!-- Control from inside -->
505
+ <Button onclick={() => myComponent.state.close()}>Close from inside</Button>
506
+ </MyComponent.Content>
507
+
508
+ <MyComponent.Footer>
509
+ <Button>Action</Button>
510
+ </MyComponent.Footer>
511
+ {/snippet}
512
+ </MyComponent.Root>
513
+ ```
514
+
515
+ ## Key Patterns
516
+
517
+ ### 1. Reactive Props with defineState
518
+
519
+ Use `defineState` and `defineProperty` to create reactive props that sync with component props:
520
+
521
+ ```typescript
522
+ const bondProps = defineState<MyBondProps>(
523
+ [
524
+ // Two-way binding: changes in component prop update bond, vice versa
525
+ defineProperty(
526
+ 'open',
527
+ () => open,
528
+ (v) => {
529
+ open = v;
530
+ }
531
+ ),
532
+
533
+ // Read-only: only reads from component prop
534
+ defineProperty('disabled', () => disabled),
535
+
536
+ // Computed: derived from other values
537
+ defineProperty('isActive', () => open && !disabled)
538
+ ],
539
+ () => ({ extend: {} }) // Base props
540
+ );
541
+ ```
542
+
543
+ ### 2. Element Prop Generators
544
+
545
+ Each Bond method returns props object with:
546
+
547
+ - **Unique ID**: `getElementId(this.id, kind)`
548
+ - **ARIA attributes**: Accessibility attributes based on state
549
+ - **Data attributes**: `data-kind`, `data-variant`, etc.
550
+ - **Attachment key**: Captures DOM element reference
551
+
552
+ ```typescript
553
+ content(props: Record<string, unknown> = {}) {
554
+ const id = getElementId(this.id, ELEMENT_KIND.content);
555
+ const isOpen = this.state.props.open ?? false;
556
+
557
+ return {
558
+ id,
559
+ role: 'region',
560
+ 'aria-expanded': isOpen,
561
+ 'data-kind': ELEMENT_KIND.content,
562
+ ...props, // Merge user props
563
+ [createAttachmentKey()]: (node: HTMLElement) => {
564
+ this.elements.content = node; // Capture element
565
+ }
566
+ };
567
+ }
568
+ ```
569
+
570
+ ### 3. Context Sharing
571
+
572
+ Bond is shared via Svelte context:
573
+
574
+ ```typescript
575
+ // Root component: Create and share
576
+ const bond = factory(bondProps).share();
577
+
578
+ // Bond.share() implementation
579
+ share(): this {
580
+ return MyComponentBond.set(this) as this;
581
+ }
582
+
583
+ static set(bond: MyComponentBond): MyComponentBond {
584
+ return setContext(MyComponentBond.CONTEXT_KEY, bond);
585
+ }
586
+
587
+ // Child components: Access from context
588
+ const bond = MyComponentBond.get();
589
+
590
+ static get(): MyComponentBond | undefined {
591
+ return getContext(MyComponentBond.CONTEXT_KEY);
592
+ }
593
+ ```
594
+
595
+ ### 4. State Methods
596
+
597
+ Add component-specific methods to BondState:
598
+
599
+ ```typescript
600
+ export class MyComponentBondState extends BondState<MyComponentBondProps> {
601
+ open() {
602
+ this.props.open = true;
603
+ }
604
+
605
+ close() {
606
+ this.props.open = false;
607
+ }
608
+
609
+ toggle() {
610
+ this.props.open = !this.props.open;
611
+ }
612
+
613
+ // Complex state logic
614
+ validateAndClose() {
615
+ if (this.isValid()) {
616
+ this.close();
617
+ }
618
+ }
619
+
620
+ isValid() {
621
+ return this.props.value !== undefined;
622
+ }
623
+ }
624
+ ```
625
+
626
+ ### 5. Binding Lifecycle Hooks
627
+
628
+ Bind animation/lifecycle hooks to bond.state to pass state to callbacks:
629
+
630
+ ```svelte
631
+ <HtmlAtom
632
+ {bond}
633
+ enter={enter?.bind(bond.state)}
634
+ exit={exit?.bind(bond.state)}
635
+ initial={initial?.bind(bond.state)}
636
+ animate={animate?.bind(bond.state)}
637
+ onmount={onmount?.bind(bond.state)}
638
+ ondestroy={ondestroy?.bind(bond.state)}
639
+ {...props}
640
+ >
641
+ ```
642
+
643
+ This allows hooks to receive bond state as `this`:
644
+
645
+ ```svelte
646
+ <MyComponent.Root
647
+ enter={function(node) {
648
+ // `this` is MyComponentBondState
649
+ console.log('Opening:', this.props.open);
650
+ return animate(node, { opacity: [0, 1] });
651
+ }}
652
+ >
653
+ ```
654
+
655
+ ### 6. Factory Pattern
656
+
657
+ Allow custom bond creation via factory prop:
658
+
659
+ ```typescript
660
+ type MyComponentRootProps = {
661
+ factory?: Factory<MyComponentBond>;
662
+ };
663
+
664
+ function _factory(props: typeof bondProps) {
665
+ const state = new MyComponentBondState(() => props);
666
+ return new MyComponentBond(state);
667
+ }
668
+
669
+ // User can extend
670
+ <MyComponent.Root
671
+ factory={(props) => {
672
+ const state = new CustomBondState(() => props);
673
+ return new CustomBond(state);
674
+ }}
675
+ >
676
+ ```
677
+
678
+ ### 7. Attachment Key Pattern
679
+
680
+ Use `createAttachmentKey()` to capture DOM elements in bond:
681
+
682
+ ```typescript
683
+ [createAttachmentKey()]: (node: HTMLElement) => {
684
+ this.elements.content = node;
685
+
686
+ // Optional: Do setup
687
+ node.addEventListener('scroll', handleScroll);
688
+
689
+ // Optional: Return cleanup
690
+ return () => {
691
+ node.removeEventListener('scroll', handleScroll);
692
+ };
693
+ }
694
+ ```
695
+
696
+ ## Advanced Patterns
697
+
698
+ ### Extending Existing Bonds
699
+
700
+ Create component that extends another:
701
+
702
+ ```typescript
703
+ // Dropdown extends Popover
704
+ export class DropdownBond<
705
+ Props extends DropdownBondProps = DropdownBondProps,
706
+ State extends DropdownBondState<Props> = DropdownBondState<Props>,
707
+ Elements extends DropdownBondElements = DropdownBondElements
708
+ > extends PopoverBond<Props, State, Elements> {
709
+ // Add dropdown-specific functionality
710
+ selectItem(value: string) {
711
+ this.state.props.value = value;
712
+ this.state.close();
713
+ }
714
+ }
715
+
716
+ export class DropdownBondState<
717
+ Props extends DropdownBondProps = DropdownBondProps
718
+ > extends PopoverState<Props> {
719
+ // Inherit open/close/toggle from PopoverState
720
+ // Add dropdown-specific state
721
+ }
722
+ ```
723
+
724
+ ### Multiple Element References
725
+
726
+ Track multiple instances of same element type:
727
+
728
+ ```typescript
729
+ export type ListBondElements = {
730
+ root: HTMLElement;
731
+ items: HTMLElement[]; // Array of items
732
+ };
733
+
734
+ export class ListBond extends Bond<ListBondProps, ListBondState, ListBondElements> {
735
+ constructor(state: ListBondState) {
736
+ super(state);
737
+ this.elements.items = []; // Initialize array
738
+ }
739
+
740
+ item(index: number, props: Record<string, unknown> = {}) {
741
+ return {
742
+ id: getElementId(this.id, `item-${index}`),
743
+ 'data-index': index,
744
+ ...props,
745
+ [createAttachmentKey()]: (node: HTMLElement) => {
746
+ this.elements.items[index] = node;
747
+ }
748
+ };
749
+ }
750
+ }
751
+ ```
752
+
753
+ ### Validation Integration
754
+
755
+ Add validation to component state:
756
+
757
+ ```typescript
758
+ export class FieldBondState extends BondState<FieldBondProps> {
759
+ #errors = $state<ValidationError[]>([]);
760
+
761
+ get errors() {
762
+ return this.#errors;
763
+ }
764
+
765
+ validate(): ValidationResult {
766
+ const { schema, validator, value } = this.props;
767
+
768
+ if (!schema || !validator) {
769
+ return { success: true, errors: [] };
770
+ }
771
+
772
+ const result = validator.validate(schema, value);
773
+ this.#errors = result.errors;
774
+
775
+ this.props.onvalidation?.(result.errors);
776
+
777
+ return result;
778
+ }
779
+
780
+ async validateAsync(): Promise<ValidationResult> {
781
+ const { schema, validator, value } = this.props;
782
+
783
+ if (!schema || !validator?.validateAsync) {
784
+ return this.validate();
785
+ }
786
+
787
+ const result = await validator.validateAsync(schema, value);
788
+ this.#errors = result.errors;
789
+
790
+ this.props.onvalidation?.(result.errors);
791
+
792
+ return result;
793
+ }
794
+ }
795
+ ```
796
+
797
+ ## Component Checklist
798
+
799
+ When creating a new component:
800
+
801
+ - [ ] Define `MyComponentBondProps` type with all component props
802
+ - [ ] Define `MyComponentBondElements` type with all managed DOM elements
803
+ - [ ] Create element kinds object for consistent `data-kind` attributes
804
+ - [ ] Create `MyComponentBondState` class extending `BondState`
805
+ - [ ] Add state methods (open, close, validate, etc.)
806
+ - [ ] Create `MyComponentBond` class extending `Bond`
807
+ - [ ] Implement element prop generators (root, title, content, etc.)
808
+ - [ ] Implement `share()` method
809
+ - [ ] Implement static `get()` and `set()` methods with unique `CONTEXT_KEY`
810
+ - [ ] Create root component with `defineState`/`defineProperty` for reactive props
811
+ - [ ] Create factory function to instantiate bond
812
+ - [ ] Call `bond.share()` to set in context
813
+ - [ ] Create child components that access bond via `MyComponentBond.get()`
814
+ - [ ] Merge bond props with user props using `$derived`
815
+ - [ ] Bind lifecycle hooks to `bond.state`
816
+ - [ ] Export all components in `atoms.ts`
817
+ - [ ] Export bond classes and types in `index.ts`
818
+ - [ ] Add ARIA attributes for accessibility
819
+ - [ ] Add `data-kind` attributes for styling hooks
820
+ - [ ] Generate unique IDs using `getElementId()`
821
+
822
+ ## Summary
823
+
824
+ The Bond pattern centralizes component state and DOM coordination:
825
+
826
+ 1. **BondState**: Manages props and state methods
827
+ 2. **Bond**: Manages DOM elements, generates props, shares via context
828
+ 3. **Root Component**: Creates bond, shares via context, renders with children snippet
829
+ 4. **Child Components**: Access bond from context, merge with own props
830
+
831
+ Benefits:
832
+
833
+ - **Centralized state**: All state and methods in one place
834
+ - **Type-safe**: Full TypeScript support across all components
835
+ - **Context-based**: Child components automatically access shared state
836
+ - **Composable**: Components can extend existing bonds
837
+ - **Accessible**: ARIA attributes automatically generated
838
+ - **Flexible**: User props override bond defaults