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
package/docs/events.md ADDED
@@ -0,0 +1,807 @@
1
+ # Events API Documentation
2
+
3
+ Event handling in Snice provides two powerful approaches: **template event syntax** and the **`@on` decorator** (fully restored from v2.5.4!). The `@on` decorator now works in **both elements AND controllers** with full event delegation, keyboard modifiers, debounce/throttle, and more. Additionally, the `@dispatch` decorator enables automatic custom event dispatching.
4
+
5
+ ## Table of Contents
6
+ - [Template Event Syntax](#template-event-syntax)
7
+ - [@on Decorator (v2.5.4 RESTORED!)](#on-decorator-v254-restored)
8
+ - [@dispatch Decorator](#dispatch-decorator)
9
+ - [Custom Events](#custom-events)
10
+ - [Event Delegation](#event-delegation)
11
+ - [Keyboard Shortcuts](#keyboard-shortcuts)
12
+ - [Best Practices](#best-practices)
13
+
14
+ ## Template Event Syntax (Preferred for Elements)
15
+
16
+ The recommended way to handle events in elements is using template event syntax with `@event=${handler}`:
17
+
18
+ ### Basic Usage
19
+
20
+ ```typescript
21
+ import { element, property, render, html } from 'snice';
22
+
23
+ @element('click-counter')
24
+ class ClickCounter extends HTMLElement {
25
+ @property({ type: Number })
26
+ count = 0;
27
+
28
+ @render()
29
+ renderContent() {
30
+ return html`
31
+ <div class="counter">
32
+ <button @click=${this.increment}>Increment</button>
33
+ <button @click=${this.decrement}>Decrement</button>
34
+ <button @click=${this.reset}>Reset</button>
35
+ <span class="count">${this.count}</span>
36
+ </div>
37
+ `;
38
+ }
39
+
40
+ increment() {
41
+ this.count++;
42
+ }
43
+
44
+ decrement() {
45
+ this.count--;
46
+ }
47
+
48
+ reset() {
49
+ this.count = 0;
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Event Object Access
55
+
56
+ ```typescript
57
+ @element('form-handler')
58
+ class FormHandler extends HTMLElement {
59
+ @render()
60
+ renderContent() {
61
+ return html`
62
+ <form @submit=${this.handleSubmit}>
63
+ <input
64
+ type="text"
65
+ name="username"
66
+ @input=${this.handleInput}
67
+ @focus=${this.handleFocus}
68
+ @blur=${this.handleBlur}
69
+ >
70
+ <button type="submit">Submit</button>
71
+ </form>
72
+ `;
73
+ }
74
+
75
+ handleSubmit(event: Event) {
76
+ event.preventDefault();
77
+ const form = event.target as HTMLFormElement;
78
+ const formData = new FormData(form);
79
+ console.log('Form submitted:', Object.fromEntries(formData));
80
+ }
81
+
82
+ handleInput(event: Event) {
83
+ const input = event.target as HTMLInputElement;
84
+ console.log('Input value:', input.value);
85
+ }
86
+
87
+ handleFocus(event: Event) {
88
+ console.log('Input focused');
89
+ }
90
+
91
+ handleBlur(event: Event) {
92
+ console.log('Input blurred');
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### Multiple Event Types
98
+
99
+ ```typescript
100
+ @element('file-upload')
101
+ class FileUpload extends HTMLElement {
102
+ @property({ type: Boolean })
103
+ dragOver = false;
104
+
105
+ @render()
106
+ renderContent() {
107
+ return html`
108
+ <div
109
+ class="dropzone ${this.dragOver ? 'drag-over' : ''}"
110
+ @dragenter=${this.handleDragEnter}
111
+ @dragover=${this.handleDragOver}
112
+ @dragleave=${this.handleDragLeave}
113
+ @drop=${this.handleDrop}
114
+ >
115
+ Drop files here
116
+ </div>
117
+ `;
118
+ }
119
+
120
+ handleDragEnter(e: DragEvent) {
121
+ e.preventDefault();
122
+ this.dragOver = true;
123
+ }
124
+
125
+ handleDragOver(e: DragEvent) {
126
+ e.preventDefault();
127
+ }
128
+
129
+ handleDragLeave(e: DragEvent) {
130
+ this.dragOver = false;
131
+ }
132
+
133
+ handleDrop(e: DragEvent) {
134
+ e.preventDefault();
135
+ this.dragOver = false;
136
+ const files = Array.from(e.dataTransfer?.files || []);
137
+ console.log('Files dropped:', files);
138
+ }
139
+ }
140
+ ```
141
+
142
+ ### Keyboard Shortcuts in Templates
143
+
144
+ Template event syntax supports keyboard shortcuts using dot notation:
145
+
146
+ ```typescript
147
+ @element('keyboard-input')
148
+ class KeyboardInput extends HTMLElement {
149
+ @render()
150
+ renderContent() {
151
+ return html`
152
+ <div>
153
+ <input
154
+ @keydown.enter=${this.handleEnter}
155
+ placeholder="Press Enter"
156
+ >
157
+
158
+ <input
159
+ @keydown.ctrl+s=${this.handleSave}
160
+ placeholder="Press Ctrl+S"
161
+ >
162
+
163
+ <input
164
+ @keydown.escape=${this.handleCancel}
165
+ placeholder="Press Escape"
166
+ >
167
+
168
+ <input
169
+ @keydown.~enter=${this.handleAnyEnter}
170
+ placeholder="Press Enter with any modifiers"
171
+ >
172
+ </div>
173
+ `;
174
+ }
175
+
176
+ handleEnter(e: KeyboardEvent) {
177
+ console.log('Enter pressed (no modifiers)');
178
+ }
179
+
180
+ handleSave(e: KeyboardEvent) {
181
+ e.preventDefault();
182
+ console.log('Ctrl+S pressed');
183
+ }
184
+
185
+ handleCancel(e: KeyboardEvent) {
186
+ console.log('Escape pressed');
187
+ }
188
+
189
+ handleAnyEnter(e: KeyboardEvent) {
190
+ console.log('Enter pressed (with any modifiers)');
191
+ }
192
+ }
193
+ ```
194
+
195
+ **Keyboard Shortcut Syntax:**
196
+ - `@keydown.enter` - Plain Enter (no modifiers)
197
+ - `@keydown.ctrl+s` - Ctrl+S combination
198
+ - `@keydown.ctrl+shift+s` - Multiple modifiers
199
+ - `@keydown.~enter` - Enter with any modifiers
200
+ - `@keydown.escape`, `@keydown.down`, etc. - Named keys
201
+
202
+ ### Arrow Functions in Templates
203
+
204
+ Use arrow functions for inline event handling or passing parameters:
205
+
206
+ ```typescript
207
+ @element('task-list')
208
+ class TaskList extends HTMLElement {
209
+ @property()
210
+ tasks = [
211
+ { id: 1, name: 'Task 1', completed: false },
212
+ { id: 2, name: 'Task 2', completed: false }
213
+ ];
214
+
215
+ @render()
216
+ renderContent() {
217
+ return html`
218
+ <ul>
219
+ ${this.tasks.map(task => html`
220
+ <li>
221
+ <input
222
+ type="checkbox"
223
+ ?checked=${task.completed}
224
+ @change=${(e: Event) => this.toggleTask(task.id, e)}
225
+ >
226
+ <span>${task.name}</span>
227
+ <button @click=${() => this.deleteTask(task.id)}>Delete</button>
228
+ </li>
229
+ `)}
230
+ </ul>
231
+ `;
232
+ }
233
+
234
+ toggleTask(id: number, e: Event) {
235
+ const checkbox = e.target as HTMLInputElement;
236
+ const task = this.tasks.find(t => t.id === id);
237
+ if (task) {
238
+ task.completed = checkbox.checked;
239
+ // Trigger re-render
240
+ this.tasks = [...this.tasks];
241
+ }
242
+ }
243
+
244
+ deleteTask(id: number) {
245
+ this.tasks = this.tasks.filter(t => t.id !== id);
246
+ }
247
+ }
248
+ ```
249
+
250
+ ## @on Decorator (v2.5.4 RESTORED!)
251
+
252
+ The `@on` decorator is **fully restored from v2.5.4** and now works in **both elements AND controllers**! It provides powerful event delegation, keyboard modifiers, debounce/throttle, and automatic event handling features.
253
+
254
+ **Use `@on` when you need:**
255
+ - Event delegation with CSS selectors
256
+ - Keyboard modifier matching (`Enter`, `ctrl+s`, etc.)
257
+ - Debounce or throttle
258
+ - Multiple events on one handler
259
+ - Automatic preventDefault or stopPropagation
260
+
261
+ ### Basic Controller Usage
262
+
263
+ ```typescript
264
+ import { controller, on, IController } from 'snice';
265
+
266
+ @controller('button-controller')
267
+ class ButtonController implements IController {
268
+ element: HTMLElement | null = null;
269
+
270
+ async attach(element: HTMLElement) {
271
+ console.log('Controller attached');
272
+ }
273
+
274
+ async detach(element: HTMLElement) {
275
+ console.log('Controller detached');
276
+ }
277
+
278
+ @on('click')
279
+ handleClick(event: MouseEvent) {
280
+ console.log('Button clicked');
281
+ }
282
+
283
+ @on('mouseenter')
284
+ handleMouseEnter(event: MouseEvent) {
285
+ console.log('Mouse entered');
286
+ }
287
+
288
+ @on('mouseleave')
289
+ handleMouseLeave(event: MouseEvent) {
290
+ console.log('Mouse left');
291
+ }
292
+ }
293
+ ```
294
+
295
+ ### Event Delegation with Selector
296
+
297
+ ```typescript
298
+ @controller('list-controller')
299
+ class ListController implements IController {
300
+ element: HTMLElement | null = null;
301
+
302
+ async attach(element: HTMLElement) {}
303
+ async detach(element: HTMLElement) {}
304
+
305
+ // Handle clicks on list items
306
+ @on('click', '.list-item')
307
+ handleItemClick(event: MouseEvent) {
308
+ const item = event.target as HTMLElement;
309
+ console.log('Item clicked:', item.textContent);
310
+ }
311
+
312
+ // Handle clicks on delete buttons
313
+ @on('click', '.delete-button')
314
+ handleDeleteClick(event: MouseEvent) {
315
+ event.stopPropagation();
316
+ const button = event.target as HTMLElement;
317
+ const item = button.closest('.list-item');
318
+ item?.remove();
319
+ }
320
+
321
+ // Handle input on text fields
322
+ @on('input', 'input[type="text"]')
323
+ handleTextInput(event: Event) {
324
+ const input = event.target as HTMLInputElement;
325
+ console.log('Text changed:', input.value);
326
+ }
327
+ }
328
+ ```
329
+
330
+ ### Keyboard Events with @on
331
+
332
+ ```typescript
333
+ @controller('editor-controller')
334
+ class EditorController implements IController {
335
+ element: HTMLElement | null = null;
336
+
337
+ async attach(element: HTMLElement) {}
338
+ async detach(element: HTMLElement) {}
339
+
340
+ @on('keydown:Enter', 'textarea')
341
+ handleEnter(event: KeyboardEvent) {
342
+ console.log('Enter pressed in textarea');
343
+ }
344
+
345
+ @on('keydown:Ctrl+S')
346
+ handleSave(event: KeyboardEvent) {
347
+ event.preventDefault();
348
+ console.log('Save shortcut triggered');
349
+ this.save();
350
+ }
351
+
352
+ @on('keydown:Escape')
353
+ handleEscape(event: KeyboardEvent) {
354
+ console.log('Escape pressed');
355
+ this.cancel();
356
+ }
357
+
358
+ private save() {
359
+ console.log('Saving...');
360
+ }
361
+
362
+ private cancel() {
363
+ console.log('Cancelling...');
364
+ }
365
+ }
366
+ ```
367
+
368
+ ### @on Options
369
+
370
+ ```typescript
371
+ interface OnOptions {
372
+ // Standard event listener options
373
+ capture?: boolean; // Use capture phase instead of bubble phase
374
+ once?: boolean; // Remove listener after first trigger
375
+ passive?: boolean; // Passive listener (can't preventDefault)
376
+
377
+ // Automatic event handling
378
+ preventDefault?: boolean; // Automatically call preventDefault on the event
379
+ stopPropagation?: boolean; // Automatically call stopPropagation on the event
380
+
381
+ // Timing controls (v3.0.0 enhancements!)
382
+ debounce?: number; // Debounce the handler by specified milliseconds
383
+ throttle?: number; // Throttle the handler by specified milliseconds
384
+
385
+ // Event delegation
386
+ target?: string; // CSS selector to target specific elements within shadow root
387
+ }
388
+ ```
389
+
390
+ #### Throttling
391
+
392
+ ```typescript
393
+ @controller('scroll-controller')
394
+ class ScrollController implements IController {
395
+ element: HTMLElement | null = null;
396
+
397
+ async attach(element: HTMLElement) {}
398
+ async detach(element: HTMLElement) {}
399
+
400
+ // Throttle scroll events to max once per 100ms
401
+ @on('scroll', null, { throttle: 100 })
402
+ handleScroll(event: Event) {
403
+ const element = event.target as HTMLElement;
404
+ console.log('Scroll position:', element.scrollTop);
405
+ }
406
+ }
407
+ ```
408
+
409
+ #### Debouncing
410
+
411
+ ```typescript
412
+ @controller('search-controller')
413
+ class SearchController implements IController {
414
+ element: HTMLElement | null = null;
415
+
416
+ async attach(element: HTMLElement) {}
417
+ async detach(element: HTMLElement) {}
418
+
419
+ // Debounce input events by 300ms
420
+ @on('input', 'input[type="search"]', { debounce: 300 })
421
+ handleSearch(event: Event) {
422
+ const input = event.target as HTMLInputElement;
423
+ console.log('Searching for:', input.value);
424
+ this.performSearch(input.value);
425
+ }
426
+
427
+ private async performSearch(query: string) {
428
+ // Search implementation
429
+ }
430
+ }
431
+ ```
432
+
433
+ ### Using @on in Elements (Alternative)
434
+
435
+ While template syntax is preferred, `@on` can also be used in elements:
436
+
437
+ ```typescript
438
+ import { element, on, render, html } from 'snice';
439
+
440
+ @element('legacy-button')
441
+ class LegacyButton extends HTMLElement {
442
+ @render()
443
+ renderContent() {
444
+ return html`<button class="btn">Click me</button>`;
445
+ }
446
+
447
+ @on('click', '.btn')
448
+ handleClick(event: MouseEvent) {
449
+ console.log('Button clicked via @on decorator');
450
+ }
451
+
452
+ @on('input', 'input', { debounce: 300 })
453
+ handleInput(event: Event) {
454
+ console.log('Input debounced');
455
+ }
456
+ }
457
+ ```
458
+
459
+ **Note:** For new element code, prefer template event syntax for better readability and type safety.
460
+
461
+ ## @dispatch Decorator
462
+
463
+ Auto-dispatch custom events after method execution:
464
+
465
+ ### Basic Usage
466
+
467
+ ```typescript
468
+ import { element, dispatch, render, html } from 'snice';
469
+
470
+ @element('value-input')
471
+ class ValueInput extends HTMLElement {
472
+ private value = '';
473
+
474
+ @render()
475
+ renderContent() {
476
+ return html`
477
+ <input
478
+ type="text"
479
+ .value=${this.value}
480
+ @input=${this.handleInput}
481
+ >
482
+ `;
483
+ }
484
+
485
+ handleInput(e: Event) {
486
+ const input = e.target as HTMLInputElement;
487
+ this.setValue(input.value);
488
+ }
489
+
490
+ @dispatch('value-changed')
491
+ setValue(newValue: string) {
492
+ this.value = newValue;
493
+ return { value: newValue }; // Event detail
494
+ }
495
+ }
496
+ ```
497
+
498
+ Usage:
499
+ ```typescript
500
+ const input = document.querySelector('value-input');
501
+ input.addEventListener('value-changed', (e: CustomEvent) => {
502
+ console.log('New value:', e.detail.value);
503
+ });
504
+ ```
505
+
506
+ ### Event Options
507
+
508
+ ```typescript
509
+ @element('status-indicator')
510
+ class StatusIndicator extends HTMLElement {
511
+ @render()
512
+ renderContent() {
513
+ return html`<div>Status</div>`;
514
+ }
515
+
516
+ @dispatch('status-changed', { bubbles: true, composed: true })
517
+ updateStatus(status: string) {
518
+ return {
519
+ status,
520
+ timestamp: Date.now()
521
+ };
522
+ }
523
+ }
524
+ ```
525
+
526
+ ### Multiple Dispatches
527
+
528
+ ```typescript
529
+ @element('data-manager')
530
+ class DataManager extends HTMLElement {
531
+ private data: any[] = [];
532
+
533
+ @render()
534
+ renderContent() {
535
+ return html`<div>Data Manager</div>`;
536
+ }
537
+
538
+ @dispatch('item-added')
539
+ addItem(item: any) {
540
+ this.data.push(item);
541
+ return { item, total: this.data.length };
542
+ }
543
+
544
+ @dispatch('item-removed')
545
+ removeItem(id: string) {
546
+ const item = this.data.find(i => i.id === id);
547
+ this.data = this.data.filter(i => i.id !== id);
548
+ return { id, item, total: this.data.length };
549
+ }
550
+
551
+ @dispatch('data-cleared')
552
+ clearData() {
553
+ const count = this.data.length;
554
+ this.data = [];
555
+ return { clearedCount: count };
556
+ }
557
+ }
558
+ ```
559
+
560
+ ## Custom Events
561
+
562
+ ### Dispatching Manually
563
+
564
+ ```typescript
565
+ @element('manual-dispatcher')
566
+ class ManualDispatcher extends HTMLElement {
567
+ @render()
568
+ renderContent() {
569
+ return html`
570
+ <button @click=${this.notify}>Notify</button>
571
+ `;
572
+ }
573
+
574
+ notify() {
575
+ // Dispatch custom event manually
576
+ this.dispatchEvent(new CustomEvent('notification', {
577
+ detail: { message: 'Hello!', level: 'info' },
578
+ bubbles: true,
579
+ composed: true
580
+ }));
581
+ }
582
+ }
583
+ ```
584
+
585
+ ### Listening to Custom Events
586
+
587
+ ```typescript
588
+ @element('event-listener')
589
+ class EventListener extends HTMLElement {
590
+ @render()
591
+ renderContent() {
592
+ return html`
593
+ <manual-dispatcher @notification=${this.handleNotification}></manual-dispatcher>
594
+ `;
595
+ }
596
+
597
+ handleNotification(e: CustomEvent) {
598
+ console.log('Received notification:', e.detail);
599
+ }
600
+ }
601
+ ```
602
+
603
+ ## Event Delegation
604
+
605
+ ### Controller Event Delegation
606
+
607
+ ```typescript
608
+ @controller('table-controller')
609
+ class TableController implements IController {
610
+ element: HTMLElement | null = null;
611
+
612
+ async attach(element: HTMLElement) {}
613
+ async detach(element: HTMLElement) {}
614
+
615
+ // Single event listener handles all rows
616
+ @on('click', 'tr')
617
+ handleRowClick(event: MouseEvent) {
618
+ const row = event.currentTarget as HTMLTableRowElement;
619
+ console.log('Row clicked:', row.dataset.id);
620
+ }
621
+
622
+ // Handle button clicks in cells
623
+ @on('click', 'button.edit')
624
+ handleEdit(event: MouseEvent) {
625
+ const button = event.target as HTMLButtonElement;
626
+ const row = button.closest('tr');
627
+ console.log('Edit row:', row?.dataset.id);
628
+ }
629
+
630
+ @on('click', 'button.delete')
631
+ handleDelete(event: MouseEvent) {
632
+ event.stopPropagation(); // Don't trigger row click
633
+ const button = event.target as HTMLButtonElement;
634
+ const row = button.closest('tr');
635
+ row?.remove();
636
+ }
637
+ }
638
+ ```
639
+
640
+ ### Template Event Delegation
641
+
642
+ For dynamic content, use controllers with `@on` for event delegation, or handle events on a parent element:
643
+
644
+ ```typescript
645
+ @element('dynamic-list')
646
+ class DynamicList extends HTMLElement {
647
+ @property()
648
+ items = ['Item 1', 'Item 2', 'Item 3'];
649
+
650
+ @render()
651
+ renderContent() {
652
+ return html`
653
+ <ul @click=${this.handleListClick}>
654
+ ${this.items.map((item, index) => html`
655
+ <li data-index="${index}">
656
+ ${item}
657
+ <button class="delete">Delete</button>
658
+ </li>
659
+ `)}
660
+ </ul>
661
+ `;
662
+ }
663
+
664
+ handleListClick(e: MouseEvent) {
665
+ const target = e.target as HTMLElement;
666
+
667
+ // Handle delete button
668
+ if (target.classList.contains('delete')) {
669
+ const li = target.closest('li');
670
+ const index = parseInt(li?.dataset.index || '-1');
671
+ if (index >= 0) {
672
+ this.items = this.items.filter((_, i) => i !== index);
673
+ }
674
+ return;
675
+ }
676
+
677
+ // Handle li click
678
+ if (target.tagName === 'LI') {
679
+ console.log('Item clicked:', target.textContent);
680
+ }
681
+ }
682
+ }
683
+ ```
684
+
685
+ ## Keyboard Shortcuts
686
+
687
+ ### Template Syntax (Preferred)
688
+
689
+ ```typescript
690
+ @element('shortcut-handler')
691
+ class ShortcutHandler extends HTMLElement {
692
+ @render()
693
+ renderContent() {
694
+ return html`
695
+ <div>
696
+ <input @keydown.enter=${this.submit} placeholder="Press Enter">
697
+ <input @keydown.ctrl+s=${this.save} placeholder="Ctrl+S to save">
698
+ <input @keydown.ctrl+shift+s=${this.saveAs} placeholder="Ctrl+Shift+S for Save As">
699
+ <input @keydown.escape=${this.cancel} placeholder="Escape to cancel">
700
+ <input @keydown.~enter=${this.submitAny} placeholder="Enter with any mods">
701
+ </div>
702
+ `;
703
+ }
704
+
705
+ submit(e: KeyboardEvent) {
706
+ console.log('Submit');
707
+ }
708
+
709
+ save(e: KeyboardEvent) {
710
+ e.preventDefault();
711
+ console.log('Save');
712
+ }
713
+
714
+ saveAs(e: KeyboardEvent) {
715
+ e.preventDefault();
716
+ console.log('Save As');
717
+ }
718
+
719
+ cancel(e: KeyboardEvent) {
720
+ console.log('Cancel');
721
+ }
722
+
723
+ submitAny(e: KeyboardEvent) {
724
+ console.log('Submit with any modifiers');
725
+ }
726
+ }
727
+ ```
728
+
729
+ ### @on Decorator Syntax
730
+
731
+ ```typescript
732
+ @controller('keyboard-controller')
733
+ class KeyboardController implements IController {
734
+ element: HTMLElement | null = null;
735
+
736
+ async attach(element: HTMLElement) {}
737
+ async detach(element: HTMLElement) {}
738
+
739
+ @on('keydown:Enter')
740
+ handleEnter(e: KeyboardEvent) {
741
+ console.log('Enter pressed');
742
+ }
743
+
744
+ @on('keydown:Ctrl+S')
745
+ handleSave(e: KeyboardEvent) {
746
+ e.preventDefault();
747
+ console.log('Save');
748
+ }
749
+
750
+ @on('keydown:Escape')
751
+ handleEscape(e: KeyboardEvent) {
752
+ console.log('Escape');
753
+ }
754
+ }
755
+ ```
756
+
757
+ ## Best Practices
758
+
759
+ ### For Elements
760
+
761
+ 1. **Prefer template syntax**: Use `@event=${handler}` in templates
762
+ 2. **Use arrow functions for parameters**: `@click=${() => this.delete(id)}`
763
+ 3. **Handle events at appropriate level**: Use event delegation for dynamic content
764
+ 4. **Prevent default when needed**: Call `e.preventDefault()` in handlers
765
+ 5. **Use keyboard shortcuts**: Leverage `@keydown.ctrl+s` syntax
766
+
767
+ ### For Controllers
768
+
769
+ 1. **Use @on decorator**: Controllers should use `@on` for event handling
770
+ 2. **Leverage event delegation**: Use selectors to handle dynamic content
771
+ 3. **Throttle/debounce high-frequency events**: Use `{ throttle }` or `{ debounce }` options
772
+ 4. **Clean up automatically**: Event listeners are cleaned up on detach
773
+ 5. **Handle keyboard shortcuts**: Use `@on('keydown:Ctrl+S')` syntax
774
+
775
+ ### General
776
+
777
+ 1. **Type your events**: Use TypeScript event types for type safety
778
+ 2. **Stop propagation carefully**: Only use `stopPropagation()` when necessary
779
+ 3. **Use custom events**: Dispatch custom events for component communication
780
+ 4. **Compose events**: Use `{ composed: true }` for cross-shadow-DOM events
781
+ 5. **Test event handlers**: Write tests for all event handling logic
782
+
783
+ ## Event Types Reference
784
+
785
+ **Mouse Events:**
786
+ - `click`, `dblclick`, `mousedown`, `mouseup`
787
+ - `mouseenter`, `mouseleave`, `mousemove`, `mouseover`, `mouseout`
788
+ - `contextmenu`
789
+
790
+ **Keyboard Events:**
791
+ - `keydown`, `keyup`, `keypress`
792
+
793
+ **Form Events:**
794
+ - `input`, `change`, `submit`, `reset`
795
+ - `focus`, `blur`, `focusin`, `focusout`
796
+
797
+ **Drag Events:**
798
+ - `drag`, `dragstart`, `dragend`
799
+ - `dragenter`, `dragover`, `dragleave`, `drop`
800
+
801
+ **Touch Events:**
802
+ - `touchstart`, `touchmove`, `touchend`, `touchcancel`
803
+
804
+ **Other Events:**
805
+ - `scroll`, `resize`, `load`, `error`
806
+ - `animationstart`, `animationend`, `animationiteration`
807
+ - `transitionstart`, `transitionend`