snice 2.5.4 → 3.1.0

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 (323) hide show
  1. package/README.md +501 -882
  2. package/bin/templates/base/src/components/counter-button.ts +13 -26
  3. package/bin/templates/base/src/controllers/counter-controller.ts +3 -3
  4. package/dist/components/accordion/snice-accordion-item.d.ts +4 -5
  5. package/dist/components/accordion/snice-accordion-item.js +37 -39
  6. package/dist/components/accordion/snice-accordion-item.js.map +1 -1
  7. package/dist/components/accordion/snice-accordion.d.ts +5 -11
  8. package/dist/components/accordion/snice-accordion.js +51 -52
  9. package/dist/components/accordion/snice-accordion.js.map +1 -1
  10. package/dist/components/alert/snice-alert.d.ts +2 -6
  11. package/dist/components/alert/snice-alert.js +41 -56
  12. package/dist/components/alert/snice-alert.js.map +1 -1
  13. package/dist/components/avatar/snice-avatar.d.ts +2 -6
  14. package/dist/components/avatar/snice-avatar.js +64 -71
  15. package/dist/components/avatar/snice-avatar.js.map +1 -1
  16. package/dist/components/badge/snice-badge.d.ts +2 -3
  17. package/dist/components/badge/snice-badge.js +22 -23
  18. package/dist/components/badge/snice-badge.js.map +1 -1
  19. package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +5 -12
  20. package/dist/components/breadcrumbs/snice-breadcrumbs.js +88 -89
  21. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
  22. package/dist/components/button/snice-button.d.ts +3 -7
  23. package/dist/components/button/snice-button.js +37 -58
  24. package/dist/components/button/snice-button.js.map +1 -1
  25. package/dist/components/card/snice-card.d.ts +5 -8
  26. package/dist/components/card/snice-card.js +71 -56
  27. package/dist/components/card/snice-card.js.map +1 -1
  28. package/dist/components/checkbox/snice-checkbox.d.ts +4 -13
  29. package/dist/components/checkbox/snice-checkbox.js +66 -137
  30. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  31. package/dist/components/chip/snice-chip.d.ts +5 -11
  32. package/dist/components/chip/snice-chip.js +44 -47
  33. package/dist/components/chip/snice-chip.js.map +1 -1
  34. package/dist/components/date-picker/snice-date-picker.d.ts +11 -11
  35. package/dist/components/date-picker/snice-date-picker.js +134 -133
  36. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  37. package/dist/components/divider/snice-divider.d.ts +2 -4
  38. package/dist/components/divider/snice-divider.js +14 -22
  39. package/dist/components/divider/snice-divider.js.map +1 -1
  40. package/dist/components/drawer/snice-drawer.d.ts +4 -4
  41. package/dist/components/drawer/snice-drawer.js +25 -19
  42. package/dist/components/drawer/snice-drawer.js.map +1 -1
  43. package/dist/components/input/snice-input.d.ts +8 -6
  44. package/dist/components/input/snice-input.js +122 -105
  45. package/dist/components/input/snice-input.js.map +1 -1
  46. package/dist/components/layout/snice-layout-blog.d.ts +4 -4
  47. package/dist/components/layout/snice-layout-blog.js +21 -19
  48. package/dist/components/layout/snice-layout-blog.js.map +1 -1
  49. package/dist/components/layout/snice-layout-card.d.ts +2 -2
  50. package/dist/components/layout/snice-layout-card.js +16 -9
  51. package/dist/components/layout/snice-layout-card.js.map +1 -1
  52. package/dist/components/layout/snice-layout-centered.d.ts +2 -2
  53. package/dist/components/layout/snice-layout-centered.js +14 -7
  54. package/dist/components/layout/snice-layout-centered.js.map +1 -1
  55. package/dist/components/layout/snice-layout-dashboard.d.ts +5 -5
  56. package/dist/components/layout/snice-layout-dashboard.js +38 -30
  57. package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
  58. package/dist/components/layout/snice-layout-fullscreen.d.ts +2 -2
  59. package/dist/components/layout/snice-layout-fullscreen.js +17 -10
  60. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
  61. package/dist/components/layout/snice-layout-landing.d.ts +4 -4
  62. package/dist/components/layout/snice-layout-landing.js +21 -19
  63. package/dist/components/layout/snice-layout-landing.js.map +1 -1
  64. package/dist/components/layout/snice-layout-minimal.d.ts +2 -2
  65. package/dist/components/layout/snice-layout-minimal.js +17 -6
  66. package/dist/components/layout/snice-layout-minimal.js.map +1 -1
  67. package/dist/components/layout/snice-layout-sidebar.d.ts +5 -4
  68. package/dist/components/layout/snice-layout-sidebar.js +42 -20
  69. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  70. package/dist/components/layout/snice-layout-split.d.ts +2 -2
  71. package/dist/components/layout/snice-layout-split.js +14 -7
  72. package/dist/components/layout/snice-layout-split.js.map +1 -1
  73. package/dist/components/layout/snice-layout.d.ts +4 -4
  74. package/dist/components/layout/snice-layout.js +16 -10
  75. package/dist/components/layout/snice-layout.js.map +1 -1
  76. package/dist/components/login/snice-login.d.ts +6 -11
  77. package/dist/components/login/snice-login.js +97 -71
  78. package/dist/components/login/snice-login.js.map +1 -1
  79. package/dist/components/modal/snice-modal.d.ts +5 -9
  80. package/dist/components/modal/snice-modal.js +47 -78
  81. package/dist/components/modal/snice-modal.js.map +1 -1
  82. package/dist/components/nav/snice-nav.d.ts +13 -7
  83. package/dist/components/nav/snice-nav.js +191 -100
  84. package/dist/components/nav/snice-nav.js.map +1 -1
  85. package/dist/components/nav/snice-nav.types.d.ts +3 -3
  86. package/dist/components/pagination/snice-pagination.d.ts +6 -7
  87. package/dist/components/pagination/snice-pagination.js +94 -81
  88. package/dist/components/pagination/snice-pagination.js.map +1 -1
  89. package/dist/components/progress/snice-progress.d.ts +2 -7
  90. package/dist/components/progress/snice-progress.js +41 -98
  91. package/dist/components/progress/snice-progress.js.map +1 -1
  92. package/dist/components/radio/snice-radio.d.ts +4 -4
  93. package/dist/components/radio/snice-radio.js +52 -44
  94. package/dist/components/radio/snice-radio.js.map +1 -1
  95. package/dist/components/select/snice-option.d.ts +2 -1
  96. package/dist/components/select/snice-option.js +12 -5
  97. package/dist/components/select/snice-option.js.map +1 -1
  98. package/dist/components/select/snice-select.d.ts +9 -21
  99. package/dist/components/select/snice-select.js +98 -170
  100. package/dist/components/select/snice-select.js.map +1 -1
  101. package/dist/components/skeleton/snice-skeleton.d.ts +2 -6
  102. package/dist/components/skeleton/snice-skeleton.js +18 -49
  103. package/dist/components/skeleton/snice-skeleton.js.map +1 -1
  104. package/dist/components/snice-cell-BLFVdxPp.js +4 -0
  105. package/dist/components/snice-cell-BLFVdxPp.js.map +1 -0
  106. package/dist/components/switch/snice-switch.d.ts +2 -2
  107. package/dist/components/switch/snice-switch.js +38 -26
  108. package/dist/components/switch/snice-switch.js.map +1 -1
  109. package/dist/components/table/snice-cell-actions.d.ts +24 -0
  110. package/dist/components/table/snice-cell-actions.js +149 -0
  111. package/dist/components/table/snice-cell-actions.js.map +1 -0
  112. package/dist/components/table/snice-cell-boolean.d.ts +2 -2
  113. package/dist/components/table/snice-cell-boolean.js +13 -7
  114. package/dist/components/table/snice-cell-boolean.js.map +1 -1
  115. package/dist/components/table/snice-cell-color.d.ts +18 -0
  116. package/dist/components/table/snice-cell-color.js +149 -0
  117. package/dist/components/table/snice-cell-color.js.map +1 -0
  118. package/dist/components/table/snice-cell-currency.d.ts +24 -0
  119. package/dist/components/table/snice-cell-currency.js +235 -0
  120. package/dist/components/table/snice-cell-currency.js.map +1 -0
  121. package/dist/components/table/snice-cell-date.d.ts +2 -2
  122. package/dist/components/table/snice-cell-date.js +14 -8
  123. package/dist/components/table/snice-cell-date.js.map +1 -1
  124. package/dist/components/table/snice-cell-duration.d.ts +2 -2
  125. package/dist/components/table/snice-cell-duration.js +12 -6
  126. package/dist/components/table/snice-cell-duration.js.map +1 -1
  127. package/dist/components/table/snice-cell-email.d.ts +15 -0
  128. package/dist/components/table/snice-cell-email.js +125 -0
  129. package/dist/components/table/snice-cell-email.js.map +1 -0
  130. package/dist/components/table/snice-cell-filesize.d.ts +2 -2
  131. package/dist/components/table/snice-cell-filesize.js +12 -6
  132. package/dist/components/table/snice-cell-filesize.js.map +1 -1
  133. package/dist/components/table/snice-cell-image.d.ts +20 -0
  134. package/dist/components/table/snice-cell-image.js +162 -0
  135. package/dist/components/table/snice-cell-image.js.map +1 -0
  136. package/dist/components/table/snice-cell-json.d.ts +20 -0
  137. package/dist/components/table/snice-cell-json.js +186 -0
  138. package/dist/components/table/snice-cell-json.js.map +1 -0
  139. package/dist/components/table/snice-cell-link.d.ts +17 -0
  140. package/dist/components/table/snice-cell-link.js +142 -0
  141. package/dist/components/table/snice-cell-link.js.map +1 -0
  142. package/dist/components/table/snice-cell-location.d.ts +19 -0
  143. package/dist/components/table/snice-cell-location.js +185 -0
  144. package/dist/components/table/snice-cell-location.js.map +1 -0
  145. package/dist/components/table/snice-cell-number.d.ts +2 -2
  146. package/dist/components/table/snice-cell-number.js +12 -6
  147. package/dist/components/table/snice-cell-number.js.map +1 -1
  148. package/dist/components/table/snice-cell-percentage.d.ts +22 -0
  149. package/dist/components/table/snice-cell-percentage.js +208 -0
  150. package/dist/components/table/snice-cell-percentage.js.map +1 -0
  151. package/dist/components/table/snice-cell-phone.d.ts +18 -0
  152. package/dist/components/table/snice-cell-phone.js +153 -0
  153. package/dist/components/table/snice-cell-phone.js.map +1 -0
  154. package/dist/components/table/snice-cell-progress.d.ts +2 -2
  155. package/dist/components/table/snice-cell-progress.js +12 -6
  156. package/dist/components/table/snice-cell-progress.js.map +1 -1
  157. package/dist/components/table/snice-cell-rating.d.ts +2 -2
  158. package/dist/components/table/snice-cell-rating.js +12 -6
  159. package/dist/components/table/snice-cell-rating.js.map +1 -1
  160. package/dist/components/table/snice-cell-sparkline.d.ts +2 -2
  161. package/dist/components/table/snice-cell-sparkline.js +13 -7
  162. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  163. package/dist/components/table/snice-cell-status.d.ts +17 -0
  164. package/dist/components/table/snice-cell-status.js +144 -0
  165. package/dist/components/table/snice-cell-status.js.map +1 -0
  166. package/dist/components/table/snice-cell-tag.d.ts +16 -0
  167. package/dist/components/table/snice-cell-tag.js +131 -0
  168. package/dist/components/table/snice-cell-tag.js.map +1 -0
  169. package/dist/components/table/snice-cell-text.d.ts +2 -2
  170. package/dist/components/table/snice-cell-text.js +14 -8
  171. package/dist/components/table/snice-cell-text.js.map +1 -1
  172. package/dist/components/table/snice-cell.d.ts +2 -2
  173. package/dist/components/table/snice-cell.js +12 -6
  174. package/dist/components/table/snice-cell.js.map +1 -1
  175. package/dist/components/table/snice-column.d.ts +1 -1
  176. package/dist/components/table/snice-column.js +6 -3
  177. package/dist/components/table/snice-column.js.map +1 -1
  178. package/dist/components/table/snice-header.d.ts +5 -5
  179. package/dist/components/table/snice-header.js +60 -50
  180. package/dist/components/table/snice-header.js.map +1 -1
  181. package/dist/components/table/snice-progress.d.ts +2 -2
  182. package/dist/components/table/snice-progress.js +18 -11
  183. package/dist/components/table/snice-progress.js.map +1 -1
  184. package/dist/components/table/snice-rating.d.ts +2 -2
  185. package/dist/components/table/snice-rating.js +15 -8
  186. package/dist/components/table/snice-rating.js.map +1 -1
  187. package/dist/components/table/snice-row.d.ts +17 -6
  188. package/dist/components/table/snice-row.js +95 -44
  189. package/dist/components/table/snice-row.js.map +1 -1
  190. package/dist/components/table/snice-table.d.ts +18 -10
  191. package/dist/components/table/snice-table.js +355 -173
  192. package/dist/components/table/snice-table.js.map +1 -1
  193. package/dist/components/table/snice-table.types.d.ts +101 -2
  194. package/dist/components/tabs/snice-tab-panel.d.ts +2 -2
  195. package/dist/components/tabs/snice-tab-panel.js +12 -6
  196. package/dist/components/tabs/snice-tab-panel.js.map +1 -1
  197. package/dist/components/tabs/snice-tab.d.ts +6 -5
  198. package/dist/components/tabs/snice-tab.js +36 -19
  199. package/dist/components/tabs/snice-tab.js.map +1 -1
  200. package/dist/components/tabs/snice-tabs.d.ts +5 -5
  201. package/dist/components/tabs/snice-tabs.js +38 -28
  202. package/dist/components/tabs/snice-tabs.js.map +1 -1
  203. package/dist/components/toast/snice-toast-container.d.ts +7 -7
  204. package/dist/components/toast/snice-toast-container.js +19 -12
  205. package/dist/components/toast/snice-toast-container.js.map +1 -1
  206. package/dist/components/toast/snice-toast.d.ts +3 -15
  207. package/dist/components/toast/snice-toast.js +49 -108
  208. package/dist/components/toast/snice-toast.js.map +1 -1
  209. package/dist/components/tooltip/snice-tooltip.d.ts +2 -2
  210. package/dist/components/tooltip/snice-tooltip.js +14 -7
  211. package/dist/components/tooltip/snice-tooltip.js.map +1 -1
  212. package/dist/context.d.ts +44 -0
  213. package/dist/element-ready.d.ts +40 -0
  214. package/dist/{types/element.d.ts → element.d.ts} +2 -8
  215. package/dist/{types/events.d.ts → events.d.ts} +0 -4
  216. package/dist/index.cjs +2589 -605
  217. package/dist/index.cjs.map +1 -1
  218. package/dist/index.d.ts +21 -0
  219. package/dist/index.esm.js +2568 -604
  220. package/dist/index.esm.js.map +1 -1
  221. package/dist/index.iife.js +2589 -605
  222. package/dist/index.iife.js.map +1 -1
  223. package/dist/method-decorators.d.ts +121 -0
  224. package/dist/on.d.ts +59 -0
  225. package/dist/parts.d.ts +159 -0
  226. package/dist/render-debug.d.ts +27 -0
  227. package/dist/render-tracker.d.ts +14 -0
  228. package/dist/render.d.ts +96 -0
  229. package/dist/symbols.cjs +163 -0
  230. package/dist/symbols.cjs.map +1 -1
  231. package/dist/{types/symbols.d.ts → symbols.d.ts} +22 -0
  232. package/dist/symbols.esm.js +27 -3
  233. package/dist/symbols.esm.js.map +1 -1
  234. package/dist/template.d.ts +100 -0
  235. package/dist/transitions.cjs +219 -0
  236. package/dist/transitions.esm.js +2 -2
  237. package/dist/types/context.d.ts +48 -0
  238. package/dist/types/element-options.d.ts +26 -0
  239. package/dist/types/index.d.ts +25 -9
  240. package/dist/types/nav-context.d.ts +19 -0
  241. package/dist/types/{types/on-options.d.ts → on-options.d.ts} +2 -0
  242. package/dist/types/{types/placard.d.ts → placard.d.ts} +0 -1
  243. package/docs/ai/README.md +17 -0
  244. package/docs/ai/api.md +175 -0
  245. package/docs/ai/architecture.md +160 -0
  246. package/docs/ai/components/accordion.md +174 -0
  247. package/docs/ai/components/alert.md +77 -0
  248. package/docs/ai/components/avatar.md +61 -0
  249. package/docs/ai/components/badge.md +69 -0
  250. package/docs/ai/components/breadcrumbs.md +74 -0
  251. package/docs/ai/components/button.md +75 -0
  252. package/docs/ai/components/card.md +61 -0
  253. package/docs/ai/components/checkbox.md +74 -0
  254. package/docs/ai/components/chip.md +73 -0
  255. package/docs/ai/components/date-picker.md +75 -0
  256. package/docs/ai/components/divider.md +66 -0
  257. package/docs/ai/components/drawer.md +80 -0
  258. package/docs/ai/components/input.md +111 -0
  259. package/docs/ai/components/login.md +109 -0
  260. package/docs/ai/components/modal.md +67 -0
  261. package/docs/ai/components/nav.md +76 -0
  262. package/docs/ai/components/pagination.md +55 -0
  263. package/docs/ai/components/progress.md +72 -0
  264. package/docs/ai/components/radio.md +79 -0
  265. package/docs/ai/components/select.md +92 -0
  266. package/docs/ai/components/skeleton.md +57 -0
  267. package/docs/ai/components/switch.md +53 -0
  268. package/docs/ai/components/table.md +227 -0
  269. package/docs/ai/components/tabs.md +83 -0
  270. package/docs/ai/components/toast.md +140 -0
  271. package/docs/ai/components/tooltip.md +146 -0
  272. package/docs/ai/patterns.md +244 -0
  273. package/docs/components/accordion.md +558 -0
  274. package/docs/components/drawer.md +602 -0
  275. package/docs/components/modal.md +558 -0
  276. package/docs/components/nav.md +239 -0
  277. package/docs/components/pagination.md +289 -0
  278. package/docs/components/select.md +599 -0
  279. package/docs/components/switch.md +354 -0
  280. package/docs/components/tabs.md +546 -0
  281. package/docs/components/toast.md +506 -0
  282. package/docs/components/tooltip.md +523 -0
  283. package/docs/controllers.md +744 -0
  284. package/docs/elements.md +855 -0
  285. package/docs/events.md +807 -0
  286. package/docs/migration-v2-to-v3.md +569 -0
  287. package/docs/observe.md +588 -0
  288. package/docs/placards.md +401 -0
  289. package/docs/request-response.md +852 -0
  290. package/docs/routing.md +1186 -0
  291. package/package.json +10 -11
  292. package/dist/components/snice-cell-C9N6yGxQ.js +0 -4
  293. package/dist/components/snice-cell-C9N6yGxQ.js.map +0 -1
  294. package/dist/types/types/index.d.ts +0 -23
  295. /package/dist/{types/controller.d.ts → controller.d.ts} +0 -0
  296. /package/dist/{types/global.d.ts → global.d.ts} +0 -0
  297. /package/dist/{types/observe.d.ts → observe.d.ts} +0 -0
  298. /package/dist/{types/request-response.d.ts → request-response.d.ts} +0 -0
  299. /package/dist/{types/router.d.ts → router.d.ts} +0 -0
  300. /package/dist/{types/testing.d.ts → testing.d.ts} +0 -0
  301. /package/dist/{types/transitions.d.ts → transitions.d.ts} +0 -0
  302. /package/dist/types/{types/adopted-options.d.ts → adopted-options.d.ts} +0 -0
  303. /package/dist/types/{types/app-context.d.ts → app-context.d.ts} +0 -0
  304. /package/dist/types/{types/dispatch-options.d.ts → dispatch-options.d.ts} +0 -0
  305. /package/dist/types/{types/guard.d.ts → guard.d.ts} +0 -0
  306. /package/dist/types/{types/i-controller.d.ts → i-controller.d.ts} +0 -0
  307. /package/dist/types/{types/moved-options.d.ts → moved-options.d.ts} +0 -0
  308. /package/dist/types/{types/observe-options.d.ts → observe-options.d.ts} +0 -0
  309. /package/dist/types/{types/page-options.d.ts → page-options.d.ts} +0 -0
  310. /package/dist/types/{types/part-options.d.ts → part-options.d.ts} +0 -0
  311. /package/dist/types/{types/property-converter.d.ts → property-converter.d.ts} +0 -0
  312. /package/dist/types/{types/property-options.d.ts → property-options.d.ts} +0 -0
  313. /package/dist/types/{types/query-options.d.ts → query-options.d.ts} +0 -0
  314. /package/dist/types/{types/request-options.d.ts → request-options.d.ts} +0 -0
  315. /package/dist/types/{types/respond-options.d.ts → respond-options.d.ts} +0 -0
  316. /package/dist/types/{types/route-params.d.ts → route-params.d.ts} +0 -0
  317. /package/dist/types/{types/router-instance.d.ts → router-instance.d.ts} +0 -0
  318. /package/dist/types/{types/router-options.d.ts → router-options.d.ts} +0 -0
  319. /package/dist/types/{types/simple-array.d.ts → simple-array.d.ts} +0 -0
  320. /package/dist/types/{types/snice-element.d.ts → snice-element.d.ts} +0 -0
  321. /package/dist/types/{types/snice-global.d.ts → snice-global.d.ts} +0 -0
  322. /package/dist/types/{types/transition.d.ts → transition.d.ts} +0 -0
  323. /package/dist/{types/utils.d.ts → utils.d.ts} +0 -0
@@ -0,0 +1,744 @@
1
+ # Controllers API Documentation
2
+
3
+ Controllers handle data fetching, business logic, and server communication separately from visual components. They can be attached to any HTML element, including native elements.
4
+
5
+ ## Table of Contents
6
+ - [Basic Usage](#basic-usage)
7
+ - [Controller Lifecycle](#controller-lifecycle)
8
+ - [Native Element Controllers](#native-element-controllers)
9
+ - [Resource Cleanup](#resource-cleanup)
10
+ - [Event Handling in Controllers](#event-handling-in-controllers)
11
+ - [Query Selectors in Controllers](#query-selectors-in-controllers)
12
+ - [Advanced Patterns](#advanced-patterns)
13
+
14
+ ## Basic Usage
15
+
16
+ ### Creating a Controller
17
+
18
+ ```typescript
19
+ import { controller, IController } from 'snice';
20
+
21
+ @controller('user-controller')
22
+ class UserController implements IController<HTMLElement> {
23
+ element: HTMLElement | null = null;
24
+
25
+ async attach(element: HTMLElement) {
26
+ // Called when controller is attached to an element
27
+ console.log('Controller attached to', element);
28
+ }
29
+
30
+ async detach(element: HTMLElement) {
31
+ // Called when controller is detached from an element
32
+ console.log('Controller detached from', element);
33
+ }
34
+ }
35
+ ```
36
+
37
+ ### Attaching Controllers to Elements
38
+
39
+ Controllers can be attached via the `controller` attribute:
40
+
41
+ ```html
42
+ <!-- Custom element -->
43
+ <user-list controller="user-controller"></user-list>
44
+
45
+ <!-- Native element (requires useNativeElementControllers()) -->
46
+ <div controller="user-controller"></div>
47
+ ```
48
+
49
+ ### IController Interface
50
+
51
+ ```typescript
52
+ interface IController<T extends HTMLElement = HTMLElement> {
53
+ element: T | null | undefined;
54
+ attach(element: T): void | Promise<void>;
55
+ detach(element: T): void | Promise<void>;
56
+ }
57
+ ```
58
+
59
+ ## Controller Lifecycle
60
+
61
+ ### Attachment Flow
62
+
63
+ 1. Controller instance is created
64
+ 2. `element` property is set
65
+ 3. Element's `ready` promise is awaited
66
+ 4. `attach()` method is called
67
+ 5. Event and channel handlers are set up
68
+ 6. `@snice/controller-attached` event is dispatched
69
+
70
+ ### Detachment Flow
71
+
72
+ 1. `detach()` method is called
73
+ 2. `element` property is set to null
74
+ 3. Event and channel handlers are cleaned up
75
+ 4. Controller scope is cleaned up
76
+ 5. `@snice/controller-detached` event is dispatched
77
+
78
+ ### Example with Lifecycle Logging
79
+
80
+ ```typescript
81
+ @controller('lifecycle-controller')
82
+ class LifecycleController implements IController {
83
+ element: HTMLElement | null = null;
84
+ private intervalId?: number;
85
+
86
+ async attach(element: HTMLElement) {
87
+ console.log('1. Controller attaching to', element.tagName);
88
+
89
+ // Wait for any async initialization
90
+ await this.initialize();
91
+
92
+ // Set up recurring tasks
93
+ this.intervalId = setInterval(() => {
94
+ this.updateData();
95
+ }, 5000);
96
+
97
+ console.log('2. Controller attached');
98
+ }
99
+
100
+ async detach(element: HTMLElement) {
101
+ console.log('3. Controller detaching from', element.tagName);
102
+
103
+ // Clean up resources
104
+ if (this.intervalId) {
105
+ clearInterval(this.intervalId);
106
+ }
107
+
108
+ // Perform async cleanup
109
+ await this.cleanup();
110
+
111
+ console.log('4. Controller detached');
112
+ }
113
+
114
+ private async initialize() {
115
+ // Async initialization logic
116
+ }
117
+
118
+ private async cleanup() {
119
+ // Async cleanup logic
120
+ }
121
+
122
+ private updateData() {
123
+ console.log('Updating data...');
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Native Element Controllers
129
+
130
+ Enable controller support for native HTML elements:
131
+
132
+ ```typescript
133
+ import { useNativeElementControllers } from 'snice';
134
+
135
+ // Enable at application start
136
+ useNativeElementControllers();
137
+ ```
138
+
139
+ This allows you to attach controllers to any HTML element:
140
+
141
+ ```html
142
+ <div controller="content-controller">
143
+ <p>Content managed by controller</p>
144
+ </div>
145
+
146
+ <table controller="table-controller">
147
+ <tbody></tbody>
148
+ </table>
149
+
150
+ <form controller="form-controller">
151
+ <input type="text" name="username">
152
+ </form>
153
+ ```
154
+
155
+ ### Example: Table Controller
156
+
157
+ ```typescript
158
+ @controller('table-controller')
159
+ class TableController implements IController<HTMLTableElement> {
160
+ element: HTMLTableElement | null = null;
161
+ private data: any[] = [];
162
+
163
+ async attach(element: HTMLTableElement) {
164
+ // Fetch data
165
+ this.data = await this.fetchData();
166
+
167
+ // Render table
168
+ this.renderTable();
169
+ }
170
+
171
+ async detach(element: HTMLTableElement) {
172
+ // Clear table
173
+ const tbody = element.querySelector('tbody');
174
+ if (tbody) {
175
+ tbody.innerHTML = '';
176
+ }
177
+ }
178
+
179
+ private async fetchData() {
180
+ const response = await fetch('/api/data');
181
+ return response.json();
182
+ }
183
+
184
+ private renderTable() {
185
+ if (!this.element) return;
186
+
187
+ const tbody = this.element.querySelector('tbody');
188
+ if (!tbody) return;
189
+
190
+ tbody.innerHTML = this.data.map(row => `
191
+ <tr>
192
+ <td>${row.id}</td>
193
+ <td>${row.name}</td>
194
+ <td>${row.status}</td>
195
+ </tr>
196
+ `).join('');
197
+ }
198
+ }
199
+ ```
200
+
201
+ ## Resource Cleanup
202
+
203
+ Controllers should clean up resources in the `detach` method:
204
+
205
+ ```typescript
206
+ import { controller, IController } from 'snice';
207
+
208
+ @controller('resource-controller')
209
+ class ResourceController implements IController {
210
+ element: HTMLElement | null = null;
211
+ private websocket?: WebSocket;
212
+ private eventHandler?: (e: MessageEvent) => void;
213
+
214
+ async attach(element: HTMLElement) {
215
+ // Open websocket
216
+ this.websocket = new WebSocket('ws://localhost:8080');
217
+
218
+ // Set up event listener
219
+ this.eventHandler = (e: MessageEvent) => this.handleMessage(e);
220
+ this.websocket.addEventListener('message', this.eventHandler);
221
+ }
222
+
223
+ async detach(element: HTMLElement) {
224
+ // Clean up resources
225
+ if (this.websocket) {
226
+ if (this.eventHandler) {
227
+ this.websocket.removeEventListener('message', this.eventHandler);
228
+ }
229
+ this.websocket.close();
230
+ this.websocket = undefined;
231
+ }
232
+ this.eventHandler = undefined;
233
+ }
234
+
235
+ private handleMessage(event: MessageEvent) {
236
+ console.log('Received:', event.data);
237
+ }
238
+ }
239
+ ```
240
+
241
+ ## Event Handling in Controllers
242
+
243
+ Controllers can use the `@on` decorator to handle events from their attached element:
244
+
245
+ ```typescript
246
+ import { controller, on, IController } from 'snice';
247
+
248
+ @controller('form-controller')
249
+ class FormController implements IController<HTMLFormElement> {
250
+ element: HTMLFormElement | null = null;
251
+
252
+ async attach(element: HTMLFormElement) {
253
+ console.log('Form controller attached');
254
+ }
255
+
256
+ async detach(element: HTMLFormElement) {
257
+ console.log('Form controller detached');
258
+ }
259
+
260
+ @on('submit')
261
+ handleSubmit(event: Event) {
262
+ event.preventDefault();
263
+ console.log('Form submitted');
264
+ this.processForm();
265
+ }
266
+
267
+ @on('input', 'input[type="text"]')
268
+ handleTextInput(event: Event) {
269
+ const input = event.target as HTMLInputElement;
270
+ console.log('Text input changed:', input.value);
271
+ }
272
+
273
+ @on('change', 'select')
274
+ handleSelectChange(event: Event) {
275
+ const select = event.target as HTMLSelectElement;
276
+ console.log('Select changed:', select.value);
277
+ }
278
+
279
+ private processForm() {
280
+ if (!this.element) return;
281
+
282
+ const formData = new FormData(this.element);
283
+ console.log('Processing form data:', Object.fromEntries(formData));
284
+ }
285
+ }
286
+ ```
287
+
288
+ ## Query Selectors in Controllers
289
+
290
+ Controllers can use `@query` and `@queryAll` to access elements:
291
+
292
+ ```typescript
293
+ import { controller, query, queryAll, IController } from 'snice';
294
+
295
+ @controller('dashboard-controller')
296
+ class DashboardController implements IController {
297
+ element: HTMLElement | null = null;
298
+
299
+ @query('.status-indicator')
300
+ statusIndicator?: HTMLElement;
301
+
302
+ @query('#refresh-button')
303
+ refreshButton?: HTMLButtonElement;
304
+
305
+ @queryAll('.data-card')
306
+ dataCards?: NodeListOf<HTMLElement>;
307
+
308
+ async attach(element: HTMLElement) {
309
+ // Queries work on the attached element
310
+ this.updateStatus('Loading...');
311
+
312
+ // Fetch and display data
313
+ await this.loadDashboardData();
314
+
315
+ this.updateStatus('Ready');
316
+ }
317
+
318
+ async detach(element: HTMLElement) {
319
+ this.updateStatus('Offline');
320
+ }
321
+
322
+ private updateStatus(status: string) {
323
+ if (this.statusIndicator) {
324
+ this.statusIndicator.textContent = status;
325
+ }
326
+ }
327
+
328
+ private async loadDashboardData() {
329
+ // Load data for each card
330
+ this.dataCards?.forEach(async (card, index) => {
331
+ const data = await this.fetchCardData(index);
332
+ card.innerHTML = this.renderCard(data);
333
+ });
334
+ }
335
+
336
+ private async fetchCardData(index: number) {
337
+ // Simulate API call
338
+ return { title: `Card ${index + 1}`, value: Math.random() * 100 };
339
+ }
340
+
341
+ private renderCard(data: any) {
342
+ return `
343
+ <h3>${data.title}</h3>
344
+ <p>${data.value.toFixed(2)}</p>
345
+ `;
346
+ }
347
+ }
348
+ ```
349
+
350
+ ## Advanced Patterns
351
+
352
+ ### Data Fetching Controller
353
+
354
+ ```typescript
355
+ @controller('data-fetcher')
356
+ class DataFetcherController implements IController {
357
+ element: HTMLElement | null = null;
358
+ private abortController?: AbortController;
359
+ private pollingInterval?: number;
360
+
361
+ async attach(element: HTMLElement) {
362
+ // Initial data load
363
+ await this.fetchAndRender();
364
+
365
+ // Set up polling
366
+ this.pollingInterval = setInterval(() => {
367
+ this.fetchAndRender();
368
+ }, 30000); // Poll every 30 seconds
369
+ }
370
+
371
+ async detach(element: HTMLElement) {
372
+ // Cancel any pending requests
373
+ this.abortController?.abort();
374
+
375
+ // Stop polling
376
+ if (this.pollingInterval) {
377
+ clearInterval(this.pollingInterval);
378
+ }
379
+ }
380
+
381
+ private async fetchAndRender() {
382
+ try {
383
+ // Cancel previous request if still pending
384
+ this.abortController?.abort();
385
+ this.abortController = new AbortController();
386
+
387
+ // Show loading state
388
+ this.setLoadingState(true);
389
+
390
+ // Fetch data with timeout
391
+ const response = await fetch('/api/data', {
392
+ signal: this.abortController.signal
393
+ });
394
+
395
+ if (!response.ok) {
396
+ throw new Error(`HTTP error! status: ${response.status}`);
397
+ }
398
+
399
+ const data = await response.json();
400
+
401
+ // Render data
402
+ this.renderData(data);
403
+
404
+ } catch (error: any) {
405
+ if (error.name !== 'AbortError') {
406
+ this.renderError(error.message);
407
+ }
408
+ } finally {
409
+ this.setLoadingState(false);
410
+ }
411
+ }
412
+
413
+ private setLoadingState(loading: boolean) {
414
+ if (!this.element) return;
415
+
416
+ if (loading) {
417
+ this.element.classList.add('loading');
418
+ this.element.setAttribute('aria-busy', 'true');
419
+ } else {
420
+ this.element.classList.remove('loading');
421
+ this.element.setAttribute('aria-busy', 'false');
422
+ }
423
+ }
424
+
425
+ private renderData(data: any) {
426
+ if (!this.element) return;
427
+
428
+ // Type guard for custom element
429
+ if ('setData' in this.element && typeof this.element.setData === 'function') {
430
+ this.element.setData(data);
431
+ } else {
432
+ // Fallback for native elements
433
+ this.element.innerHTML = JSON.stringify(data, null, 2);
434
+ }
435
+ }
436
+
437
+ private renderError(message: string) {
438
+ if (!this.element) return;
439
+
440
+ const errorDiv = document.createElement('div');
441
+ errorDiv.className = 'error';
442
+ errorDiv.textContent = `Error: ${message}`;
443
+
444
+ this.element.innerHTML = '';
445
+ this.element.appendChild(errorDiv);
446
+ }
447
+ }
448
+ ```
449
+
450
+ ### State Management Controller
451
+
452
+ ```typescript
453
+ interface AppState {
454
+ user: { id: string; name: string } | null;
455
+ theme: 'light' | 'dark';
456
+ notifications: Notification[];
457
+ }
458
+
459
+ interface Notification {
460
+ id: string;
461
+ message: string;
462
+ type: 'info' | 'warning' | 'error';
463
+ }
464
+
465
+ @controller('state-controller')
466
+ class StateController implements IController {
467
+ element: HTMLElement | null = null;
468
+ private state: AppState = {
469
+ user: null,
470
+ theme: 'light',
471
+ notifications: []
472
+ };
473
+
474
+ private stateListeners = new Set<(state: AppState) => void>();
475
+
476
+ async attach(element: HTMLElement) {
477
+ // Load initial state
478
+ await this.loadState();
479
+
480
+ // Subscribe element to state changes
481
+ const updateElement = (state: AppState) => {
482
+ this.updateElementWithState(element, state);
483
+ };
484
+
485
+ this.stateListeners.add(updateElement);
486
+
487
+ // Initial render
488
+ updateElement(this.state);
489
+ }
490
+
491
+ async detach(element: HTMLElement) {
492
+ // Clean up listeners
493
+ this.stateListeners.clear();
494
+
495
+ // Save state
496
+ await this.saveState();
497
+ }
498
+
499
+ // Public methods for state management
500
+ setUser(user: AppState['user']) {
501
+ this.updateState({ ...this.state, user });
502
+ }
503
+
504
+ setTheme(theme: AppState['theme']) {
505
+ this.updateState({ ...this.state, theme });
506
+ }
507
+
508
+ addNotification(notification: Omit<Notification, 'id'>) {
509
+ const newNotification: Notification = {
510
+ ...notification,
511
+ id: Date.now().toString()
512
+ };
513
+
514
+ this.updateState({
515
+ ...this.state,
516
+ notifications: [...this.state.notifications, newNotification]
517
+ });
518
+
519
+ // Auto-remove after 5 seconds
520
+ setTimeout(() => {
521
+ this.removeNotification(newNotification.id);
522
+ }, 5000);
523
+ }
524
+
525
+ removeNotification(id: string) {
526
+ this.updateState({
527
+ ...this.state,
528
+ notifications: this.state.notifications.filter(n => n.id !== id)
529
+ });
530
+ }
531
+
532
+ private updateState(newState: AppState) {
533
+ this.state = newState;
534
+
535
+ // Notify all listeners
536
+ this.stateListeners.forEach(listener => listener(this.state));
537
+
538
+ // Persist state
539
+ this.saveState();
540
+ }
541
+
542
+ private updateElementWithState(element: HTMLElement, state: AppState) {
543
+ // Update element based on state
544
+ element.setAttribute('data-theme', state.theme);
545
+
546
+ // If element has state methods, call them
547
+ if ('setState' in element && typeof element.setState === 'function') {
548
+ (element as any).setState(state);
549
+ }
550
+
551
+ // Dispatch state change event
552
+ element.dispatchEvent(new CustomEvent('state-changed', {
553
+ detail: state,
554
+ bubbles: true
555
+ }));
556
+ }
557
+
558
+ private async loadState() {
559
+ try {
560
+ const saved = localStorage.getItem('app-state');
561
+ if (saved) {
562
+ this.state = JSON.parse(saved);
563
+ }
564
+ } catch (error) {
565
+ console.error('Failed to load state:', error);
566
+ }
567
+ }
568
+
569
+ private async saveState() {
570
+ try {
571
+ localStorage.setItem('app-state', JSON.stringify(this.state));
572
+ } catch (error) {
573
+ console.error('Failed to save state:', error);
574
+ }
575
+ }
576
+ }
577
+ ```
578
+
579
+ ### WebSocket Controller
580
+
581
+ ```typescript
582
+ @controller('websocket-controller')
583
+ class WebSocketController implements IController {
584
+ element: HTMLElement | null = null;
585
+ private ws?: WebSocket;
586
+ private reconnectTimer?: number;
587
+ private reconnectAttempts = 0;
588
+ private maxReconnectAttempts = 5;
589
+ private reconnectDelay = 1000; // Start with 1 second
590
+
591
+ async attach(element: HTMLElement) {
592
+ this.connect();
593
+ }
594
+
595
+ async detach(element: HTMLElement) {
596
+ this.disconnect();
597
+ }
598
+
599
+ private connect() {
600
+ try {
601
+ this.ws = new WebSocket('ws://localhost:8080');
602
+
603
+ this.ws.onopen = () => {
604
+ console.log('WebSocket connected');
605
+ this.reconnectAttempts = 0;
606
+ this.reconnectDelay = 1000;
607
+ this.onConnected();
608
+ };
609
+
610
+ this.ws.onmessage = (event) => {
611
+ this.handleMessage(event.data);
612
+ };
613
+
614
+ this.ws.onerror = (error) => {
615
+ console.error('WebSocket error:', error);
616
+ this.onError(error);
617
+ };
618
+
619
+ this.ws.onclose = () => {
620
+ console.log('WebSocket disconnected');
621
+ this.onDisconnected();
622
+ this.scheduleReconnect();
623
+ };
624
+
625
+ } catch (error) {
626
+ console.error('Failed to create WebSocket:', error);
627
+ this.scheduleReconnect();
628
+ }
629
+ }
630
+
631
+ private disconnect() {
632
+ if (this.reconnectTimer) {
633
+ clearTimeout(this.reconnectTimer);
634
+ this.reconnectTimer = undefined;
635
+ }
636
+
637
+ if (this.ws) {
638
+ this.ws.close();
639
+ this.ws = undefined;
640
+ }
641
+ }
642
+
643
+ private scheduleReconnect() {
644
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
645
+ console.error('Max reconnection attempts reached');
646
+ this.onReconnectFailed();
647
+ return;
648
+ }
649
+
650
+ this.reconnectAttempts++;
651
+
652
+ console.log(`Reconnecting in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempts})`);
653
+
654
+ this.reconnectTimer = setTimeout(() => {
655
+ this.connect();
656
+ }, this.reconnectDelay);
657
+
658
+ // Exponential backoff
659
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
660
+ }
661
+
662
+ private handleMessage(data: string) {
663
+ try {
664
+ const message = JSON.parse(data);
665
+
666
+ // Update element with message
667
+ if (this.element && 'onMessage' in this.element) {
668
+ (this.element as any).onMessage(message);
669
+ }
670
+
671
+ // Dispatch event
672
+ this.element?.dispatchEvent(new CustomEvent('ws-message', {
673
+ detail: message,
674
+ bubbles: true
675
+ }));
676
+
677
+ } catch (error) {
678
+ console.error('Failed to parse message:', error);
679
+ }
680
+ }
681
+
682
+ send(data: any) {
683
+ if (this.ws?.readyState === WebSocket.OPEN) {
684
+ this.ws.send(JSON.stringify(data));
685
+ } else {
686
+ console.warn('WebSocket not connected, queuing message');
687
+ // Could implement message queue here
688
+ }
689
+ }
690
+
691
+ private onConnected() {
692
+ this.element?.classList.remove('disconnected');
693
+ this.element?.classList.add('connected');
694
+ }
695
+
696
+ private onDisconnected() {
697
+ this.element?.classList.remove('connected');
698
+ this.element?.classList.add('disconnected');
699
+ }
700
+
701
+ private onError(error: Event) {
702
+ this.element?.classList.add('error');
703
+ }
704
+
705
+ private onReconnectFailed() {
706
+ this.element?.classList.add('reconnect-failed');
707
+
708
+ // Show user notification
709
+ if (this.element) {
710
+ const notification = document.createElement('div');
711
+ notification.className = 'connection-error';
712
+ notification.textContent = 'Connection lost. Please refresh the page.';
713
+ this.element.appendChild(notification);
714
+ }
715
+ }
716
+ }
717
+ ```
718
+
719
+ ## Controller Registry
720
+
721
+ Controllers are automatically registered when decorated with `@controller`:
722
+
723
+ ```typescript
724
+ import { getController } from 'snice';
725
+
726
+ // Get controller instance from an element
727
+ const element = document.querySelector('#my-element');
728
+ const controller = getController(element);
729
+
730
+ if (controller) {
731
+ console.log('Controller found:', controller);
732
+ }
733
+ ```
734
+
735
+ ## Best Practices
736
+
737
+ 1. **Separation of Concerns**: Keep controllers focused on data and business logic
738
+ 2. **Cleanup Resources**: Always clean up timers, listeners, and connections
739
+ 3. **Error Handling**: Handle errors gracefully in async operations
740
+ 4. **Type Safety**: Use TypeScript generics for element types
741
+ 5. **State Management**: Consider using a state controller for complex state
742
+ 6. **Abort Requests**: Cancel pending requests when detaching
743
+ 7. **Memory Management**: Clear references to prevent memory leaks
744
+ 8. **Event Delegation**: Use event delegation for dynamic content