snice 1.14.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/bin/templates/base/tsconfig.json +5 -4
  2. package/components/accordion/demo.html +403 -0
  3. package/components/accordion/snice-accordion-item.css +85 -0
  4. package/components/accordion/snice-accordion-item.ts +226 -0
  5. package/components/accordion/snice-accordion.css +31 -0
  6. package/components/accordion/snice-accordion.ts +182 -0
  7. package/components/accordion/snice-accordion.types.ts +32 -0
  8. package/components/alert/demo.html +445 -0
  9. package/components/alert/snice-alert.css +195 -0
  10. package/components/alert/snice-alert.ts +141 -0
  11. package/components/alert/snice-alert.types.ts +12 -0
  12. package/components/avatar/demo.html +598 -0
  13. package/components/avatar/snice-avatar.css +131 -0
  14. package/components/avatar/snice-avatar.ts +136 -0
  15. package/components/avatar/snice-avatar.types.ts +13 -0
  16. package/components/badge/demo.html +523 -0
  17. package/components/badge/snice-badge.css +161 -0
  18. package/components/badge/snice-badge.ts +117 -0
  19. package/components/badge/snice-badge.types.ts +16 -0
  20. package/components/breadcrumbs/demo.html +404 -0
  21. package/components/breadcrumbs/snice-breadcrumbs.css +133 -0
  22. package/components/breadcrumbs/snice-breadcrumbs.ts +191 -0
  23. package/components/breadcrumbs/snice-breadcrumbs.types.ts +26 -0
  24. package/components/breadcrumbs/snice-crumb.ts +26 -0
  25. package/components/button/demo.html +42 -0
  26. package/components/button/snice-button.css +230 -0
  27. package/components/button/snice-button.ts +169 -0
  28. package/components/button/snice-button.types.ts +25 -0
  29. package/components/card/demo.html +525 -0
  30. package/components/card/snice-card.css +140 -0
  31. package/components/card/snice-card.ts +102 -0
  32. package/components/card/snice-card.types.ts +10 -0
  33. package/components/checkbox/demo.html +253 -0
  34. package/components/checkbox/snice-checkbox.css +164 -0
  35. package/components/checkbox/snice-checkbox.ts +223 -0
  36. package/components/checkbox/snice-checkbox.types.ts +22 -0
  37. package/components/chip/demo.html +383 -0
  38. package/components/chip/snice-chip.css +195 -0
  39. package/components/chip/snice-chip.ts +139 -0
  40. package/components/chip/snice-chip.types.ts +15 -0
  41. package/components/date-picker/README.md +233 -0
  42. package/components/date-picker/demo.html +191 -0
  43. package/components/date-picker/snice-date-picker.css +330 -0
  44. package/components/date-picker/snice-date-picker.ts +777 -0
  45. package/components/date-picker/snice-date-picker.types.ts +83 -0
  46. package/components/divider/demo.html +233 -0
  47. package/components/divider/snice-divider.css +155 -0
  48. package/components/divider/snice-divider.ts +69 -0
  49. package/components/divider/snice-divider.types.ts +15 -0
  50. package/components/drawer/demo.html +328 -0
  51. package/components/drawer/snice-drawer.css +476 -0
  52. package/components/drawer/snice-drawer.ts +287 -0
  53. package/components/drawer/snice-drawer.types.ts +17 -0
  54. package/components/global.d.ts +14 -0
  55. package/components/input/demo.html +303 -0
  56. package/components/input/snice-input.css +257 -0
  57. package/components/input/snice-input.ts +442 -0
  58. package/components/input/snice-input.types.ts +59 -0
  59. package/components/input/test.html +77 -0
  60. package/components/layout/README.md +260 -0
  61. package/components/layout/demo.html +538 -0
  62. package/components/layout/snice-layout-blog.css +129 -0
  63. package/components/layout/snice-layout-blog.ts +48 -0
  64. package/components/layout/snice-layout-card.css +104 -0
  65. package/components/layout/snice-layout-card.ts +35 -0
  66. package/components/layout/snice-layout-centered.css +51 -0
  67. package/components/layout/snice-layout-centered.ts +22 -0
  68. package/components/layout/snice-layout-dashboard.css +98 -0
  69. package/components/layout/snice-layout-dashboard.ts +45 -0
  70. package/components/layout/snice-layout-fullscreen.css +72 -0
  71. package/components/layout/snice-layout-fullscreen.ts +34 -0
  72. package/components/layout/snice-layout-landing.css +92 -0
  73. package/components/layout/snice-layout-landing.ts +47 -0
  74. package/components/layout/snice-layout-minimal.css +16 -0
  75. package/components/layout/snice-layout-minimal.ts +19 -0
  76. package/components/layout/snice-layout-sidebar.css +117 -0
  77. package/components/layout/snice-layout-sidebar.ts +48 -0
  78. package/components/layout/snice-layout-split.css +103 -0
  79. package/components/layout/snice-layout-split.ts +29 -0
  80. package/components/layout/snice-layout.css +72 -0
  81. package/components/layout/snice-layout.ts +35 -0
  82. package/components/layout/snice-layout.types.ts +5 -0
  83. package/components/login/demo-auth-controller.ts +185 -0
  84. package/components/login/demo.html +470 -0
  85. package/components/login/snice-login.css +204 -0
  86. package/components/login/snice-login.ts +337 -0
  87. package/components/login/snice-login.types.ts +34 -0
  88. package/components/modal/demo.html +291 -0
  89. package/components/modal/snice-modal.css +203 -0
  90. package/components/modal/snice-modal.ts +233 -0
  91. package/components/modal/snice-modal.types.ts +21 -0
  92. package/components/pagination/demo.html +395 -0
  93. package/components/pagination/snice-pagination.ts +333 -0
  94. package/components/pagination/snice-pagination.types.ts +21 -0
  95. package/components/progress/demo.html +510 -0
  96. package/components/progress/snice-progress.css +267 -0
  97. package/components/progress/snice-progress.ts +247 -0
  98. package/components/progress/snice-progress.types.ts +19 -0
  99. package/components/radio/demo.html +287 -0
  100. package/components/radio/snice-radio.css +171 -0
  101. package/components/radio/snice-radio.ts +218 -0
  102. package/components/radio/snice-radio.types.ts +21 -0
  103. package/components/select/demo.html +511 -0
  104. package/components/select/snice-option.ts +52 -0
  105. package/components/select/snice-option.types.ts +14 -0
  106. package/components/select/snice-select.css +392 -0
  107. package/components/select/snice-select.ts +796 -0
  108. package/components/select/snice-select.types.ts +55 -0
  109. package/components/skeleton/demo.html +514 -0
  110. package/components/skeleton/snice-skeleton.css +109 -0
  111. package/components/skeleton/snice-skeleton.ts +126 -0
  112. package/components/skeleton/snice-skeleton.types.ts +11 -0
  113. package/components/switch/demo.html +284 -0
  114. package/components/switch/snice-switch.css +221 -0
  115. package/components/switch/snice-switch.ts +229 -0
  116. package/components/switch/snice-switch.types.ts +23 -0
  117. package/components/symbols.ts +23 -0
  118. package/components/table/demo-table-controller.ts +100 -0
  119. package/components/table/demo.html +480 -0
  120. package/components/table/snice-cell-boolean.ts +112 -0
  121. package/components/table/snice-cell-date.ts +210 -0
  122. package/components/table/snice-cell-duration.ts +91 -0
  123. package/components/table/snice-cell-filesize.ts +90 -0
  124. package/components/table/snice-cell-number.ts +165 -0
  125. package/components/table/snice-cell-progress.ts +83 -0
  126. package/components/table/snice-cell-rating.ts +82 -0
  127. package/components/table/snice-cell-sparkline.ts +253 -0
  128. package/components/table/snice-cell-text.ts +125 -0
  129. package/components/table/snice-cell.css +296 -0
  130. package/components/table/snice-cell.ts +473 -0
  131. package/components/table/snice-column.ts +353 -0
  132. package/components/table/snice-header.css +243 -0
  133. package/components/table/snice-header.ts +261 -0
  134. package/components/table/snice-progress.ts +66 -0
  135. package/components/table/snice-rating.ts +45 -0
  136. package/components/table/snice-row.css +255 -0
  137. package/components/table/snice-row.ts +331 -0
  138. package/components/table/snice-table.css +241 -0
  139. package/components/table/snice-table.ts +737 -0
  140. package/components/table/snice-table.types.ts +158 -0
  141. package/components/tabs/demo.html +487 -0
  142. package/components/tabs/snice-tab-panel.css +264 -0
  143. package/components/tabs/snice-tab-panel.ts +47 -0
  144. package/components/tabs/snice-tab.css +96 -0
  145. package/components/tabs/snice-tab.ts +65 -0
  146. package/components/tabs/snice-tabs.css +189 -0
  147. package/components/tabs/snice-tabs.ts +332 -0
  148. package/components/tabs/snice-tabs.types.ts +28 -0
  149. package/components/theme/theme.css +234 -0
  150. package/components/toast/demo.html +329 -0
  151. package/components/toast/snice-toast-container.ts +256 -0
  152. package/components/toast/snice-toast.css +213 -0
  153. package/components/toast/snice-toast.ts +276 -0
  154. package/components/toast/snice-toast.types.ts +35 -0
  155. package/components/tooltip/demo.html +350 -0
  156. package/components/tooltip/snice-tooltip-portal.css +79 -0
  157. package/components/tooltip/snice-tooltip.css +117 -0
  158. package/components/tooltip/snice-tooltip.ts +612 -0
  159. package/components/tooltip/snice-tooltip.types.ts +32 -0
  160. package/components/transitions.ts +94 -0
  161. package/components/tsconfig.json +18 -0
  162. package/dist/index.cjs +441 -329
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.cjs.min.map +1 -1
  165. package/dist/index.esm.js +441 -329
  166. package/dist/index.esm.js.map +1 -1
  167. package/dist/index.esm.min.js +3 -3
  168. package/dist/index.esm.min.js.map +1 -1
  169. package/dist/index.iife.js +441 -329
  170. package/dist/index.iife.js.map +1 -1
  171. package/dist/index.iife.min.js +3 -3
  172. package/dist/index.iife.min.js.map +1 -1
  173. package/dist/symbols.esm.js +1 -1
  174. package/dist/transitions.esm.js +1 -1
  175. package/dist/types/controller.d.ts +1 -1
  176. package/dist/types/element.d.ts +10 -10
  177. package/dist/types/events.d.ts +2 -2
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/types/observe.d.ts +1 -1
  180. package/dist/types/request-response.d.ts +2 -3
  181. package/dist/types/router.d.ts +1 -1
  182. package/package.json +9 -3
  183. package/dist/index.cjs.min +0 -15
  184. package/dist/symbols.cjs +0 -103
  185. package/dist/transitions.cjs +0 -219
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.14.2
2
+ * snice v1.14.3
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -82,24 +82,27 @@ function on(eventName, selectorOrOptions, options) {
82
82
  selector = undefined;
83
83
  opts = selectorOrOptions;
84
84
  }
85
- return function (target, propertyKey, descriptor) {
86
- // Store event handler metadata
87
- if (!target[ON_HANDLERS]) {
88
- target[ON_HANDLERS] = [];
89
- }
90
- // Normalize to array and expand at decoration time
91
- const eventNames = Array.isArray(eventName) ? eventName : [eventName];
92
- // Create a handler entry for each event
93
- for (const event of eventNames) {
94
- target[ON_HANDLERS].push({
95
- eventName: event,
96
- selector,
97
- methodName: propertyKey,
98
- method: descriptor.value,
99
- options: opts
100
- });
101
- }
102
- return descriptor;
85
+ return function (target, context) {
86
+ const propertyKey = context.name;
87
+ context.addInitializer(function () {
88
+ const constructor = this.constructor;
89
+ // Store event handler metadata
90
+ if (!constructor.prototype[ON_HANDLERS]) {
91
+ constructor.prototype[ON_HANDLERS] = [];
92
+ }
93
+ // Normalize to array and expand at decoration time
94
+ const eventNames = Array.isArray(eventName) ? eventName : [eventName];
95
+ // Create a handler entry for each event
96
+ for (const event of eventNames) {
97
+ constructor.prototype[ON_HANDLERS].push({
98
+ eventName: event,
99
+ selector,
100
+ methodName: propertyKey,
101
+ method: target,
102
+ options: opts
103
+ });
104
+ }
105
+ });
103
106
  };
104
107
  }
105
108
  // Helper to setup event handlers for elements
@@ -293,13 +296,12 @@ function cleanupEventHandlers(instance) {
293
296
  * @param options Optional configuration extending EventInit
294
297
  */
295
298
  function dispatch(eventName, options) {
296
- return function (_target, _propertyKey, descriptor) {
297
- const originalMethod = descriptor.value;
299
+ return function (originalMethod, _context) {
298
300
  // Create timing wrappers for dispatch
299
301
  let debounceTimeout;
300
302
  let throttleLastCall = 0;
301
303
  let throttleTimeout;
302
- descriptor.value = function (...args) {
304
+ return function (...args) {
303
305
  // Call the original method
304
306
  const result = originalMethod.apply(this, args);
305
307
  // Helper to dispatch the event
@@ -354,7 +356,6 @@ function dispatch(eventName, options) {
354
356
  timedDispatch(result);
355
357
  return result;
356
358
  };
357
- return descriptor;
358
359
  };
359
360
  }
360
361
 
@@ -381,27 +382,30 @@ function observe(observeTarget, selectorOrOptions, options) {
381
382
  selector = undefined;
382
383
  opts = selectorOrOptions;
383
384
  }
384
- return function (target, propertyKey, descriptor) {
385
- // Store observer metadata
386
- if (!target[OBSERVERS]) {
387
- target[OBSERVERS] = [];
388
- }
389
- // Normalize to array
390
- const observeTargets = Array.isArray(observeTarget) ? observeTarget : [observeTarget];
391
- // Create an observer entry for each target
392
- for (const targetString of observeTargets) {
393
- // Parse the observation type from the observeTarget string
394
- const [type, ...modifiers] = targetString.split(':');
395
- target[OBSERVERS].push({
396
- type,
397
- target: modifiers.join(':'), // Rejoin for media queries or mutation types
398
- selector,
399
- methodName: propertyKey,
400
- method: descriptor.value,
401
- options: opts
402
- });
403
- }
404
- return descriptor;
385
+ return function (target, context) {
386
+ const propertyKey = context.name;
387
+ context.addInitializer(function () {
388
+ const constructor = this.constructor;
389
+ // Store observer metadata
390
+ if (!constructor.prototype[OBSERVERS]) {
391
+ constructor.prototype[OBSERVERS] = [];
392
+ }
393
+ // Normalize to array
394
+ const observeTargets = Array.isArray(observeTarget) ? observeTarget : [observeTarget];
395
+ // Create an observer entry for each target
396
+ for (const targetString of observeTargets) {
397
+ // Parse the observation type from the observeTarget string
398
+ const [type, ...modifiers] = targetString.split(':');
399
+ constructor.prototype[OBSERVERS].push({
400
+ type,
401
+ target: modifiers.join(':'), // Rejoin for media queries or mutation types
402
+ selector,
403
+ methodName: propertyKey,
404
+ method: target,
405
+ options: opts
406
+ });
407
+ }
408
+ });
405
409
  };
406
410
  }
407
411
  // Helper to setup observers for elements
@@ -694,13 +698,12 @@ function cleanupObservers(instance) {
694
698
  * @param options Optional configuration
695
699
  */
696
700
  function request(requestName, options) {
697
- return function (_target, _propertyKey, descriptor) {
698
- const originalMethod = descriptor.value;
701
+ return function (originalMethod, _context) {
699
702
  // Create timing variables for debounce/throttle
700
703
  let debounceTimeout;
701
704
  let throttleLastCall = 0;
702
705
  let throttleTimeout;
703
- descriptor.value = async function (...args) {
706
+ return async function (...args) {
704
707
  const actualRequest = async () => {
705
708
  // @request always acts as requester (client side)
706
709
  const responseTimeout = options?.timeout ?? 120000; // Default 2 minute timeout
@@ -824,7 +827,6 @@ function request(requestName, options) {
824
827
  // No timing applied, execute immediately
825
828
  return actualRequest();
826
829
  };
827
- return descriptor;
828
830
  };
829
831
  }
830
832
  /**
@@ -834,20 +836,22 @@ function request(requestName, options) {
834
836
  * @param options Optional configuration
835
837
  */
836
838
  function respond(requestName, options) {
837
- return function (target, propertyKey, descriptor) {
838
- const originalMethod = descriptor.value;
839
- // Store response metadata on the prototype
840
- // This will be picked up by setupResponseHandlers
841
- if (!target[CHANNEL_HANDLERS]) {
842
- target[CHANNEL_HANDLERS] = [];
843
- }
844
- target[CHANNEL_HANDLERS].push({
845
- channelName: requestName,
846
- methodName: propertyKey,
847
- method: originalMethod,
848
- options: options
839
+ return function (target, context) {
840
+ const propertyKey = context.name;
841
+ context.addInitializer(function () {
842
+ const constructor = this.constructor;
843
+ // Store response metadata on the prototype
844
+ // This will be picked up by setupResponseHandlers
845
+ if (!constructor.prototype[CHANNEL_HANDLERS]) {
846
+ constructor.prototype[CHANNEL_HANDLERS] = [];
847
+ }
848
+ constructor.prototype[CHANNEL_HANDLERS].push({
849
+ channelName: requestName,
850
+ methodName: propertyKey,
851
+ method: target,
852
+ options: options
853
+ });
849
854
  });
850
- return descriptor;
851
855
  };
852
856
  }
853
857
  // Helper to setup response handlers for elements and controllers
@@ -998,7 +1002,7 @@ class ControllerScope {
998
1002
  * @param name The name to register the controller under
999
1003
  */
1000
1004
  function controller(name) {
1001
- return function (constructor) {
1005
+ return function (constructor, _context) {
1002
1006
  snice.controllerRegistry.set(name, constructor);
1003
1007
  // Mark as controller class for channel decorator detection
1004
1008
  constructor.prototype[IS_CONTROLLER_CLASS] = true;
@@ -1345,10 +1349,12 @@ function applyElementFunctionality(constructor) {
1345
1349
  // Mark that properties have been initialized
1346
1350
  this[PROPERTIES_INITIALIZED] = true;
1347
1351
  // Reflect properties that were explicitly set before connection
1348
- // but skip default values that were never explicitly set
1349
- if (properties && this[EXPLICITLY_SET_PROPERTIES]) {
1352
+ // AND also reflect initial values that have reflect: true
1353
+ if (properties) {
1350
1354
  for (const [propName, propOptions] of properties) {
1351
- if (propOptions.reflect && this[EXPLICITLY_SET_PROPERTIES].has(propName) && propName in this[PROPERTY_VALUES]) {
1355
+ const wasExplicitlySet = this[EXPLICITLY_SET_PROPERTIES] && this[EXPLICITLY_SET_PROPERTIES].has(propName);
1356
+ const hasInitialValue = propName in this[PROPERTY_VALUES];
1357
+ if (propOptions.reflect && hasInitialValue && (wasExplicitlySet || this[PROPERTY_VALUES][propName] !== undefined)) {
1352
1358
  const value = this[PROPERTY_VALUES][propName];
1353
1359
  const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1354
1360
  if (value !== null && value !== undefined && value !== false &&
@@ -1597,176 +1603,224 @@ function applyElementFunctionality(constructor) {
1597
1603
  };
1598
1604
  }
1599
1605
  function element(tagName) {
1600
- return function (constructor) {
1606
+ return function (constructor, context) {
1607
+ // Transfer metadata from context to constructor
1608
+ if (context.metadata && context.metadata[PROPERTIES]) {
1609
+ if (!constructor[PROPERTIES]) {
1610
+ constructor[PROPERTIES] = new Map();
1611
+ }
1612
+ for (const [key, value] of context.metadata[PROPERTIES]) {
1613
+ constructor[PROPERTIES].set(key, value);
1614
+ }
1615
+ }
1601
1616
  applyElementFunctionality(constructor);
1602
1617
  customElements.define(tagName, constructor);
1618
+ return constructor;
1603
1619
  };
1604
1620
  }
1605
1621
  function layout(tagName) {
1606
- return function (constructor) {
1622
+ return function (constructor, context) {
1623
+ // Transfer metadata from context to constructor
1624
+ if (context.metadata && context.metadata[PROPERTIES]) {
1625
+ if (!constructor[PROPERTIES]) {
1626
+ constructor[PROPERTIES] = new Map();
1627
+ }
1628
+ for (const [key, value] of context.metadata[PROPERTIES]) {
1629
+ constructor[PROPERTIES].set(key, value);
1630
+ }
1631
+ }
1607
1632
  applyElementFunctionality(constructor);
1608
1633
  customElements.define(tagName, constructor);
1634
+ return constructor;
1609
1635
  };
1610
1636
  }
1611
1637
  function property(options) {
1612
- return function (target, propertyKey) {
1613
- const constructor = target.constructor;
1614
- // Warn about problematic reflection usage
1638
+ return function (_value, context) {
1639
+ const propertyKey = context.name;
1640
+ // Use metadata to store property information at decoration time
1641
+ if (!context.metadata) {
1642
+ context.metadata = {};
1643
+ }
1644
+ if (!context.metadata[PROPERTIES]) {
1645
+ context.metadata[PROPERTIES] = new Map();
1646
+ }
1647
+ context.metadata[PROPERTIES].set(propertyKey, options || {});
1648
+ // Warn about problematic reflection usage at decoration time
1615
1649
  if (options?.reflect && options?.type === Array) {
1616
1650
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Array type.`);
1617
1651
  }
1618
1652
  if (options?.reflect && options?.type === Object) {
1619
1653
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
1620
1654
  }
1621
- if (!constructor[PROPERTIES]) {
1622
- constructor[PROPERTIES] = new Map();
1623
- }
1624
- constructor[PROPERTIES].set(propertyKey, options || {});
1625
- const descriptor = {
1626
- get() {
1627
- if (!this[PROPERTY_VALUES]) {
1628
- this[PROPERTY_VALUES] = {};
1629
- }
1630
- return this[PROPERTY_VALUES][propertyKey];
1631
- },
1632
- set(value) {
1633
- if (!this[PROPERTY_VALUES]) {
1634
- this[PROPERTY_VALUES] = {};
1635
- }
1636
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
1637
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
1638
- }
1639
- const oldValue = this[PROPERTY_VALUES][propertyKey];
1640
- // Don't update if value hasn't changed
1641
- if (oldValue === value)
1642
- return;
1643
- // Mark as explicitly set in these cases:
1644
- // 1. There was a previous value (normal property update)
1645
- // 2. This is during element construction and we have a non-null/non-undefined value
1646
- // (this handles default values declared in class properties)
1647
- const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
1648
- if (oldValue !== undefined || (isInitialDefaultValue && value !== null && value !== undefined)) {
1649
- this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1650
- }
1651
- this[PROPERTY_VALUES][propertyKey] = value;
1652
- // Only reflect to attributes if:
1653
- // 1. Properties have been initialized from attributes
1654
- // 2. The property was explicitly set (not just default value)
1655
- // This prevents default values from creating attributes
1656
- if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1657
- const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1658
- if (value === null || value === undefined || value === false ||
1659
- (options?.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
1660
- this.removeAttribute(attributeName);
1661
- }
1662
- else {
1663
- // Handle special types for reflection
1664
- let attributeValue;
1665
- if (value instanceof Date) {
1666
- attributeValue = value.toISOString();
1655
+ context.addInitializer(function () {
1656
+ // No longer need warnings here since they're at decoration time
1657
+ });
1658
+ // Return a field initializer function for new decorators
1659
+ return function (initialValue) {
1660
+ // Set up the property descriptor on first access
1661
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1662
+ const descriptor = {
1663
+ get() {
1664
+ if (!this[PROPERTY_VALUES]) {
1665
+ this[PROPERTY_VALUES] = {};
1667
1666
  }
1668
- else if (typeof value === 'bigint') {
1669
- attributeValue = value.toString() + 'n';
1667
+ return this[PROPERTY_VALUES][propertyKey];
1668
+ },
1669
+ set(newValue) {
1670
+ if (!this[PROPERTY_VALUES]) {
1671
+ this[PROPERTY_VALUES] = {};
1670
1672
  }
1671
- else if (options?.type === SimpleArray && Array.isArray(value)) {
1672
- attributeValue = SimpleArray.serialize(value);
1673
+ if (!this[EXPLICITLY_SET_PROPERTIES]) {
1674
+ this[EXPLICITLY_SET_PROPERTIES] = new Set();
1673
1675
  }
1674
- else {
1675
- attributeValue = String(value);
1676
+ const oldValue = this[PROPERTY_VALUES][propertyKey];
1677
+ if (oldValue === newValue)
1678
+ return;
1679
+ const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
1680
+ if (oldValue !== undefined || (isInitialDefaultValue && newValue !== null && newValue !== undefined)) {
1681
+ this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1676
1682
  }
1677
- this.setAttribute(attributeName, attributeValue);
1678
- }
1679
- }
1680
- // Call watchers for this property
1681
- const watchers = constructor[PROPERTY_WATCHERS];
1682
- if (watchers) {
1683
- // Call specific property watchers
1684
- if (watchers.has(propertyKey)) {
1685
- const propertyWatchers = watchers.get(propertyKey);
1686
- for (const watcher of propertyWatchers) {
1687
- try {
1688
- // Always pass oldValue, newValue, and propertyName
1689
- watcher.method.call(this, oldValue, value, propertyKey);
1683
+ this[PROPERTY_VALUES][propertyKey] = newValue;
1684
+ if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1685
+ const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1686
+ if (newValue === null || newValue === undefined || newValue === false ||
1687
+ (options?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1688
+ this.removeAttribute(attributeName);
1690
1689
  }
1691
- catch (error) {
1692
- console.error(`Error in @watch('${propertyKey}') method ${watcher.methodName}:`, error);
1690
+ else {
1691
+ let attributeValue;
1692
+ if (newValue instanceof Date) {
1693
+ attributeValue = newValue.toISOString();
1694
+ }
1695
+ else if (typeof newValue === 'bigint') {
1696
+ attributeValue = newValue.toString() + 'n';
1697
+ }
1698
+ else if (options?.type === SimpleArray && Array.isArray(newValue)) {
1699
+ attributeValue = SimpleArray.serialize(newValue);
1700
+ }
1701
+ else {
1702
+ attributeValue = String(newValue);
1703
+ }
1704
+ this.setAttribute(attributeName, attributeValue);
1693
1705
  }
1694
1706
  }
1695
- }
1696
- // Call wildcard watchers (watching "*")
1697
- if (watchers.has('*')) {
1698
- const wildcardWatchers = watchers.get('*');
1699
- for (const watcher of wildcardWatchers) {
1700
- try {
1701
- // Same signature for consistency
1702
- watcher.method.call(this, oldValue, value, propertyKey);
1707
+ const constructor = this.constructor;
1708
+ const watchers = constructor[PROPERTY_WATCHERS];
1709
+ if (watchers) {
1710
+ if (watchers.has(propertyKey)) {
1711
+ const propertyWatchers = watchers.get(propertyKey);
1712
+ for (const watcher of propertyWatchers) {
1713
+ try {
1714
+ watcher.method.call(this, oldValue, newValue, propertyKey);
1715
+ }
1716
+ catch (error) {
1717
+ console.error(`Error in @watch('${propertyKey}') method ${watcher.methodName}:`, error);
1718
+ }
1719
+ }
1703
1720
  }
1704
- catch (error) {
1705
- console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1721
+ if (watchers.has('*')) {
1722
+ const wildcardWatchers = watchers.get('*');
1723
+ for (const watcher of wildcardWatchers) {
1724
+ try {
1725
+ watcher.method.call(this, oldValue, newValue, propertyKey);
1726
+ }
1727
+ catch (error) {
1728
+ console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1729
+ }
1730
+ }
1706
1731
  }
1707
1732
  }
1708
- }
1709
- }
1710
- // Call requestUpdate if available and value changed
1711
- if (this.requestUpdate) {
1712
- this.requestUpdate(propertyKey, oldValue);
1713
- }
1714
- },
1715
- enumerable: true,
1716
- configurable: true,
1733
+ if (this.requestUpdate) {
1734
+ this.requestUpdate(propertyKey, oldValue);
1735
+ }
1736
+ },
1737
+ configurable: true,
1738
+ enumerable: true
1739
+ };
1740
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1741
+ }
1742
+ // Initialize the property value
1743
+ if (!this[PROPERTY_VALUES]) {
1744
+ this[PROPERTY_VALUES] = {};
1745
+ }
1746
+ this[PROPERTY_VALUES][propertyKey] = initialValue;
1747
+ return initialValue;
1717
1748
  };
1718
- Object.defineProperty(target, propertyKey, descriptor);
1719
1749
  };
1720
1750
  }
1721
1751
  function query(selector, options = {}) {
1722
- return function (target, propertyKey) {
1752
+ return function (_value, context) {
1723
1753
  // Default to shadow DOM only
1724
1754
  const { light = false, shadow = true } = options;
1725
- Object.defineProperty(target, propertyKey, {
1726
- get() {
1727
- // Check if this is a controller using the symbol
1728
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1729
- const root = isController && this.element ? this.element : this;
1730
- // Query in specified contexts
1731
- let result = null;
1732
- if (shadow && root.shadowRoot) {
1733
- result = root.shadowRoot.querySelector(selector);
1734
- }
1735
- if (!result && light) {
1736
- result = root.querySelector(selector);
1737
- }
1738
- return result || null;
1739
- },
1740
- enumerable: true,
1741
- configurable: true,
1742
- });
1755
+ const propertyKey = context.name;
1756
+ // Return a field initializer function for new decorators
1757
+ return function (initialValue) {
1758
+ // Set up the property descriptor on first access
1759
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1760
+ const descriptor = {
1761
+ get() {
1762
+ // Check if this is a controller using the symbol
1763
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1764
+ const root = isController && this.element ? this.element : this;
1765
+ // Query in specified contexts
1766
+ let result = null;
1767
+ if (shadow && root.shadowRoot) {
1768
+ result = root.shadowRoot.querySelector(selector);
1769
+ }
1770
+ if (!result && light) {
1771
+ result = root.querySelector(selector);
1772
+ }
1773
+ return result || null;
1774
+ },
1775
+ set() {
1776
+ // Query results are read-only
1777
+ },
1778
+ configurable: true,
1779
+ enumerable: true
1780
+ };
1781
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1782
+ }
1783
+ return initialValue;
1784
+ };
1743
1785
  };
1744
1786
  }
1745
1787
  function queryAll(selector, options = {}) {
1746
- return function (target, propertyKey) {
1788
+ return function (_value, context) {
1747
1789
  // Default to shadow DOM only
1748
1790
  const { light = false, shadow = true } = options;
1749
- Object.defineProperty(target, propertyKey, {
1750
- get() {
1751
- // Check if this is a controller using the symbol
1752
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1753
- const root = isController && this.element ? this.element : this;
1754
- // Query in specified contexts and combine results
1755
- const results = [];
1756
- if (shadow && root.shadowRoot) {
1757
- const shadowResults = root.shadowRoot.querySelectorAll(selector);
1758
- results.push(...shadowResults);
1759
- }
1760
- if (light) {
1761
- const lightResults = root.querySelectorAll(selector);
1762
- results.push(...lightResults);
1763
- }
1764
- // Return a static NodeList-like object
1765
- return results;
1766
- },
1767
- enumerable: true,
1768
- configurable: true,
1769
- });
1791
+ const propertyKey = context.name;
1792
+ // Return a field initializer function for new decorators
1793
+ return function (initialValue) {
1794
+ // Set up the property descriptor on first access
1795
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1796
+ const descriptor = {
1797
+ get() {
1798
+ // Check if this is a controller using the symbol
1799
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1800
+ const root = isController && this.element ? this.element : this;
1801
+ // Query in specified contexts and combine results
1802
+ const results = [];
1803
+ if (shadow && root.shadowRoot) {
1804
+ const shadowResults = root.shadowRoot.querySelectorAll(selector);
1805
+ results.push(...shadowResults);
1806
+ }
1807
+ if (light) {
1808
+ const lightResults = root.querySelectorAll(selector);
1809
+ results.push(...lightResults);
1810
+ }
1811
+ // Return a static NodeList-like object
1812
+ return results;
1813
+ },
1814
+ set() {
1815
+ // Query results are read-only
1816
+ },
1817
+ configurable: true,
1818
+ enumerable: true
1819
+ };
1820
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1821
+ }
1822
+ return initialValue;
1823
+ };
1770
1824
  };
1771
1825
  }
1772
1826
  /**
@@ -1776,6 +1830,7 @@ function queryAll(selector, options = {}) {
1776
1830
  * Strings cannot contain the full-width comma character
1777
1831
  */
1778
1832
  class SimpleArray {
1833
+ static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1779
1834
  /**
1780
1835
  * Serialize array to string for attribute storage
1781
1836
  */
@@ -1825,24 +1880,25 @@ class SimpleArray {
1825
1880
  });
1826
1881
  }
1827
1882
  }
1828
- SimpleArray.SEPARATOR = ','; // U+FF0C Full-width comma
1829
1883
  function watch(...propertyNames) {
1830
- return function (target, methodName, descriptor) {
1831
- const constructor = target.constructor;
1832
- if (!constructor[PROPERTY_WATCHERS]) {
1833
- constructor[PROPERTY_WATCHERS] = new Map();
1834
- }
1835
- // Store the watcher method for each property
1836
- for (const propertyName of propertyNames) {
1837
- if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
1838
- constructor[PROPERTY_WATCHERS].set(propertyName, []);
1884
+ return function (target, context) {
1885
+ const methodName = context.name;
1886
+ context.addInitializer(function () {
1887
+ const constructor = this.constructor;
1888
+ if (!constructor[PROPERTY_WATCHERS]) {
1889
+ constructor[PROPERTY_WATCHERS] = new Map();
1890
+ }
1891
+ // Store the watcher method for each property
1892
+ for (const propertyName of propertyNames) {
1893
+ if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
1894
+ constructor[PROPERTY_WATCHERS].set(propertyName, []);
1895
+ }
1896
+ constructor[PROPERTY_WATCHERS].get(propertyName).push({
1897
+ methodName,
1898
+ method: target
1899
+ });
1839
1900
  }
1840
- constructor[PROPERTY_WATCHERS].get(propertyName).push({
1841
- methodName,
1842
- method: descriptor.value
1843
- });
1844
- }
1845
- return descriptor;
1901
+ });
1846
1902
  };
1847
1903
  }
1848
1904
  /**
@@ -1850,46 +1906,57 @@ function watch(...propertyNames) {
1850
1906
  * The context is automatically provided to page components by the router
1851
1907
  */
1852
1908
  function context() {
1853
- return function (target, propertyKey) {
1854
- // Define property getter that returns the context
1855
- Object.defineProperty(target, propertyKey, {
1856
- get() {
1857
- // First check if context is stored directly on this element
1858
- if (this[ROUTER_CONTEXT] !== undefined) {
1859
- return this[ROUTER_CONTEXT];
1860
- }
1861
- // Otherwise, request context from parent page via event
1862
- const detail = { target: this };
1863
- const event = new CustomEvent('@context/request', {
1864
- bubbles: true,
1865
- cancelable: true,
1866
- detail
1867
- });
1868
- // Dispatch event and wait for response
1869
- // Check if this is a controller using the symbol
1870
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1871
- let targetElement = isController && this.element ? this.element : this;
1872
- // If element is null (e.g., controller was detached), can't get context
1873
- if (!targetElement || !targetElement.dispatchEvent) {
1874
- return undefined;
1875
- }
1876
- // If we're in shadow DOM, dispatch on the host element to ensure proper bubbling
1877
- if (targetElement.getRootNode && targetElement.getRootNode() instanceof ShadowRoot) {
1878
- const shadowRoot = targetElement.getRootNode();
1879
- targetElement = shadowRoot.host;
1880
- }
1881
- targetElement.dispatchEvent(event);
1882
- // Check if context was provided via the event
1883
- if (detail.context !== undefined) {
1884
- // Cache it for future use
1885
- this[ROUTER_CONTEXT] = detail.context;
1886
- return detail.context;
1887
- }
1888
- return undefined;
1889
- },
1890
- enumerable: true,
1891
- configurable: true
1892
- });
1909
+ return function (_value, context) {
1910
+ const propertyKey = context.name;
1911
+ // Return a field initializer function for new decorators
1912
+ return function (initialValue) {
1913
+ // Set up the property descriptor on first access
1914
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1915
+ const descriptor = {
1916
+ get() {
1917
+ // First check if context is stored directly on this element
1918
+ if (this[ROUTER_CONTEXT] !== undefined) {
1919
+ return this[ROUTER_CONTEXT];
1920
+ }
1921
+ // Otherwise, request context from parent page via event
1922
+ const detail = { target: this };
1923
+ const event = new CustomEvent('@context/request', {
1924
+ bubbles: true,
1925
+ cancelable: true,
1926
+ detail
1927
+ });
1928
+ // Dispatch event and wait for response
1929
+ // Check if this is a controller using the symbol
1930
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1931
+ let targetElement = isController && this.element ? this.element : this;
1932
+ // If element is null (e.g., controller was detached), can't get context
1933
+ if (!targetElement || !targetElement.dispatchEvent) {
1934
+ return undefined;
1935
+ }
1936
+ // If we're in shadow DOM, dispatch on the host element to ensure proper bubbling
1937
+ if (targetElement.getRootNode && targetElement.getRootNode() instanceof ShadowRoot) {
1938
+ const shadowRoot = targetElement.getRootNode();
1939
+ targetElement = shadowRoot.host;
1940
+ }
1941
+ targetElement.dispatchEvent(event);
1942
+ // Check if context was provided via the event
1943
+ if (detail.context !== undefined) {
1944
+ // Cache it for future use
1945
+ this[ROUTER_CONTEXT] = detail.context;
1946
+ return detail.context;
1947
+ }
1948
+ return undefined;
1949
+ },
1950
+ set() {
1951
+ // Context is read-only
1952
+ },
1953
+ configurable: true,
1954
+ enumerable: true
1955
+ };
1956
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1957
+ }
1958
+ return initialValue;
1959
+ };
1893
1960
  };
1894
1961
  }
1895
1962
  /**
@@ -1898,16 +1965,18 @@ function context() {
1898
1965
  * Supports async methods
1899
1966
  */
1900
1967
  function ready() {
1901
- return function (target, methodName, descriptor) {
1902
- const constructor = target.constructor;
1903
- if (!constructor[READY_HANDLERS]) {
1904
- constructor[READY_HANDLERS] = [];
1905
- }
1906
- constructor[READY_HANDLERS].push({
1907
- methodName,
1908
- method: descriptor.value
1968
+ return function (target, context) {
1969
+ const methodName = context.name;
1970
+ context.addInitializer(function () {
1971
+ const constructor = this.constructor;
1972
+ if (!constructor[READY_HANDLERS]) {
1973
+ constructor[READY_HANDLERS] = [];
1974
+ }
1975
+ constructor[READY_HANDLERS].push({
1976
+ methodName,
1977
+ method: target
1978
+ });
1909
1979
  });
1910
- return descriptor;
1911
1980
  };
1912
1981
  }
1913
1982
  /**
@@ -1915,16 +1984,18 @@ function ready() {
1915
1984
  * Used for cleanup tasks when element is removed from DOM
1916
1985
  */
1917
1986
  function dispose() {
1918
- return function (target, methodName, descriptor) {
1919
- const constructor = target.constructor;
1920
- if (!constructor[DISPOSE_HANDLERS]) {
1921
- constructor[DISPOSE_HANDLERS] = [];
1922
- }
1923
- constructor[DISPOSE_HANDLERS].push({
1924
- methodName,
1925
- method: descriptor.value
1987
+ return function (target, context) {
1988
+ const methodName = context.name;
1989
+ context.addInitializer(function () {
1990
+ const constructor = this.constructor;
1991
+ if (!constructor[DISPOSE_HANDLERS]) {
1992
+ constructor[DISPOSE_HANDLERS] = [];
1993
+ }
1994
+ constructor[DISPOSE_HANDLERS].push({
1995
+ methodName,
1996
+ method: target
1997
+ });
1926
1998
  });
1927
- return descriptor;
1928
1999
  };
1929
2000
  }
1930
2001
  /**
@@ -1933,27 +2004,20 @@ function dispose() {
1933
2004
  * When the decorated method is called, it automatically re-renders its part
1934
2005
  */
1935
2006
  function part(partName, options = {}) {
1936
- return function (target, methodName, descriptor) {
1937
- const constructor = target.constructor;
1938
- // Handle case where descriptor might be undefined (TypeScript experimental decorators)
1939
- if (!descriptor) {
1940
- descriptor = Object.getOwnPropertyDescriptor(target, methodName) || {
1941
- value: target[methodName],
1942
- writable: true,
1943
- enumerable: true,
1944
- configurable: true
1945
- };
1946
- }
1947
- const originalMethod = descriptor.value;
1948
- if (!constructor[PARTS]) {
1949
- constructor[PARTS] = new Map();
1950
- }
1951
- constructor[PARTS].set(partName, {
1952
- methodName,
1953
- method: originalMethod
2007
+ return function (originalMethod, context) {
2008
+ const methodName = context.name;
2009
+ context.addInitializer(function () {
2010
+ const constructor = this.constructor;
2011
+ if (!constructor[PARTS]) {
2012
+ constructor[PARTS] = new Map();
2013
+ }
2014
+ constructor[PARTS].set(partName, {
2015
+ methodName,
2016
+ method: originalMethod
2017
+ });
1954
2018
  });
1955
- // Wrap the original method to automatically re-render the part when called
1956
- descriptor.value = async function (...args) {
2019
+ // Return wrapped method that automatically re-renders the part when called
2020
+ return function (...args) {
1957
2021
  // Initialize timers storage if not present
1958
2022
  if (!this[PART_TIMERS]) {
1959
2023
  this[PART_TIMERS] = new Map();
@@ -1967,58 +2031,96 @@ function part(partName, options = {}) {
1967
2031
  });
1968
2032
  }
1969
2033
  const timers = this[PART_TIMERS].get(partName);
1970
- // Create the render function
1971
- const renderPart = async () => {
1972
- // Call the original method to get the content
1973
- const result = originalMethod.apply(this, args);
1974
- const content = result instanceof Promise ? await result : result;
1975
- // Re-render the part if shadow DOM exists and content is defined
2034
+ // Call the original method first to get its result
2035
+ const result = originalMethod.apply(this, args);
2036
+ // Helper function to update DOM
2037
+ const updateDOM = (content) => {
1976
2038
  if (this.shadowRoot && content !== undefined) {
1977
2039
  const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
1978
2040
  if (partElement) {
1979
2041
  partElement.innerHTML = content;
1980
2042
  }
1981
2043
  }
1982
- return content;
1983
2044
  };
1984
- // Handle debounce (only if positive value)
1985
- if (options.debounce !== undefined && options.debounce > 0) {
1986
- if (timers.debounceTimer) {
1987
- clearTimeout(timers.debounceTimer);
1988
- }
1989
- return new Promise((resolve) => {
2045
+ // Check if result is a Promise (async method)
2046
+ if (result instanceof Promise) {
2047
+ // Handle async method
2048
+ if (options.debounce !== undefined && options.debounce > 0) {
2049
+ // Debounce: defer DOM update, return original Promise
2050
+ if (timers.debounceTimer) {
2051
+ clearTimeout(timers.debounceTimer);
2052
+ }
1990
2053
  timers.debounceTimer = setTimeout(async () => {
1991
- const result = await renderPart();
1992
- resolve(result);
2054
+ const content = await result;
2055
+ updateDOM(content);
1993
2056
  }, options.debounce);
2057
+ return result;
2058
+ }
2059
+ if (options.throttle !== undefined && options.throttle > 0) {
2060
+ // Throttle: handle timing but return original Promise
2061
+ const now = Date.now();
2062
+ if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2063
+ timers.lastThrottleCall = now;
2064
+ return result.then(content => {
2065
+ updateDOM(content);
2066
+ return content;
2067
+ });
2068
+ }
2069
+ else {
2070
+ if (!timers.throttleTimer) {
2071
+ const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2072
+ timers.throttleTimer = setTimeout(async () => {
2073
+ timers.throttleTimer = null;
2074
+ timers.lastThrottleCall = Date.now();
2075
+ const content = await result;
2076
+ updateDOM(content);
2077
+ }, remainingTime);
2078
+ }
2079
+ return result;
2080
+ }
2081
+ }
2082
+ // No timing: update DOM after Promise resolves
2083
+ return result.then(content => {
2084
+ updateDOM(content);
2085
+ return content;
1994
2086
  });
1995
2087
  }
1996
- // Handle throttle (only if positive value)
1997
- if (options.throttle !== undefined && options.throttle > 0) {
1998
- const now = Date.now();
1999
- if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2000
- timers.lastThrottleCall = now;
2001
- return await renderPart();
2088
+ else {
2089
+ // Handle sync method
2090
+ if (options.debounce !== undefined && options.debounce > 0) {
2091
+ // Debounce: defer DOM update, return result immediately
2092
+ if (timers.debounceTimer) {
2093
+ clearTimeout(timers.debounceTimer);
2094
+ }
2095
+ timers.debounceTimer = setTimeout(() => {
2096
+ updateDOM(result);
2097
+ }, options.debounce);
2098
+ return result;
2002
2099
  }
2003
- else {
2004
- // If throttled, schedule the next call if not already scheduled
2005
- if (!timers.throttleTimer) {
2006
- const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2007
- timers.throttleTimer = setTimeout(async () => {
2008
- timers.throttleTimer = null;
2009
- timers.lastThrottleCall = Date.now();
2010
- await renderPart();
2011
- }, remainingTime);
2100
+ if (options.throttle !== undefined && options.throttle > 0) {
2101
+ // Throttle: handle timing for DOM updates
2102
+ const now = Date.now();
2103
+ if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2104
+ timers.lastThrottleCall = now;
2105
+ updateDOM(result);
2106
+ }
2107
+ else {
2108
+ if (!timers.throttleTimer) {
2109
+ const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2110
+ timers.throttleTimer = setTimeout(() => {
2111
+ timers.throttleTimer = null;
2112
+ timers.lastThrottleCall = Date.now();
2113
+ updateDOM(result);
2114
+ }, remainingTime);
2115
+ }
2012
2116
  }
2013
- // For throttled calls, don't execute the original method, just return undefined
2014
- // The actual render will happen in the scheduled timeout
2015
- return undefined;
2117
+ return result;
2016
2118
  }
2119
+ // No timing: update DOM immediately
2120
+ updateDOM(result);
2121
+ return result;
2017
2122
  }
2018
- // No throttle/debounce - render immediately
2019
- return await renderPart();
2020
2123
  };
2021
- return descriptor;
2022
2124
  };
2023
2125
  }
2024
2126
 
@@ -2790,7 +2892,16 @@ function Router(options) {
2790
2892
  * @returns A decorator function to apply to a custom element class.
2791
2893
  */
2792
2894
  function page(pageOptions) {
2793
- return function (constructor) {
2895
+ return function (constructor, context) {
2896
+ // Transfer metadata from context to constructor (for new decorators)
2897
+ if (context.metadata && context.metadata[PROPERTIES]) {
2898
+ if (!constructor[PROPERTIES]) {
2899
+ constructor[PROPERTIES] = new Map();
2900
+ }
2901
+ for (const [key, value] of context.metadata[PROPERTIES]) {
2902
+ constructor[PROPERTIES].set(key, value);
2903
+ }
2904
+ }
2794
2905
  // Apply all element functionality (properties, queries, watchers, controllers, etc.)
2795
2906
  applyElementFunctionality(constructor);
2796
2907
  // Store transition config on constructor for later use
@@ -2830,6 +2941,7 @@ function Router(options) {
2830
2941
  customElements.define(pageOptions.tag, constructor);
2831
2942
  // Register the routes with guards and layout
2832
2943
  pageOptions.routes.forEach(route => register(route, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout));
2944
+ return constructor;
2833
2945
  };
2834
2946
  }
2835
2947
  /**