snice 1.13.2 → 1.13.4

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 (295) hide show
  1. package/bin/templates/base/README.md +1 -1
  2. package/dist/components/accordion/snice-accordion-item.d.ts +25 -0
  3. package/dist/components/accordion/snice-accordion-item.js +260 -0
  4. package/dist/components/accordion/snice-accordion-item.js.map +1 -0
  5. package/dist/components/accordion/snice-accordion.d.ts +28 -0
  6. package/dist/components/accordion/snice-accordion.js +221 -0
  7. package/dist/components/accordion/snice-accordion.js.map +1 -0
  8. package/dist/components/accordion/snice-accordion.types.d.ts +29 -0
  9. package/dist/components/accordion/snice-accordion.types.js +2 -0
  10. package/dist/components/accordion/snice-accordion.types.js.map +1 -0
  11. package/dist/components/alert/snice-alert.d.ts +26 -0
  12. package/dist/components/alert/snice-alert.js +191 -0
  13. package/dist/components/alert/snice-alert.js.map +1 -0
  14. package/dist/components/alert/snice-alert.types.d.ts +11 -0
  15. package/dist/components/alert/snice-alert.types.js +2 -0
  16. package/dist/components/alert/snice-alert.types.js.map +1 -0
  17. package/dist/components/avatar/snice-avatar.d.ts +24 -0
  18. package/dist/components/avatar/snice-avatar.js +177 -0
  19. package/dist/components/avatar/snice-avatar.js.map +1 -0
  20. package/dist/components/avatar/snice-avatar.types.d.ts +12 -0
  21. package/dist/components/avatar/snice-avatar.types.js +2 -0
  22. package/dist/components/avatar/snice-avatar.types.js.map +1 -0
  23. package/dist/components/badge/snice-badge.d.ts +25 -0
  24. package/dist/components/badge/snice-badge.js +157 -0
  25. package/dist/components/badge/snice-badge.js.map +1 -0
  26. package/dist/components/badge/snice-badge.types.d.ts +15 -0
  27. package/dist/components/badge/snice-badge.types.js +2 -0
  28. package/dist/components/badge/snice-badge.types.js.map +1 -0
  29. package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +27 -0
  30. package/dist/components/breadcrumbs/snice-breadcrumbs.js +212 -0
  31. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -0
  32. package/dist/components/breadcrumbs/snice-breadcrumbs.types.d.ts +23 -0
  33. package/dist/components/breadcrumbs/snice-breadcrumbs.types.js +2 -0
  34. package/dist/components/breadcrumbs/snice-breadcrumbs.types.js.map +1 -0
  35. package/dist/components/breadcrumbs/snice-crumb.d.ts +9 -0
  36. package/dist/components/breadcrumbs/snice-crumb.js +50 -0
  37. package/dist/components/breadcrumbs/snice-crumb.js.map +1 -0
  38. package/dist/components/button/snice-button.d.ts +32 -0
  39. package/dist/components/button/snice-button.js +212 -0
  40. package/dist/components/button/snice-button.js.map +1 -0
  41. package/dist/components/button/snice-button.types.d.ts +23 -0
  42. package/dist/components/button/snice-button.types.js +2 -0
  43. package/dist/components/button/snice-button.types.js.map +1 -0
  44. package/dist/components/card/snice-card.d.ts +19 -0
  45. package/dist/components/card/snice-card.js +132 -0
  46. package/dist/components/card/snice-card.js.map +1 -0
  47. package/dist/components/card/snice-card.types.d.ts +9 -0
  48. package/dist/components/card/snice-card.types.js +2 -0
  49. package/dist/components/card/snice-card.types.js.map +1 -0
  50. package/dist/components/checkbox/snice-checkbox.d.ts +34 -0
  51. package/dist/components/checkbox/snice-checkbox.js +289 -0
  52. package/dist/components/checkbox/snice-checkbox.js.map +1 -0
  53. package/dist/components/checkbox/snice-checkbox.types.d.ts +20 -0
  54. package/dist/components/checkbox/snice-checkbox.types.js +2 -0
  55. package/dist/components/checkbox/snice-checkbox.types.js.map +1 -0
  56. package/dist/components/chip/snice-chip.d.ts +28 -0
  57. package/dist/components/chip/snice-chip.js +203 -0
  58. package/dist/components/chip/snice-chip.js.map +1 -0
  59. package/dist/components/chip/snice-chip.types.d.ts +14 -0
  60. package/dist/components/chip/snice-chip.types.js +2 -0
  61. package/dist/components/chip/snice-chip.types.js.map +1 -0
  62. package/dist/components/date-picker/snice-date-picker.d.ts +82 -0
  63. package/dist/components/date-picker/snice-date-picker.js +880 -0
  64. package/dist/components/date-picker/snice-date-picker.js.map +1 -0
  65. package/dist/components/date-picker/snice-date-picker.types.d.ts +71 -0
  66. package/dist/components/date-picker/snice-date-picker.types.js +2 -0
  67. package/dist/components/date-picker/snice-date-picker.types.js.map +1 -0
  68. package/dist/components/divider/snice-divider.d.ts +17 -0
  69. package/dist/components/divider/snice-divider.js +111 -0
  70. package/dist/components/divider/snice-divider.js.map +1 -0
  71. package/dist/components/divider/snice-divider.types.d.ts +14 -0
  72. package/dist/components/divider/snice-divider.types.js +2 -0
  73. package/dist/components/divider/snice-divider.types.js.map +1 -0
  74. package/dist/components/drawer/snice-drawer.d.ts +37 -0
  75. package/dist/components/drawer/snice-drawer.js +335 -0
  76. package/dist/components/drawer/snice-drawer.js.map +1 -0
  77. package/dist/components/drawer/snice-drawer.types.d.ts +16 -0
  78. package/dist/components/drawer/snice-drawer.types.js +2 -0
  79. package/dist/components/drawer/snice-drawer.types.js.map +1 -0
  80. package/dist/components/input/snice-input.d.ts +65 -0
  81. package/dist/components/input/snice-input.js +603 -0
  82. package/dist/components/input/snice-input.js.map +1 -0
  83. package/dist/components/input/snice-input.types.d.ts +53 -0
  84. package/dist/components/input/snice-input.types.js +2 -0
  85. package/dist/components/input/snice-input.types.js.map +1 -0
  86. package/dist/components/layout/snice-layout-blog.d.ts +4 -0
  87. package/dist/components/layout/snice-layout-blog.js +56 -0
  88. package/dist/components/layout/snice-layout-blog.js.map +1 -0
  89. package/dist/components/layout/snice-layout-card.d.ts +6 -0
  90. package/dist/components/layout/snice-layout-card.js +53 -0
  91. package/dist/components/layout/snice-layout-card.js.map +1 -0
  92. package/dist/components/layout/snice-layout-centered.d.ts +5 -0
  93. package/dist/components/layout/snice-layout-centered.js +38 -0
  94. package/dist/components/layout/snice-layout-centered.js.map +1 -0
  95. package/dist/components/layout/snice-layout-dashboard.d.ts +4 -0
  96. package/dist/components/layout/snice-layout-dashboard.js +53 -0
  97. package/dist/components/layout/snice-layout-dashboard.js.map +1 -0
  98. package/dist/components/layout/snice-layout-fullscreen.d.ts +5 -0
  99. package/dist/components/layout/snice-layout-fullscreen.js +50 -0
  100. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -0
  101. package/dist/components/layout/snice-layout-landing.d.ts +4 -0
  102. package/dist/components/layout/snice-layout-landing.js +55 -0
  103. package/dist/components/layout/snice-layout-landing.js.map +1 -0
  104. package/dist/components/layout/snice-layout-minimal.d.ts +4 -0
  105. package/dist/components/layout/snice-layout-minimal.js +27 -0
  106. package/dist/components/layout/snice-layout-minimal.js.map +1 -0
  107. package/dist/components/layout/snice-layout-sidebar.d.ts +5 -0
  108. package/dist/components/layout/snice-layout-sidebar.js +64 -0
  109. package/dist/components/layout/snice-layout-sidebar.js.map +1 -0
  110. package/dist/components/layout/snice-layout-split.d.ts +6 -0
  111. package/dist/components/layout/snice-layout-split.js +47 -0
  112. package/dist/components/layout/snice-layout-split.js.map +1 -0
  113. package/dist/components/layout/snice-layout.d.ts +4 -0
  114. package/dist/components/layout/snice-layout.js +43 -0
  115. package/dist/components/layout/snice-layout.js.map +1 -0
  116. package/dist/components/layout/snice-layout.types.d.ts +3 -0
  117. package/dist/components/layout/snice-layout.types.js +2 -0
  118. package/dist/components/layout/snice-layout.types.js.map +1 -0
  119. package/dist/components/login/snice-login.d.ts +45 -0
  120. package/dist/components/login/snice-login.js +385 -0
  121. package/dist/components/login/snice-login.js.map +1 -0
  122. package/dist/components/login/snice-login.types.d.ts +31 -0
  123. package/dist/components/login/snice-login.types.js +2 -0
  124. package/dist/components/login/snice-login.types.js.map +1 -0
  125. package/dist/components/modal/snice-modal.d.ts +32 -0
  126. package/dist/components/modal/snice-modal.js +288 -0
  127. package/dist/components/modal/snice-modal.js.map +1 -0
  128. package/dist/components/modal/snice-modal.types.d.ts +18 -0
  129. package/dist/components/modal/snice-modal.types.js +2 -0
  130. package/dist/components/modal/snice-modal.types.js.map +1 -0
  131. package/dist/components/pagination/snice-pagination.d.ts +26 -0
  132. package/dist/components/pagination/snice-pagination.js +373 -0
  133. package/dist/components/pagination/snice-pagination.js.map +1 -0
  134. package/dist/components/pagination/snice-pagination.types.d.ts +18 -0
  135. package/dist/components/pagination/snice-pagination.types.js +2 -0
  136. package/dist/components/pagination/snice-pagination.types.js.map +1 -0
  137. package/dist/components/progress/snice-progress.d.ts +35 -0
  138. package/dist/components/progress/snice-progress.js +295 -0
  139. package/dist/components/progress/snice-progress.js.map +1 -0
  140. package/dist/components/progress/snice-progress.types.d.ts +18 -0
  141. package/dist/components/progress/snice-progress.types.js +2 -0
  142. package/dist/components/progress/snice-progress.types.js.map +1 -0
  143. package/dist/components/radio/snice-radio.d.ts +33 -0
  144. package/dist/components/radio/snice-radio.js +286 -0
  145. package/dist/components/radio/snice-radio.js.map +1 -0
  146. package/dist/components/radio/snice-radio.types.d.ts +19 -0
  147. package/dist/components/radio/snice-radio.types.js +2 -0
  148. package/dist/components/radio/snice-radio.types.js.map +1 -0
  149. package/dist/components/select/snice-option.d.ts +17 -0
  150. package/dist/components/select/snice-option.js +77 -0
  151. package/dist/components/select/snice-option.js.map +1 -0
  152. package/dist/components/select/snice-option.types.d.ts +14 -0
  153. package/dist/components/select/snice-option.types.js +2 -0
  154. package/dist/components/select/snice-option.types.js.map +1 -0
  155. package/dist/components/select/snice-select.d.ts +89 -0
  156. package/dist/components/select/snice-select.js +900 -0
  157. package/dist/components/select/snice-select.js.map +1 -0
  158. package/dist/components/select/snice-select.types.d.ts +49 -0
  159. package/dist/components/select/snice-select.types.js +2 -0
  160. package/dist/components/select/snice-select.types.js.map +1 -0
  161. package/dist/components/skeleton/snice-skeleton.d.ts +16 -0
  162. package/dist/components/skeleton/snice-skeleton.js +159 -0
  163. package/dist/components/skeleton/snice-skeleton.js.map +1 -0
  164. package/dist/components/skeleton/snice-skeleton.types.d.ts +10 -0
  165. package/dist/components/skeleton/snice-skeleton.types.js +2 -0
  166. package/dist/components/skeleton/snice-skeleton.types.js.map +1 -0
  167. package/dist/components/switch/snice-switch.d.ts +38 -0
  168. package/dist/components/switch/snice-switch.js +309 -0
  169. package/dist/components/switch/snice-switch.js.map +1 -0
  170. package/dist/components/switch/snice-switch.types.d.ts +21 -0
  171. package/dist/components/switch/snice-switch.types.js +2 -0
  172. package/dist/components/switch/snice-switch.types.js.map +1 -0
  173. package/dist/components/symbols.d.ts +1 -0
  174. package/dist/components/symbols.js +20 -0
  175. package/dist/components/symbols.js.map +1 -0
  176. package/dist/components/table/snice-cell-boolean.d.ts +21 -0
  177. package/dist/components/table/snice-cell-boolean.js +152 -0
  178. package/dist/components/table/snice-cell-boolean.js.map +1 -0
  179. package/dist/components/table/snice-cell-date.d.ts +24 -0
  180. package/dist/components/table/snice-cell-date.js +240 -0
  181. package/dist/components/table/snice-cell-date.js.map +1 -0
  182. package/dist/components/table/snice-cell-duration.d.ts +16 -0
  183. package/dist/components/table/snice-cell-duration.js +123 -0
  184. package/dist/components/table/snice-cell-duration.js.map +1 -0
  185. package/dist/components/table/snice-cell-filesize.d.ts +16 -0
  186. package/dist/components/table/snice-cell-filesize.js +119 -0
  187. package/dist/components/table/snice-cell-filesize.js.map +1 -0
  188. package/dist/components/table/snice-cell-number.d.ts +23 -0
  189. package/dist/components/table/snice-cell-number.js +202 -0
  190. package/dist/components/table/snice-cell-number.js.map +1 -0
  191. package/dist/components/table/snice-cell-progress.d.ts +17 -0
  192. package/dist/components/table/snice-cell-progress.js +114 -0
  193. package/dist/components/table/snice-cell-progress.js.map +1 -0
  194. package/dist/components/table/snice-cell-rating.d.ts +17 -0
  195. package/dist/components/table/snice-cell-rating.js +113 -0
  196. package/dist/components/table/snice-cell-rating.js.map +1 -0
  197. package/dist/components/table/snice-cell-sparkline.d.ts +29 -0
  198. package/dist/components/table/snice-cell-sparkline.js +290 -0
  199. package/dist/components/table/snice-cell-sparkline.js.map +1 -0
  200. package/dist/components/table/snice-cell-text.d.ts +19 -0
  201. package/dist/components/table/snice-cell-text.js +153 -0
  202. package/dist/components/table/snice-cell-text.js.map +1 -0
  203. package/dist/components/table/snice-cell.d.ts +32 -0
  204. package/dist/components/table/snice-cell.js +451 -0
  205. package/dist/components/table/snice-cell.js.map +1 -0
  206. package/dist/components/table/snice-column.d.ts +62 -0
  207. package/dist/components/table/snice-column.js +440 -0
  208. package/dist/components/table/snice-column.js.map +1 -0
  209. package/dist/components/table/snice-header.d.ts +33 -0
  210. package/dist/components/table/snice-header.js +303 -0
  211. package/dist/components/table/snice-header.js.map +1 -0
  212. package/dist/components/table/snice-progress.d.ts +10 -0
  213. package/dist/components/table/snice-progress.js +91 -0
  214. package/dist/components/table/snice-progress.js.map +1 -0
  215. package/dist/components/table/snice-rating.d.ts +9 -0
  216. package/dist/components/table/snice-rating.js +68 -0
  217. package/dist/components/table/snice-rating.js.map +1 -0
  218. package/dist/components/table/snice-row.d.ts +43 -0
  219. package/dist/components/table/snice-row.js +365 -0
  220. package/dist/components/table/snice-row.js.map +1 -0
  221. package/dist/components/table/snice-table.d.ts +69 -0
  222. package/dist/components/table/snice-table.js +814 -0
  223. package/dist/components/table/snice-table.js.map +1 -0
  224. package/dist/components/table/snice-table.types.d.ts +137 -0
  225. package/dist/components/table/snice-table.types.js +2 -0
  226. package/dist/components/table/snice-table.types.js.map +1 -0
  227. package/dist/components/tabs/snice-tab-panel.d.ts +12 -0
  228. package/dist/components/tabs/snice-tab-panel.js +78 -0
  229. package/dist/components/tabs/snice-tab-panel.js.map +1 -0
  230. package/dist/components/tabs/snice-tab.d.ts +13 -0
  231. package/dist/components/tabs/snice-tab.js +90 -0
  232. package/dist/components/tabs/snice-tab.js.map +1 -0
  233. package/dist/components/tabs/snice-tabs.d.ts +34 -0
  234. package/dist/components/tabs/snice-tabs.js +367 -0
  235. package/dist/components/tabs/snice-tabs.js.map +1 -0
  236. package/dist/components/tabs/snice-tabs.types.d.ts +23 -0
  237. package/dist/components/tabs/snice-tabs.types.js +2 -0
  238. package/dist/components/tabs/snice-tabs.types.js.map +1 -0
  239. package/dist/components/toast/snice-toast-container.d.ts +25 -0
  240. package/dist/components/toast/snice-toast-container.js +251 -0
  241. package/dist/components/toast/snice-toast-container.js.map +1 -0
  242. package/dist/components/toast/snice-toast.d.ts +23 -0
  243. package/dist/components/toast/snice-toast.js +316 -0
  244. package/dist/components/toast/snice-toast.js.map +1 -0
  245. package/dist/components/toast/snice-toast.types.d.ts +30 -0
  246. package/dist/components/toast/snice-toast.types.js +2 -0
  247. package/dist/components/toast/snice-toast.types.js.map +1 -0
  248. package/dist/components/tooltip/snice-tooltip.d.ts +50 -0
  249. package/dist/components/tooltip/snice-tooltip.js +656 -0
  250. package/dist/components/tooltip/snice-tooltip.js.map +1 -0
  251. package/dist/components/tooltip/snice-tooltip.types.d.ts +18 -0
  252. package/dist/components/tooltip/snice-tooltip.types.js +2 -0
  253. package/dist/components/tooltip/snice-tooltip.types.js.map +1 -0
  254. package/dist/components/transitions.d.ts +11 -0
  255. package/dist/components/transitions.js +69 -0
  256. package/dist/components/transitions.js.map +1 -0
  257. package/dist/src/controller.d.ts +61 -0
  258. package/dist/src/controller.js +297 -0
  259. package/dist/src/controller.js.map +1 -0
  260. package/dist/src/element.d.ts +77 -0
  261. package/dist/src/element.js +805 -0
  262. package/dist/src/element.js.map +1 -0
  263. package/dist/src/events.d.ts +37 -0
  264. package/dist/src/events.js +289 -0
  265. package/dist/src/events.js.map +1 -0
  266. package/dist/src/global.d.ts +7 -0
  267. package/dist/src/global.js +23 -0
  268. package/dist/src/global.js.map +1 -0
  269. package/{src/index.ts → dist/src/index.d.ts} +1 -1
  270. package/dist/src/index.js +8 -0
  271. package/dist/src/index.js.map +1 -0
  272. package/dist/src/observe.d.ts +26 -0
  273. package/dist/src/observe.js +329 -0
  274. package/dist/src/observe.js.map +1 -0
  275. package/dist/src/request-response.d.ts +46 -0
  276. package/dist/src/request-response.js +267 -0
  277. package/dist/src/request-response.js.map +1 -0
  278. package/dist/src/router.d.ts +87 -0
  279. package/dist/src/router.js +375 -0
  280. package/dist/src/router.js.map +1 -0
  281. package/dist/src/symbols.d.ts +29 -0
  282. package/{src/symbols.ts → dist/src/symbols.js} +2 -12
  283. package/dist/src/symbols.js.map +1 -0
  284. package/dist/src/transitions.d.ts +50 -0
  285. package/dist/src/transitions.js +199 -0
  286. package/dist/src/transitions.js.map +1 -0
  287. package/package.json +6 -8
  288. package/src/controller.ts +0 -347
  289. package/src/element.ts +0 -897
  290. package/src/events.ts +0 -349
  291. package/src/global.ts +0 -31
  292. package/src/observe.ts +0 -414
  293. package/src/request-response.ts +0 -336
  294. package/src/router.ts +0 -552
  295. package/src/transitions.ts +0 -264
package/src/element.ts DELETED
@@ -1,897 +0,0 @@
1
- import { attachController, detachController } from './controller';
2
- import { setupEventHandlers, cleanupEventHandlers } from './events';
3
- import { setupObservers, cleanupObservers } from './observe';
4
- import { setupResponseHandlers, cleanupResponseHandlers } from './request-response';
5
- import { IS_ELEMENT_CLASS, IS_CONTROLLER_INSTANCE, READY_PROMISE, READY_RESOLVE, CONTROLLER, PROPERTIES, PROPERTY_VALUES, PROPERTIES_INITIALIZED, PROPERTY_WATCHERS, EXPLICITLY_SET_PROPERTIES, ROUTER_CONTEXT, READY_HANDLERS, DISPOSE_HANDLERS, PARTS, PART_TIMERS } from './symbols';
6
-
7
- /**
8
- * Applies core element functionality to a constructor
9
- * This is shared between @element and @page decorators
10
- */
11
- export function applyElementFunctionality(constructor: any) {
12
- // Mark as element class for channel decorator detection
13
- (constructor.prototype as any)[IS_ELEMENT_CLASS] = true;
14
-
15
- // Add controller property to all elements
16
- const originalConnectedCallback = constructor.prototype.connectedCallback;
17
- const originalDisconnectedCallback = constructor.prototype.disconnectedCallback;
18
- const originalAttributeChangedCallback = constructor.prototype.attributeChangedCallback;
19
-
20
- // Add 'controller' and all reflected properties to observed attributes
21
- const observedAttributes = constructor.observedAttributes || [];
22
- if (!observedAttributes.includes('controller')) {
23
- observedAttributes.push('controller');
24
- }
25
-
26
- // Add all properties to observed attributes (not just reflected ones)
27
- const properties = constructor[PROPERTIES];
28
- if (properties) {
29
- for (const [propName, propOptions] of properties) {
30
- const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
31
- if (!observedAttributes.includes(attributeName)) {
32
- observedAttributes.push(attributeName);
33
- }
34
- }
35
- }
36
-
37
- Object.defineProperty(constructor, 'observedAttributes', {
38
- get() { return observedAttributes; },
39
- configurable: true
40
- });
41
-
42
- // Add ready property - always returns a promise
43
- Object.defineProperty(constructor.prototype, 'ready', {
44
- get() {
45
- if (!this[READY_PROMISE]) {
46
- // Create a pending promise if not yet initialized
47
- this[READY_PROMISE] = new Promise<void>((resolve) => {
48
- this[READY_RESOLVE] = resolve;
49
- });
50
- }
51
- return this[READY_PROMISE];
52
- },
53
- enumerable: true,
54
- configurable: true
55
- });
56
-
57
- // Add controller property
58
- Object.defineProperty(constructor.prototype, 'controller', {
59
- get() {
60
- return this[CONTROLLER];
61
- },
62
- set(value: string) {
63
- const oldValue = this[CONTROLLER];
64
- this[CONTROLLER] = value;
65
- if (value !== oldValue && value) {
66
- // Attach controller asynchronously
67
- attachController(this, value).catch(error => {
68
- console.error(`Failed to attach controller "${value}":`, error);
69
- });
70
- } else if (!value && oldValue) {
71
- // Detach controller asynchronously
72
- detachController(this).catch(error => {
73
- console.error(`Failed to detach controller:`, error);
74
- });
75
- }
76
- },
77
- enumerable: true,
78
- configurable: true
79
- });
80
-
81
-
82
- constructor.prototype.connectedCallback = async function() {
83
- // If ready promise was already created (controller attached before connected), use existing resolve
84
- // Otherwise create the ready promise now
85
- if (!this[READY_PROMISE]) {
86
- this[READY_PROMISE] = new Promise<void>((resolve) => {
87
- this[READY_RESOLVE] = resolve;
88
- });
89
- }
90
-
91
- try {
92
- // Initialize properties from attributes before rendering
93
- const properties = constructor[PROPERTIES];
94
- if (properties) {
95
- for (const [propName, propOptions] of properties) {
96
- // If attribute exists, it always wins
97
- if (this.hasAttribute(propName)) {
98
- // Attribute exists, parse and set the property value
99
- const attrValue = this.getAttribute(propName);
100
-
101
- // Mark as explicitly set since it came from an attribute
102
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
103
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
104
- }
105
- this[EXPLICITLY_SET_PROPERTIES].add(propName);
106
-
107
- switch (propOptions.type) {
108
- case Boolean:
109
- this[propName] = attrValue !== null && attrValue !== 'false';
110
- break;
111
- case Number:
112
- this[propName] = Number(attrValue);
113
- break;
114
- case String:
115
- this[propName] = attrValue;
116
- break;
117
- case Date:
118
- this[propName] = attrValue ? new Date(attrValue) : null;
119
- break;
120
- case BigInt:
121
- if (attrValue && attrValue.endsWith('n')) {
122
- this[propName] = BigInt(attrValue.slice(0, -1));
123
- } else {
124
- this[propName] = attrValue ? BigInt(attrValue) : null;
125
- }
126
- break;
127
- case SimpleArray:
128
- this[propName] = SimpleArray.parse(attrValue);
129
- break;
130
- default:
131
- this[propName] = attrValue;
132
- }
133
- }
134
- }
135
- }
136
-
137
- // Mark that properties have been initialized
138
- this[PROPERTIES_INITIALIZED] = true;
139
-
140
- // Reflect properties that were explicitly set before connection
141
- // but skip default values that were never explicitly set
142
- if (properties && this[EXPLICITLY_SET_PROPERTIES]) {
143
- for (const [propName, propOptions] of properties) {
144
- if (propOptions.reflect && this[EXPLICITLY_SET_PROPERTIES].has(propName) && propName in this[PROPERTY_VALUES]) {
145
- const value = this[PROPERTY_VALUES][propName];
146
- const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
147
-
148
- if (value !== null && value !== undefined && value !== false &&
149
- !(propOptions.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
150
- // Handle special types for reflection
151
- let attributeValue: string;
152
- if (value instanceof Date) {
153
- attributeValue = value.toISOString();
154
- } else if (typeof value === 'bigint') {
155
- attributeValue = value.toString() + 'n';
156
- } else if (propOptions.type === SimpleArray && Array.isArray(value)) {
157
- attributeValue = SimpleArray.serialize(value);
158
- } else {
159
- attributeValue = String(value);
160
- }
161
- this.setAttribute(attributeName, attributeValue);
162
- }
163
- }
164
- }
165
- }
166
-
167
- // Clean up any existing event handlers first (for reconnection)
168
- cleanupEventHandlers(this);
169
-
170
- // Create shadow root if it doesn't exist
171
- if (!this.shadowRoot) {
172
- this.attachShadow({ mode: 'open' });
173
- }
174
-
175
- // Build the shadow DOM content
176
- let shadowContent = '';
177
-
178
- // Add HTML first (maintaining original order)
179
- if (this.html) {
180
- try {
181
- const htmlResult = this.html();
182
- // Handle both async and sync html
183
- const htmlContent = htmlResult instanceof Promise ? await htmlResult : htmlResult;
184
- if (htmlContent !== undefined) {
185
- shadowContent += htmlContent;
186
- }
187
- } catch (error) {
188
- console.error(`Error in html() method for ${this.tagName}:`, error);
189
- }
190
- }
191
-
192
- // Add CSS after HTML (maintaining original order)
193
- if (this.css) {
194
- try {
195
- const cssResult = this.css();
196
- // Handle both async and sync css
197
- const cssResolved = cssResult instanceof Promise ? await cssResult : cssResult;
198
- if (cssResolved) {
199
- // Handle both string and array of strings
200
- const cssContent = Array.isArray(cssResolved) ? cssResolved.join('\n') : cssResolved;
201
- // No need for scoping with Shadow DOM, but add data attribute for compatibility
202
- shadowContent += `<style data-component-css>${cssContent}</style>`;
203
- }
204
- } catch (error) {
205
- console.error(`Error in css() method for ${this.tagName}:`, error);
206
- }
207
- }
208
-
209
- // Set shadow DOM content
210
- if (shadowContent) {
211
- this.shadowRoot.innerHTML = shadowContent;
212
- }
213
-
214
- // Render all @part methods into their corresponding elements
215
- const parts = constructor[PARTS];
216
- if (parts && this.shadowRoot) {
217
- for (const [partName, partHandler] of parts) {
218
- try {
219
- const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
220
- if (partElement) {
221
- const partResult = partHandler.method.call(this);
222
- const partContent = partResult instanceof Promise ? await partResult : partResult;
223
- if (partContent !== undefined) {
224
- partElement.innerHTML = partContent;
225
- }
226
- }
227
- } catch (error) {
228
- console.error(`Error rendering @part('${partName}') in ${this.tagName}:`, error);
229
- }
230
- }
231
- }
232
-
233
- // NOW call the original user-defined connectedCallback after shadow DOM is set up
234
- if (originalConnectedCallback) {
235
- originalConnectedCallback.call(this);
236
- }
237
-
238
- const controllerName = this.getAttribute('controller');
239
- if (controllerName) {
240
- this.controller = controllerName;
241
- }
242
- // Setup @on event handlers - use element for host events, shadow root for delegated events
243
- setupEventHandlers(this, this);
244
-
245
- // Setup @respond handlers for elements
246
- setupResponseHandlers(this, this);
247
-
248
- // Setup @observe observers
249
- try {
250
- setupObservers(this, this);
251
- } catch (error) {
252
- console.error(`Error setting up observers for ${this.tagName}:`, error);
253
- }
254
-
255
- // Call @ready handlers after everything is set up
256
- const readyHandlers = constructor[READY_HANDLERS];
257
- if (readyHandlers) {
258
- for (const handler of readyHandlers) {
259
- try {
260
- await handler.method.call(this);
261
- } catch (error) {
262
- console.error(`Error in @ready handler ${handler.methodName}:`, error);
263
- }
264
- }
265
- }
266
- } finally {
267
- // Always mark element as ready, even if there were errors
268
- if (this[READY_RESOLVE]) {
269
- this[READY_RESOLVE]();
270
- this[READY_RESOLVE] = null; // Clear the resolver
271
- }
272
- }
273
- };
274
-
275
- constructor.prototype.disconnectedCallback = async function() {
276
- // Call @dispose handlers
277
- const disposeHandlers = constructor[DISPOSE_HANDLERS];
278
- if (disposeHandlers) {
279
- for (const handler of disposeHandlers) {
280
- try {
281
- await handler.method.call(this);
282
- } catch (error) {
283
- console.error(`Error in @dispose handler ${handler.methodName}:`, error);
284
- }
285
- }
286
- }
287
-
288
- // Call original user-defined disconnectedCallback
289
- if (originalDisconnectedCallback) {
290
- originalDisconnectedCallback.call(this);
291
- }
292
- if (this[CONTROLLER]) {
293
- detachController(this).catch(error => {
294
- console.error(`Failed to detach controller:`, error);
295
- });
296
- }
297
- // Cleanup @on event handlers
298
- cleanupEventHandlers(this);
299
- // Cleanup @respond handlers
300
- cleanupResponseHandlers(this);
301
- // Cleanup @observe observers
302
- cleanupObservers(this);
303
- };
304
-
305
- constructor.prototype.attributeChangedCallback = function(name: string, oldValue: string, newValue: string) {
306
- originalAttributeChangedCallback?.call(this, name, oldValue, newValue);
307
- if (name === 'controller') {
308
- this.controller = newValue;
309
- } else {
310
- // Handle all properties (not just reflected ones)
311
- const properties = constructor[PROPERTIES];
312
- if (properties) {
313
- for (const [propName, propOptions] of properties) {
314
- const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
315
- if (attributeName === name) {
316
- // Check if the current property value already matches to avoid feedback loops
317
- const currentValue = this[PROPERTY_VALUES]?.[propName];
318
-
319
- // Parse the new value based on type
320
- let parsedValue: any;
321
- if (propOptions.type === Boolean) {
322
- parsedValue = newValue !== null && newValue !== 'false';
323
- } else if (propOptions.type === Number) {
324
- parsedValue = Number(newValue);
325
- } else if (propOptions.type === Date) {
326
- parsedValue = newValue ? new Date(newValue) : null;
327
- } else if (propOptions.type === BigInt) {
328
- if (newValue && newValue.endsWith('n')) {
329
- parsedValue = BigInt(newValue.slice(0, -1));
330
- } else {
331
- parsedValue = newValue ? BigInt(newValue) : null;
332
- }
333
- } else if (propOptions.type === SimpleArray) {
334
- parsedValue = SimpleArray.parse(newValue);
335
- } else {
336
- // If no type specified, try to infer from current value type
337
- if (typeof currentValue === 'number' && newValue !== null) {
338
- parsedValue = Number(newValue);
339
- } else {
340
- parsedValue = newValue;
341
- }
342
- }
343
-
344
- // Only update if the value actually changed and avoid infinite loops
345
- if (currentValue !== parsedValue) {
346
- // Mark as explicitly set since it came from an attribute change
347
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
348
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
349
- }
350
- this[EXPLICITLY_SET_PROPERTIES].add(propName);
351
-
352
- // Set the property value directly in the storage to avoid triggering setter
353
- if (!this[PROPERTY_VALUES]) {
354
- this[PROPERTY_VALUES] = {};
355
- }
356
- this[PROPERTY_VALUES][propName] = parsedValue;
357
-
358
- // Call watchers manually since we bypassed the setter
359
- const watchers = constructor[PROPERTY_WATCHERS];
360
- if (watchers) {
361
- // Call specific property watchers
362
- if (watchers.has(propName)) {
363
- const propertyWatchers = watchers.get(propName);
364
- for (const watcher of propertyWatchers) {
365
- try {
366
- watcher.method.call(this, currentValue, parsedValue, propName);
367
- } catch (error) {
368
- console.error(`Error in @watch('${propName}') method ${watcher.methodName}:`, error);
369
- }
370
- }
371
- }
372
-
373
- // Call wildcard watchers (watching "*")
374
- if (watchers.has('*')) {
375
- const wildcardWatchers = watchers.get('*');
376
- for (const watcher of wildcardWatchers) {
377
- try {
378
- watcher.method.call(this, currentValue, parsedValue, propName);
379
- } catch (error) {
380
- console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
381
- }
382
- }
383
- }
384
- }
385
- }
386
- break;
387
- }
388
- }
389
- }
390
- }
391
- };
392
- }
393
-
394
- export function element(tagName: string) {
395
- return function (constructor: any) {
396
- applyElementFunctionality(constructor);
397
- customElements.define(tagName, constructor);
398
- };
399
- }
400
-
401
- export function layout(tagName: string) {
402
- return function (constructor: any) {
403
- applyElementFunctionality(constructor);
404
- customElements.define(tagName, constructor);
405
- };
406
- }
407
-
408
- export function property(options?: PropertyOptions) {
409
- return function (target: any, propertyKey: string) {
410
- const constructor = target.constructor;
411
-
412
- // Warn about problematic reflection usage
413
- if (options?.reflect && options?.type === Array) {
414
- console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Array type.`);
415
- }
416
-
417
- if (options?.reflect && options?.type === Object) {
418
- console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
419
- }
420
-
421
- if (!constructor[PROPERTIES]) {
422
- constructor[PROPERTIES] = new Map();
423
- }
424
-
425
- constructor[PROPERTIES].set(propertyKey, options || {});
426
-
427
- const descriptor: PropertyDescriptor = {
428
- get(this: any) {
429
- if (!this[PROPERTY_VALUES]) {
430
- this[PROPERTY_VALUES] = {};
431
- }
432
- return this[PROPERTY_VALUES][propertyKey];
433
- },
434
- set(this: any, value: any) {
435
- if (!this[PROPERTY_VALUES]) {
436
- this[PROPERTY_VALUES] = {};
437
- }
438
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
439
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
440
- }
441
-
442
- const oldValue = this[PROPERTY_VALUES][propertyKey];
443
-
444
- // Don't update if value hasn't changed
445
- if (oldValue === value) return;
446
-
447
- // Mark as explicitly set in these cases:
448
- // 1. There was a previous value (normal property update)
449
- // 2. This is during element construction and we have a non-null/non-undefined value
450
- // (this handles default values declared in class properties)
451
- const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
452
- if (oldValue !== undefined || (isInitialDefaultValue && value !== null && value !== undefined)) {
453
- this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
454
- }
455
-
456
- this[PROPERTY_VALUES][propertyKey] = value;
457
-
458
- // Only reflect to attributes if:
459
- // 1. Properties have been initialized from attributes
460
- // 2. The property was explicitly set (not just default value)
461
- // This prevents default values from creating attributes
462
- if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
463
- const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
464
-
465
- if (value === null || value === undefined || value === false ||
466
- (options?.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
467
- this.removeAttribute(attributeName);
468
- } else {
469
- // Handle special types for reflection
470
- let attributeValue: string;
471
- if (value instanceof Date) {
472
- attributeValue = value.toISOString();
473
- } else if (typeof value === 'bigint') {
474
- attributeValue = value.toString() + 'n';
475
- } else if (options?.type === SimpleArray && Array.isArray(value)) {
476
- attributeValue = SimpleArray.serialize(value);
477
- } else {
478
- attributeValue = String(value);
479
- }
480
- this.setAttribute(attributeName, attributeValue);
481
- }
482
- }
483
-
484
- // Call watchers for this property
485
- const watchers = constructor[PROPERTY_WATCHERS];
486
- if (watchers) {
487
- // Call specific property watchers
488
- if (watchers.has(propertyKey)) {
489
- const propertyWatchers = watchers.get(propertyKey);
490
- for (const watcher of propertyWatchers) {
491
- try {
492
- // Always pass oldValue, newValue, and propertyName
493
- watcher.method.call(this, oldValue, value, propertyKey);
494
- } catch (error) {
495
- console.error(`Error in @watch('${propertyKey}') method ${watcher.methodName}:`, error);
496
- }
497
- }
498
- }
499
-
500
- // Call wildcard watchers (watching "*")
501
- if (watchers.has('*')) {
502
- const wildcardWatchers = watchers.get('*');
503
- for (const watcher of wildcardWatchers) {
504
- try {
505
- // Same signature for consistency
506
- watcher.method.call(this, oldValue, value, propertyKey);
507
- } catch (error) {
508
- console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
509
- }
510
- }
511
- }
512
- }
513
-
514
- // Call requestUpdate if available and value changed
515
- if (this.requestUpdate) {
516
- this.requestUpdate(propertyKey, oldValue);
517
- }
518
- },
519
- enumerable: true,
520
- configurable: true,
521
- };
522
-
523
- Object.defineProperty(target, propertyKey, descriptor);
524
- };
525
- }
526
-
527
- export interface QueryOptions {
528
- light?: boolean;
529
- shadow?: boolean;
530
- }
531
-
532
- export function query(selector: string, options: QueryOptions = {}) {
533
- return function (target: any, propertyKey: string) {
534
- // Default to shadow DOM only
535
- const { light = false, shadow = true } = options;
536
-
537
- Object.defineProperty(target, propertyKey, {
538
- get() {
539
- // Check if this is a controller using the symbol
540
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
541
- const root = isController && this.element ? this.element : this;
542
-
543
- // Query in specified contexts
544
- let result = null;
545
-
546
- if (shadow && root.shadowRoot) {
547
- result = root.shadowRoot.querySelector(selector);
548
- }
549
-
550
- if (!result && light) {
551
- result = root.querySelector(selector);
552
- }
553
-
554
- return result || null;
555
- },
556
- enumerable: true,
557
- configurable: true,
558
- });
559
- };
560
- }
561
-
562
- export function queryAll(selector: string, options: QueryOptions = {}) {
563
- return function (target: any, propertyKey: string) {
564
- // Default to shadow DOM only
565
- const { light = false, shadow = true } = options;
566
-
567
- Object.defineProperty(target, propertyKey, {
568
- get() {
569
- // Check if this is a controller using the symbol
570
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
571
- const root = isController && this.element ? this.element : this;
572
-
573
- // Query in specified contexts and combine results
574
- const results: Element[] = [];
575
-
576
- if (shadow && root.shadowRoot) {
577
- const shadowResults = root.shadowRoot.querySelectorAll(selector);
578
- results.push(...shadowResults);
579
- }
580
-
581
- if (light) {
582
- const lightResults = root.querySelectorAll(selector);
583
- results.push(...lightResults);
584
- }
585
-
586
- // Return a static NodeList-like object
587
- return results as any as NodeListOf<Element>;
588
- },
589
- enumerable: true,
590
- configurable: true,
591
- });
592
- };
593
- }
594
-
595
- /**
596
- * SimpleArray type for arrays that can be safely reflected to attributes
597
- * Supports arrays of: string, number, boolean
598
- * Uses full-width comma (,) as separator to avoid conflicts
599
- * Strings cannot contain the full-width comma character
600
- */
601
- export class SimpleArray {
602
- static readonly SEPARATOR = ','; // U+FF0C Full-width comma
603
-
604
- /**
605
- * Serialize array to string for attribute storage
606
- */
607
- static serialize(arr: (string | number | boolean)[]): string {
608
- if (!Array.isArray(arr)) return '';
609
-
610
- return arr.map(item => {
611
- if (typeof item === 'string') {
612
- // Validate string doesn't contain our separator
613
- if (item.includes(SimpleArray.SEPARATOR)) {
614
- throw new Error(`SimpleArray strings cannot contain the character "${SimpleArray.SEPARATOR}" (U+FF0C)`);
615
- }
616
- return item;
617
- } else if (typeof item === 'number' || typeof item === 'boolean') {
618
- return String(item);
619
- } else {
620
- throw new Error(`SimpleArray only supports string, number, and boolean types. Got: ${typeof item}`);
621
- }
622
- }).join(SimpleArray.SEPARATOR);
623
- }
624
-
625
- /**
626
- * Parse string from attribute back to array
627
- */
628
- static parse(str: string | null): (string | number | boolean)[] {
629
- if (str === null || str === undefined) return [];
630
- // Empty string should not be parsed as containing an empty string
631
- // since empty arrays don't get reflected (handled by the reflection logic)
632
- if (str === '') return [];
633
-
634
- return str.split(SimpleArray.SEPARATOR).map(item => {
635
- // Try to parse as number
636
- if (/^-?\d+\.?\d*$/.test(item)) {
637
- const num = Number(item);
638
- if (!isNaN(num)) return num;
639
- }
640
-
641
- // Parse as boolean
642
- if (item === 'true') return true;
643
- if (item === 'false') return false;
644
-
645
- // Default to string
646
- return item;
647
- });
648
- }
649
- }
650
-
651
- export interface PropertyOptions {
652
- type?: StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | BigIntConstructor | typeof SimpleArray;
653
- reflect?: boolean;
654
- attribute?: string | boolean;
655
- converter?: PropertyConverter;
656
- hasChanged?: (value: any, oldValue: any) => boolean;
657
- }
658
-
659
- export interface PropertyConverter {
660
- fromAttribute?(value: string | null, type?: any): any;
661
- toAttribute?(value: any, type?: any): string | null;
662
- }
663
-
664
- /**
665
- * Interface for Snice elements with all the framework-provided properties and methods
666
- */
667
- export interface SniceElement extends HTMLElement {
668
- ready: Promise<void>;
669
- html?(): string | Promise<string>;
670
- css?(): string | string[] | Promise<string | string[]>;
671
- }
672
-
673
- export interface PartOptions {
674
- throttle?: number; // Throttle in milliseconds - limits calls to once per interval
675
- debounce?: number; // Debounce in milliseconds - delays execution until after calls stop
676
- }
677
-
678
- export function watch(...propertyNames: string[]) {
679
- return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
680
- const constructor = target.constructor;
681
-
682
- if (!constructor[PROPERTY_WATCHERS]) {
683
- constructor[PROPERTY_WATCHERS] = new Map();
684
- }
685
-
686
- // Store the watcher method for each property
687
- for (const propertyName of propertyNames) {
688
- if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
689
- constructor[PROPERTY_WATCHERS].set(propertyName, []);
690
- }
691
-
692
- constructor[PROPERTY_WATCHERS].get(propertyName).push({
693
- methodName,
694
- method: descriptor.value
695
- });
696
- }
697
-
698
- return descriptor;
699
- };
700
- }
701
-
702
- /**
703
- * Decorator that injects router context into a property
704
- * The context is automatically provided to page components by the router
705
- */
706
- export function context() {
707
- return function(target: any, propertyKey: string) {
708
- // Define property getter that returns the context
709
- Object.defineProperty(target, propertyKey, {
710
- get() {
711
- // First check if context is stored directly on this element
712
- if (this[ROUTER_CONTEXT] !== undefined) {
713
- return this[ROUTER_CONTEXT];
714
- }
715
-
716
- // Otherwise, request context from parent page via event
717
- const detail: any = { target: this };
718
- const event = new CustomEvent('@context/request', {
719
- bubbles: true,
720
- cancelable: true,
721
- detail
722
- });
723
-
724
- // Dispatch event and wait for response
725
- // Check if this is a controller using the symbol
726
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
727
- let targetElement = isController && this.element ? this.element : this;
728
-
729
- // If element is null (e.g., controller was detached), can't get context
730
- if (!targetElement || !targetElement.dispatchEvent) {
731
- return undefined;
732
- }
733
-
734
- // If we're in shadow DOM, dispatch on the host element to ensure proper bubbling
735
- if (targetElement.getRootNode && targetElement.getRootNode() instanceof ShadowRoot) {
736
- const shadowRoot = targetElement.getRootNode() as ShadowRoot;
737
- targetElement = shadowRoot.host as HTMLElement;
738
- }
739
-
740
- targetElement.dispatchEvent(event);
741
-
742
- // Check if context was provided via the event
743
- if (detail.context !== undefined) {
744
- // Cache it for future use
745
- this[ROUTER_CONTEXT] = detail.context;
746
- return detail.context;
747
- }
748
-
749
- return undefined;
750
- },
751
- enumerable: true,
752
- configurable: true
753
- });
754
- };
755
- }
756
-
757
- /**
758
- * Decorator for methods that should run when element is ready
759
- * Runs after shadow DOM, controller attachment, and event setup
760
- * Supports async methods
761
- */
762
- export function ready() {
763
- return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
764
- const constructor = target.constructor;
765
-
766
- if (!constructor[READY_HANDLERS]) {
767
- constructor[READY_HANDLERS] = [];
768
- }
769
-
770
- constructor[READY_HANDLERS].push({
771
- methodName,
772
- method: descriptor.value
773
- });
774
-
775
- return descriptor;
776
- };
777
- }
778
-
779
- /**
780
- * Decorator for methods that should run when element is being disposed
781
- * Used for cleanup tasks when element is removed from DOM
782
- */
783
- export function dispose() {
784
- return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
785
- const constructor = target.constructor;
786
-
787
- if (!constructor[DISPOSE_HANDLERS]) {
788
- constructor[DISPOSE_HANDLERS] = [];
789
- }
790
-
791
- constructor[DISPOSE_HANDLERS].push({
792
- methodName,
793
- method: descriptor.value
794
- });
795
-
796
- return descriptor;
797
- };
798
- }
799
-
800
- /**
801
- * Decorator for methods that render specific parts of the template
802
- * Parts are identified by the 'part' attribute in the HTML template
803
- * When the decorated method is called, it automatically re-renders its part
804
- */
805
- export function part(partName: string, options: PartOptions = {}) {
806
- return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
807
- const constructor = target.constructor;
808
- const originalMethod = descriptor.value;
809
-
810
- if (!constructor[PARTS]) {
811
- constructor[PARTS] = new Map();
812
- }
813
-
814
- constructor[PARTS].set(partName, {
815
- methodName,
816
- method: originalMethod
817
- });
818
-
819
- // Wrap the original method to automatically re-render the part when called
820
- descriptor.value = async function (this: HTMLElement, ...args: any[]) {
821
- // Initialize timers storage if not present
822
- if (!(this as any)[PART_TIMERS]) {
823
- (this as any)[PART_TIMERS] = new Map();
824
- }
825
-
826
- // Get or create timers for this specific part
827
- if (!(this as any)[PART_TIMERS].has(partName)) {
828
- (this as any)[PART_TIMERS].set(partName, {
829
- throttleTimer: null,
830
- debounceTimer: null,
831
- lastThrottleCall: 0
832
- });
833
- }
834
-
835
- const timers = (this as any)[PART_TIMERS].get(partName);
836
-
837
- // Create the render function
838
- const renderPart = async () => {
839
- // Call the original method to get the content
840
- const result = originalMethod.apply(this, args);
841
- const content = result instanceof Promise ? await result : result;
842
-
843
- // Re-render the part if shadow DOM exists and content is defined
844
- if (this.shadowRoot && content !== undefined) {
845
- const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
846
- if (partElement) {
847
- partElement.innerHTML = content;
848
- }
849
- }
850
-
851
- return content;
852
- };
853
-
854
- // Handle debounce (only if positive value)
855
- if (options.debounce !== undefined && options.debounce > 0) {
856
- if (timers.debounceTimer) {
857
- clearTimeout(timers.debounceTimer);
858
- }
859
-
860
- return new Promise((resolve) => {
861
- timers.debounceTimer = setTimeout(async () => {
862
- const result = await renderPart();
863
- resolve(result);
864
- }, options.debounce);
865
- });
866
- }
867
-
868
- // Handle throttle (only if positive value)
869
- if (options.throttle !== undefined && options.throttle > 0) {
870
- const now = Date.now();
871
-
872
- if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
873
- timers.lastThrottleCall = now;
874
- return await renderPart();
875
- } else {
876
- // If throttled, schedule the next call if not already scheduled
877
- if (!timers.throttleTimer) {
878
- const remainingTime = options.throttle - (now - timers.lastThrottleCall);
879
- timers.throttleTimer = setTimeout(async () => {
880
- timers.throttleTimer = null;
881
- timers.lastThrottleCall = Date.now();
882
- await renderPart();
883
- }, remainingTime);
884
- }
885
- // For throttled calls, don't execute the original method, just return undefined
886
- // The actual render will happen in the scheduled timeout
887
- return undefined;
888
- }
889
- }
890
-
891
- // No throttle/debounce - render immediately
892
- return await renderPart();
893
- };
894
-
895
- return descriptor;
896
- };
897
- }