snice 2.5.4 → 3.2.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 (411) hide show
  1. package/README.md +537 -869
  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/banner/snice-banner.d.ts +22 -0
  20. package/dist/components/banner/snice-banner.js +180 -0
  21. package/dist/components/banner/snice-banner.js.map +1 -0
  22. package/dist/components/banner/snice-banner.types.d.ts +14 -0
  23. package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +5 -12
  24. package/dist/components/breadcrumbs/snice-breadcrumbs.js +88 -89
  25. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
  26. package/dist/components/button/snice-button.d.ts +3 -7
  27. package/dist/components/button/snice-button.js +37 -58
  28. package/dist/components/button/snice-button.js.map +1 -1
  29. package/dist/components/card/snice-card.d.ts +5 -8
  30. package/dist/components/card/snice-card.js +71 -56
  31. package/dist/components/card/snice-card.js.map +1 -1
  32. package/dist/components/checkbox/snice-checkbox.d.ts +4 -13
  33. package/dist/components/checkbox/snice-checkbox.js +66 -137
  34. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  35. package/dist/components/chip/snice-chip.d.ts +5 -11
  36. package/dist/components/chip/snice-chip.js +44 -47
  37. package/dist/components/chip/snice-chip.js.map +1 -1
  38. package/dist/components/color-display/snice-color-display.d.ts +14 -0
  39. package/dist/components/color-display/snice-color-display.js +151 -0
  40. package/dist/components/color-display/snice-color-display.js.map +1 -0
  41. package/dist/components/color-display/snice-color-display.types.d.ts +10 -0
  42. package/dist/components/color-picker/snice-color-picker.d.ts +50 -0
  43. package/dist/components/color-picker/snice-color-picker.js +489 -0
  44. package/dist/components/color-picker/snice-color-picker.js.map +1 -0
  45. package/dist/components/color-picker/snice-color-picker.types.d.ts +19 -0
  46. package/dist/components/date-picker/snice-date-picker.d.ts +11 -11
  47. package/dist/components/date-picker/snice-date-picker.js +134 -133
  48. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  49. package/dist/components/divider/snice-divider.d.ts +2 -4
  50. package/dist/components/divider/snice-divider.js +14 -22
  51. package/dist/components/divider/snice-divider.js.map +1 -1
  52. package/dist/components/drawer/snice-drawer.d.ts +4 -4
  53. package/dist/components/drawer/snice-drawer.js +25 -19
  54. package/dist/components/drawer/snice-drawer.js.map +1 -1
  55. package/dist/components/empty-state/snice-empty-state.d.ts +13 -0
  56. package/dist/components/empty-state/snice-empty-state.js +121 -0
  57. package/dist/components/empty-state/snice-empty-state.js.map +1 -0
  58. package/dist/components/empty-state/snice-empty-state.types.d.ts +9 -0
  59. package/dist/components/file-upload/snice-file-upload.d.ts +45 -0
  60. package/dist/components/file-upload/snice-file-upload.js +394 -0
  61. package/dist/components/file-upload/snice-file-upload.js.map +1 -0
  62. package/dist/components/file-upload/snice-file-upload.types.d.ts +22 -0
  63. package/dist/components/image/snice-image.d.ts +22 -0
  64. package/dist/components/image/snice-image.js +201 -0
  65. package/dist/components/image/snice-image.js.map +1 -0
  66. package/dist/components/image/snice-image.types.d.ts +17 -0
  67. package/dist/components/input/snice-input.d.ts +8 -6
  68. package/dist/components/input/snice-input.js +122 -105
  69. package/dist/components/input/snice-input.js.map +1 -1
  70. package/dist/components/kpi/snice-kpi.d.ts +16 -0
  71. package/dist/components/kpi/snice-kpi.js +162 -0
  72. package/dist/components/kpi/snice-kpi.js.map +1 -0
  73. package/dist/components/kpi/snice-kpi.types.d.ts +12 -0
  74. package/dist/components/layout/snice-layout-blog.d.ts +4 -4
  75. package/dist/components/layout/snice-layout-blog.js +21 -19
  76. package/dist/components/layout/snice-layout-blog.js.map +1 -1
  77. package/dist/components/layout/snice-layout-card.d.ts +2 -2
  78. package/dist/components/layout/snice-layout-card.js +16 -9
  79. package/dist/components/layout/snice-layout-card.js.map +1 -1
  80. package/dist/components/layout/snice-layout-centered.d.ts +2 -2
  81. package/dist/components/layout/snice-layout-centered.js +14 -7
  82. package/dist/components/layout/snice-layout-centered.js.map +1 -1
  83. package/dist/components/layout/snice-layout-dashboard.d.ts +5 -5
  84. package/dist/components/layout/snice-layout-dashboard.js +38 -30
  85. package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
  86. package/dist/components/layout/snice-layout-fullscreen.d.ts +2 -2
  87. package/dist/components/layout/snice-layout-fullscreen.js +17 -10
  88. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
  89. package/dist/components/layout/snice-layout-landing.d.ts +4 -4
  90. package/dist/components/layout/snice-layout-landing.js +21 -19
  91. package/dist/components/layout/snice-layout-landing.js.map +1 -1
  92. package/dist/components/layout/snice-layout-minimal.d.ts +2 -2
  93. package/dist/components/layout/snice-layout-minimal.js +17 -6
  94. package/dist/components/layout/snice-layout-minimal.js.map +1 -1
  95. package/dist/components/layout/snice-layout-sidebar.d.ts +5 -4
  96. package/dist/components/layout/snice-layout-sidebar.js +42 -20
  97. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  98. package/dist/components/layout/snice-layout-split.d.ts +2 -2
  99. package/dist/components/layout/snice-layout-split.js +14 -7
  100. package/dist/components/layout/snice-layout-split.js.map +1 -1
  101. package/dist/components/layout/snice-layout.d.ts +4 -4
  102. package/dist/components/layout/snice-layout.js +16 -10
  103. package/dist/components/layout/snice-layout.js.map +1 -1
  104. package/dist/components/link/snice-link.d.ts +13 -0
  105. package/dist/components/link/snice-link.js +137 -0
  106. package/dist/components/link/snice-link.js.map +1 -0
  107. package/dist/components/link/snice-link.types.d.ts +11 -0
  108. package/dist/components/login/snice-login.d.ts +6 -11
  109. package/dist/components/login/snice-login.js +97 -71
  110. package/dist/components/login/snice-login.js.map +1 -1
  111. package/dist/components/modal/snice-modal.d.ts +5 -9
  112. package/dist/components/modal/snice-modal.js +47 -78
  113. package/dist/components/modal/snice-modal.js.map +1 -1
  114. package/dist/components/nav/snice-nav.d.ts +13 -7
  115. package/dist/components/nav/snice-nav.js +191 -100
  116. package/dist/components/nav/snice-nav.js.map +1 -1
  117. package/dist/components/nav/snice-nav.types.d.ts +3 -3
  118. package/dist/components/pagination/snice-pagination.d.ts +6 -7
  119. package/dist/components/pagination/snice-pagination.js +94 -81
  120. package/dist/components/pagination/snice-pagination.js.map +1 -1
  121. package/dist/components/progress/snice-progress.d.ts +2 -7
  122. package/dist/components/progress/snice-progress.js +41 -98
  123. package/dist/components/progress/snice-progress.js.map +1 -1
  124. package/dist/components/radio/snice-radio.d.ts +4 -4
  125. package/dist/components/radio/snice-radio.js +52 -44
  126. package/dist/components/radio/snice-radio.js.map +1 -1
  127. package/dist/components/select/snice-option.d.ts +2 -1
  128. package/dist/components/select/snice-option.js +12 -5
  129. package/dist/components/select/snice-option.js.map +1 -1
  130. package/dist/components/select/snice-select.d.ts +9 -21
  131. package/dist/components/select/snice-select.js +98 -170
  132. package/dist/components/select/snice-select.js.map +1 -1
  133. package/dist/components/skeleton/snice-skeleton.d.ts +2 -6
  134. package/dist/components/skeleton/snice-skeleton.js +18 -49
  135. package/dist/components/skeleton/snice-skeleton.js.map +1 -1
  136. package/dist/components/slider/snice-slider.d.ts +53 -0
  137. package/dist/components/slider/snice-slider.js +479 -0
  138. package/dist/components/slider/snice-slider.js.map +1 -0
  139. package/dist/components/slider/snice-slider.types.d.ts +26 -0
  140. package/dist/components/snice-cell-C0slgOpe.js +4 -0
  141. package/dist/components/snice-cell-C0slgOpe.js.map +1 -0
  142. package/dist/components/sparkline/snice-sparkline.d.ts +21 -0
  143. package/dist/components/sparkline/snice-sparkline.js +228 -0
  144. package/dist/components/sparkline/snice-sparkline.js.map +1 -0
  145. package/dist/components/sparkline/snice-sparkline.types.d.ts +16 -0
  146. package/dist/components/spinner/snice-spinner.d.ts +10 -0
  147. package/dist/components/spinner/snice-spinner.js +109 -0
  148. package/dist/components/spinner/snice-spinner.js.map +1 -0
  149. package/dist/components/spinner/snice-spinner.types.d.ts +8 -0
  150. package/dist/components/stepper/snice-stepper-panel.d.ts +8 -0
  151. package/dist/components/stepper/snice-stepper-panel.js +70 -0
  152. package/dist/components/stepper/snice-stepper-panel.js.map +1 -0
  153. package/dist/components/stepper/snice-stepper-panel.types.d.ts +4 -0
  154. package/dist/components/stepper/snice-stepper.d.ts +15 -0
  155. package/dist/components/stepper/snice-stepper.js +163 -0
  156. package/dist/components/stepper/snice-stepper.js.map +1 -0
  157. package/dist/components/stepper/snice-stepper.types.d.ts +13 -0
  158. package/dist/components/switch/snice-switch.d.ts +2 -2
  159. package/dist/components/switch/snice-switch.js +38 -26
  160. package/dist/components/switch/snice-switch.js.map +1 -1
  161. package/dist/components/table/snice-cell-actions.d.ts +24 -0
  162. package/dist/components/table/snice-cell-actions.js +149 -0
  163. package/dist/components/table/snice-cell-actions.js.map +1 -0
  164. package/dist/components/table/snice-cell-boolean.d.ts +2 -2
  165. package/dist/components/table/snice-cell-boolean.js +13 -7
  166. package/dist/components/table/snice-cell-boolean.js.map +1 -1
  167. package/dist/components/table/snice-cell-color.d.ts +18 -0
  168. package/dist/components/table/snice-cell-color.js +149 -0
  169. package/dist/components/table/snice-cell-color.js.map +1 -0
  170. package/dist/components/table/snice-cell-currency.d.ts +24 -0
  171. package/dist/components/table/snice-cell-currency.js +235 -0
  172. package/dist/components/table/snice-cell-currency.js.map +1 -0
  173. package/dist/components/table/snice-cell-date.d.ts +2 -2
  174. package/dist/components/table/snice-cell-date.js +14 -8
  175. package/dist/components/table/snice-cell-date.js.map +1 -1
  176. package/dist/components/table/snice-cell-duration.d.ts +2 -2
  177. package/dist/components/table/snice-cell-duration.js +12 -6
  178. package/dist/components/table/snice-cell-duration.js.map +1 -1
  179. package/dist/components/table/snice-cell-email.d.ts +15 -0
  180. package/dist/components/table/snice-cell-email.js +125 -0
  181. package/dist/components/table/snice-cell-email.js.map +1 -0
  182. package/dist/components/table/snice-cell-filesize.d.ts +2 -2
  183. package/dist/components/table/snice-cell-filesize.js +12 -6
  184. package/dist/components/table/snice-cell-filesize.js.map +1 -1
  185. package/dist/components/table/snice-cell-image.d.ts +20 -0
  186. package/dist/components/table/snice-cell-image.js +162 -0
  187. package/dist/components/table/snice-cell-image.js.map +1 -0
  188. package/dist/components/table/snice-cell-json.d.ts +20 -0
  189. package/dist/components/table/snice-cell-json.js +186 -0
  190. package/dist/components/table/snice-cell-json.js.map +1 -0
  191. package/dist/components/table/snice-cell-link.d.ts +17 -0
  192. package/dist/components/table/snice-cell-link.js +142 -0
  193. package/dist/components/table/snice-cell-link.js.map +1 -0
  194. package/dist/components/table/snice-cell-location.d.ts +19 -0
  195. package/dist/components/table/snice-cell-location.js +185 -0
  196. package/dist/components/table/snice-cell-location.js.map +1 -0
  197. package/dist/components/table/snice-cell-number.d.ts +2 -2
  198. package/dist/components/table/snice-cell-number.js +12 -6
  199. package/dist/components/table/snice-cell-number.js.map +1 -1
  200. package/dist/components/table/snice-cell-percentage.d.ts +22 -0
  201. package/dist/components/table/snice-cell-percentage.js +208 -0
  202. package/dist/components/table/snice-cell-percentage.js.map +1 -0
  203. package/dist/components/table/snice-cell-phone.d.ts +18 -0
  204. package/dist/components/table/snice-cell-phone.js +153 -0
  205. package/dist/components/table/snice-cell-phone.js.map +1 -0
  206. package/dist/components/table/snice-cell-progress.d.ts +2 -2
  207. package/dist/components/table/snice-cell-progress.js +12 -6
  208. package/dist/components/table/snice-cell-progress.js.map +1 -1
  209. package/dist/components/table/snice-cell-rating.d.ts +2 -2
  210. package/dist/components/table/snice-cell-rating.js +12 -6
  211. package/dist/components/table/snice-cell-rating.js.map +1 -1
  212. package/dist/components/table/snice-cell-sparkline.d.ts +2 -2
  213. package/dist/components/table/snice-cell-sparkline.js +13 -7
  214. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  215. package/dist/components/table/snice-cell-status.d.ts +17 -0
  216. package/dist/components/table/snice-cell-status.js +144 -0
  217. package/dist/components/table/snice-cell-status.js.map +1 -0
  218. package/dist/components/table/snice-cell-tag.d.ts +16 -0
  219. package/dist/components/table/snice-cell-tag.js +131 -0
  220. package/dist/components/table/snice-cell-tag.js.map +1 -0
  221. package/dist/components/table/snice-cell-text.d.ts +2 -2
  222. package/dist/components/table/snice-cell-text.js +14 -8
  223. package/dist/components/table/snice-cell-text.js.map +1 -1
  224. package/dist/components/table/snice-cell.d.ts +2 -2
  225. package/dist/components/table/snice-cell.js +12 -6
  226. package/dist/components/table/snice-cell.js.map +1 -1
  227. package/dist/components/table/snice-column.d.ts +1 -1
  228. package/dist/components/table/snice-column.js +6 -3
  229. package/dist/components/table/snice-column.js.map +1 -1
  230. package/dist/components/table/snice-header.d.ts +5 -5
  231. package/dist/components/table/snice-header.js +60 -50
  232. package/dist/components/table/snice-header.js.map +1 -1
  233. package/dist/components/table/snice-progress.d.ts +2 -2
  234. package/dist/components/table/snice-progress.js +18 -11
  235. package/dist/components/table/snice-progress.js.map +1 -1
  236. package/dist/components/table/snice-rating.d.ts +2 -2
  237. package/dist/components/table/snice-rating.js +15 -8
  238. package/dist/components/table/snice-rating.js.map +1 -1
  239. package/dist/components/table/snice-row.d.ts +17 -6
  240. package/dist/components/table/snice-row.js +95 -44
  241. package/dist/components/table/snice-row.js.map +1 -1
  242. package/dist/components/table/snice-table.d.ts +18 -10
  243. package/dist/components/table/snice-table.js +355 -173
  244. package/dist/components/table/snice-table.js.map +1 -1
  245. package/dist/components/table/snice-table.types.d.ts +101 -2
  246. package/dist/components/tabs/snice-tab-panel.d.ts +2 -2
  247. package/dist/components/tabs/snice-tab-panel.js +12 -6
  248. package/dist/components/tabs/snice-tab-panel.js.map +1 -1
  249. package/dist/components/tabs/snice-tab.d.ts +6 -5
  250. package/dist/components/tabs/snice-tab.js +36 -19
  251. package/dist/components/tabs/snice-tab.js.map +1 -1
  252. package/dist/components/tabs/snice-tabs.d.ts +5 -5
  253. package/dist/components/tabs/snice-tabs.js +38 -28
  254. package/dist/components/tabs/snice-tabs.js.map +1 -1
  255. package/dist/components/textarea/snice-textarea.d.ts +52 -0
  256. package/dist/components/textarea/snice-textarea.js +407 -0
  257. package/dist/components/textarea/snice-textarea.js.map +1 -0
  258. package/dist/components/textarea/snice-textarea.types.d.ts +30 -0
  259. package/dist/components/timeline/snice-timeline.d.ts +11 -0
  260. package/dist/components/timeline/snice-timeline.js +112 -0
  261. package/dist/components/timeline/snice-timeline.js.map +1 -0
  262. package/dist/components/timeline/snice-timeline.types.d.ts +16 -0
  263. package/dist/components/toast/snice-toast-container.d.ts +7 -7
  264. package/dist/components/toast/snice-toast-container.js +19 -12
  265. package/dist/components/toast/snice-toast-container.js.map +1 -1
  266. package/dist/components/toast/snice-toast.d.ts +3 -15
  267. package/dist/components/toast/snice-toast.js +49 -108
  268. package/dist/components/toast/snice-toast.js.map +1 -1
  269. package/dist/components/tooltip/snice-tooltip.d.ts +2 -2
  270. package/dist/components/tooltip/snice-tooltip.js +15 -8
  271. package/dist/components/tooltip/snice-tooltip.js.map +1 -1
  272. package/dist/context.d.ts +44 -0
  273. package/dist/element-ready.d.ts +40 -0
  274. package/dist/{types/element.d.ts → element.d.ts} +2 -8
  275. package/dist/{types/events.d.ts → events.d.ts} +0 -4
  276. package/dist/index.cjs +2556 -605
  277. package/dist/index.cjs.map +1 -1
  278. package/dist/index.d.ts +21 -0
  279. package/dist/index.esm.js +2535 -604
  280. package/dist/index.esm.js.map +1 -1
  281. package/dist/index.iife.js +2556 -605
  282. package/dist/index.iife.js.map +1 -1
  283. package/dist/method-decorators.d.ts +121 -0
  284. package/dist/on.d.ts +59 -0
  285. package/dist/parts.d.ts +156 -0
  286. package/dist/render-debug.d.ts +27 -0
  287. package/dist/render-tracker.d.ts +14 -0
  288. package/dist/render.d.ts +96 -0
  289. package/dist/symbols.cjs +163 -0
  290. package/dist/symbols.cjs.map +1 -1
  291. package/dist/{types/symbols.d.ts → symbols.d.ts} +22 -0
  292. package/dist/symbols.esm.js +27 -3
  293. package/dist/symbols.esm.js.map +1 -1
  294. package/dist/template.d.ts +99 -0
  295. package/dist/transitions.cjs +219 -0
  296. package/dist/transitions.esm.js +2 -2
  297. package/dist/types/context.d.ts +48 -0
  298. package/dist/types/element-options.d.ts +26 -0
  299. package/dist/types/index.d.ts +25 -9
  300. package/dist/types/nav-context.d.ts +19 -0
  301. package/dist/types/{types/on-options.d.ts → on-options.d.ts} +2 -0
  302. package/dist/types/{types/placard.d.ts → placard.d.ts} +0 -1
  303. package/docs/ai/README.md +26 -0
  304. package/docs/ai/api.md +175 -0
  305. package/docs/ai/architecture.md +160 -0
  306. package/docs/ai/components/accordion.md +174 -0
  307. package/docs/ai/components/alert.md +77 -0
  308. package/docs/ai/components/avatar.md +61 -0
  309. package/docs/ai/components/badge.md +69 -0
  310. package/docs/ai/components/banner.md +84 -0
  311. package/docs/ai/components/breadcrumbs.md +74 -0
  312. package/docs/ai/components/button.md +75 -0
  313. package/docs/ai/components/card.md +61 -0
  314. package/docs/ai/components/checkbox.md +74 -0
  315. package/docs/ai/components/chip.md +73 -0
  316. package/docs/ai/components/color-display.md +48 -0
  317. package/docs/ai/components/color-picker.md +75 -0
  318. package/docs/ai/components/date-picker.md +75 -0
  319. package/docs/ai/components/divider.md +66 -0
  320. package/docs/ai/components/drawer.md +80 -0
  321. package/docs/ai/components/empty-state.md +72 -0
  322. package/docs/ai/components/file-upload.md +93 -0
  323. package/docs/ai/components/image.md +60 -0
  324. package/docs/ai/components/input.md +111 -0
  325. package/docs/ai/components/kpi.md +158 -0
  326. package/docs/ai/components/link.md +77 -0
  327. package/docs/ai/components/login.md +109 -0
  328. package/docs/ai/components/modal.md +67 -0
  329. package/docs/ai/components/nav.md +76 -0
  330. package/docs/ai/components/pagination.md +55 -0
  331. package/docs/ai/components/progress.md +72 -0
  332. package/docs/ai/components/radio.md +79 -0
  333. package/docs/ai/components/select.md +92 -0
  334. package/docs/ai/components/skeleton.md +57 -0
  335. package/docs/ai/components/slider.md +87 -0
  336. package/docs/ai/components/sparkline.md +168 -0
  337. package/docs/ai/components/spinner.md +47 -0
  338. package/docs/ai/components/stepper.md +216 -0
  339. package/docs/ai/components/switch.md +53 -0
  340. package/docs/ai/components/table.md +227 -0
  341. package/docs/ai/components/tabs.md +83 -0
  342. package/docs/ai/components/textarea.md +87 -0
  343. package/docs/ai/components/timeline.md +77 -0
  344. package/docs/ai/components/toast.md +140 -0
  345. package/docs/ai/components/tooltip.md +146 -0
  346. package/docs/ai/patterns.md +244 -0
  347. package/docs/components/accordion.md +558 -0
  348. package/docs/components/banner.md +106 -0
  349. package/docs/components/color-display.md +96 -0
  350. package/docs/components/color-picker.md +81 -0
  351. package/docs/components/drawer.md +602 -0
  352. package/docs/components/empty-state.md +79 -0
  353. package/docs/components/file-upload.md +263 -0
  354. package/docs/components/image.md +110 -0
  355. package/docs/components/kpi.md +251 -0
  356. package/docs/components/link.md +229 -0
  357. package/docs/components/modal.md +558 -0
  358. package/docs/components/nav.md +239 -0
  359. package/docs/components/pagination.md +289 -0
  360. package/docs/components/select.md +599 -0
  361. package/docs/components/slider.md +297 -0
  362. package/docs/components/sparkline.md +293 -0
  363. package/docs/components/spinner.md +63 -0
  364. package/docs/components/stepper.md +410 -0
  365. package/docs/components/switch.md +354 -0
  366. package/docs/components/tabs.md +546 -0
  367. package/docs/components/textarea.md +235 -0
  368. package/docs/components/timeline.md +192 -0
  369. package/docs/components/toast.md +506 -0
  370. package/docs/components/tooltip.md +523 -0
  371. package/docs/controllers.md +744 -0
  372. package/docs/elements.md +855 -0
  373. package/docs/events.md +807 -0
  374. package/docs/migration-v2-to-v3.md +569 -0
  375. package/docs/observe.md +588 -0
  376. package/docs/placards.md +401 -0
  377. package/docs/request-response.md +852 -0
  378. package/docs/routing.md +1186 -0
  379. package/package.json +11 -11
  380. package/dist/components/snice-cell-C9N6yGxQ.js +0 -4
  381. package/dist/components/snice-cell-C9N6yGxQ.js.map +0 -1
  382. package/dist/types/types/index.d.ts +0 -23
  383. /package/dist/{types/controller.d.ts → controller.d.ts} +0 -0
  384. /package/dist/{types/global.d.ts → global.d.ts} +0 -0
  385. /package/dist/{types/observe.d.ts → observe.d.ts} +0 -0
  386. /package/dist/{types/request-response.d.ts → request-response.d.ts} +0 -0
  387. /package/dist/{types/router.d.ts → router.d.ts} +0 -0
  388. /package/dist/{types/testing.d.ts → testing.d.ts} +0 -0
  389. /package/dist/{types/transitions.d.ts → transitions.d.ts} +0 -0
  390. /package/dist/types/{types/adopted-options.d.ts → adopted-options.d.ts} +0 -0
  391. /package/dist/types/{types/app-context.d.ts → app-context.d.ts} +0 -0
  392. /package/dist/types/{types/dispatch-options.d.ts → dispatch-options.d.ts} +0 -0
  393. /package/dist/types/{types/guard.d.ts → guard.d.ts} +0 -0
  394. /package/dist/types/{types/i-controller.d.ts → i-controller.d.ts} +0 -0
  395. /package/dist/types/{types/moved-options.d.ts → moved-options.d.ts} +0 -0
  396. /package/dist/types/{types/observe-options.d.ts → observe-options.d.ts} +0 -0
  397. /package/dist/types/{types/page-options.d.ts → page-options.d.ts} +0 -0
  398. /package/dist/types/{types/part-options.d.ts → part-options.d.ts} +0 -0
  399. /package/dist/types/{types/property-converter.d.ts → property-converter.d.ts} +0 -0
  400. /package/dist/types/{types/property-options.d.ts → property-options.d.ts} +0 -0
  401. /package/dist/types/{types/query-options.d.ts → query-options.d.ts} +0 -0
  402. /package/dist/types/{types/request-options.d.ts → request-options.d.ts} +0 -0
  403. /package/dist/types/{types/respond-options.d.ts → respond-options.d.ts} +0 -0
  404. /package/dist/types/{types/route-params.d.ts → route-params.d.ts} +0 -0
  405. /package/dist/types/{types/router-instance.d.ts → router-instance.d.ts} +0 -0
  406. /package/dist/types/{types/router-options.d.ts → router-options.d.ts} +0 -0
  407. /package/dist/types/{types/simple-array.d.ts → simple-array.d.ts} +0 -0
  408. /package/dist/types/{types/snice-element.d.ts → snice-element.d.ts} +0 -0
  409. /package/dist/types/{types/snice-global.d.ts → snice-global.d.ts} +0 -0
  410. /package/dist/types/{types/transition.d.ts → transition.d.ts} +0 -0
  411. /package/dist/{types/utils.d.ts → utils.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * snice v2.5.3
3
- * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
2
+ * snice v3.1.0
3
+ * Imperative TypeScript framework for building vanilla web components with decorators, differential rendering, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
6
6
  */
@@ -39,10 +39,12 @@ var Snice = (function (exports) {
39
39
  // Internal element state symbols
40
40
  const READY_PROMISE = getSymbol('ready-promise');
41
41
  const READY_RESOLVE = getSymbol('ready-resolve');
42
+ getSymbol('rendered-promise');
43
+ getSymbol('rendered-resolve');
42
44
  const CONTROLLER = getSymbol('controller');
43
45
  const INITIALIZED = getSymbol('initialized');
44
46
  // Event handler symbols
45
- const ON_HANDLERS = getSymbol('on-handlers');
47
+ getSymbol('on-handlers');
46
48
  // Controller symbols
47
49
  const CONTROLLER_KEY = getSymbol('controller-key');
48
50
  const CONTROLLER_NAME_KEY = getSymbol('controller-name');
@@ -54,13 +56,14 @@ var Snice = (function (exports) {
54
56
  // Property symbols
55
57
  const PROPERTIES = getSymbol('properties');
56
58
  const PROPERTY_VALUES = getSymbol('property-values');
59
+ const PRE_INIT_PROPERTY_VALUES = getSymbol('pre-init-property-values');
57
60
  const PROPERTIES_INITIALIZED = getSymbol('properties-initialized');
58
61
  const PROPERTY_WATCHERS = getSymbol('property-watchers');
59
62
  const EXPLICITLY_SET_PROPERTIES = getSymbol('explicitly-set-properties');
60
63
  // Router context symbol
61
64
  const ROUTER_CONTEXT = getSymbol('router-context');
62
65
  getSymbol('current-page-marker');
63
- const CONTEXT_REQUEST_HANDLER = getSymbol('context-request-handler');
66
+ getSymbol('context-request-handler');
64
67
  const PAGE_TRANSITION = getSymbol('page-transition');
65
68
  const CREATED_AT = getSymbol('created-at');
66
69
  // Lifecycle symbols
@@ -71,313 +74,34 @@ var Snice = (function (exports) {
71
74
  // Observer symbols
72
75
  const OBSERVERS = getSymbol('observers');
73
76
  // Part symbols
74
- const PARTS = getSymbol('parts');
75
- const PART_TIMERS = getSymbol('part-timers');
77
+ getSymbol('parts');
78
+ getSymbol('part-timers');
76
79
  // Lifecycle callback timers
77
80
  const MOVED_TIMERS = getSymbol('moved-timers');
78
81
  const ADOPTED_TIMERS = getSymbol('adopted-timers');
79
82
  // Dispatch timing symbols
80
83
  const DISPATCH_TIMERS = getSymbol('dispatch-timers');
81
-
82
- function on(eventName, selectorOrOptions, options) {
83
- // Handle overloaded parameters
84
- let selector;
85
- let opts;
86
- if (typeof selectorOrOptions === 'string') {
87
- selector = selectorOrOptions;
88
- opts = options;
89
- }
90
- else {
91
- selector = undefined;
92
- opts = selectorOrOptions;
93
- }
94
- return function (target, context) {
95
- const propertyKey = context.name;
96
- context.addInitializer(function () {
97
- const constructor = this.constructor;
98
- // Store event handler metadata
99
- if (!constructor.prototype[ON_HANDLERS]) {
100
- constructor.prototype[ON_HANDLERS] = [];
101
- }
102
- // Normalize to array and expand at decoration time
103
- const eventNames = Array.isArray(eventName) ? eventName : [eventName];
104
- // Create a handler entry for each event
105
- for (const event of eventNames) {
106
- constructor.prototype[ON_HANDLERS].push({
107
- eventName: event,
108
- selector,
109
- methodName: propertyKey,
110
- method: target,
111
- options: opts
112
- });
113
- }
114
- });
115
- };
116
- }
117
- // Helper to setup event handlers for elements
118
- function setupEventHandlers(instance, element) {
119
- const handlers = instance.constructor.prototype[ON_HANDLERS];
120
- if (!handlers)
121
- return;
122
- // Initialize cleanup object if needed
123
- if (!instance[CLEANUP]) {
124
- instance[CLEANUP] = { events: [], channels: [] };
125
- }
126
- for (const handler of handlers) {
127
- // Get the current method from the instance (preserves decorator stacking)
128
- const currentMethod = instance[handler.method.name];
129
- const originalMethod = currentMethod ? currentMethod.bind(instance) : handler.method.bind(instance);
130
- const handlerOptions = handler.options || {};
131
- // Parse event name for key modifiers
132
- const [baseEventName, keyModifier] = handler.eventName.split(':');
133
- // Create debounced/throttled wrapper if needed
134
- const createTimedWrapper = (method) => {
135
- if (handlerOptions.debounce) {
136
- let timeoutId;
137
- return function (...args) {
138
- clearTimeout(timeoutId);
139
- timeoutId = setTimeout(() => method.apply(this, args), handlerOptions.debounce);
140
- };
141
- }
142
- if (handlerOptions.throttle) {
143
- let lastCall = 0;
144
- let timeoutId;
145
- return function (...args) {
146
- const now = Date.now();
147
- const remaining = handlerOptions.throttle - (now - lastCall);
148
- if (remaining <= 0) {
149
- clearTimeout(timeoutId);
150
- lastCall = now;
151
- method.apply(this, args);
152
- }
153
- else if (!timeoutId) {
154
- timeoutId = setTimeout(() => {
155
- lastCall = Date.now();
156
- timeoutId = null;
157
- method.apply(this, args);
158
- }, remaining);
159
- }
160
- };
161
- }
162
- return method;
163
- };
164
- // Create the event handler with key modifier support
165
- const createEventHandler = (method) => {
166
- if (keyModifier && (baseEventName === 'keydown' || baseEventName === 'keyup' || baseEventName === 'keypress')) {
167
- return (event) => {
168
- const keyEvent = event;
169
- // Helper to normalize key names (e.g., "Space" -> " ")
170
- const normalizeKey = (key) => {
171
- if (key === 'Space')
172
- return ' ';
173
- return key;
174
- };
175
- // Check for "any modifiers" match with ~ prefix
176
- if (keyModifier.startsWith('~')) {
177
- const key = normalizeKey(keyModifier.slice(1)); // Remove the ~ and normalize
178
- // Match if key matches, regardless of modifiers
179
- if (keyEvent.key === key) {
180
- method(event);
181
- }
182
- return;
183
- }
184
- // Check for modifier combinations using +
185
- if (keyModifier.includes('+')) {
186
- const parts = keyModifier.split('+');
187
- const key = normalizeKey(parts[parts.length - 1]); // Last part is the actual key
188
- const modifiers = parts.slice(0, -1); // Everything else is modifiers
189
- // Check the actual key
190
- if (keyEvent.key !== key)
191
- return;
192
- // Create a set of expected modifiers
193
- const expectedModifiers = new Set(modifiers.map((m) => m.toLowerCase()));
194
- const hasCtrl = expectedModifiers.has('ctrl');
195
- const hasShift = expectedModifiers.has('shift');
196
- const hasAlt = expectedModifiers.has('alt');
197
- const hasMeta = expectedModifiers.has('meta') || expectedModifiers.has('cmd');
198
- // Check that expected modifiers are pressed and unexpected ones are not
199
- const modifiersMatch = keyEvent.ctrlKey === hasCtrl &&
200
- keyEvent.shiftKey === hasShift &&
201
- keyEvent.altKey === hasAlt &&
202
- keyEvent.metaKey === hasMeta;
203
- if (modifiersMatch) {
204
- method(event);
205
- }
206
- }
207
- else {
208
- // Default: exact match (no modifiers allowed)
209
- const key = normalizeKey(keyModifier);
210
- // Only match if key matches AND no modifiers are pressed
211
- if (keyEvent.key === key &&
212
- !keyEvent.ctrlKey &&
213
- !keyEvent.shiftKey &&
214
- !keyEvent.altKey &&
215
- !keyEvent.metaKey) {
216
- method(event);
217
- }
218
- }
219
- };
220
- }
221
- return method;
222
- };
223
- // Apply timing wrapper (debounce/throttle)
224
- const timedMethod = createTimedWrapper(originalMethod);
225
- // Wrap boundMethod in try-catch for error isolation
226
- const wrappedMethod = createEventHandler((event) => {
227
- try {
228
- // Apply automatic preventDefault/stopPropagation if configured
229
- if (handlerOptions.preventDefault) {
230
- event.preventDefault();
231
- }
232
- if (handlerOptions.stopPropagation) {
233
- event.stopPropagation();
234
- }
235
- return timedMethod(event);
236
- }
237
- catch (error) {
238
- console.error(`Error in event handler ${handler.methodName}:`, error);
239
- // Don't rethrow - allow other handlers to continue
240
- }
241
- });
242
- if (handler.selector) {
243
- // Delegated event handling - use shadow root if available
244
- const eventRoot = element.shadowRoot || element;
245
- const delegatedHandler = (event) => {
246
- const target = event.target;
247
- let shouldHandle = false;
248
- if (target.matches && target.matches(handler.selector)) {
249
- shouldHandle = true;
250
- }
251
- else if (target.closest) {
252
- const closest = target.closest(handler.selector);
253
- if (closest) {
254
- shouldHandle = true;
255
- }
256
- }
257
- if (shouldHandle) {
258
- // Apply automatic preventDefault/stopPropagation only if we're handling this event
259
- if (handlerOptions.preventDefault) {
260
- event.preventDefault();
261
- }
262
- if (handlerOptions.stopPropagation) {
263
- event.stopPropagation();
264
- event.stopImmediatePropagation(); // Also stop other handlers on same element
265
- }
266
- wrappedMethod(event);
267
- }
268
- };
269
- const listenerOptions = {
270
- capture: handlerOptions.capture || false,
271
- once: handlerOptions.once || false,
272
- passive: handlerOptions.passive || false
273
- };
274
- eventRoot.addEventListener(baseEventName, delegatedHandler, listenerOptions);
275
- instance[CLEANUP].events.push(() => {
276
- eventRoot.removeEventListener(baseEventName, delegatedHandler, listenerOptions);
277
- });
278
- }
279
- else {
280
- // Direct event handling - always on the element itself
281
- const listenerOptions = {
282
- capture: handlerOptions.capture || false,
283
- once: handlerOptions.once || false,
284
- passive: handlerOptions.passive || false
285
- };
286
- element.addEventListener(baseEventName, wrappedMethod, listenerOptions);
287
- instance[CLEANUP].events.push(() => {
288
- element.removeEventListener(baseEventName, wrappedMethod, listenerOptions);
289
- });
290
- }
291
- }
292
- }
293
- // Helper to cleanup event handlers
294
- function cleanupEventHandlers(instance) {
295
- if (instance[CLEANUP]?.events) {
296
- for (const cleanup of instance[CLEANUP].events) {
297
- cleanup();
298
- }
299
- instance[CLEANUP].events = [];
300
- }
301
- }
302
- /**
303
- * Decorator that automatically dispatches a custom event after a method is called.
304
- * The return value of the method becomes the event detail.
305
- *
306
- * @param eventName The name of the event to dispatch
307
- * @param options Optional configuration extending EventInit
308
- */
309
- function dispatch(eventName, options) {
310
- return function (originalMethod, _context) {
311
- return function (...args) {
312
- // Create timing wrappers for dispatch (per-instance)
313
- if (!this[DISPATCH_TIMERS]) {
314
- this[DISPATCH_TIMERS] = new Map();
315
- }
316
- const timerKey = `${eventName}_${_context.name}`;
317
- if (!this[DISPATCH_TIMERS].has(timerKey)) {
318
- this[DISPATCH_TIMERS].set(timerKey, {
319
- debounceTimeout: null,
320
- throttleLastCall: 0,
321
- throttleTimeout: null
322
- });
323
- }
324
- const timers = this[DISPATCH_TIMERS].get(timerKey);
325
- // Call the original method with preserved this context
326
- const result = originalMethod.apply(this, args);
327
- // Helper to dispatch the event
328
- const doDispatch = (detail) => {
329
- // Skip dispatch if result is undefined and dispatchOnUndefined is false
330
- if (detail === undefined && options?.dispatchOnUndefined === false) {
331
- return;
332
- }
333
- // Create event with spread operator for options
334
- const event = new CustomEvent(eventName, {
335
- bubbles: true, // Default to true for component events
336
- composed: true, // Allow crossing shadow DOM boundaries
337
- ...options, // Spread all EventInit options
338
- detail
339
- });
340
- this.dispatchEvent(event);
341
- };
342
- // Helper to handle timed dispatch
343
- const timedDispatch = (detail) => {
344
- if (options?.debounce) {
345
- clearTimeout(timers.debounceTimeout);
346
- timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
347
- }
348
- else if (options?.throttle) {
349
- const now = Date.now();
350
- const remaining = options.throttle - (now - timers.throttleLastCall);
351
- if (remaining <= 0) {
352
- clearTimeout(timers.throttleTimeout);
353
- timers.throttleLastCall = now;
354
- doDispatch(detail);
355
- }
356
- else if (!timers.throttleTimeout) {
357
- timers.throttleTimeout = setTimeout(() => {
358
- timers.throttleLastCall = Date.now();
359
- timers.throttleTimeout = null;
360
- doDispatch(detail);
361
- }, remaining);
362
- }
363
- }
364
- else {
365
- doDispatch(detail);
366
- }
367
- };
368
- // Handle async methods
369
- if (result instanceof Promise) {
370
- return result.then((resolvedResult) => {
371
- timedDispatch(resolvedResult);
372
- return resolvedResult;
373
- });
374
- }
375
- // Sync method
376
- timedDispatch(result);
377
- return result;
378
- };
379
- };
380
- }
84
+ // Render symbols (v3.0.0)
85
+ const RENDER_METHOD = getSymbol('render-method');
86
+ const RENDER_OPTIONS = getSymbol('render-options');
87
+ const RENDER_INSTANCE = getSymbol('render-instance');
88
+ const RENDER_SCHEDULED = getSymbol('render-scheduled');
89
+ const RENDER_TIMERS = getSymbol('render-timers');
90
+ const RENDER_CALLBACKS = getSymbol('render-callbacks');
91
+ const STYLES_METHOD = getSymbol('styles-method');
92
+ const STYLES_APPLIED = getSymbol('styles-applied');
93
+ // Navigation context symbols
94
+ const CONTEXT_HANDLER = getSymbol('context-handler');
95
+ getSymbol('context-method-name');
96
+ const NAVIGATION_CONTEXT_INSTANCE = getSymbol('navigation-context-instance');
97
+ const REGISTERED_ELEMENTS = getSymbol('registered-elements');
98
+ const IS_UPDATING = getSymbol('is-updating');
99
+ const CONTEXT_REGISTER = getSymbol('context-register');
100
+ const CONTEXT_UNREGISTER = getSymbol('context-unregister');
101
+ const CONTEXT_NOTIFY_ELEMENT = getSymbol('context-notify-element');
102
+ getSymbol('context-options');
103
+ const CONTEXT_TIMER = getSymbol('context-timer');
104
+ const CONTEXT_CALLED = getSymbol('context-called');
381
105
 
382
106
  // Global cache for MediaQueryList objects
383
107
  const mediaQueryCache = new Map();
@@ -404,21 +128,22 @@ var Snice = (function (exports) {
404
128
  }
405
129
  return function (target, context) {
406
130
  const propertyKey = context.name;
131
+ const initKey = `__observe_init_${propertyKey}`;
407
132
  context.addInitializer(function () {
408
133
  const constructor = this.constructor;
409
- // Store observer metadata
410
- if (!constructor.prototype[OBSERVERS]) {
411
- constructor.prototype[OBSERVERS] = [];
134
+ if (constructor[initKey])
135
+ return;
136
+ constructor[initKey] = true;
137
+ if (!constructor[OBSERVERS]) {
138
+ constructor[OBSERVERS] = [];
412
139
  }
413
- // Normalize to array
414
140
  const observeTargets = Array.isArray(observeTarget) ? observeTarget : [observeTarget];
415
- // Create an observer entry for each target
416
141
  for (const targetString of observeTargets) {
417
- // Parse the observation type from the observeTarget string
418
142
  const [type, ...modifiers] = targetString.split(':');
419
- constructor.prototype[OBSERVERS].push({
143
+ const targetStr = modifiers.join(':');
144
+ constructor[OBSERVERS].push({
420
145
  type,
421
- target: modifiers.join(':'), // Rejoin for media queries or mutation types
146
+ target: targetStr,
422
147
  selector,
423
148
  methodName: propertyKey,
424
149
  method: target,
@@ -430,8 +155,7 @@ var Snice = (function (exports) {
430
155
  }
431
156
  // Helper to setup observers for elements
432
157
  function setupObservers(instance, element) {
433
- // Only check the prototype, not the instance itself to avoid property access issues
434
- const observers = instance.constructor.prototype[OBSERVERS];
158
+ const observers = instance.constructor[OBSERVERS];
435
159
  if (!observers || !Array.isArray(observers) || observers.length === 0) {
436
160
  return;
437
161
  }
@@ -859,14 +583,16 @@ var Snice = (function (exports) {
859
583
  function respond(requestName, options) {
860
584
  return function (target, context) {
861
585
  const propertyKey = context.name;
586
+ const initKey = `__respond_init_${requestName}_${propertyKey}`;
862
587
  context.addInitializer(function () {
863
588
  const constructor = this.constructor;
864
- // Store response metadata on the prototype
865
- // This will be picked up by setupResponseHandlers
866
- if (!constructor.prototype[CHANNEL_HANDLERS]) {
867
- constructor.prototype[CHANNEL_HANDLERS] = [];
589
+ if (constructor[initKey])
590
+ return;
591
+ constructor[initKey] = true;
592
+ if (!constructor[CHANNEL_HANDLERS]) {
593
+ constructor[CHANNEL_HANDLERS] = [];
868
594
  }
869
- constructor.prototype[CHANNEL_HANDLERS].push({
595
+ constructor[CHANNEL_HANDLERS].push({
870
596
  channelName: requestName,
871
597
  methodName: propertyKey,
872
598
  method: target,
@@ -877,7 +603,7 @@ var Snice = (function (exports) {
877
603
  }
878
604
  // Helper to setup response handlers for elements and controllers
879
605
  function setupResponseHandlers(instance, element) {
880
- const handlers = instance.constructor.prototype[CHANNEL_HANDLERS];
606
+ const handlers = instance.constructor[CHANNEL_HANDLERS];
881
607
  if (!handlers)
882
608
  return;
883
609
  // Store cleanup functions
@@ -977,71 +703,1263 @@ var Snice = (function (exports) {
977
703
  }
978
704
  }
979
705
 
980
- // Controller-scoped cleanup registry
981
- class ControllerScope {
982
- constructor() {
983
- this.cleanupFns = new Map();
984
- this.pendingOperations = new Set();
985
- }
986
- register(key, cleanup) {
987
- this.cleanupFns.set(key, cleanup);
988
- }
989
- unregister(key) {
990
- this.cleanupFns.delete(key);
991
- }
992
- async cleanup() {
993
- // Wait for all pending operations
994
- await Promise.all(this.pendingOperations);
995
- // Run all cleanup functions
996
- for (const cleanup of this.cleanupFns.values()) {
997
- try {
998
- await cleanup();
999
- }
1000
- catch (error) {
1001
- console.error('Error during cleanup:', error);
1002
- }
1003
- }
1004
- this.cleanupFns.clear();
706
+ /**
707
+ * Template system for Snice v3.0.0
708
+ * Provides html`` and css`` tagged template processors with differential rendering
709
+ */
710
+ // Unique symbols for type checking
711
+ const HTML_RESULT = Symbol('html-result');
712
+ const CSS_RESULT = Symbol('css-result');
713
+ /**
714
+ * Tagged template function for creating HTML templates
715
+ *
716
+ * @example
717
+ * ```typescript
718
+ * html`<div class="card">
719
+ * <h1>${this.title}</h1>
720
+ * <button @click=${this.handleClick}>Click me</button>
721
+ * </div>`
722
+ * ```
723
+ */
724
+ function html(strings, ...values) {
725
+ return {
726
+ _$litType$: HTML_RESULT,
727
+ strings,
728
+ values
729
+ };
730
+ }
731
+ /**
732
+ * Tagged template function for creating CSS
733
+ *
734
+ * @example
735
+ * ```typescript
736
+ * css`:host {
737
+ * display: block;
738
+ * padding: 1rem;
739
+ * }`
740
+ * ```
741
+ */
742
+ function css(strings, ...values) {
743
+ // Combine strings and values into final CSS text
744
+ let cssText = strings[0];
745
+ for (let i = 0; i < values.length; i++) {
746
+ cssText += String(values[i]) + strings[i + 1];
1005
747
  }
1006
- async runOperation(operation) {
1007
- const promise = operation();
1008
- const voidPromise = promise.then(() => { }, () => { });
1009
- this.pendingOperations.add(voidPromise);
748
+ const result = {
749
+ _$litType$: CSS_RESULT,
750
+ cssText
751
+ };
752
+ // Try to create constructable stylesheet for better performance
753
+ // This will be cached and reused across instances
754
+ if (typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in Document.prototype) {
1010
755
  try {
1011
- const result = await promise;
1012
- this.pendingOperations.delete(voidPromise);
1013
- return result;
756
+ const sheet = new CSSStyleSheet();
757
+ sheet.replaceSync(cssText);
758
+ result.styleSheet = sheet;
1014
759
  }
1015
- catch (error) {
1016
- this.pendingOperations.delete(voidPromise);
1017
- throw error;
760
+ catch (e) {
761
+ // Fall back to regular <style> tag if constructable stylesheets fail
1018
762
  }
1019
763
  }
764
+ return result;
1020
765
  }
1021
766
  /**
1022
- * Decorator to register a controller class with a name
1023
- * @param name The name to register the controller under
767
+ * Check if a value is a TemplateResult
1024
768
  */
1025
- function controller(name) {
1026
- return function (constructor, _context) {
1027
- snice.controllerRegistry.set(name, constructor);
1028
- // Mark as controller class for channel decorator detection
1029
- constructor.prototype[IS_CONTROLLER_CLASS] = true;
1030
- return constructor;
1031
- };
769
+ function isTemplateResult(value) {
770
+ return value && value._$litType$ === HTML_RESULT;
1032
771
  }
1033
772
  /**
1034
- * Attaches a controller to an element
1035
- * @param element The element to attach the controller to
1036
- * @param controllerName The name of the controller to attach
773
+ * Check if a value is a CSSResult
1037
774
  */
1038
- async function attachController(element, controllerName) {
1039
- const existingController = element[CONTROLLER_KEY];
1040
- const existingName = element[CONTROLLER_NAME_KEY];
1041
- // For native elements, check if this is actually the desired controller
1042
- const nativeController = element[NATIVE_CONTROLLER];
1043
- if (nativeController !== undefined && nativeController !== controllerName) {
1044
- // This attachment is outdated, skip it
775
+ function isCSSResult(value) {
776
+ return value && value._$litType$ === CSS_RESULT;
777
+ }
778
+ /**
779
+ * Nothing - represents no value (different from null/undefined)
780
+ * Used to remove content from templates
781
+ */
782
+ const nothing = Symbol('nothing');
783
+ // Unique symbol for unsafe HTML
784
+ const UNSAFE_HTML = Symbol('unsafe-html');
785
+ /**
786
+ * Mark a string as raw HTML that should not be escaped
787
+ * WARNING: Only use with sanitized content - using user input can lead to XSS!
788
+ *
789
+ * @example
790
+ * ```typescript
791
+ * const htmlString = '<span class="bold">Hello</span>';
792
+ * html`<div>${unsafeHTML(htmlString)}</div>`
793
+ * ```
794
+ */
795
+ function unsafeHTML(html) {
796
+ return {
797
+ _$litType$: UNSAFE_HTML,
798
+ html
799
+ };
800
+ }
801
+ /**
802
+ * Check if a value is an UnsafeHTML wrapper
803
+ */
804
+ function isUnsafeHTML(value) {
805
+ return value && value._$litType$ === UNSAFE_HTML;
806
+ }
807
+
808
+ // Unique marker for dynamic parts
809
+ // This parses as a comment node but doesn't get escaped in attributes
810
+ const marker = `snice$${Math.random().toFixed(9).slice(2)}$`;
811
+ const markerMatch = '?' + marker;
812
+ const nodeMarker = `<${markerMatch}>`;
813
+ const markerRegex = new RegExp(marker, 'g');
814
+ // Template cache - templates with same string array can be reused
815
+ const templateCache = new WeakMap();
816
+ /**
817
+ * A prepared template ready for rendering
818
+ */
819
+ class Template {
820
+ constructor(result, element, attrNamesForParts) {
821
+ this.parts = [];
822
+ this.element = element;
823
+ const walker = document.createTreeWalker(element.content, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
824
+ let partIndex = 0;
825
+ const nodesToRemove = [];
826
+ let node;
827
+ while ((node = walker.nextNode()) !== null) {
828
+ if (node.nodeType === Node.ELEMENT_NODE) {
829
+ const element = node;
830
+ const tagName = element.tagName.toLowerCase();
831
+ // Handle virtual elements: <if>, <case>
832
+ // Keep them in the DOM with display:contents for now
833
+ // Will optimize later with proper template extraction
834
+ if (tagName === 'if') {
835
+ // <if value="${condition}">children</if>
836
+ const valueAttr = element.getAttribute('value');
837
+ if (valueAttr && valueAttr.includes(marker)) {
838
+ // Remove the value attribute
839
+ element.removeAttribute('value');
840
+ this.parts.push({
841
+ type: 'conditional-if',
842
+ index: partIndex++,
843
+ element // Keep the <if> element
844
+ });
845
+ // Continue processing children normally
846
+ }
847
+ continue;
848
+ }
849
+ // Handle <case> element
850
+ if (tagName === 'case') {
851
+ // <case value="${value}">children</case>
852
+ const valueAttr = element.getAttribute('value');
853
+ if (valueAttr && valueAttr.includes(marker)) {
854
+ // Remove the value attribute
855
+ element.removeAttribute('value');
856
+ this.parts.push({
857
+ type: 'conditional-case',
858
+ index: partIndex++,
859
+ element // Keep the <case> element
860
+ });
861
+ // Continue processing children normally
862
+ }
863
+ continue;
864
+ }
865
+ if (element.hasAttributes()) {
866
+ const attributes = element.attributes;
867
+ const attrsToRemove = [];
868
+ for (let i = 0; i < attributes.length; i++) {
869
+ const attr = attributes[i];
870
+ const value = attr.value;
871
+ // Check for attribute bindings
872
+ if (value.includes(marker)) {
873
+ attrsToRemove.push(attr);
874
+ // Get original attribute name with preserved case
875
+ const originalName = attrNamesForParts[partIndex] || attr.name;
876
+ // Extract static string segments by splitting on marker
877
+ const attrStrings = value.split(marker);
878
+ if (originalName.startsWith('@')) {
879
+ // Event binding
880
+ this.parts.push({
881
+ type: 'event',
882
+ index: partIndex++,
883
+ name: originalName.slice(1),
884
+ element
885
+ });
886
+ }
887
+ else if (originalName.startsWith('.')) {
888
+ // Property binding - preserve original case for JavaScript properties
889
+ this.parts.push({
890
+ type: 'property',
891
+ index: partIndex++,
892
+ name: originalName.slice(1),
893
+ element
894
+ });
895
+ }
896
+ else if (originalName.startsWith('?')) {
897
+ // Boolean attribute
898
+ this.parts.push({
899
+ type: 'boolean-attribute',
900
+ index: partIndex++,
901
+ name: originalName.slice(1),
902
+ element
903
+ });
904
+ }
905
+ else {
906
+ // Regular attribute - use lowercased name from DOM
907
+ // Store static string segments for interpolation
908
+ this.parts.push({
909
+ type: 'attribute',
910
+ index: partIndex++,
911
+ name: attr.name,
912
+ element,
913
+ attrStrings
914
+ });
915
+ }
916
+ }
917
+ }
918
+ // Remove marker attributes
919
+ for (const attr of attrsToRemove) {
920
+ element.removeAttribute(attr.name);
921
+ }
922
+ }
923
+ }
924
+ else if (node.nodeType === Node.COMMENT_NODE) {
925
+ const comment = node;
926
+ // Check for marker match (processing instruction becomes comment)
927
+ if (comment.data === markerMatch) {
928
+ // Node part
929
+ const parent = comment.parentNode;
930
+ const endNode = document.createComment('');
931
+ parent.insertBefore(endNode, comment.nextSibling);
932
+ this.parts.push({
933
+ type: 'node',
934
+ index: partIndex++,
935
+ startNode: comment,
936
+ endNode
937
+ });
938
+ }
939
+ }
940
+ else if (node.nodeType === Node.TEXT_NODE) {
941
+ const text = node;
942
+ const data = text.data;
943
+ if (data.includes(marker)) {
944
+ // Split text node at markers
945
+ const parent = text.parentNode;
946
+ const parts = data.split(markerRegex);
947
+ const lastIndex = parts.length - 1;
948
+ for (let i = 0; i < lastIndex; i++) {
949
+ parent.insertBefore(document.createTextNode(parts[i]), text);
950
+ const comment = document.createComment('');
951
+ const endNode = document.createComment('');
952
+ parent.insertBefore(comment, text);
953
+ parent.insertBefore(endNode, text);
954
+ this.parts.push({
955
+ type: 'node',
956
+ index: partIndex++,
957
+ startNode: comment,
958
+ endNode
959
+ });
960
+ }
961
+ // Last part
962
+ if (parts[lastIndex] !== '') {
963
+ text.data = parts[lastIndex];
964
+ }
965
+ else {
966
+ nodesToRemove.push(text);
967
+ }
968
+ }
969
+ }
970
+ }
971
+ // Remove marker nodes
972
+ for (const node of nodesToRemove) {
973
+ node.parentNode?.removeChild(node);
974
+ }
975
+ }
976
+ }
977
+ /**
978
+ * Prepare a template for rendering
979
+ */
980
+ function prepareTemplate(result) {
981
+ // Check cache first
982
+ const { strings } = result;
983
+ const cached = templateCache.get(strings);
984
+ if (cached) {
985
+ return cached;
986
+ }
987
+ // Build HTML with markers and extract original attribute names
988
+ const htmlParts = [];
989
+ const attrNamesForParts = [];
990
+ for (let i = 0; i < strings.length; i++) {
991
+ const str = strings[i];
992
+ htmlParts.push(str);
993
+ if (i < strings.length - 1) {
994
+ // Check if we're in an attribute context
995
+ // Look backwards for = sign
996
+ const lastEquals = str.lastIndexOf('=');
997
+ const lastCloseTag = str.lastIndexOf('>');
998
+ if (lastEquals > lastCloseTag) {
999
+ // We're in an attribute value - extract and preserve the original attribute name
1000
+ let attrStart = lastEquals - 1;
1001
+ while (attrStart >= 0 && /\S/.test(str[attrStart])) {
1002
+ attrStart--;
1003
+ }
1004
+ const attrName = str.substring(attrStart + 1, lastEquals).trim();
1005
+ attrNamesForParts.push(attrName);
1006
+ htmlParts.push(marker);
1007
+ }
1008
+ else {
1009
+ // Check if this is a meta element (<if> or <case>) by looking backwards
1010
+ // Match pattern: <if or <case followed by whitespace or >
1011
+ const metaElementMatch = str.match(/<(if|case)\s*$/);
1012
+ if (metaElementMatch) {
1013
+ // This is a meta element - add value attribute
1014
+ attrNamesForParts.push('value');
1015
+ htmlParts.push(`value="${marker}"`);
1016
+ }
1017
+ else {
1018
+ // We're in node content
1019
+ attrNamesForParts.push(''); // Empty string for node parts
1020
+ htmlParts.push(nodeMarker);
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ const html = htmlParts.join('');
1026
+ const template = document.createElement('template');
1027
+ template.innerHTML = html;
1028
+ const tmpl = new Template(result, template, attrNamesForParts);
1029
+ // Cache the template for reuse
1030
+ templateCache.set(strings, tmpl);
1031
+ return tmpl;
1032
+ }
1033
+ /**
1034
+ * Instance of a rendered template
1035
+ */
1036
+ class TemplateInstance {
1037
+ constructor(result) {
1038
+ this.parts = [];
1039
+ this.fragment = null;
1040
+ this.conditionalParts = []; // if/case parts with their indices
1041
+ this.regularParts = []; // all other parts with their indices
1042
+ this.template = prepareTemplate(result);
1043
+ }
1044
+ renderFragment() {
1045
+ if (!this.fragment) {
1046
+ // First render - clone template and create parts
1047
+ this.fragment = this.template.element.content.cloneNode(true);
1048
+ // Build a map of nodes from template to cloned fragment
1049
+ const walker = document.createTreeWalker(this.template.element.content, NodeFilter.SHOW_ALL);
1050
+ const clonedWalker = document.createTreeWalker(this.fragment, NodeFilter.SHOW_ALL);
1051
+ const nodeMap = new Map();
1052
+ let templateNode = walker.currentNode;
1053
+ let clonedNode = clonedWalker.currentNode;
1054
+ while (templateNode && clonedNode) {
1055
+ nodeMap.set(templateNode, clonedNode);
1056
+ templateNode = walker.nextNode();
1057
+ clonedNode = clonedWalker.nextNode();
1058
+ }
1059
+ for (let i = 0; i < this.template.parts.length; i++) {
1060
+ const partDef = this.template.parts[i];
1061
+ let part;
1062
+ switch (partDef.type) {
1063
+ case 'node':
1064
+ const startNode = nodeMap.get(partDef.startNode);
1065
+ const endNode = nodeMap.get(partDef.endNode);
1066
+ part = new NodePart(startNode, endNode);
1067
+ break;
1068
+ case 'attribute':
1069
+ const attrElement = nodeMap.get(partDef.element);
1070
+ part = new AttributePart(attrElement, partDef.name, partDef.attrStrings);
1071
+ break;
1072
+ case 'property':
1073
+ const propElement = nodeMap.get(partDef.element);
1074
+ part = new PropertyPart(propElement, partDef.name);
1075
+ break;
1076
+ case 'boolean-attribute':
1077
+ const boolElement = nodeMap.get(partDef.element);
1078
+ part = new BooleanAttributePart(boolElement, partDef.name);
1079
+ break;
1080
+ case 'event':
1081
+ const eventElement = nodeMap.get(partDef.element);
1082
+ part = new EventPart(eventElement, partDef.name);
1083
+ break;
1084
+ case 'conditional-if':
1085
+ const conditionalIfElement = nodeMap.get(partDef.element);
1086
+ part = new ConditionalIfPart(conditionalIfElement);
1087
+ break;
1088
+ case 'conditional-case':
1089
+ const conditionalCaseElement = nodeMap.get(partDef.element);
1090
+ part = new ConditionalCasePart(conditionalCaseElement);
1091
+ break;
1092
+ default:
1093
+ throw new Error(`Unknown part type: ${partDef.type}`);
1094
+ }
1095
+ this.parts.push(part);
1096
+ // Separate conditional parts from regular parts for optimized update
1097
+ if (part instanceof ConditionalIfPart || part instanceof ConditionalCasePart) {
1098
+ this.conditionalParts.push({ part, index: i });
1099
+ }
1100
+ else {
1101
+ this.regularParts.push({ part, index: i });
1102
+ }
1103
+ }
1104
+ }
1105
+ return this.fragment;
1106
+ }
1107
+ render(values) {
1108
+ const fragment = this.renderFragment();
1109
+ // Commit values to parts
1110
+ this.update(values);
1111
+ return fragment;
1112
+ }
1113
+ update(values) {
1114
+ // Optimized: Process conditional parts first (if any), then regular parts
1115
+ // Using pre-separated arrays with cached indices avoids instanceof and indexOf calls
1116
+ // Process conditional parts first (they control visibility)
1117
+ for (const { part, index } of this.conditionalParts) {
1118
+ part.commit(values[index]);
1119
+ }
1120
+ // Then process regular parts
1121
+ for (const { part, index } of this.regularParts) {
1122
+ part.commit(values[index]);
1123
+ }
1124
+ }
1125
+ clear() {
1126
+ for (const part of this.parts) {
1127
+ part.clear();
1128
+ }
1129
+ }
1130
+ }
1131
+ /**
1132
+ * Base class for all parts
1133
+ */
1134
+ class Part {
1135
+ }
1136
+ /**
1137
+ * NodePart handles text content and nested templates
1138
+ */
1139
+ class NodePart extends Part {
1140
+ constructor(startNode, endNode) {
1141
+ super();
1142
+ this.value = undefined;
1143
+ this.startNode = startNode;
1144
+ this.endNode = endNode;
1145
+ }
1146
+ commit(value) {
1147
+ if (value === this.value)
1148
+ return;
1149
+ this.value = value;
1150
+ // Handle arrays
1151
+ if (Array.isArray(value)) {
1152
+ this.commitArray(value);
1153
+ return;
1154
+ }
1155
+ // Handle nested templates
1156
+ if (isTemplateResult(value)) {
1157
+ this.commitTemplate(value);
1158
+ return;
1159
+ }
1160
+ // Handle unsafe HTML
1161
+ if (isUnsafeHTML(value)) {
1162
+ this.commitUnsafeHTML(value);
1163
+ return;
1164
+ }
1165
+ // Handle primitives
1166
+ this.commitPrimitive(value);
1167
+ }
1168
+ commitPrimitive(value) {
1169
+ this.clear();
1170
+ const text = value === null || value === undefined ? '' : String(value);
1171
+ const textNode = document.createTextNode(text);
1172
+ this.insertBefore(textNode);
1173
+ }
1174
+ commitTemplate(template) {
1175
+ this.clear();
1176
+ const instance = new TemplateInstance(template);
1177
+ const fragment = instance.render(template.values);
1178
+ this.insertBefore(fragment);
1179
+ }
1180
+ commitArray(values) {
1181
+ this.clear();
1182
+ // Template caching (via prepareTemplate) still provides significant performance benefit
1183
+ for (const value of values) {
1184
+ if (isTemplateResult(value)) {
1185
+ const instance = new TemplateInstance(value);
1186
+ const fragment = instance.render(value.values);
1187
+ this.insertBefore(fragment);
1188
+ }
1189
+ else {
1190
+ const text = value === null || value === undefined ? '' : String(value);
1191
+ const textNode = document.createTextNode(text);
1192
+ this.insertBefore(textNode);
1193
+ }
1194
+ }
1195
+ }
1196
+ commitUnsafeHTML(value) {
1197
+ this.clear();
1198
+ // Create a temporary container to parse the HTML
1199
+ const temp = document.createElement('template');
1200
+ temp.innerHTML = value.html;
1201
+ // Insert all parsed nodes
1202
+ const fragment = temp.content;
1203
+ this.insertBefore(fragment);
1204
+ }
1205
+ insertBefore(node) {
1206
+ this.endNode.parentNode?.insertBefore(node, this.endNode);
1207
+ }
1208
+ clear() {
1209
+ const parent = this.startNode.parentNode;
1210
+ if (!parent)
1211
+ return;
1212
+ let node = this.startNode.nextSibling;
1213
+ while (node && node !== this.endNode) {
1214
+ const next = node.nextSibling;
1215
+ parent.removeChild(node);
1216
+ node = next;
1217
+ }
1218
+ }
1219
+ }
1220
+ /**
1221
+ * AttributePart handles regular attribute updates
1222
+ */
1223
+ class AttributePart extends Part {
1224
+ constructor(element, name, attrStrings) {
1225
+ super();
1226
+ this.value = undefined;
1227
+ this.element = element;
1228
+ this.name = name;
1229
+ this.attrStrings = attrStrings;
1230
+ }
1231
+ commit(value) {
1232
+ if (value === this.value)
1233
+ return;
1234
+ this.value = value;
1235
+ if (value === null || value === undefined) {
1236
+ this.element.removeAttribute(this.name);
1237
+ }
1238
+ else {
1239
+ // Inline attribute value computation for performance
1240
+ let finalValue;
1241
+ if (!this.attrStrings || this.attrStrings.length === 0) {
1242
+ finalValue = String(value);
1243
+ }
1244
+ else if (this.attrStrings.length === 1) {
1245
+ finalValue = this.attrStrings[0];
1246
+ }
1247
+ else {
1248
+ // Multiple segments: "prefix" + value + "suffix"
1249
+ finalValue = this.attrStrings[0] + String(value) + this.attrStrings[1];
1250
+ }
1251
+ this.element.setAttribute(this.name, finalValue);
1252
+ }
1253
+ }
1254
+ clear() {
1255
+ this.element.removeAttribute(this.name);
1256
+ }
1257
+ }
1258
+ /**
1259
+ * PropertyPart handles property bindings
1260
+ */
1261
+ class PropertyPart extends Part {
1262
+ constructor(element, name) {
1263
+ super();
1264
+ this.value = undefined;
1265
+ this.element = element;
1266
+ this.name = name;
1267
+ }
1268
+ commit(value) {
1269
+ if (value === this.value)
1270
+ return;
1271
+ this.value = value;
1272
+ this.element[this.name] = value;
1273
+ }
1274
+ clear() {
1275
+ this.element[this.name] = undefined;
1276
+ }
1277
+ }
1278
+ /**
1279
+ * BooleanAttributePart handles boolean attributes
1280
+ */
1281
+ class BooleanAttributePart extends Part {
1282
+ constructor(element, name) {
1283
+ super();
1284
+ this.value = undefined;
1285
+ this.element = element;
1286
+ this.name = name;
1287
+ }
1288
+ commit(value) {
1289
+ if (value === this.value)
1290
+ return;
1291
+ this.value = value;
1292
+ if (value) {
1293
+ this.element.setAttribute(this.name, '');
1294
+ }
1295
+ else {
1296
+ this.element.removeAttribute(this.name);
1297
+ }
1298
+ }
1299
+ clear() {
1300
+ this.element.removeAttribute(this.name);
1301
+ }
1302
+ }
1303
+ /**
1304
+ * EventPart handles event listener bindings with keyboard shortcut support
1305
+ */
1306
+ class EventPart extends Part {
1307
+ constructor(element, eventName) {
1308
+ super();
1309
+ this.listener = null;
1310
+ this.value = undefined;
1311
+ this.keyFilter = null;
1312
+ this.host = null; // Cache host element
1313
+ this.element = element;
1314
+ // Parse keyboard shortcuts:
1315
+ // Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter) to match @on decorator
1316
+ const dotIndex = eventName.indexOf('.');
1317
+ const colonIndex = eventName.indexOf(':');
1318
+ // Use whichever delimiter comes first (dot or colon)
1319
+ const delimiterIndex = dotIndex > 0 && colonIndex > 0
1320
+ ? Math.min(dotIndex, colonIndex)
1321
+ : Math.max(dotIndex, colonIndex);
1322
+ if (delimiterIndex > 0) {
1323
+ const baseEvent = eventName.substring(0, delimiterIndex);
1324
+ const keySpec = eventName.substring(delimiterIndex + 1);
1325
+ this.eventName = baseEvent;
1326
+ this.keyFilter = parseKeyboardFilter(keySpec);
1327
+ }
1328
+ else {
1329
+ this.eventName = eventName;
1330
+ }
1331
+ }
1332
+ commit(value) {
1333
+ // Skip if same value (but null/undefined always triggers update)
1334
+ if (value === this.value && value !== null && value !== undefined)
1335
+ return;
1336
+ // Remove old listener
1337
+ if (this.listener) {
1338
+ this.element.removeEventListener(this.eventName, this.listener);
1339
+ this.listener = null;
1340
+ }
1341
+ this.value = value;
1342
+ // Add new listener
1343
+ if (value === null || value === undefined) {
1344
+ return;
1345
+ }
1346
+ if (typeof value === 'function') {
1347
+ // Auto-bind to host element (the custom element with shadow root)
1348
+ // Cache host lookup for performance
1349
+ if (!this.host) {
1350
+ const rootNode = this.element.getRootNode();
1351
+ this.host = rootNode.host || null;
1352
+ }
1353
+ // Create a wrapper that calls the handler with the host as context
1354
+ if (this.host) {
1355
+ const host = this.host; // Capture for closure
1356
+ this.listener = ((event) => {
1357
+ if (this.keyFilter && !matchesKeyboardFilter(event, this.keyFilter)) {
1358
+ return;
1359
+ }
1360
+ value.call(host, event);
1361
+ });
1362
+ }
1363
+ else {
1364
+ this.listener = ((event) => {
1365
+ if (this.keyFilter && !matchesKeyboardFilter(event, this.keyFilter)) {
1366
+ return;
1367
+ }
1368
+ value.call(null, event);
1369
+ });
1370
+ }
1371
+ this.element.addEventListener(this.eventName, this.listener);
1372
+ }
1373
+ }
1374
+ clear() {
1375
+ if (this.listener) {
1376
+ this.element.removeEventListener(this.eventName, this.listener);
1377
+ this.listener = null;
1378
+ }
1379
+ }
1380
+ }
1381
+ /**
1382
+ * Parse keyboard shortcut specification
1383
+ * Examples:
1384
+ * "enter" -> { key: "Enter" }
1385
+ * "ctrl+s" -> { key: "s", ctrl: true }
1386
+ * "ctrl+shift+s" -> { key: "s", ctrl: true, shift: true }
1387
+ * "~enter" -> { key: "Enter", anyModifiers: true }
1388
+ */
1389
+ function parseKeyboardFilter(spec) {
1390
+ // Handle ~ prefix for matching regardless of modifiers
1391
+ const anyModifiers = spec.startsWith('~');
1392
+ if (anyModifiers) {
1393
+ spec = spec.substring(1);
1394
+ }
1395
+ const parts = spec.split('+');
1396
+ const filter = {
1397
+ key: '',
1398
+ anyModifiers
1399
+ };
1400
+ for (const part of parts) {
1401
+ const lower = part.toLowerCase();
1402
+ if (lower === 'ctrl' || lower === 'control') {
1403
+ filter.ctrl = true;
1404
+ }
1405
+ else if (lower === 'alt') {
1406
+ filter.alt = true;
1407
+ }
1408
+ else if (lower === 'shift') {
1409
+ filter.shift = true;
1410
+ }
1411
+ else if (lower === 'meta' || lower === 'cmd' || lower === 'command') {
1412
+ filter.meta = true;
1413
+ }
1414
+ else {
1415
+ // This is the key itself - normalize common keys
1416
+ filter.key = normalizeKey(part);
1417
+ }
1418
+ }
1419
+ return filter;
1420
+ }
1421
+ /**
1422
+ * Normalize key names to match KeyboardEvent.key
1423
+ */
1424
+ function normalizeKey(key) {
1425
+ const keyMap = {
1426
+ 'esc': 'Escape',
1427
+ 'escape': 'Escape',
1428
+ 'enter': 'Enter',
1429
+ 'return': 'Enter',
1430
+ 'space': ' ',
1431
+ 'spacebar': ' ',
1432
+ 'up': 'ArrowUp',
1433
+ 'down': 'ArrowDown',
1434
+ 'left': 'ArrowLeft',
1435
+ 'right': 'ArrowRight',
1436
+ 'arrowup': 'ArrowUp',
1437
+ 'arrowdown': 'ArrowDown',
1438
+ 'arrowleft': 'ArrowLeft',
1439
+ 'arrowright': 'ArrowRight',
1440
+ 'delete': 'Delete',
1441
+ 'del': 'Delete',
1442
+ 'backspace': 'Backspace',
1443
+ 'tab': 'Tab',
1444
+ 'home': 'Home',
1445
+ 'end': 'End',
1446
+ 'pageup': 'PageUp',
1447
+ 'pagedown': 'PageDown'
1448
+ };
1449
+ const lower = key.toLowerCase();
1450
+ return keyMap[lower] || key;
1451
+ }
1452
+ /**
1453
+ * Check if keyboard event matches the filter
1454
+ */
1455
+ function matchesKeyboardFilter(event, filter) {
1456
+ // Check key match
1457
+ if (event.key !== filter.key) {
1458
+ return false;
1459
+ }
1460
+ // If anyModifiers is true, we don't care about modifiers
1461
+ if (filter.anyModifiers) {
1462
+ return true;
1463
+ }
1464
+ // Check modifiers - by default, exact match
1465
+ // If filter specifies ctrl: true, event must have ctrlKey
1466
+ // If filter doesn't specify ctrl, event must NOT have ctrlKey
1467
+ const ctrlMatch = filter.ctrl ? event.ctrlKey : !event.ctrlKey;
1468
+ const altMatch = filter.alt ? event.altKey : !event.altKey;
1469
+ const shiftMatch = filter.shift ? event.shiftKey : !event.shiftKey;
1470
+ const metaMatch = filter.meta ? event.metaKey : !event.metaKey;
1471
+ return ctrlMatch && altMatch && shiftMatch && metaMatch;
1472
+ }
1473
+ /**
1474
+ * ConditionalIfPart handles <if> conditional rendering
1475
+ * Removes/inserts DOM nodes based on condition
1476
+ */
1477
+ class ConditionalIfPart extends Part {
1478
+ constructor(ifElement) {
1479
+ super();
1480
+ this.value = undefined;
1481
+ this.fragment = null;
1482
+ this.ifElement = ifElement;
1483
+ this.ifElement.style.display = 'contents';
1484
+ }
1485
+ commit(value) {
1486
+ const condition = Boolean(value);
1487
+ if (this.value === value)
1488
+ return;
1489
+ this.value = value;
1490
+ if (condition) {
1491
+ // Show: restore children from fragment
1492
+ if (this.fragment && this.fragment.hasChildNodes()) {
1493
+ this.ifElement.appendChild(this.fragment);
1494
+ }
1495
+ }
1496
+ else {
1497
+ // Hide: move children to fragment
1498
+ if (!this.fragment) {
1499
+ this.fragment = document.createDocumentFragment();
1500
+ }
1501
+ while (this.ifElement.firstChild) {
1502
+ this.fragment.appendChild(this.ifElement.firstChild);
1503
+ }
1504
+ }
1505
+ }
1506
+ clear() {
1507
+ if (!this.fragment) {
1508
+ this.fragment = document.createDocumentFragment();
1509
+ }
1510
+ while (this.ifElement.firstChild) {
1511
+ this.fragment.appendChild(this.ifElement.firstChild);
1512
+ }
1513
+ }
1514
+ }
1515
+ /**
1516
+ * ConditionalCasePart handles <case>/<when>/<default> conditional rendering
1517
+ * Removes/inserts matching branch based on value
1518
+ */
1519
+ class ConditionalCasePart extends Part {
1520
+ constructor(caseElement) {
1521
+ super();
1522
+ this.value = undefined;
1523
+ this.childrenMap = new Map();
1524
+ this.fragments = new Map();
1525
+ this.defaultChild = null;
1526
+ this.currentChild = null;
1527
+ this.caseElement = caseElement;
1528
+ // Build map and store children in fragments initially
1529
+ for (const child of Array.from(this.caseElement.children)) {
1530
+ const childTag = child.tagName.toLowerCase();
1531
+ if (childTag === 'when') {
1532
+ const whenValue = child.getAttribute('value') || '';
1533
+ this.childrenMap.set(whenValue, child);
1534
+ const fragment = document.createDocumentFragment();
1535
+ fragment.appendChild(child);
1536
+ this.fragments.set(child, fragment);
1537
+ }
1538
+ else if (childTag === 'default') {
1539
+ this.defaultChild = child;
1540
+ const fragment = document.createDocumentFragment();
1541
+ fragment.appendChild(child);
1542
+ this.fragments.set(child, fragment);
1543
+ }
1544
+ }
1545
+ }
1546
+ commit(value) {
1547
+ if (this.value === value)
1548
+ return;
1549
+ this.value = value;
1550
+ const valueStr = String(value);
1551
+ // Remove current child
1552
+ if (this.currentChild) {
1553
+ const fragment = this.fragments.get(this.currentChild);
1554
+ if (fragment && !fragment.hasChildNodes()) {
1555
+ fragment.appendChild(this.currentChild);
1556
+ }
1557
+ }
1558
+ // Insert matching child
1559
+ const matchingChild = this.childrenMap.get(valueStr) || this.defaultChild;
1560
+ if (matchingChild) {
1561
+ const fragment = this.fragments.get(matchingChild);
1562
+ if (fragment && fragment.hasChildNodes()) {
1563
+ this.caseElement.appendChild(fragment);
1564
+ }
1565
+ this.currentChild = matchingChild;
1566
+ }
1567
+ }
1568
+ clear() {
1569
+ if (this.currentChild) {
1570
+ const fragment = this.fragments.get(this.currentChild);
1571
+ if (fragment && !fragment.hasChildNodes()) {
1572
+ fragment.appendChild(this.currentChild);
1573
+ }
1574
+ this.currentChild = null;
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ /**
1580
+ * @on decorator for listening to events
1581
+ * Use in elements or controllers to listen to DOM events or custom events
1582
+ */
1583
+ const ON_HANDLERS = getSymbol('on-handlers');
1584
+ /**
1585
+ * @on decorator for listening to events
1586
+ *
1587
+ * Works in both elements and controllers with full event delegation support.
1588
+ *
1589
+ * @param eventName - Event name(s) to listen for
1590
+ * @param selector - Optional CSS selector for event delegation
1591
+ * @param options - Event listener options including debounce/throttle
1592
+ *
1593
+ * @example
1594
+ * ```typescript
1595
+ * // In elements
1596
+ * @element('my-button')
1597
+ * class MyButton extends HTMLElement {
1598
+ * @on('click', 'button')
1599
+ * handleClick(e: MouseEvent) {
1600
+ * console.log('Button clicked!', e);
1601
+ * }
1602
+ *
1603
+ * @on('input', 'input', { debounce: 300 })
1604
+ * handleInput(e: Event) {
1605
+ * console.log('Input changed:', (e.target as HTMLInputElement).value);
1606
+ * }
1607
+ * }
1608
+ *
1609
+ * // In controllers
1610
+ * @controller('my-controller')
1611
+ * class MyController {
1612
+ * element!: HTMLElement;
1613
+ *
1614
+ * @on('count-changed')
1615
+ * handleCountChanged(e: CustomEvent) {
1616
+ * console.log('Count changed to:', e.detail.count);
1617
+ * }
1618
+ *
1619
+ * @on('click', '.item', { throttle: 100 })
1620
+ * handleItemClick(e: MouseEvent) {
1621
+ * console.log('Item clicked');
1622
+ * }
1623
+ * }
1624
+ * ```
1625
+ */
1626
+ function on(eventName, selectorOrOptions, options) {
1627
+ // Parse arguments to support multiple call signatures
1628
+ let selector = null;
1629
+ let opts = {};
1630
+ if (typeof selectorOrOptions === 'string') {
1631
+ // With selector: (eventName, selector, options)
1632
+ selector = selectorOrOptions;
1633
+ opts = options || {};
1634
+ }
1635
+ else if (selectorOrOptions === null && options) {
1636
+ // With null selector: (eventName, null, options)
1637
+ opts = options;
1638
+ }
1639
+ else if (selectorOrOptions && typeof selectorOrOptions === 'object') {
1640
+ // Without selector: (eventName, options)
1641
+ opts = selectorOrOptions;
1642
+ }
1643
+ return function (originalMethod, context) {
1644
+ const methodName = context.name;
1645
+ const initKey = `__on_init_${methodName}_${selector || ''}_${JSON.stringify(eventName)}`;
1646
+ context.addInitializer(function () {
1647
+ const constructor = this.constructor;
1648
+ // Only initialize once per class, not per instance
1649
+ if (constructor[initKey])
1650
+ return;
1651
+ constructor[initKey] = true;
1652
+ if (!constructor[ON_HANDLERS]) {
1653
+ constructor[ON_HANDLERS] = [];
1654
+ }
1655
+ const eventNames = Array.isArray(eventName) ? eventName : [eventName];
1656
+ for (const event of eventNames) {
1657
+ constructor[ON_HANDLERS].push({
1658
+ eventName: event,
1659
+ selector,
1660
+ methodName,
1661
+ method: originalMethod,
1662
+ options: opts
1663
+ });
1664
+ }
1665
+ });
1666
+ return originalMethod;
1667
+ };
1668
+ }
1669
+ /**
1670
+ * Create a debounced version of a function
1671
+ */
1672
+ function debounce$1(fn, delay) {
1673
+ let timeoutId = null;
1674
+ return function (...args) {
1675
+ if (timeoutId !== null) {
1676
+ clearTimeout(timeoutId);
1677
+ }
1678
+ timeoutId = setTimeout(() => {
1679
+ fn.apply(this, args);
1680
+ timeoutId = null;
1681
+ }, delay);
1682
+ };
1683
+ }
1684
+ /**
1685
+ * Create a throttled version of a function (leading edge only)
1686
+ */
1687
+ function throttle$1(fn, delay) {
1688
+ let lastCall = 0;
1689
+ return function (...args) {
1690
+ const now = Date.now();
1691
+ const timeSinceLastCall = now - lastCall;
1692
+ if (timeSinceLastCall >= delay) {
1693
+ lastCall = now;
1694
+ fn.apply(this, args);
1695
+ }
1696
+ // Events within the throttle window are simply ignored (leading edge only)
1697
+ };
1698
+ }
1699
+ /**
1700
+ * Events that don't bubble - these require capture phase for delegation
1701
+ */
1702
+ const NON_BUBBLING_EVENTS = new Set([
1703
+ 'scroll',
1704
+ 'focus',
1705
+ 'blur',
1706
+ 'load',
1707
+ 'unload',
1708
+ 'error',
1709
+ 'resize',
1710
+ 'abort',
1711
+ 'mouseenter',
1712
+ 'mouseleave',
1713
+ 'pointerenter',
1714
+ 'pointerleave',
1715
+ ]);
1716
+ /**
1717
+ * Setup event listeners for an element or controller instance
1718
+ * Called automatically during element connection or controller attachment
1719
+ */
1720
+ function setupEventHandlers(instance, targetElement) {
1721
+ const handlers = instance.constructor[ON_HANDLERS];
1722
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
1723
+ return;
1724
+ }
1725
+ // Initialize cleanup object if needed
1726
+ if (!instance[CLEANUP]) {
1727
+ instance[CLEANUP] = { events: [], channels: [], observers: [] };
1728
+ }
1729
+ else if (!instance[CLEANUP].events) {
1730
+ instance[CLEANUP].events = [];
1731
+ }
1732
+ else if (instance[CLEANUP].events.length > 0) {
1733
+ // Events already set up - clean them up first to avoid duplicates
1734
+ cleanupEventHandlers(instance);
1735
+ }
1736
+ for (const handler of handlers) {
1737
+ // Get current method from instance (preserves decorator stacking)
1738
+ const currentMethod = instance[handler.methodName];
1739
+ let boundMethod = currentMethod ? currentMethod.bind(instance) : handler.method.bind(instance);
1740
+ const handlerOptions = handler.options || {};
1741
+ // Parse event name for key modifiers
1742
+ // Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter)
1743
+ const dotIndex = handler.eventName.indexOf('.');
1744
+ const colonIndex = handler.eventName.indexOf(':');
1745
+ const delimiterIndex = dotIndex > 0 && colonIndex > 0
1746
+ ? Math.min(dotIndex, colonIndex)
1747
+ : Math.max(dotIndex, colonIndex);
1748
+ const baseEventName = delimiterIndex > 0
1749
+ ? handler.eventName.substring(0, delimiterIndex)
1750
+ : handler.eventName;
1751
+ const keyModifier = delimiterIndex > 0
1752
+ ? handler.eventName.substring(delimiterIndex + 1)
1753
+ : null;
1754
+ // Apply debounce if specified
1755
+ if (handlerOptions.debounce && handlerOptions.debounce > 0) {
1756
+ boundMethod = debounce$1(boundMethod, handlerOptions.debounce);
1757
+ }
1758
+ // Apply throttle if specified (debounce takes precedence)
1759
+ else if (handlerOptions.throttle && handlerOptions.throttle > 0) {
1760
+ boundMethod = throttle$1(boundMethod, handlerOptions.throttle);
1761
+ }
1762
+ // Create event handler with key modifier support
1763
+ // Uses shared keyboard filter implementation from parts.ts
1764
+ let keyFilter = null;
1765
+ if (keyModifier && ['keydown', 'keyup', 'keypress'].includes(baseEventName)) {
1766
+ keyFilter = parseKeyboardFilter(keyModifier);
1767
+ }
1768
+ const createKeyModifierHandler = (method) => {
1769
+ if (!keyFilter) {
1770
+ return method;
1771
+ }
1772
+ return (event) => {
1773
+ const keyEvent = event;
1774
+ if (matchesKeyboardFilter(keyEvent, keyFilter)) {
1775
+ method(event);
1776
+ }
1777
+ };
1778
+ };
1779
+ // Apply key modifier wrapper
1780
+ const keyModifierMethod = createKeyModifierHandler(boundMethod);
1781
+ // Main event handler with error handling and event delegation
1782
+ if (handler.selector) {
1783
+ // Delegated event handling - use shadow root if available
1784
+ const eventRoot = targetElement.shadowRoot || targetElement;
1785
+ const delegatedHandler = (event) => {
1786
+ const target = event.target;
1787
+ let matchingElement = null;
1788
+ // Check if target itself matches the selector
1789
+ if (target.matches && target.matches(handler.selector)) {
1790
+ matchingElement = target;
1791
+ }
1792
+ // Check if any parent matches the selector (event delegation)
1793
+ else if (target.closest) {
1794
+ matchingElement = target.closest(handler.selector);
1795
+ }
1796
+ // Only handle if we found a match
1797
+ // Note: No need to check contains() since the event bubbled to eventRoot
1798
+ if (matchingElement) {
1799
+ // Apply automatic preventDefault/stopPropagation
1800
+ if (handlerOptions.preventDefault) {
1801
+ event.preventDefault();
1802
+ }
1803
+ if (handlerOptions.stopPropagation) {
1804
+ event.stopPropagation();
1805
+ event.stopImmediatePropagation();
1806
+ }
1807
+ try {
1808
+ keyModifierMethod(event);
1809
+ }
1810
+ catch (error) {
1811
+ console.error(`Error in event handler ${handler.methodName}:`, error);
1812
+ }
1813
+ }
1814
+ };
1815
+ // Auto-enable capture for non-bubbling events when using delegation
1816
+ const needsCapture = NON_BUBBLING_EVENTS.has(baseEventName);
1817
+ const useCapture = handlerOptions.capture !== undefined
1818
+ ? handlerOptions.capture
1819
+ : needsCapture;
1820
+ const listenerOptions = {
1821
+ capture: useCapture,
1822
+ once: handlerOptions.once || false,
1823
+ passive: handlerOptions.passive || false,
1824
+ };
1825
+ eventRoot.addEventListener(baseEventName, delegatedHandler, listenerOptions);
1826
+ instance[CLEANUP].events.push({
1827
+ target: eventRoot,
1828
+ eventName: baseEventName,
1829
+ handler: delegatedHandler,
1830
+ options: listenerOptions,
1831
+ });
1832
+ }
1833
+ else {
1834
+ // Direct event handling - on the element itself
1835
+ // If element has shadow root, listen on both shadow root AND host element
1836
+ // to catch events from inside shadow DOM (with correct target) and on host itself
1837
+ const shadowRoot = targetElement.shadowRoot;
1838
+ const handledSymbol = Symbol('snice-event-handled');
1839
+ const wrappedMethod = (event) => {
1840
+ // Prevent double-triggering when listening on both shadow root and host
1841
+ if (event[handledSymbol]) {
1842
+ return;
1843
+ }
1844
+ event[handledSymbol] = true;
1845
+ try {
1846
+ // Apply automatic preventDefault/stopPropagation
1847
+ if (handlerOptions.preventDefault) {
1848
+ event.preventDefault();
1849
+ }
1850
+ if (handlerOptions.stopPropagation) {
1851
+ event.stopPropagation();
1852
+ }
1853
+ keyModifierMethod(event);
1854
+ }
1855
+ catch (error) {
1856
+ console.error(`Error in event handler ${handler.methodName}:`, error);
1857
+ }
1858
+ };
1859
+ const listenerOptions = {
1860
+ capture: handlerOptions.capture || false,
1861
+ once: handlerOptions.once || false,
1862
+ passive: handlerOptions.passive || false,
1863
+ };
1864
+ if (shadowRoot) {
1865
+ // Listen on shadow root for events inside shadow DOM
1866
+ shadowRoot.addEventListener(baseEventName, wrappedMethod, listenerOptions);
1867
+ instance[CLEANUP].events.push({
1868
+ target: shadowRoot,
1869
+ eventName: baseEventName,
1870
+ handler: wrappedMethod,
1871
+ options: listenerOptions,
1872
+ });
1873
+ }
1874
+ // Also listen on host element (for clicks on host itself or when no shadow root)
1875
+ targetElement.addEventListener(baseEventName, wrappedMethod, listenerOptions);
1876
+ instance[CLEANUP].events.push({
1877
+ target: targetElement,
1878
+ eventName: baseEventName,
1879
+ handler: wrappedMethod,
1880
+ options: listenerOptions,
1881
+ });
1882
+ }
1883
+ }
1884
+ }
1885
+ /**
1886
+ * Cleanup event listeners for a controller instance
1887
+ * Called automatically by the controller system during detach
1888
+ */
1889
+ function cleanupEventHandlers(instance) {
1890
+ if (!instance[CLEANUP]?.events)
1891
+ return;
1892
+ for (const { target, eventName, handler, options } of instance[CLEANUP].events) {
1893
+ target.removeEventListener(eventName, handler, options);
1894
+ }
1895
+ instance[CLEANUP].events = [];
1896
+ }
1897
+
1898
+ // Controller-scoped cleanup registry
1899
+ class ControllerScope {
1900
+ constructor() {
1901
+ this.cleanupFns = new Map();
1902
+ this.pendingOperations = new Set();
1903
+ }
1904
+ register(key, cleanup) {
1905
+ this.cleanupFns.set(key, cleanup);
1906
+ }
1907
+ unregister(key) {
1908
+ this.cleanupFns.delete(key);
1909
+ }
1910
+ async cleanup() {
1911
+ // Wait for all pending operations
1912
+ await Promise.all(this.pendingOperations);
1913
+ // Run all cleanup functions
1914
+ for (const cleanup of this.cleanupFns.values()) {
1915
+ try {
1916
+ await cleanup();
1917
+ }
1918
+ catch (error) {
1919
+ console.error('Error during cleanup:', error);
1920
+ }
1921
+ }
1922
+ this.cleanupFns.clear();
1923
+ }
1924
+ async runOperation(operation) {
1925
+ const promise = operation();
1926
+ const voidPromise = promise.then(() => { }, () => { });
1927
+ this.pendingOperations.add(voidPromise);
1928
+ try {
1929
+ const result = await promise;
1930
+ this.pendingOperations.delete(voidPromise);
1931
+ return result;
1932
+ }
1933
+ catch (error) {
1934
+ this.pendingOperations.delete(voidPromise);
1935
+ throw error;
1936
+ }
1937
+ }
1938
+ }
1939
+ /**
1940
+ * Decorator to register a controller class with a name
1941
+ * @param name The name to register the controller under
1942
+ */
1943
+ function controller(name) {
1944
+ return function (constructor, _context) {
1945
+ snice.controllerRegistry.set(name, constructor);
1946
+ // Mark as controller class for channel decorator detection
1947
+ constructor.prototype[IS_CONTROLLER_CLASS] = true;
1948
+ return constructor;
1949
+ };
1950
+ }
1951
+ /**
1952
+ * Attaches a controller to an element
1953
+ * @param element The element to attach the controller to
1954
+ * @param controllerName The name of the controller to attach
1955
+ */
1956
+ async function attachController(element, controllerName) {
1957
+ const existingController = element[CONTROLLER_KEY];
1958
+ const existingName = element[CONTROLLER_NAME_KEY];
1959
+ // For native elements, check if this is actually the desired controller
1960
+ const nativeController = element[NATIVE_CONTROLLER];
1961
+ if (nativeController !== undefined && nativeController !== controllerName) {
1962
+ // This attachment is outdated, skip it
1045
1963
  return;
1046
1964
  }
1047
1965
  if (existingName === controllerName && existingController) {
@@ -1080,12 +1998,12 @@ var Snice = (function (exports) {
1080
1998
  await scope.runOperation(async () => {
1081
1999
  await controllerInstance.attach(element);
1082
2000
  });
1083
- // Setup @on event handlers for controller
1084
- setupEventHandlers(controllerInstance, element);
1085
2001
  // Setup @observe observers for controller
1086
2002
  setupObservers(controllerInstance, element);
1087
2003
  // Setup @channel handlers for controller
1088
2004
  setupResponseHandlers(controllerInstance, element);
2005
+ // Setup @on event handlers for controller
2006
+ setupEventHandlers(controllerInstance, element);
1089
2007
  element.dispatchEvent(new CustomEvent('@snice/controller-attached', {
1090
2008
  detail: { name: controllerName, controller: controllerInstance }
1091
2009
  }));
@@ -1111,12 +2029,12 @@ var Snice = (function (exports) {
1111
2029
  await controllerInstance.detach(element);
1112
2030
  }
1113
2031
  controllerInstance.element = null;
1114
- // Cleanup @on event handlers for controller
1115
- cleanupEventHandlers(controllerInstance);
1116
2032
  // Cleanup @observe observers for controller
1117
2033
  cleanupObservers(controllerInstance);
1118
2034
  // Cleanup @channel handlers for controller
1119
2035
  cleanupResponseHandlers(controllerInstance);
2036
+ // Cleanup @on event handlers for controller
2037
+ cleanupEventHandlers(controllerInstance);
1120
2038
  // Cleanup the controller scope
1121
2039
  if (scope) {
1122
2040
  await scope.cleanup();
@@ -1199,33 +2117,168 @@ var Snice = (function (exports) {
1199
2117
  });
1200
2118
  }
1201
2119
  }
1202
- });
1203
- // Start observing when DOM is ready
1204
- if (document.readyState === 'loading') {
1205
- document.addEventListener('DOMContentLoaded', () => {
1206
- // Process existing elements (excluding custom elements)
1207
- document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
1208
- // Start observing
1209
- observer.observe(document.body, {
1210
- attributes: true,
1211
- attributeFilter: ['controller'],
1212
- childList: true,
1213
- subtree: true
1214
- });
1215
- });
2120
+ });
2121
+ // Start observing when DOM is ready
2122
+ if (document.readyState === 'loading') {
2123
+ document.addEventListener('DOMContentLoaded', () => {
2124
+ // Process existing elements (excluding custom elements)
2125
+ document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
2126
+ // Start observing
2127
+ observer.observe(document.body, {
2128
+ attributes: true,
2129
+ attributeFilter: ['controller'],
2130
+ childList: true,
2131
+ subtree: true
2132
+ });
2133
+ });
2134
+ }
2135
+ else {
2136
+ // DOM already loaded
2137
+ document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
2138
+ observer.observe(document.body, {
2139
+ attributes: true,
2140
+ attributeFilter: ['controller'],
2141
+ childList: true,
2142
+ subtree: true
2143
+ });
2144
+ }
2145
+ // Store observer reference for cleanup if needed
2146
+ globalThis.sniceNativeControllerObserver = observer;
2147
+ }
2148
+
2149
+ /**
2150
+ * @context decorator for receiving router context updates
2151
+ */
2152
+ const CONTEXT_HANDLERS = getSymbol('context-handlers');
2153
+ /**
2154
+ * @context decorator for receiving router context updates
2155
+ *
2156
+ * @example
2157
+ * ```typescript
2158
+ * @element('my-layout')
2159
+ * class MyLayout extends HTMLElement {
2160
+ * @context
2161
+ * handleContext(ctx: Context) {
2162
+ * this.renderNav(ctx.placards, ctx.currentRoute);
2163
+ * }
2164
+ *
2165
+ * @context({ debounce: 300 })
2166
+ * handleContextDebounced(ctx: Context) {
2167
+ * // Called after 300ms of no updates
2168
+ * }
2169
+ * }
2170
+ * ```
2171
+ */
2172
+ function context$1(options = {}) {
2173
+ return function (originalMethod, context) {
2174
+ const methodName = context.name;
2175
+ const initKey = `__context_init_${methodName}`;
2176
+ context.addInitializer(function () {
2177
+ const constructor = this.constructor;
2178
+ if (constructor[initKey])
2179
+ return;
2180
+ constructor[initKey] = true;
2181
+ if (!constructor[CONTEXT_HANDLERS]) {
2182
+ constructor[CONTEXT_HANDLERS] = [];
2183
+ }
2184
+ constructor[CONTEXT_HANDLERS].push({
2185
+ methodName,
2186
+ method: originalMethod,
2187
+ options,
2188
+ });
2189
+ });
2190
+ return originalMethod;
2191
+ };
2192
+ }
2193
+ /**
2194
+ * Setup context handler for an element instance
2195
+ * Called automatically during element connection
2196
+ */
2197
+ function setupContextHandler(element) {
2198
+ const handlers = element.constructor[CONTEXT_HANDLERS];
2199
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2200
+ return;
2201
+ }
2202
+ // Get the Context instance from the router
2203
+ const ctx = element[CONTEXT_HANDLER];
2204
+ if (!ctx) {
2205
+ return;
2206
+ }
2207
+ // Store the Context instance for cleanup
2208
+ element[NAVIGATION_CONTEXT_INSTANCE] = ctx;
2209
+ // Register each handler with the Context
2210
+ for (const handler of handlers) {
2211
+ const { methodName, method, options } = handler;
2212
+ const wrappedMethodName = `__wrapped_${methodName}`;
2213
+ // Create wrapped method with timing controls
2214
+ element[wrappedMethodName] = function (context) {
2215
+ // Skip if already called once
2216
+ if (options.once && element[CONTEXT_CALLED]) {
2217
+ return;
2218
+ }
2219
+ const callMethod = () => {
2220
+ method.call(element, context);
2221
+ // Handle once option
2222
+ if (options.once) {
2223
+ element[CONTEXT_CALLED] = true;
2224
+ // Unregister after first call
2225
+ const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2226
+ if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2227
+ ctx[CONTEXT_UNREGISTER](element);
2228
+ }
2229
+ }
2230
+ };
2231
+ // Handle debounce
2232
+ if (options.debounce) {
2233
+ clearTimeout(element[CONTEXT_TIMER]);
2234
+ element[CONTEXT_TIMER] = setTimeout(callMethod, options.debounce);
2235
+ }
2236
+ // Handle throttle
2237
+ else if (options.throttle) {
2238
+ const now = Date.now();
2239
+ const lastCall = element[CONTEXT_TIMER] || 0;
2240
+ if (now - lastCall >= options.throttle) {
2241
+ element[CONTEXT_TIMER] = now;
2242
+ callMethod();
2243
+ }
2244
+ }
2245
+ // No timing options - call immediately
2246
+ else {
2247
+ callMethod();
2248
+ }
2249
+ };
2250
+ // Register with the Context using the wrapped method name
2251
+ if (typeof ctx[CONTEXT_REGISTER] === 'function') {
2252
+ ctx[CONTEXT_REGISTER](element, wrappedMethodName);
2253
+ }
2254
+ }
2255
+ }
2256
+ /**
2257
+ * Cleanup context handler for an element instance
2258
+ * Called automatically during element disconnection
2259
+ */
2260
+ function cleanupContextHandler(element) {
2261
+ const handlers = element.constructor[CONTEXT_HANDLERS];
2262
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2263
+ return;
2264
+ }
2265
+ // Clear any pending debounce timer
2266
+ for (const handler of handlers) {
2267
+ if (handler.options.debounce && element[CONTEXT_TIMER]) {
2268
+ clearTimeout(element[CONTEXT_TIMER]);
2269
+ delete element[CONTEXT_TIMER];
2270
+ }
2271
+ // Clean up wrapped method
2272
+ const wrappedMethodName = `__wrapped_${handler.methodName}`;
2273
+ delete element[wrappedMethodName];
1216
2274
  }
1217
- else {
1218
- // DOM already loaded
1219
- document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
1220
- observer.observe(document.body, {
1221
- attributes: true,
1222
- attributeFilter: ['controller'],
1223
- childList: true,
1224
- subtree: true
1225
- });
2275
+ // Unregister from Context if available
2276
+ const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2277
+ if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2278
+ ctx[CONTEXT_UNREGISTER](element);
1226
2279
  }
1227
- // Store observer reference for cleanup if needed
1228
- globalThis.sniceNativeControllerObserver = observer;
2280
+ delete element[NAVIGATION_CONTEXT_INSTANCE];
2281
+ delete element[CONTEXT_CALLED];
1229
2282
  }
1230
2283
 
1231
2284
  /**
@@ -1286,6 +2339,87 @@ var Snice = (function (exports) {
1286
2339
  }
1287
2340
  }
1288
2341
 
2342
+ var _a, _b, _c;
2343
+ // Symbol for storing the Set of elements
2344
+ const REGISTERED_ELEMENTS_SET = Symbol('registered-elements-set');
2345
+ // Counter for generating unique context IDs
2346
+ let contextIdCounter = 0;
2347
+ /**
2348
+ * Represents the bundled router state that can notify registered elements of changes
2349
+ */
2350
+ class Context {
2351
+ constructor(context = {}, placards = [], currentRoute = '', routeParams = {}) {
2352
+ this[_a] = new WeakMap();
2353
+ this[_b] = new Set();
2354
+ this[_c] = false;
2355
+ this.id = contextIdCounter++;
2356
+ this.application = context;
2357
+ this.navigation = {
2358
+ placards,
2359
+ route: currentRoute,
2360
+ params: routeParams
2361
+ };
2362
+ }
2363
+ /**
2364
+ * Register an element to receive context updates
2365
+ * @internal Used by @context decorator
2366
+ */
2367
+ [(_a = REGISTERED_ELEMENTS, _b = REGISTERED_ELEMENTS_SET, _c = IS_UPDATING, CONTEXT_REGISTER)](element, methodName) {
2368
+ this[REGISTERED_ELEMENTS].set(element, methodName);
2369
+ this[REGISTERED_ELEMENTS_SET].add(element);
2370
+ }
2371
+ /**
2372
+ * Unregister an element from receiving context updates
2373
+ * @internal Used by @context decorator cleanup
2374
+ */
2375
+ [CONTEXT_UNREGISTER](element) {
2376
+ this[REGISTERED_ELEMENTS].delete(element);
2377
+ this[REGISTERED_ELEMENTS_SET].delete(element);
2378
+ }
2379
+ /**
2380
+ * Update the context and notify all registered elements
2381
+ * Prevents infinite loops by tracking update state
2382
+ */
2383
+ update(context, placards, currentRoute, routeParams) {
2384
+ // Prevent infinite loops
2385
+ if (this[IS_UPDATING]) {
2386
+ return;
2387
+ }
2388
+ this[IS_UPDATING] = true;
2389
+ // Update properties (id remains immutable)
2390
+ this.application = context;
2391
+ this.navigation.placards = placards;
2392
+ this.navigation.route = currentRoute;
2393
+ this.navigation.params = routeParams;
2394
+ // Notify all registered elements by calling their methods directly
2395
+ const elementsSet = this[REGISTERED_ELEMENTS_SET];
2396
+ const elementsMap = this[REGISTERED_ELEMENTS];
2397
+ for (const element of elementsSet) {
2398
+ const methodName = elementsMap.get(element);
2399
+ if (methodName && typeof element[methodName] === 'function') {
2400
+ try {
2401
+ element[methodName](this);
2402
+ }
2403
+ catch (error) {
2404
+ // Log error but continue notifying other elements
2405
+ console.error(`Error calling @context method ${methodName}:`, error);
2406
+ }
2407
+ }
2408
+ }
2409
+ this[IS_UPDATING] = false;
2410
+ }
2411
+ /**
2412
+ * Notify a specific element of the current context state
2413
+ * @internal Used by @context decorator
2414
+ */
2415
+ [CONTEXT_NOTIFY_ELEMENT](element) {
2416
+ const methodName = this[REGISTERED_ELEMENTS].get(element);
2417
+ if (methodName && typeof element[methodName] === 'function') {
2418
+ element[methodName](this);
2419
+ }
2420
+ }
2421
+ }
2422
+
1289
2423
  /**
1290
2424
  * Detects the type constructor from an initial value
1291
2425
  * @param initialValue - The default value assigned to a property field (e.g., `name = "default"` -> "default")
@@ -1414,6 +2548,297 @@ var Snice = (function (exports) {
1414
2548
  }
1415
2549
  }
1416
2550
 
2551
+ /**
2552
+ * @render and @styles decorators for Snice v3.0.0
2553
+ * Provides automatic differential rendering on property changes
2554
+ */
2555
+ /**
2556
+ * Global render scheduler for microtask batching
2557
+ * Batches multiple property changes into a single render
2558
+ */
2559
+ class RenderScheduler {
2560
+ constructor() {
2561
+ this.pending = new Set();
2562
+ this.scheduled = false;
2563
+ }
2564
+ /**
2565
+ * Schedule an element for rendering
2566
+ * Batches renders in a microtask unless sync option is enabled
2567
+ */
2568
+ schedule(element, options) {
2569
+ // Sync rendering - execute immediately
2570
+ if (options.sync) {
2571
+ performRender(element, options);
2572
+ return;
2573
+ }
2574
+ // Async rendering - batch in microtask
2575
+ this.pending.add(element);
2576
+ if (!this.scheduled) {
2577
+ this.scheduled = true;
2578
+ queueMicrotask(() => this.flush());
2579
+ }
2580
+ }
2581
+ /**
2582
+ * Flush all pending renders
2583
+ */
2584
+ flush() {
2585
+ const elements = Array.from(this.pending);
2586
+ this.pending.clear();
2587
+ this.scheduled = false;
2588
+ for (const element of elements) {
2589
+ const options = element[RENDER_OPTIONS] || {};
2590
+ performRender(element, options);
2591
+ }
2592
+ }
2593
+ }
2594
+ const renderScheduler = new RenderScheduler();
2595
+ /**
2596
+ * Perform the actual render of an element
2597
+ */
2598
+ function performRender(element, options, precomputedResult) {
2599
+ const renderMethod = element[RENDER_METHOD];
2600
+ if (!renderMethod)
2601
+ return;
2602
+ // If once is true and we've already rendered, skip
2603
+ if (options.once && element[RENDER_INSTANCE]) {
2604
+ return;
2605
+ }
2606
+ try {
2607
+ // Use precomputed result if provided, otherwise call the render method
2608
+ const result = precomputedResult !== undefined ? precomputedResult : renderMethod.call(element);
2609
+ // Check if differential rendering is disabled (expects string)
2610
+ const differential = options.differential !== false;
2611
+ if (!differential) {
2612
+ // Non-differential expects string
2613
+ if (typeof result !== 'string') {
2614
+ console.warn('Render method with differential: false must return a string');
2615
+ return;
2616
+ }
2617
+ // Simple string rendering
2618
+ if (!element.shadowRoot) {
2619
+ element.attachShadow({ mode: 'open' });
2620
+ }
2621
+ element.shadowRoot.innerHTML = result;
2622
+ // Mark scheduled flag as false
2623
+ element[RENDER_SCHEDULED] = false;
2624
+ // Call all registered render callbacks
2625
+ const callbacks = element[RENDER_CALLBACKS];
2626
+ if (callbacks && callbacks.length > 0) {
2627
+ const cbs = [...callbacks];
2628
+ element[RENDER_CALLBACKS] = [];
2629
+ cbs.forEach(cb => cb());
2630
+ }
2631
+ return;
2632
+ }
2633
+ if (!isTemplateResult(result)) {
2634
+ console.warn('Render method must return html`` template result');
2635
+ return;
2636
+ }
2637
+ // Get or create template instance (differential rendering)
2638
+ let instance = element[RENDER_INSTANCE];
2639
+ if (!instance) {
2640
+ // First render - create shadow root if needed and initial instance
2641
+ if (!element.shadowRoot) {
2642
+ element.attachShadow({ mode: 'open' });
2643
+ }
2644
+ instance = new TemplateInstance(result);
2645
+ element[RENDER_INSTANCE] = instance;
2646
+ // Create the fragment but don't commit values yet
2647
+ const fragment = instance.renderFragment();
2648
+ // Append to shadow root first so getRootNode() works in event handlers
2649
+ element.shadowRoot.appendChild(fragment);
2650
+ // Now commit values (this binds event handlers with correct host)
2651
+ instance.update(result.values);
2652
+ }
2653
+ else {
2654
+ // Subsequent render - just update the parts (differential rendering!)
2655
+ instance.update(result.values);
2656
+ }
2657
+ // Mark scheduled flag as false
2658
+ element[RENDER_SCHEDULED] = false;
2659
+ // Call all registered render callbacks (for testing/debugging)
2660
+ const callbacks = element[RENDER_CALLBACKS];
2661
+ if (callbacks && callbacks.length > 0) {
2662
+ const cbs = [...callbacks];
2663
+ element[RENDER_CALLBACKS] = [];
2664
+ cbs.forEach(cb => cb());
2665
+ }
2666
+ }
2667
+ catch (error) {
2668
+ console.error('Error rendering element:', error);
2669
+ }
2670
+ }
2671
+ /**
2672
+ * Request a render for an element
2673
+ * Respects debounce/throttle/once/sync options
2674
+ * @param immediate - Force immediate render (used for initial render)
2675
+ */
2676
+ function requestRender(element, immediate = false) {
2677
+ const options = element[RENDER_OPTIONS] || {};
2678
+ // Handle once option
2679
+ if (options.once && element[RENDER_INSTANCE]) {
2680
+ return;
2681
+ }
2682
+ // Force immediate render (for initial render)
2683
+ if (immediate) {
2684
+ performRender(element, options);
2685
+ return;
2686
+ }
2687
+ // Handle debounce
2688
+ if (options.debounce !== undefined && options.debounce > 0) {
2689
+ if (!element[RENDER_TIMERS]) {
2690
+ element[RENDER_TIMERS] = {};
2691
+ }
2692
+ clearTimeout(element[RENDER_TIMERS].debounce);
2693
+ element[RENDER_TIMERS].debounce = setTimeout(() => {
2694
+ renderScheduler.schedule(element, options);
2695
+ }, options.debounce);
2696
+ return;
2697
+ }
2698
+ // Handle throttle
2699
+ if (options.throttle !== undefined && options.throttle > 0) {
2700
+ if (!element[RENDER_TIMERS]) {
2701
+ element[RENDER_TIMERS] = { lastThrottle: 0 };
2702
+ }
2703
+ const timers = element[RENDER_TIMERS];
2704
+ const now = Date.now();
2705
+ if (timers.lastThrottle === 0 || now - timers.lastThrottle >= options.throttle) {
2706
+ timers.lastThrottle = now;
2707
+ renderScheduler.schedule(element, options);
2708
+ }
2709
+ else {
2710
+ // Schedule for later if not already scheduled
2711
+ if (!timers.throttleTimer) {
2712
+ const remaining = options.throttle - (now - timers.lastThrottle);
2713
+ timers.throttleTimer = setTimeout(() => {
2714
+ timers.throttleTimer = null;
2715
+ timers.lastThrottle = Date.now();
2716
+ renderScheduler.schedule(element, options);
2717
+ }, remaining);
2718
+ }
2719
+ }
2720
+ return;
2721
+ }
2722
+ // Normal rendering (with microtask batching unless sync)
2723
+ renderScheduler.schedule(element, options);
2724
+ }
2725
+ /**
2726
+ * @render decorator for component rendering
2727
+ *
2728
+ * Marks a method as the render method for the component.
2729
+ * The method should return html`...` template.
2730
+ * Automatically re-renders when properties change (unless once: true).
2731
+ *
2732
+ * @example
2733
+ * ```typescript
2734
+ * @render()
2735
+ * renderContent() {
2736
+ * return html`<div>${this.count}</div>`;
2737
+ * }
2738
+ * ```
2739
+ *
2740
+ * @example
2741
+ * ```typescript
2742
+ * // Debounced rendering
2743
+ * @render({ debounce: 100 })
2744
+ * renderContent() {
2745
+ * return html`<div>${this.searchTerm}</div>`;
2746
+ * }
2747
+ * ```
2748
+ *
2749
+ * @example
2750
+ * ```typescript
2751
+ * // Render only once (manual re-renders only)
2752
+ * @render({ once: true })
2753
+ * renderContent() {
2754
+ * return html`<div>Static content</div>`;
2755
+ * }
2756
+ * ```
2757
+ */
2758
+ function render(options = {}) {
2759
+ return function (originalMethod, context) {
2760
+ context.name;
2761
+ context.addInitializer(function () {
2762
+ // Store the render method and options
2763
+ this[RENDER_METHOD] = originalMethod;
2764
+ this[RENDER_OPTIONS] = options;
2765
+ });
2766
+ // Return wrapped method that triggers re-render when called manually
2767
+ return function (...args) {
2768
+ // Call original method to get the template
2769
+ const result = originalMethod.apply(this, args);
2770
+ // Always render when method is called manually (even if once: true)
2771
+ // Force immediate render to bypass all options, pass precomputed result to avoid calling method twice
2772
+ performRender(this, {}, result);
2773
+ return result;
2774
+ };
2775
+ };
2776
+ }
2777
+ /**
2778
+ * @styles decorator for component styles
2779
+ *
2780
+ * Marks a method as the styles method for the component.
2781
+ * The method should return css`...` template.
2782
+ * Styles are applied once when the component is connected.
2783
+ *
2784
+ * @example
2785
+ * ```typescript
2786
+ * @styles()
2787
+ * styles() {
2788
+ * return css`:host { display: block; }`;
2789
+ * }
2790
+ * ```
2791
+ */
2792
+ function styles() {
2793
+ return function (originalMethod, context) {
2794
+ context.addInitializer(function () {
2795
+ // Store the styles method
2796
+ this[STYLES_METHOD] = originalMethod;
2797
+ });
2798
+ return originalMethod;
2799
+ };
2800
+ }
2801
+ /**
2802
+ * Apply styles to an element
2803
+ * Called during element initialization
2804
+ */
2805
+ function applyStyles(element) {
2806
+ const stylesMethod = element[STYLES_METHOD];
2807
+ if (!stylesMethod)
2808
+ return;
2809
+ // Only apply once
2810
+ if (element[STYLES_APPLIED])
2811
+ return;
2812
+ element[STYLES_APPLIED] = true;
2813
+ try {
2814
+ const result = stylesMethod.call(element);
2815
+ if (!isCSSResult(result)) {
2816
+ console.warn('Styles method must return css`` template result');
2817
+ return;
2818
+ }
2819
+ // Ensure shadow root exists
2820
+ if (!element.shadowRoot) {
2821
+ element.attachShadow({ mode: 'open' });
2822
+ }
2823
+ // Create base styles for meta elements (if, case)
2824
+ const baseStyleSheet = new CSSStyleSheet();
2825
+ baseStyleSheet.replaceSync('if, case { display: contents; }');
2826
+ // Try to use constructable stylesheets for better performance
2827
+ if (element.shadowRoot && result.styleSheet && 'adoptedStyleSheets' in element.shadowRoot) {
2828
+ element.shadowRoot.adoptedStyleSheets = [baseStyleSheet, result.styleSheet];
2829
+ }
2830
+ else if (element.shadowRoot) {
2831
+ // Fallback to <style> tag
2832
+ const style = document.createElement('style');
2833
+ style.textContent = 'if, case { display: contents; }\n' + result.cssText;
2834
+ element.shadowRoot.appendChild(style);
2835
+ }
2836
+ }
2837
+ catch (error) {
2838
+ console.error('Error applying styles:', error);
2839
+ }
2840
+ }
2841
+
1417
2842
  /**
1418
2843
  * Applies core element functionality to a constructor
1419
2844
  * This is shared between @element and @page decorators
@@ -1458,6 +2883,9 @@ var Snice = (function (exports) {
1458
2883
  enumerable: true,
1459
2884
  configurable: true
1460
2885
  });
2886
+ // Note: rendered promise is stored via symbols RENDERED_PROMISE and RENDERED_RESOLVE
2887
+ // It's not exposed as a public property - only accessible via test utilities
2888
+ // This prevents accidental misuse in production code
1461
2889
  // Add controller property
1462
2890
  Object.defineProperty(constructor.prototype, 'controller', {
1463
2891
  get() {
@@ -1495,6 +2923,7 @@ var Snice = (function (exports) {
1495
2923
  // Re-establish handlers that get cleaned up on disconnect
1496
2924
  setupEventHandlers(this, this);
1497
2925
  setupResponseHandlers(this, this);
2926
+ setupContextHandler(this);
1498
2927
  // Re-establish observers that get cleaned up on disconnect
1499
2928
  try {
1500
2929
  setupObservers(this, this);
@@ -1509,6 +2938,9 @@ var Snice = (function (exports) {
1509
2938
  return;
1510
2939
  }
1511
2940
  try {
2941
+ // Mark that properties are being initialized from attributes
2942
+ // This allows property setters to work during initialization
2943
+ this[PROPERTIES_INITIALIZED] = true;
1512
2944
  // Initialize properties from attributes before rendering
1513
2945
  const properties = constructor[PROPERTIES];
1514
2946
  if (properties) {
@@ -1527,82 +2959,69 @@ var Snice = (function (exports) {
1527
2959
  }
1528
2960
  }
1529
2961
  }
1530
- // Mark that properties have been initialized
1531
- this[PROPERTIES_INITIALIZED] = true;
2962
+ // Apply any properties that were set before element was connected
2963
+ // BUT only if they don't have an HTML attribute (don't override HTML attributes)
2964
+ if (this[PRE_INIT_PROPERTY_VALUES]) {
2965
+ for (const [propName, propValue] of this[PRE_INIT_PROPERTY_VALUES]) {
2966
+ // Remove from map first so getter doesn't return it during setter call
2967
+ this[PRE_INIT_PROPERTY_VALUES].delete(propName);
2968
+ // Only apply pre-init value if NO HTML attribute exists
2969
+ // This prevents field initializers from overwriting HTML attributes
2970
+ const propOptions = properties?.get(propName);
2971
+ const attributeName = typeof propOptions?.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
2972
+ if (!this.hasAttribute(attributeName)) {
2973
+ this[propName] = propValue;
2974
+ }
2975
+ }
2976
+ // Clear the pre-init values map
2977
+ delete this[PRE_INIT_PROPERTY_VALUES];
2978
+ }
1532
2979
  // Properties are now stateless and read from DOM attributes only
1533
2980
  // Initial values are not automatically reflected
1534
- // Create shadow root if it doesn't exist
1535
- if (!this.shadowRoot) {
1536
- this.attachShadow({ mode: 'open' });
1537
- }
1538
- // Build the shadow DOM content
1539
- let shadowContent = '';
1540
- // Add HTML first (maintaining original order)
1541
- if (this.html) {
1542
- try {
1543
- const htmlResult = this.html();
1544
- // Handle both async and sync html
1545
- const htmlContent = htmlResult instanceof Promise ? await htmlResult : htmlResult;
1546
- if (htmlContent !== undefined) {
1547
- shadowContent += htmlContent;
2981
+ // v3.0.0: Apply @styles decorator if present
2982
+ // This creates the shadow root and applies styles
2983
+ applyStyles(this);
2984
+ // v3.0.0: Perform initial @render if present
2985
+ // This uses differential rendering with template system
2986
+ // Defer initial render to next microtask to allow property bindings
2987
+ // from parent to be set first (avoids infinite loops in nested elements)
2988
+ if (this[RENDER_METHOD]) {
2989
+ queueMicrotask(() => {
2990
+ requestRender(this, true);
2991
+ // Setup observers after first render completes so shadow DOM content exists
2992
+ try {
2993
+ setupObservers(this, this);
1548
2994
  }
1549
- }
1550
- catch (error) {
1551
- console.error(`Error in html() method for ${this.tagName}:`, error);
1552
- }
2995
+ catch (error) {
2996
+ console.error(`Error setting up observers for ${this.tagName}:`, error);
2997
+ }
2998
+ // Mark element as ready after initial render completes
2999
+ if (this[READY_RESOLVE]) {
3000
+ this[READY_RESOLVE]();
3001
+ this[READY_RESOLVE] = null;
3002
+ }
3003
+ });
1553
3004
  }
1554
- // Add CSS after HTML (maintaining original order)
1555
- if (this.css) {
3005
+ else {
3006
+ // No render method, setup observers immediately
1556
3007
  try {
1557
- const cssResult = this.css();
1558
- // Handle both async and sync css
1559
- const cssResolved = cssResult instanceof Promise ? await cssResult : cssResult;
1560
- if (cssResolved) {
1561
- // Handle both string and array of strings
1562
- const cssContent = Array.isArray(cssResolved) ? cssResolved.join('\n') : cssResolved;
1563
- // No need for scoping with Shadow DOM, but add data attribute for compatibility
1564
- shadowContent += `<style data-component-css>${cssContent}</style>`;
1565
- }
3008
+ setupObservers(this, this);
1566
3009
  }
1567
3010
  catch (error) {
1568
- console.error(`Error in css() method for ${this.tagName}:`, error);
3011
+ console.error(`Error setting up observers for ${this.tagName}:`, error);
1569
3012
  }
1570
- }
1571
- // Set shadow DOM content
1572
- if (shadowContent) {
1573
- this.shadowRoot.innerHTML = shadowContent;
1574
- }
1575
- // Render all @part methods into their corresponding elements
1576
- const parts = constructor[PARTS];
1577
- if (parts && this.shadowRoot) {
1578
- for (const [partName, partHandler] of parts) {
1579
- try {
1580
- const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
1581
- if (partElement) {
1582
- // For initial render, call original method directly to avoid timing restrictions
1583
- const partResult = partHandler.method.call(this);
1584
- const partContent = partResult instanceof Promise ? await partResult : partResult;
1585
- if (partContent !== undefined) {
1586
- partElement.innerHTML = partContent;
1587
- }
1588
- }
1589
- }
1590
- catch (error) {
1591
- console.error(`Error rendering @part('${partName}') in ${this.tagName}:`, error);
1592
- }
3013
+ // Mark element as ready immediately if no render method
3014
+ if (this[READY_RESOLVE]) {
3015
+ this[READY_RESOLVE]();
3016
+ this[READY_RESOLVE] = null;
1593
3017
  }
1594
3018
  }
1595
- // Setup @on event handlers - use element for host events, shadow root for delegated events
3019
+ // Setup @on event handlers (v2.5.4 compatibility restored!)
1596
3020
  setupEventHandlers(this, this);
1597
3021
  // Setup @respond handlers for elements
1598
3022
  setupResponseHandlers(this, this);
1599
- // Setup @observe observers
1600
- try {
1601
- setupObservers(this, this);
1602
- }
1603
- catch (error) {
1604
- console.error(`Error setting up observers for ${this.tagName}:`, error);
1605
- }
3023
+ // Setup @context handler for elements
3024
+ setupContextHandler(this);
1606
3025
  // Mark as initialized
1607
3026
  this[INITIALIZED] = true;
1608
3027
  // NOW call the original user-defined connectedCallback after shadow DOM is set up
@@ -1611,11 +3030,8 @@ var Snice = (function (exports) {
1611
3030
  }
1612
3031
  }
1613
3032
  finally {
1614
- // Always mark element as ready, even if there were errors
1615
- if (this[READY_RESOLVE]) {
1616
- this[READY_RESOLVE]();
1617
- this[READY_RESOLVE] = null; // Clear the resolver
1618
- }
3033
+ // Ready is now resolved inside the render microtask (or immediately if no render)
3034
+ // This ensures ready waits for initial render to complete
1619
3035
  }
1620
3036
  // Call @ready handlers after everything is set up and ready promise is resolved
1621
3037
  const readyHandlers = constructor[READY_HANDLERS];
@@ -1652,10 +3068,12 @@ var Snice = (function (exports) {
1652
3068
  console.error(`Failed to detach controller:`, error);
1653
3069
  });
1654
3070
  }
1655
- // Cleanup @on event handlers
3071
+ // Cleanup @on event handlers (v2.5.4 compatibility restored!)
1656
3072
  cleanupEventHandlers(this);
1657
3073
  // Cleanup @respond handlers
1658
3074
  cleanupResponseHandlers(this);
3075
+ // Cleanup @context handler
3076
+ cleanupContextHandler(this);
1659
3077
  // Cleanup @observe observers
1660
3078
  cleanupObservers(this);
1661
3079
  };
@@ -1718,6 +3136,10 @@ var Snice = (function (exports) {
1718
3136
  }
1719
3137
  }
1720
3138
  }
3139
+ // Trigger auto-render on attribute change (same as property setter)
3140
+ if (this[RENDER_METHOD] && this[INITIALIZED]) {
3141
+ requestRender(this);
3142
+ }
1721
3143
  }
1722
3144
  }
1723
3145
  break;
@@ -1757,7 +3179,7 @@ var Snice = (function (exports) {
1757
3179
  }
1758
3180
  };
1759
3181
  }
1760
- function element(tagName) {
3182
+ function element(tagName, options) {
1761
3183
  return function (constructor, context) {
1762
3184
  // Transfer metadata from context to constructor
1763
3185
  if (context.metadata && context.metadata[PROPERTIES]) {
@@ -1768,6 +3190,11 @@ var Snice = (function (exports) {
1768
3190
  constructor[PROPERTIES].set(key, value);
1769
3191
  }
1770
3192
  }
3193
+ // Set up form association if requested via options
3194
+ // MUST be done BEFORE applyElementFunctionality and customElements.define
3195
+ if (options?.formAssociated === true) {
3196
+ constructor.formAssociated = true;
3197
+ }
1771
3198
  applyElementFunctionality(constructor);
1772
3199
  customElements.define(tagName, constructor);
1773
3200
  return constructor;
@@ -1823,29 +3250,43 @@ var Snice = (function (exports) {
1823
3250
  if (attrValue !== null) {
1824
3251
  return parseAttributeValue(attrValue, finalOptions || {}, undefined, initialValue);
1825
3252
  }
1826
- // For Boolean properties that have been explicitly set via attribute,
3253
+ // For Boolean properties that have been explicitly set via attribute (and then removed),
1827
3254
  // follow HTML boolean attribute semantics (absence = false)
1828
3255
  const inferredType = finalOptions?.type || detectType(initialValue);
1829
3256
  if (inferredType === Boolean && this[EXPLICITLY_SET_PROPERTIES]?.has(propertyKey)) {
1830
3257
  return false;
1831
3258
  }
1832
- // Otherwise return initial value
3259
+ // Check for pre-init property values (set before element was connected)
3260
+ if (this[PRE_INIT_PROPERTY_VALUES]?.has(propertyKey)) {
3261
+ return this[PRE_INIT_PROPERTY_VALUES].get(propertyKey);
3262
+ }
3263
+ // Otherwise return initial value (respects default values like showRememberMe = true)
1833
3264
  return initialValue;
1834
3265
  },
1835
3266
  set(newValue) {
1836
- // Get old value from stored state for change detection
1837
- if (!this[PROPERTY_VALUES]) {
1838
- this[PROPERTY_VALUES] = {};
1839
- }
1840
- const oldValue = this[PROPERTY_VALUES][propertyKey];
3267
+ // Get old value by calling the getter (which reads from attribute)
3268
+ const oldValue = this[propertyKey];
1841
3269
  // Check if value actually changed
1842
3270
  if (oldValue === newValue)
1843
3271
  return;
1844
- // Update stored value
1845
- this[PROPERTY_VALUES][propertyKey] = newValue;
1846
- // Always reflect to DOM - properties are always backed by attributes
3272
+ // Don't reflect to DOM until connectedCallback has started
3273
+ // This prevents field initializers from overwriting HTML attributes
3274
+ if (!this[PROPERTIES_INITIALIZED]) {
3275
+ // Store value for later application when element is connected
3276
+ if (!this[PRE_INIT_PROPERTY_VALUES]) {
3277
+ this[PRE_INIT_PROPERTY_VALUES] = new Map();
3278
+ }
3279
+ this[PRE_INIT_PROPERTY_VALUES].set(propertyKey, newValue);
3280
+ return;
3281
+ }
3282
+ // Reflect to DOM - properties are backed by attributes
1847
3283
  const attributeName = typeof finalOptions.attribute === 'string' ? finalOptions.attribute : propertyKey.toLowerCase();
1848
3284
  const attributeValue = valueToAttribute(newValue, finalOptions, initialValue);
3285
+ // Mark as explicitly set for boolean handling
3286
+ if (!this[EXPLICITLY_SET_PROPERTIES]) {
3287
+ this[EXPLICITLY_SET_PROPERTIES] = new Set();
3288
+ }
3289
+ this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1849
3290
  // Flag to prevent attributeChangedCallback from triggering watchers for this change
1850
3291
  if (!this._settingFromProperty)
1851
3292
  this._settingFromProperty = new Set();
@@ -1887,8 +3328,12 @@ var Snice = (function (exports) {
1887
3328
  }
1888
3329
  }
1889
3330
  }
1890
- if (this.requestUpdate) {
1891
- this.requestUpdate(propertyKey, oldValue);
3331
+ // v3.0.0: Trigger auto-render on property change
3332
+ // This respects @render options (debounce, throttle, once, sync)
3333
+ // Only trigger renders after element is fully initialized to avoid
3334
+ // infinite loops during initial setup
3335
+ if (this[RENDER_METHOD] && this[INITIALIZED]) {
3336
+ requestRender(this);
1892
3337
  }
1893
3338
  },
1894
3339
  configurable: true,
@@ -1983,12 +3428,15 @@ var Snice = (function (exports) {
1983
3428
  function watch(...propertyNames) {
1984
3429
  return function (target, context) {
1985
3430
  const methodName = context.name;
3431
+ const initKey = `__watch_init_${methodName}`;
1986
3432
  context.addInitializer(function () {
1987
3433
  const constructor = this.constructor;
3434
+ if (constructor[initKey])
3435
+ return;
3436
+ constructor[initKey] = true;
1988
3437
  if (!constructor[PROPERTY_WATCHERS]) {
1989
3438
  constructor[PROPERTY_WATCHERS] = new Map();
1990
3439
  }
1991
- // Store the watcher method for each property
1992
3440
  for (const propertyName of propertyNames) {
1993
3441
  if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
1994
3442
  constructor[PROPERTY_WATCHERS].set(propertyName, []);
@@ -2067,8 +3515,12 @@ var Snice = (function (exports) {
2067
3515
  function ready() {
2068
3516
  return function (target, context) {
2069
3517
  const methodName = context.name;
3518
+ const initKey = `__ready_init_${methodName}`;
2070
3519
  context.addInitializer(function () {
2071
3520
  const constructor = this.constructor;
3521
+ if (constructor[initKey])
3522
+ return;
3523
+ constructor[initKey] = true;
2072
3524
  if (!constructor[READY_HANDLERS]) {
2073
3525
  constructor[READY_HANDLERS] = [];
2074
3526
  }
@@ -2086,8 +3538,12 @@ var Snice = (function (exports) {
2086
3538
  function dispose() {
2087
3539
  return function (target, context) {
2088
3540
  const methodName = context.name;
3541
+ const initKey = `__dispose_init_${methodName}`;
2089
3542
  context.addInitializer(function () {
2090
3543
  const constructor = this.constructor;
3544
+ if (constructor[initKey])
3545
+ return;
3546
+ constructor[initKey] = true;
2091
3547
  if (!constructor[DISPOSE_HANDLERS]) {
2092
3548
  constructor[DISPOSE_HANDLERS] = [];
2093
3549
  }
@@ -2105,8 +3561,12 @@ var Snice = (function (exports) {
2105
3561
  function moved(options = {}) {
2106
3562
  return function (originalMethod, context) {
2107
3563
  const methodName = context.name;
3564
+ const initKey = `__moved_init_${methodName}`;
2108
3565
  context.addInitializer(function () {
2109
3566
  const constructor = this.constructor;
3567
+ if (constructor[initKey])
3568
+ return;
3569
+ constructor[initKey] = true;
2110
3570
  if (!constructor[MOVED_HANDLERS]) {
2111
3571
  constructor[MOVED_HANDLERS] = [];
2112
3572
  }
@@ -2191,102 +3651,20 @@ var Snice = (function (exports) {
2191
3651
  return function (...args) {
2192
3652
  // Initialize timers storage if not present
2193
3653
  if (!this[ADOPTED_TIMERS]) {
2194
- this[ADOPTED_TIMERS] = new Map();
2195
- }
2196
- // Get or create timers for this specific method
2197
- if (!this[ADOPTED_TIMERS].has(methodName)) {
2198
- this[ADOPTED_TIMERS].set(methodName, {
2199
- throttleTimer: null,
2200
- debounceTimer: null,
2201
- lastThrottleCall: 0
2202
- });
2203
- }
2204
- const timers = this[ADOPTED_TIMERS].get(methodName);
2205
- // Helper function to execute method
2206
- const executeMethod = (...methodArgs) => {
2207
- return originalMethod.apply(this, methodArgs);
2208
- };
2209
- const hasDebounce = options.debounce !== undefined && options.debounce > 0;
2210
- const hasThrottle = options.throttle !== undefined && options.throttle > 0;
2211
- // Handle timing based on priority: debounce > throttle > immediate
2212
- switch (true) {
2213
- case hasDebounce: {
2214
- clearTimeout(timers.debounceTimer);
2215
- timers.debounceTimer = setTimeout(() => executeMethod(...args), options.debounce);
2216
- return undefined;
2217
- }
2218
- case hasThrottle: {
2219
- const throttleMs = options.throttle;
2220
- const now = Date.now();
2221
- const canExecuteImmediately = timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= throttleMs;
2222
- if (canExecuteImmediately) {
2223
- timers.lastThrottleCall = now;
2224
- return executeMethod(...args);
2225
- }
2226
- const hasScheduledTimer = !!timers.throttleTimer;
2227
- if (!hasScheduledTimer) {
2228
- const remainingTime = throttleMs - (now - timers.lastThrottleCall);
2229
- timers.throttleTimer = setTimeout(() => {
2230
- timers.throttleTimer = null;
2231
- timers.lastThrottleCall = Date.now();
2232
- executeMethod(...args);
2233
- }, remainingTime);
2234
- }
2235
- return undefined;
2236
- }
2237
- default:
2238
- return executeMethod(...args);
2239
- }
2240
- };
2241
- };
2242
- }
2243
- /**
2244
- * Decorator for methods that render specific parts of the template
2245
- * Parts are identified by the 'part' attribute in the HTML template
2246
- * When the decorated method is called, it automatically re-renders its part
2247
- */
2248
- function part(partName, options = {}) {
2249
- return function (originalMethod, context) {
2250
- const methodName = context.name;
2251
- context.addInitializer(function () {
2252
- const constructor = this.constructor;
2253
- if (!constructor[PARTS]) {
2254
- constructor[PARTS] = new Map();
2255
- }
2256
- constructor[PARTS].set(partName, {
2257
- methodName,
2258
- method: originalMethod
2259
- });
2260
- });
2261
- // Return wrapped method that automatically re-renders the part when called
2262
- return function (...args) {
2263
- // Initialize timers storage if not present
2264
- if (!this[PART_TIMERS]) {
2265
- this[PART_TIMERS] = new Map();
3654
+ this[ADOPTED_TIMERS] = new Map();
2266
3655
  }
2267
- // Get or create timers for this specific part
2268
- if (!this[PART_TIMERS].has(partName)) {
2269
- this[PART_TIMERS].set(partName, {
3656
+ // Get or create timers for this specific method
3657
+ if (!this[ADOPTED_TIMERS].has(methodName)) {
3658
+ this[ADOPTED_TIMERS].set(methodName, {
2270
3659
  throttleTimer: null,
2271
3660
  debounceTimer: null,
2272
3661
  lastThrottleCall: 0
2273
3662
  });
2274
3663
  }
2275
- const timers = this[PART_TIMERS].get(partName);
2276
- // Helper function to execute method and update DOM
2277
- const executeAndUpdate = (...methodArgs) => {
2278
- const result = originalMethod.apply(this, methodArgs);
2279
- const updateDOM = (content) => {
2280
- const hasContent = content !== undefined;
2281
- const hasElement = this.shadowRoot?.querySelector(`[part="${partName}"]`);
2282
- if (hasContent && hasElement) {
2283
- hasElement.innerHTML = content;
2284
- }
2285
- };
2286
- const isPromise = result instanceof Promise;
2287
- return isPromise
2288
- ? result.then(content => { updateDOM(content); return content; })
2289
- : (updateDOM(result), result);
3664
+ const timers = this[ADOPTED_TIMERS].get(methodName);
3665
+ // Helper function to execute method
3666
+ const executeMethod = (...methodArgs) => {
3667
+ return originalMethod.apply(this, methodArgs);
2290
3668
  };
2291
3669
  const hasDebounce = options.debounce !== undefined && options.debounce > 0;
2292
3670
  const hasThrottle = options.throttle !== undefined && options.throttle > 0;
@@ -2294,7 +3672,7 @@ var Snice = (function (exports) {
2294
3672
  switch (true) {
2295
3673
  case hasDebounce: {
2296
3674
  clearTimeout(timers.debounceTimer);
2297
- timers.debounceTimer = setTimeout(() => executeAndUpdate(...args), options.debounce);
3675
+ timers.debounceTimer = setTimeout(() => executeMethod(...args), options.debounce);
2298
3676
  return undefined;
2299
3677
  }
2300
3678
  case hasThrottle: {
@@ -2303,7 +3681,7 @@ var Snice = (function (exports) {
2303
3681
  const canExecuteImmediately = timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= throttleMs;
2304
3682
  if (canExecuteImmediately) {
2305
3683
  timers.lastThrottleCall = now;
2306
- return executeAndUpdate(...args);
3684
+ return executeMethod(...args);
2307
3685
  }
2308
3686
  const hasScheduledTimer = !!timers.throttleTimer;
2309
3687
  if (!hasScheduledTimer) {
@@ -2311,17 +3689,19 @@ var Snice = (function (exports) {
2311
3689
  timers.throttleTimer = setTimeout(() => {
2312
3690
  timers.throttleTimer = null;
2313
3691
  timers.lastThrottleCall = Date.now();
2314
- executeAndUpdate(...args);
3692
+ executeMethod(...args);
2315
3693
  }, remainingTime);
2316
3694
  }
2317
3695
  return undefined;
2318
3696
  }
2319
3697
  default:
2320
- return executeAndUpdate(...args);
3698
+ return executeMethod(...args);
2321
3699
  }
2322
3700
  };
2323
3701
  };
2324
3702
  }
3703
+ // @part decorator removed in v3.0.0
3704
+ // Use @render with differential rendering instead
2325
3705
 
2326
3706
  /*!
2327
3707
  * pica-route v1.1.2
@@ -3072,6 +4452,8 @@ var Snice = (function (exports) {
3072
4452
  let currentLayoutName = null; // Track current layout name
3073
4453
  let currentLayoutTimestamp = null; // Track current layout timestamp
3074
4454
  const context = options.context || {}; // Store context for guards
4455
+ // Create Context instance for managing router state
4456
+ const navigationContext = new Context(context, [], '', {});
3075
4457
  function getCurrentLayoutElement(target) {
3076
4458
  const noCurrentLayout = !currentLayoutName || !currentLayoutTimestamp;
3077
4459
  if (noCurrentLayout) {
@@ -3109,33 +4491,19 @@ var Snice = (function (exports) {
3109
4491
  // Extend the connectedCallback to add router-specific functionality
3110
4492
  const elementConnectedCallback = constructor.prototype.connectedCallback;
3111
4493
  constructor.prototype.connectedCallback = function () {
4494
+ // Store the Context instance for @context decorated methods to access
4495
+ this[CONTEXT_HANDLER] = navigationContext;
3112
4496
  // Call the element's connectedCallback first
3113
4497
  elementConnectedCallback?.call(this);
3114
- // Setup context request handler for nested elements
3115
- const contextRequestHandler = (event) => {
3116
- // Only respond if this element has context
3117
- if (this[ROUTER_CONTEXT] !== undefined) {
3118
- event.detail.context = this[ROUTER_CONTEXT];
3119
- event.stopPropagation(); // Stop bubbling once context is provided
3120
- }
3121
- };
3122
- this.addEventListener('@context/request', contextRequestHandler);
3123
- // Store handler for cleanup
3124
- this[CONTEXT_REQUEST_HANDLER] = contextRequestHandler;
3125
4498
  };
3126
4499
  // Extend the disconnectedCallback to clean up router-specific stuff
3127
4500
  const elementDisconnectedCallback = constructor.prototype.disconnectedCallback;
3128
4501
  constructor.prototype.disconnectedCallback = function () {
3129
4502
  // Call element's disconnectedCallback first
3130
4503
  elementDisconnectedCallback?.call(this);
3131
- // Clean up context request handler
3132
- const handler = this[CONTEXT_REQUEST_HANDLER];
3133
- if (handler) {
3134
- this.removeEventListener('@context/request', handler);
3135
- delete this[CONTEXT_REQUEST_HANDLER];
3136
- }
3137
- // Clean up context reference
4504
+ // Clean up context references
3138
4505
  delete this[ROUTER_CONTEXT];
4506
+ delete this[CONTEXT_HANDLER];
3139
4507
  };
3140
4508
  // Define the custom element
3141
4509
  customElements.define(pageOptions.tag, constructor);
@@ -3194,8 +4562,8 @@ var Snice = (function (exports) {
3194
4562
  * initialize();
3195
4563
  */
3196
4564
  function initialize() {
3197
- const targetExists = !!document.querySelector(options.target);
3198
- if (!targetExists) {
4565
+ const target = document.querySelector(options.target);
4566
+ if (!target) {
3199
4567
  throw new Error(`Target element not found: ${options.target}`);
3200
4568
  }
3201
4569
  const needsSorting = !is_sorted;
@@ -3219,6 +4587,10 @@ var Snice = (function (exports) {
3219
4587
  : placard;
3220
4588
  });
3221
4589
  }
4590
+ function emitContextUpdate(target, currentPath, routeParams) {
4591
+ // Update the navigation context and notify all registered elements
4592
+ navigationContext.update(context, placards, currentPath, routeParams);
4593
+ }
3222
4594
  function updateLayout(layoutElement, currentPath, routeParams) {
3223
4595
  // Check if layout implements the update method
3224
4596
  if (typeof layoutElement.update === 'function') {
@@ -3301,6 +4673,7 @@ var Snice = (function (exports) {
3301
4673
  }
3302
4674
  const newPageElement = document.createElement(route.tag);
3303
4675
  newPageElement[ROUTER_CONTEXT] = context;
4676
+ newPageElement[CONTEXT_HANDLER] = navigationContext;
3304
4677
  const routeParams = params;
3305
4678
  Object.keys(routeParams).forEach(key => newPageElement.setAttribute(key, routeParams[key]));
3306
4679
  return { result: RouteResult.SUCCESS, element: newPageElement, transition: route.transition, layout: route.layout, routeParams };
@@ -3391,7 +4764,7 @@ var Snice = (function (exports) {
3391
4764
  // Collect fresh placards before navigation
3392
4765
  collectPlacards();
3393
4766
  window.scrollTo(0, 0);
3394
- const isHomePath = (path.trim() === '' || path === '/') && !!home;
4767
+ const isHomePath = (path?.trim() === '' || path === '/') && !!home;
3395
4768
  if (isHomePath) {
3396
4769
  const homeRoute = routes.find(r => r.route.match('/'));
3397
4770
  const guardsAllowed = checkGuards(homeRoute?.guards, {}, target);
@@ -3405,11 +4778,15 @@ var Snice = (function (exports) {
3405
4778
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3406
4779
  if (hasLayout) {
3407
4780
  await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, {});
4781
+ emitContextUpdate(target, path, {});
3408
4782
  return;
3409
4783
  }
3410
4784
  await renderDirect(target, element, finalTransition);
4785
+ emitContextUpdate(target, path, {});
3411
4786
  return;
3412
4787
  }
4788
+ if (!path)
4789
+ return;
3413
4790
  const routeResult = resolveRoute(path, target);
3414
4791
  const isGuardsFailed = routeResult.result === RouteResult.GUARDS_FAILED;
3415
4792
  if (isGuardsFailed) {
@@ -3424,9 +4801,11 @@ var Snice = (function (exports) {
3424
4801
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3425
4802
  if (hasLayout) {
3426
4803
  await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, routeParams);
4804
+ emitContextUpdate(target, path, routeParams);
3427
4805
  return;
3428
4806
  }
3429
4807
  await renderDirect(target, element, finalTransition);
4808
+ emitContextUpdate(target, path, routeParams);
3430
4809
  return;
3431
4810
  }
3432
4811
  const { element, transition, layout } = create404Element();
@@ -3436,9 +4815,11 @@ var Snice = (function (exports) {
3436
4815
  const hasLayout = layoutElement !== null || getCurrentLayoutElement(target) !== null;
3437
4816
  if (hasLayout) {
3438
4817
  await renderWithLayout(target, element, finalTransition, layoutElement, needsNewLayout, path, {});
4818
+ emitContextUpdate(target, path, {});
3439
4819
  return;
3440
4820
  }
3441
4821
  await renderDirect(target, element, finalTransition);
4822
+ emitContextUpdate(target, path, {});
3442
4823
  }
3443
4824
  async function performTransition$1(container, oldElement, newElement, transition) {
3444
4825
  return performTransition(container, oldElement, newElement, transition);
@@ -3451,29 +4832,599 @@ var Snice = (function (exports) {
3451
4832
  };
3452
4833
  }
3453
4834
 
4835
+ // @on decorator removed in v3.0.0 - use template event syntax instead: @click=${handler}
4836
+ /**
4837
+ * Decorator that automatically dispatches a custom event after a method is called.
4838
+ * The return value of the method becomes the event detail.
4839
+ *
4840
+ * @param eventName The name of the event to dispatch
4841
+ * @param options Optional configuration extending EventInit
4842
+ */
4843
+ function dispatch(eventName, options) {
4844
+ return function (originalMethod, _context) {
4845
+ return function (...args) {
4846
+ // Create timing wrappers for dispatch (per-instance)
4847
+ if (!this[DISPATCH_TIMERS]) {
4848
+ this[DISPATCH_TIMERS] = new Map();
4849
+ }
4850
+ const timerKey = `${eventName}_${_context.name}`;
4851
+ if (!this[DISPATCH_TIMERS].has(timerKey)) {
4852
+ this[DISPATCH_TIMERS].set(timerKey, {
4853
+ debounceTimeout: null,
4854
+ throttleLastCall: 0,
4855
+ throttleTimeout: null
4856
+ });
4857
+ }
4858
+ const timers = this[DISPATCH_TIMERS].get(timerKey);
4859
+ // Call the original method with preserved this context
4860
+ const result = originalMethod.apply(this, args);
4861
+ // Helper to dispatch the event
4862
+ const doDispatch = (detail) => {
4863
+ // Skip dispatch if result is undefined and dispatchOnUndefined is false
4864
+ if (detail === undefined && options?.dispatchOnUndefined === false) {
4865
+ return;
4866
+ }
4867
+ // Create event with spread operator for options
4868
+ const event = new CustomEvent(eventName, {
4869
+ bubbles: true, // Default to true for component events
4870
+ composed: true, // Allow crossing shadow DOM boundaries
4871
+ ...options, // Spread all EventInit options
4872
+ detail
4873
+ });
4874
+ this.dispatchEvent(event);
4875
+ };
4876
+ // Helper to handle timed dispatch
4877
+ const timedDispatch = (detail) => {
4878
+ if (options?.debounce) {
4879
+ clearTimeout(timers.debounceTimeout);
4880
+ timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
4881
+ }
4882
+ else if (options?.throttle) {
4883
+ const now = Date.now();
4884
+ const remaining = options.throttle - (now - timers.throttleLastCall);
4885
+ if (remaining <= 0) {
4886
+ clearTimeout(timers.throttleTimeout);
4887
+ timers.throttleLastCall = now;
4888
+ doDispatch(detail);
4889
+ }
4890
+ else if (!timers.throttleTimeout) {
4891
+ timers.throttleTimeout = setTimeout(() => {
4892
+ timers.throttleLastCall = Date.now();
4893
+ timers.throttleTimeout = null;
4894
+ doDispatch(detail);
4895
+ }, remaining);
4896
+ }
4897
+ }
4898
+ else {
4899
+ doDispatch(detail);
4900
+ }
4901
+ };
4902
+ // Handle async methods
4903
+ if (result instanceof Promise) {
4904
+ return result.then((resolvedResult) => {
4905
+ timedDispatch(resolvedResult);
4906
+ return resolvedResult;
4907
+ });
4908
+ }
4909
+ // Sync method
4910
+ timedDispatch(result);
4911
+ return result;
4912
+ };
4913
+ };
4914
+ }
4915
+
4916
+ /**
4917
+ * Custom element readiness utilities
4918
+ * Handles waiting for custom elements to be defined with timeout warnings
4919
+ */
4920
+ /**
4921
+ * Global flag to disable custom element readiness timeout warnings
4922
+ * Set this to true in environments where slow element registration is expected
4923
+ */
4924
+ let DISABLE_ELEMENT_READY_WARNINGS = false;
4925
+ /**
4926
+ * Set whether to disable custom element readiness timeout warnings
4927
+ */
4928
+ function setDisableElementReadyWarnings(value) {
4929
+ DISABLE_ELEMENT_READY_WARNINGS = value;
4930
+ }
4931
+ /**
4932
+ * Default timeout for custom element registration warning (500ms)
4933
+ */
4934
+ const DEFAULT_WARNING_TIMEOUT = 500;
4935
+ /**
4936
+ * Wait for a custom element to be defined
4937
+ * Logs a warning if it takes longer than the warning timeout
4938
+ *
4939
+ * @param tagName - The custom element tag name
4940
+ * @param warningTimeout - Time in ms before warning (default 500ms)
4941
+ * @returns Promise that resolves when element is defined
4942
+ */
4943
+ async function waitForElementDefined(tagName, warningTimeout = DEFAULT_WARNING_TIMEOUT) {
4944
+ // If already defined, return immediately
4945
+ if (customElements.get(tagName)) {
4946
+ return;
4947
+ }
4948
+ // Set up warning timer if not disabled
4949
+ let warningTimer = null;
4950
+ if (!DISABLE_ELEMENT_READY_WARNINGS) {
4951
+ warningTimer = setTimeout(() => {
4952
+ console.warn(`Custom element <${tagName}> is taking longer than ${warningTimeout}ms to register. ` +
4953
+ `This may indicate a missing import or circular dependency. ` +
4954
+ `Set DISABLE_ELEMENT_READY_WARNINGS=true to disable this warning.`);
4955
+ }, warningTimeout);
4956
+ }
4957
+ try {
4958
+ // Wait for element to be defined
4959
+ await customElements.whenDefined(tagName);
4960
+ }
4961
+ finally {
4962
+ // Clear warning timer
4963
+ if (warningTimer) {
4964
+ clearTimeout(warningTimer);
4965
+ }
4966
+ }
4967
+ }
4968
+ /**
4969
+ * Wait for a custom element to be defined and ready
4970
+ * First waits for the element to be registered, then waits for its ready promise
4971
+ *
4972
+ * @param element - The custom element instance
4973
+ * @param warningTimeout - Time in ms before warning about registration (default 500ms)
4974
+ * @returns Promise that resolves when element is defined and ready
4975
+ */
4976
+ async function waitForElementReady(element, warningTimeout = DEFAULT_WARNING_TIMEOUT) {
4977
+ const tagName = element.tagName.toLowerCase();
4978
+ // Wait for element to be defined
4979
+ await waitForElementDefined(tagName, warningTimeout);
4980
+ // Wait for element's ready promise if it exists
4981
+ if ('ready' in element && typeof element.ready?.then === 'function') {
4982
+ await element.ready;
4983
+ }
4984
+ }
4985
+ /**
4986
+ * Process all custom elements in a node tree and wait for them to be ready
4987
+ * This is useful after inserting a template with custom elements
4988
+ *
4989
+ * @param node - The root node to scan for custom elements
4990
+ * @param warningTimeout - Time in ms before warning about registration (default 500ms)
4991
+ * @returns Promise that resolves when all custom elements are ready
4992
+ */
4993
+ async function waitForAllCustomElements(node, warningTimeout = DEFAULT_WARNING_TIMEOUT) {
4994
+ const customElements = [];
4995
+ // Find all custom elements (tag names with hyphens)
4996
+ if (node instanceof Element) {
4997
+ if (node.tagName.includes('-')) {
4998
+ customElements.push(node);
4999
+ }
5000
+ // Also check in shadow DOM
5001
+ if (node.shadowRoot) {
5002
+ const shadowCustomElements = node.shadowRoot.querySelectorAll('*');
5003
+ shadowCustomElements.forEach(el => {
5004
+ if (el.tagName.includes('-')) {
5005
+ customElements.push(el);
5006
+ }
5007
+ });
5008
+ }
5009
+ }
5010
+ // If it's a DocumentFragment, check all children
5011
+ if (node instanceof DocumentFragment) {
5012
+ const elements = node.querySelectorAll('*');
5013
+ elements.forEach(el => {
5014
+ if (el.tagName.includes('-')) {
5015
+ customElements.push(el);
5016
+ }
5017
+ });
5018
+ }
5019
+ // Wait for all custom elements to be ready
5020
+ await Promise.all(customElements.map(el => waitForElementReady(el, warningTimeout)));
5021
+ }
5022
+
5023
+ /**
5024
+ * Render debugging utilities for Snice v3.0.0
5025
+ * For testing and debugging only - not recommended for production use
5026
+ */
5027
+ /**
5028
+ * Track renders of an element using an async generator
5029
+ * Each call to tracker.next() waits for the next render to complete
5030
+ *
5031
+ * @example
5032
+ * ```typescript
5033
+ * const tracker = trackRenders(element);
5034
+ *
5035
+ * element.someProp = 'new value';
5036
+ * await tracker.next(); // Waits for render
5037
+ * // DOM is now updated
5038
+ *
5039
+ * element.someProp = 'another value';
5040
+ * await tracker.next(); // Waits for next render
5041
+ * // DOM is updated again
5042
+ * ```
5043
+ *
5044
+ * WARNING: For testing/debugging only!
5045
+ * - Do not use in production code
5046
+ * - The generator yields indefinitely - use it only in controlled test environments
5047
+ * - Each yield waits for the next render event
5048
+ */
5049
+ async function* trackRenders(element) {
5050
+ while (true) {
5051
+ await new Promise(resolve => {
5052
+ if (!element[RENDER_CALLBACKS]) {
5053
+ element[RENDER_CALLBACKS] = [];
5054
+ }
5055
+ element[RENDER_CALLBACKS].push(resolve);
5056
+ });
5057
+ yield;
5058
+ }
5059
+ }
5060
+
5061
+ /**
5062
+ * Method decorators for common patterns
5063
+ * @debounce, @throttle, @once, @memoize
5064
+ */
5065
+ const DEBOUNCE_TIMERS = getSymbol('debounce-timers');
5066
+ const THROTTLE_TIMERS = getSymbol('throttle-timers');
5067
+ const ONCE_CALLED = getSymbol('once-called');
5068
+ const MEMOIZE_CACHE = getSymbol('memoize-cache');
5069
+ /**
5070
+ * @debounce decorator - delays function execution until after wait time has elapsed
5071
+ * since the last invocation
5072
+ *
5073
+ * @param wait - Time to wait in milliseconds (default: 300)
5074
+ * @param options - Debounce options
5075
+ * @param options.leading - Invoke on the leading edge (default: false)
5076
+ * @param options.trailing - Invoke on the trailing edge (default: true)
5077
+ * @param options.maxWait - Maximum time to wait before invoking (default: undefined)
5078
+ *
5079
+ * @example
5080
+ * ```typescript
5081
+ * @element('search-input')
5082
+ * class SearchInput extends HTMLElement {
5083
+ * @debounce(500)
5084
+ * handleSearch(query: string) {
5085
+ * // Only called 500ms after last keystroke
5086
+ * fetch(`/api/search?q=${query}`);
5087
+ * }
5088
+ * }
5089
+ * ```
5090
+ */
5091
+ function debounce(wait = 300, options = {}) {
5092
+ const { leading = false, trailing = true, maxWait } = options;
5093
+ return function (originalMethod, context) {
5094
+ const methodName = context.name;
5095
+ return function (...args) {
5096
+ if (!this[DEBOUNCE_TIMERS]) {
5097
+ this[DEBOUNCE_TIMERS] = {};
5098
+ }
5099
+ const timers = this[DEBOUNCE_TIMERS];
5100
+ const timerKey = methodName;
5101
+ // Clear existing timer
5102
+ if (timers[timerKey]) {
5103
+ clearTimeout(timers[timerKey].timeout);
5104
+ }
5105
+ // Track when debounce started for maxWait
5106
+ const now = Date.now();
5107
+ const isFirstCall = !timers[timerKey];
5108
+ const startTime = isFirstCall ? now : timers[timerKey].startTime;
5109
+ // Check if maxWait exceeded
5110
+ const shouldInvokeFromMaxWait = maxWait !== undefined && now - startTime >= maxWait;
5111
+ // Leading edge invocation
5112
+ if (leading && isFirstCall) {
5113
+ const result = originalMethod.apply(this, args);
5114
+ timers[timerKey] = {
5115
+ timeout: null,
5116
+ startTime,
5117
+ lastArgs: args,
5118
+ };
5119
+ return result;
5120
+ }
5121
+ // Set new timer for trailing edge
5122
+ const timeout = setTimeout(() => {
5123
+ if (trailing || shouldInvokeFromMaxWait) {
5124
+ originalMethod.apply(this, timers[timerKey].lastArgs);
5125
+ }
5126
+ delete timers[timerKey];
5127
+ }, shouldInvokeFromMaxWait ? 0 : wait);
5128
+ timers[timerKey] = {
5129
+ timeout,
5130
+ startTime,
5131
+ lastArgs: args,
5132
+ };
5133
+ };
5134
+ };
5135
+ }
5136
+ /**
5137
+ * @throttle decorator - ensures function is called at most once per specified time period
5138
+ *
5139
+ * @param wait - Time to wait in milliseconds (default: 300)
5140
+ * @param options - Throttle options
5141
+ * @param options.leading - Invoke on the leading edge (default: true)
5142
+ * @param options.trailing - Invoke on the trailing edge (default: true)
5143
+ *
5144
+ * @example
5145
+ * ```typescript
5146
+ * @element('scroll-tracker')
5147
+ * class ScrollTracker extends HTMLElement {
5148
+ * @throttle(100)
5149
+ * handleScroll(e: Event) {
5150
+ * // Called at most once every 100ms
5151
+ * this.updateScrollPosition();
5152
+ * }
5153
+ * }
5154
+ * ```
5155
+ */
5156
+ function throttle(wait = 300, options = {}) {
5157
+ const { leading = true, trailing = true } = options;
5158
+ return function (originalMethod, context) {
5159
+ const methodName = context.name;
5160
+ return function (...args) {
5161
+ if (!this[THROTTLE_TIMERS]) {
5162
+ this[THROTTLE_TIMERS] = {};
5163
+ }
5164
+ const timers = this[THROTTLE_TIMERS];
5165
+ const timerKey = methodName;
5166
+ const now = Date.now();
5167
+ if (!timers[timerKey]) {
5168
+ // First call
5169
+ if (leading) {
5170
+ originalMethod.apply(this, args);
5171
+ }
5172
+ timers[timerKey] = {
5173
+ lastInvoke: now,
5174
+ timeout: null,
5175
+ lastArgs: args,
5176
+ };
5177
+ if (trailing && !leading) {
5178
+ // If no leading edge, set up trailing
5179
+ timers[timerKey].timeout = setTimeout(() => {
5180
+ originalMethod.apply(this, timers[timerKey].lastArgs);
5181
+ delete timers[timerKey];
5182
+ }, wait);
5183
+ }
5184
+ }
5185
+ else {
5186
+ // Subsequent calls
5187
+ const timeSinceLastInvoke = now - timers[timerKey].lastInvoke;
5188
+ // Update last args
5189
+ timers[timerKey].lastArgs = args;
5190
+ // Clear any pending trailing call
5191
+ if (timers[timerKey].timeout) {
5192
+ clearTimeout(timers[timerKey].timeout);
5193
+ }
5194
+ if (timeSinceLastInvoke >= wait) {
5195
+ // Enough time has passed, invoke immediately
5196
+ originalMethod.apply(this, args);
5197
+ timers[timerKey].lastInvoke = now;
5198
+ }
5199
+ else if (trailing) {
5200
+ // Set up trailing call
5201
+ const remaining = wait - timeSinceLastInvoke;
5202
+ timers[timerKey].timeout = setTimeout(() => {
5203
+ originalMethod.apply(this, timers[timerKey].lastArgs);
5204
+ timers[timerKey].lastInvoke = Date.now();
5205
+ timers[timerKey].timeout = null;
5206
+ }, remaining);
5207
+ }
5208
+ }
5209
+ };
5210
+ };
5211
+ }
5212
+ /**
5213
+ * @once decorator - ensures function is only called once
5214
+ * Subsequent calls return the result of the first call
5215
+ *
5216
+ * @param perInstance - If true, function can be called once per instance (default: true)
5217
+ * If false, function can only be called once globally across all instances
5218
+ *
5219
+ * @example
5220
+ * ```typescript
5221
+ * @element('data-loader')
5222
+ * class DataLoader extends HTMLElement {
5223
+ * @once()
5224
+ * async loadData() {
5225
+ * // Only loads data once, even if called multiple times
5226
+ * const data = await fetch('/api/data');
5227
+ * return data.json();
5228
+ * }
5229
+ * }
5230
+ * ```
5231
+ */
5232
+ function once(perInstance = true) {
5233
+ let globalCalled = false;
5234
+ let globalResult;
5235
+ return function (originalMethod, context) {
5236
+ const methodName = context.name;
5237
+ return function (...args) {
5238
+ if (perInstance) {
5239
+ // Per-instance tracking
5240
+ if (!this[ONCE_CALLED]) {
5241
+ this[ONCE_CALLED] = {};
5242
+ }
5243
+ if (!this[ONCE_CALLED][methodName]) {
5244
+ this[ONCE_CALLED][methodName] = {
5245
+ called: true,
5246
+ result: originalMethod.apply(this, args),
5247
+ };
5248
+ }
5249
+ return this[ONCE_CALLED][methodName].result;
5250
+ }
5251
+ else {
5252
+ // Global tracking
5253
+ if (!globalCalled) {
5254
+ globalCalled = true;
5255
+ globalResult = originalMethod.apply(this, args);
5256
+ }
5257
+ return globalResult;
5258
+ }
5259
+ };
5260
+ };
5261
+ }
5262
+ /**
5263
+ * @memoize decorator - caches function results based on arguments
5264
+ * Uses JSON.stringify for argument comparison by default
5265
+ *
5266
+ * @param options - Memoization options
5267
+ * @param options.keyGenerator - Custom function to generate cache key from arguments
5268
+ * @param options.maxSize - Maximum cache size (default: 100)
5269
+ * @param options.ttl - Time to live in milliseconds (default: undefined - no expiration)
5270
+ *
5271
+ * @example
5272
+ * ```typescript
5273
+ * @element('calculator')
5274
+ * class Calculator extends HTMLElement {
5275
+ * @memoize({ maxSize: 50 })
5276
+ * fibonacci(n: number): number {
5277
+ * // Results are cached, subsequent calls with same n are instant
5278
+ * if (n <= 1) return n;
5279
+ * return this.fibonacci(n - 1) + this.fibonacci(n - 2);
5280
+ * }
5281
+ * }
5282
+ * ```
5283
+ */
5284
+ function memoize(options = {}) {
5285
+ const { keyGenerator = (...args) => JSON.stringify(args), maxSize = 100, ttl } = options;
5286
+ return function (originalMethod, context) {
5287
+ const methodName = context.name;
5288
+ return function (...args) {
5289
+ if (!this[MEMOIZE_CACHE]) {
5290
+ this[MEMOIZE_CACHE] = {};
5291
+ }
5292
+ if (!this[MEMOIZE_CACHE][methodName]) {
5293
+ this[MEMOIZE_CACHE][methodName] = new Map();
5294
+ }
5295
+ const cache = this[MEMOIZE_CACHE][methodName];
5296
+ const key = keyGenerator(...args);
5297
+ // Check if cached
5298
+ if (cache.has(key)) {
5299
+ const cached = cache.get(key);
5300
+ // Check TTL
5301
+ if (ttl !== undefined) {
5302
+ const age = Date.now() - cached.timestamp;
5303
+ if (age > ttl) {
5304
+ cache.delete(key);
5305
+ }
5306
+ else {
5307
+ return cached.value;
5308
+ }
5309
+ }
5310
+ else {
5311
+ return cached.value;
5312
+ }
5313
+ }
5314
+ // Compute result
5315
+ const result = originalMethod.apply(this, args);
5316
+ // Store in cache
5317
+ cache.set(key, {
5318
+ value: result,
5319
+ timestamp: Date.now(),
5320
+ });
5321
+ // Enforce max size (LRU - delete oldest)
5322
+ if (cache.size > maxSize) {
5323
+ const firstKey = cache.keys().next().value;
5324
+ cache.delete(firstKey);
5325
+ }
5326
+ return result;
5327
+ };
5328
+ };
5329
+ }
5330
+ /**
5331
+ * Clear all debounce timers for an instance
5332
+ * Useful in cleanup/disconnectedCallback
5333
+ */
5334
+ function clearDebounceTimers(instance) {
5335
+ if (instance[DEBOUNCE_TIMERS]) {
5336
+ for (const timerKey in instance[DEBOUNCE_TIMERS]) {
5337
+ if (instance[DEBOUNCE_TIMERS][timerKey]?.timeout) {
5338
+ clearTimeout(instance[DEBOUNCE_TIMERS][timerKey].timeout);
5339
+ }
5340
+ }
5341
+ instance[DEBOUNCE_TIMERS] = {};
5342
+ }
5343
+ }
5344
+ /**
5345
+ * Clear all throttle timers for an instance
5346
+ * Useful in cleanup/disconnectedCallback
5347
+ */
5348
+ function clearThrottleTimers(instance) {
5349
+ if (instance[THROTTLE_TIMERS]) {
5350
+ for (const timerKey in instance[THROTTLE_TIMERS]) {
5351
+ if (instance[THROTTLE_TIMERS][timerKey]?.timeout) {
5352
+ clearTimeout(instance[THROTTLE_TIMERS][timerKey].timeout);
5353
+ }
5354
+ }
5355
+ instance[THROTTLE_TIMERS] = {};
5356
+ }
5357
+ }
5358
+ /**
5359
+ * Clear memoize cache for an instance
5360
+ */
5361
+ function clearMemoizeCache(instance, methodName) {
5362
+ if (instance[MEMOIZE_CACHE]) {
5363
+ if (methodName) {
5364
+ delete instance[MEMOIZE_CACHE][methodName];
5365
+ }
5366
+ else {
5367
+ instance[MEMOIZE_CACHE] = {};
5368
+ }
5369
+ }
5370
+ }
5371
+ /**
5372
+ * Reset once-called state for an instance
5373
+ */
5374
+ function resetOnce(instance, methodName) {
5375
+ if (instance[ONCE_CALLED]) {
5376
+ if (methodName) {
5377
+ delete instance[ONCE_CALLED][methodName];
5378
+ }
5379
+ else {
5380
+ instance[ONCE_CALLED] = {};
5381
+ }
5382
+ }
5383
+ }
5384
+
5385
+ exports.Context = Context;
3454
5386
  exports.IS_CONTROLLER_INSTANCE = IS_CONTROLLER_INSTANCE;
3455
5387
  exports.Router = Router;
3456
5388
  exports.SimpleArray = SimpleArray;
3457
5389
  exports.adopted = adopted;
3458
5390
  exports.applyElementFunctionality = applyElementFunctionality;
3459
- exports.context = context;
5391
+ exports.clearDebounceTimers = clearDebounceTimers;
5392
+ exports.clearMemoizeCache = clearMemoizeCache;
5393
+ exports.clearThrottleTimers = clearThrottleTimers;
5394
+ exports.context = context$1;
5395
+ exports.contextProperty = context;
3460
5396
  exports.controller = controller;
5397
+ exports.css = css;
5398
+ exports.debounce = debounce;
3461
5399
  exports.dispatch = dispatch;
3462
5400
  exports.dispose = dispose;
3463
5401
  exports.element = element;
3464
5402
  exports.getSymbol = getSymbol;
5403
+ exports.html = html;
3465
5404
  exports.layout = layout;
5405
+ exports.memoize = memoize;
3466
5406
  exports.moved = moved;
5407
+ exports.nothing = nothing;
3467
5408
  exports.observe = observe;
3468
5409
  exports.on = on;
3469
- exports.part = part;
5410
+ exports.once = once;
3470
5411
  exports.property = property;
3471
5412
  exports.query = query;
3472
5413
  exports.queryAll = queryAll;
3473
5414
  exports.ready = ready;
5415
+ exports.render = render;
3474
5416
  exports.request = request;
5417
+ exports.resetOnce = resetOnce;
3475
5418
  exports.respond = respond;
5419
+ exports.setDisableElementReadyWarnings = setDisableElementReadyWarnings;
5420
+ exports.styles = styles;
5421
+ exports.throttle = throttle;
5422
+ exports.trackRenders = trackRenders;
5423
+ exports.unsafeHTML = unsafeHTML;
3476
5424
  exports.useNativeElementControllers = useNativeElementControllers;
5425
+ exports.waitForAllCustomElements = waitForAllCustomElements;
5426
+ exports.waitForElementDefined = waitForElementDefined;
5427
+ exports.waitForElementReady = waitForElementReady;
3477
5428
  exports.watch = watch;
3478
5429
 
3479
5430
  return exports;