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.esm.js 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.
@@ -80,24 +80,27 @@ function on(eventName, selectorOrOptions, options) {
80
80
  selector = undefined;
81
81
  opts = selectorOrOptions;
82
82
  }
83
- return function (target, propertyKey, descriptor) {
84
- // Store event handler metadata
85
- if (!target[ON_HANDLERS]) {
86
- target[ON_HANDLERS] = [];
87
- }
88
- // Normalize to array and expand at decoration time
89
- const eventNames = Array.isArray(eventName) ? eventName : [eventName];
90
- // Create a handler entry for each event
91
- for (const event of eventNames) {
92
- target[ON_HANDLERS].push({
93
- eventName: event,
94
- selector,
95
- methodName: propertyKey,
96
- method: descriptor.value,
97
- options: opts
98
- });
99
- }
100
- return descriptor;
83
+ return function (target, context) {
84
+ const propertyKey = context.name;
85
+ context.addInitializer(function () {
86
+ const constructor = this.constructor;
87
+ // Store event handler metadata
88
+ if (!constructor.prototype[ON_HANDLERS]) {
89
+ constructor.prototype[ON_HANDLERS] = [];
90
+ }
91
+ // Normalize to array and expand at decoration time
92
+ const eventNames = Array.isArray(eventName) ? eventName : [eventName];
93
+ // Create a handler entry for each event
94
+ for (const event of eventNames) {
95
+ constructor.prototype[ON_HANDLERS].push({
96
+ eventName: event,
97
+ selector,
98
+ methodName: propertyKey,
99
+ method: target,
100
+ options: opts
101
+ });
102
+ }
103
+ });
101
104
  };
102
105
  }
103
106
  // Helper to setup event handlers for elements
@@ -291,13 +294,12 @@ function cleanupEventHandlers(instance) {
291
294
  * @param options Optional configuration extending EventInit
292
295
  */
293
296
  function dispatch(eventName, options) {
294
- return function (_target, _propertyKey, descriptor) {
295
- const originalMethod = descriptor.value;
297
+ return function (originalMethod, _context) {
296
298
  // Create timing wrappers for dispatch
297
299
  let debounceTimeout;
298
300
  let throttleLastCall = 0;
299
301
  let throttleTimeout;
300
- descriptor.value = function (...args) {
302
+ return function (...args) {
301
303
  // Call the original method
302
304
  const result = originalMethod.apply(this, args);
303
305
  // Helper to dispatch the event
@@ -352,7 +354,6 @@ function dispatch(eventName, options) {
352
354
  timedDispatch(result);
353
355
  return result;
354
356
  };
355
- return descriptor;
356
357
  };
357
358
  }
358
359
 
@@ -379,27 +380,30 @@ function observe(observeTarget, selectorOrOptions, options) {
379
380
  selector = undefined;
380
381
  opts = selectorOrOptions;
381
382
  }
382
- return function (target, propertyKey, descriptor) {
383
- // Store observer metadata
384
- if (!target[OBSERVERS]) {
385
- target[OBSERVERS] = [];
386
- }
387
- // Normalize to array
388
- const observeTargets = Array.isArray(observeTarget) ? observeTarget : [observeTarget];
389
- // Create an observer entry for each target
390
- for (const targetString of observeTargets) {
391
- // Parse the observation type from the observeTarget string
392
- const [type, ...modifiers] = targetString.split(':');
393
- target[OBSERVERS].push({
394
- type,
395
- target: modifiers.join(':'), // Rejoin for media queries or mutation types
396
- selector,
397
- methodName: propertyKey,
398
- method: descriptor.value,
399
- options: opts
400
- });
401
- }
402
- return descriptor;
383
+ return function (target, context) {
384
+ const propertyKey = context.name;
385
+ context.addInitializer(function () {
386
+ const constructor = this.constructor;
387
+ // Store observer metadata
388
+ if (!constructor.prototype[OBSERVERS]) {
389
+ constructor.prototype[OBSERVERS] = [];
390
+ }
391
+ // Normalize to array
392
+ const observeTargets = Array.isArray(observeTarget) ? observeTarget : [observeTarget];
393
+ // Create an observer entry for each target
394
+ for (const targetString of observeTargets) {
395
+ // Parse the observation type from the observeTarget string
396
+ const [type, ...modifiers] = targetString.split(':');
397
+ constructor.prototype[OBSERVERS].push({
398
+ type,
399
+ target: modifiers.join(':'), // Rejoin for media queries or mutation types
400
+ selector,
401
+ methodName: propertyKey,
402
+ method: target,
403
+ options: opts
404
+ });
405
+ }
406
+ });
403
407
  };
404
408
  }
405
409
  // Helper to setup observers for elements
@@ -692,13 +696,12 @@ function cleanupObservers(instance) {
692
696
  * @param options Optional configuration
693
697
  */
694
698
  function request(requestName, options) {
695
- return function (_target, _propertyKey, descriptor) {
696
- const originalMethod = descriptor.value;
699
+ return function (originalMethod, _context) {
697
700
  // Create timing variables for debounce/throttle
698
701
  let debounceTimeout;
699
702
  let throttleLastCall = 0;
700
703
  let throttleTimeout;
701
- descriptor.value = async function (...args) {
704
+ return async function (...args) {
702
705
  const actualRequest = async () => {
703
706
  // @request always acts as requester (client side)
704
707
  const responseTimeout = options?.timeout ?? 120000; // Default 2 minute timeout
@@ -822,7 +825,6 @@ function request(requestName, options) {
822
825
  // No timing applied, execute immediately
823
826
  return actualRequest();
824
827
  };
825
- return descriptor;
826
828
  };
827
829
  }
828
830
  /**
@@ -832,20 +834,22 @@ function request(requestName, options) {
832
834
  * @param options Optional configuration
833
835
  */
834
836
  function respond(requestName, options) {
835
- return function (target, propertyKey, descriptor) {
836
- const originalMethod = descriptor.value;
837
- // Store response metadata on the prototype
838
- // This will be picked up by setupResponseHandlers
839
- if (!target[CHANNEL_HANDLERS]) {
840
- target[CHANNEL_HANDLERS] = [];
841
- }
842
- target[CHANNEL_HANDLERS].push({
843
- channelName: requestName,
844
- methodName: propertyKey,
845
- method: originalMethod,
846
- options: options
837
+ return function (target, context) {
838
+ const propertyKey = context.name;
839
+ context.addInitializer(function () {
840
+ const constructor = this.constructor;
841
+ // Store response metadata on the prototype
842
+ // This will be picked up by setupResponseHandlers
843
+ if (!constructor.prototype[CHANNEL_HANDLERS]) {
844
+ constructor.prototype[CHANNEL_HANDLERS] = [];
845
+ }
846
+ constructor.prototype[CHANNEL_HANDLERS].push({
847
+ channelName: requestName,
848
+ methodName: propertyKey,
849
+ method: target,
850
+ options: options
851
+ });
847
852
  });
848
- return descriptor;
849
853
  };
850
854
  }
851
855
  // Helper to setup response handlers for elements and controllers
@@ -996,7 +1000,7 @@ class ControllerScope {
996
1000
  * @param name The name to register the controller under
997
1001
  */
998
1002
  function controller(name) {
999
- return function (constructor) {
1003
+ return function (constructor, _context) {
1000
1004
  snice.controllerRegistry.set(name, constructor);
1001
1005
  // Mark as controller class for channel decorator detection
1002
1006
  constructor.prototype[IS_CONTROLLER_CLASS] = true;
@@ -1343,10 +1347,12 @@ function applyElementFunctionality(constructor) {
1343
1347
  // Mark that properties have been initialized
1344
1348
  this[PROPERTIES_INITIALIZED] = true;
1345
1349
  // Reflect properties that were explicitly set before connection
1346
- // but skip default values that were never explicitly set
1347
- if (properties && this[EXPLICITLY_SET_PROPERTIES]) {
1350
+ // AND also reflect initial values that have reflect: true
1351
+ if (properties) {
1348
1352
  for (const [propName, propOptions] of properties) {
1349
- if (propOptions.reflect && this[EXPLICITLY_SET_PROPERTIES].has(propName) && propName in this[PROPERTY_VALUES]) {
1353
+ const wasExplicitlySet = this[EXPLICITLY_SET_PROPERTIES] && this[EXPLICITLY_SET_PROPERTIES].has(propName);
1354
+ const hasInitialValue = propName in this[PROPERTY_VALUES];
1355
+ if (propOptions.reflect && hasInitialValue && (wasExplicitlySet || this[PROPERTY_VALUES][propName] !== undefined)) {
1350
1356
  const value = this[PROPERTY_VALUES][propName];
1351
1357
  const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1352
1358
  if (value !== null && value !== undefined && value !== false &&
@@ -1595,176 +1601,224 @@ function applyElementFunctionality(constructor) {
1595
1601
  };
1596
1602
  }
1597
1603
  function element(tagName) {
1598
- return function (constructor) {
1604
+ return function (constructor, context) {
1605
+ // Transfer metadata from context to constructor
1606
+ if (context.metadata && context.metadata[PROPERTIES]) {
1607
+ if (!constructor[PROPERTIES]) {
1608
+ constructor[PROPERTIES] = new Map();
1609
+ }
1610
+ for (const [key, value] of context.metadata[PROPERTIES]) {
1611
+ constructor[PROPERTIES].set(key, value);
1612
+ }
1613
+ }
1599
1614
  applyElementFunctionality(constructor);
1600
1615
  customElements.define(tagName, constructor);
1616
+ return constructor;
1601
1617
  };
1602
1618
  }
1603
1619
  function layout(tagName) {
1604
- return function (constructor) {
1620
+ return function (constructor, context) {
1621
+ // Transfer metadata from context to constructor
1622
+ if (context.metadata && context.metadata[PROPERTIES]) {
1623
+ if (!constructor[PROPERTIES]) {
1624
+ constructor[PROPERTIES] = new Map();
1625
+ }
1626
+ for (const [key, value] of context.metadata[PROPERTIES]) {
1627
+ constructor[PROPERTIES].set(key, value);
1628
+ }
1629
+ }
1605
1630
  applyElementFunctionality(constructor);
1606
1631
  customElements.define(tagName, constructor);
1632
+ return constructor;
1607
1633
  };
1608
1634
  }
1609
1635
  function property(options) {
1610
- return function (target, propertyKey) {
1611
- const constructor = target.constructor;
1612
- // Warn about problematic reflection usage
1636
+ return function (_value, context) {
1637
+ const propertyKey = context.name;
1638
+ // Use metadata to store property information at decoration time
1639
+ if (!context.metadata) {
1640
+ context.metadata = {};
1641
+ }
1642
+ if (!context.metadata[PROPERTIES]) {
1643
+ context.metadata[PROPERTIES] = new Map();
1644
+ }
1645
+ context.metadata[PROPERTIES].set(propertyKey, options || {});
1646
+ // Warn about problematic reflection usage at decoration time
1613
1647
  if (options?.reflect && options?.type === Array) {
1614
1648
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Array type.`);
1615
1649
  }
1616
1650
  if (options?.reflect && options?.type === Object) {
1617
1651
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
1618
1652
  }
1619
- if (!constructor[PROPERTIES]) {
1620
- constructor[PROPERTIES] = new Map();
1621
- }
1622
- constructor[PROPERTIES].set(propertyKey, options || {});
1623
- const descriptor = {
1624
- get() {
1625
- if (!this[PROPERTY_VALUES]) {
1626
- this[PROPERTY_VALUES] = {};
1627
- }
1628
- return this[PROPERTY_VALUES][propertyKey];
1629
- },
1630
- set(value) {
1631
- if (!this[PROPERTY_VALUES]) {
1632
- this[PROPERTY_VALUES] = {};
1633
- }
1634
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
1635
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
1636
- }
1637
- const oldValue = this[PROPERTY_VALUES][propertyKey];
1638
- // Don't update if value hasn't changed
1639
- if (oldValue === value)
1640
- return;
1641
- // Mark as explicitly set in these cases:
1642
- // 1. There was a previous value (normal property update)
1643
- // 2. This is during element construction and we have a non-null/non-undefined value
1644
- // (this handles default values declared in class properties)
1645
- const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
1646
- if (oldValue !== undefined || (isInitialDefaultValue && value !== null && value !== undefined)) {
1647
- this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1648
- }
1649
- this[PROPERTY_VALUES][propertyKey] = value;
1650
- // Only reflect to attributes if:
1651
- // 1. Properties have been initialized from attributes
1652
- // 2. The property was explicitly set (not just default value)
1653
- // This prevents default values from creating attributes
1654
- if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1655
- const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1656
- if (value === null || value === undefined || value === false ||
1657
- (options?.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
1658
- this.removeAttribute(attributeName);
1659
- }
1660
- else {
1661
- // Handle special types for reflection
1662
- let attributeValue;
1663
- if (value instanceof Date) {
1664
- attributeValue = value.toISOString();
1653
+ context.addInitializer(function () {
1654
+ // No longer need warnings here since they're at decoration time
1655
+ });
1656
+ // Return a field initializer function for new decorators
1657
+ return function (initialValue) {
1658
+ // Set up the property descriptor on first access
1659
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1660
+ const descriptor = {
1661
+ get() {
1662
+ if (!this[PROPERTY_VALUES]) {
1663
+ this[PROPERTY_VALUES] = {};
1665
1664
  }
1666
- else if (typeof value === 'bigint') {
1667
- attributeValue = value.toString() + 'n';
1665
+ return this[PROPERTY_VALUES][propertyKey];
1666
+ },
1667
+ set(newValue) {
1668
+ if (!this[PROPERTY_VALUES]) {
1669
+ this[PROPERTY_VALUES] = {};
1668
1670
  }
1669
- else if (options?.type === SimpleArray && Array.isArray(value)) {
1670
- attributeValue = SimpleArray.serialize(value);
1671
+ if (!this[EXPLICITLY_SET_PROPERTIES]) {
1672
+ this[EXPLICITLY_SET_PROPERTIES] = new Set();
1671
1673
  }
1672
- else {
1673
- attributeValue = String(value);
1674
+ const oldValue = this[PROPERTY_VALUES][propertyKey];
1675
+ if (oldValue === newValue)
1676
+ return;
1677
+ const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
1678
+ if (oldValue !== undefined || (isInitialDefaultValue && newValue !== null && newValue !== undefined)) {
1679
+ this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1674
1680
  }
1675
- this.setAttribute(attributeName, attributeValue);
1676
- }
1677
- }
1678
- // Call watchers for this property
1679
- const watchers = constructor[PROPERTY_WATCHERS];
1680
- if (watchers) {
1681
- // Call specific property watchers
1682
- if (watchers.has(propertyKey)) {
1683
- const propertyWatchers = watchers.get(propertyKey);
1684
- for (const watcher of propertyWatchers) {
1685
- try {
1686
- // Always pass oldValue, newValue, and propertyName
1687
- watcher.method.call(this, oldValue, value, propertyKey);
1681
+ this[PROPERTY_VALUES][propertyKey] = newValue;
1682
+ if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1683
+ const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1684
+ if (newValue === null || newValue === undefined || newValue === false ||
1685
+ (options?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1686
+ this.removeAttribute(attributeName);
1688
1687
  }
1689
- catch (error) {
1690
- console.error(`Error in @watch('${propertyKey}') method ${watcher.methodName}:`, error);
1688
+ else {
1689
+ let attributeValue;
1690
+ if (newValue instanceof Date) {
1691
+ attributeValue = newValue.toISOString();
1692
+ }
1693
+ else if (typeof newValue === 'bigint') {
1694
+ attributeValue = newValue.toString() + 'n';
1695
+ }
1696
+ else if (options?.type === SimpleArray && Array.isArray(newValue)) {
1697
+ attributeValue = SimpleArray.serialize(newValue);
1698
+ }
1699
+ else {
1700
+ attributeValue = String(newValue);
1701
+ }
1702
+ this.setAttribute(attributeName, attributeValue);
1691
1703
  }
1692
1704
  }
1693
- }
1694
- // Call wildcard watchers (watching "*")
1695
- if (watchers.has('*')) {
1696
- const wildcardWatchers = watchers.get('*');
1697
- for (const watcher of wildcardWatchers) {
1698
- try {
1699
- // Same signature for consistency
1700
- watcher.method.call(this, oldValue, value, propertyKey);
1705
+ const constructor = this.constructor;
1706
+ const watchers = constructor[PROPERTY_WATCHERS];
1707
+ if (watchers) {
1708
+ if (watchers.has(propertyKey)) {
1709
+ const propertyWatchers = watchers.get(propertyKey);
1710
+ for (const watcher of propertyWatchers) {
1711
+ try {
1712
+ watcher.method.call(this, oldValue, newValue, propertyKey);
1713
+ }
1714
+ catch (error) {
1715
+ console.error(`Error in @watch('${propertyKey}') method ${watcher.methodName}:`, error);
1716
+ }
1717
+ }
1701
1718
  }
1702
- catch (error) {
1703
- console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1719
+ if (watchers.has('*')) {
1720
+ const wildcardWatchers = watchers.get('*');
1721
+ for (const watcher of wildcardWatchers) {
1722
+ try {
1723
+ watcher.method.call(this, oldValue, newValue, propertyKey);
1724
+ }
1725
+ catch (error) {
1726
+ console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1727
+ }
1728
+ }
1704
1729
  }
1705
1730
  }
1706
- }
1707
- }
1708
- // Call requestUpdate if available and value changed
1709
- if (this.requestUpdate) {
1710
- this.requestUpdate(propertyKey, oldValue);
1711
- }
1712
- },
1713
- enumerable: true,
1714
- configurable: true,
1731
+ if (this.requestUpdate) {
1732
+ this.requestUpdate(propertyKey, oldValue);
1733
+ }
1734
+ },
1735
+ configurable: true,
1736
+ enumerable: true
1737
+ };
1738
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1739
+ }
1740
+ // Initialize the property value
1741
+ if (!this[PROPERTY_VALUES]) {
1742
+ this[PROPERTY_VALUES] = {};
1743
+ }
1744
+ this[PROPERTY_VALUES][propertyKey] = initialValue;
1745
+ return initialValue;
1715
1746
  };
1716
- Object.defineProperty(target, propertyKey, descriptor);
1717
1747
  };
1718
1748
  }
1719
1749
  function query(selector, options = {}) {
1720
- return function (target, propertyKey) {
1750
+ return function (_value, context) {
1721
1751
  // Default to shadow DOM only
1722
1752
  const { light = false, shadow = true } = options;
1723
- Object.defineProperty(target, propertyKey, {
1724
- get() {
1725
- // Check if this is a controller using the symbol
1726
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1727
- const root = isController && this.element ? this.element : this;
1728
- // Query in specified contexts
1729
- let result = null;
1730
- if (shadow && root.shadowRoot) {
1731
- result = root.shadowRoot.querySelector(selector);
1732
- }
1733
- if (!result && light) {
1734
- result = root.querySelector(selector);
1735
- }
1736
- return result || null;
1737
- },
1738
- enumerable: true,
1739
- configurable: true,
1740
- });
1753
+ const propertyKey = context.name;
1754
+ // Return a field initializer function for new decorators
1755
+ return function (initialValue) {
1756
+ // Set up the property descriptor on first access
1757
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1758
+ const descriptor = {
1759
+ get() {
1760
+ // Check if this is a controller using the symbol
1761
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1762
+ const root = isController && this.element ? this.element : this;
1763
+ // Query in specified contexts
1764
+ let result = null;
1765
+ if (shadow && root.shadowRoot) {
1766
+ result = root.shadowRoot.querySelector(selector);
1767
+ }
1768
+ if (!result && light) {
1769
+ result = root.querySelector(selector);
1770
+ }
1771
+ return result || null;
1772
+ },
1773
+ set() {
1774
+ // Query results are read-only
1775
+ },
1776
+ configurable: true,
1777
+ enumerable: true
1778
+ };
1779
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1780
+ }
1781
+ return initialValue;
1782
+ };
1741
1783
  };
1742
1784
  }
1743
1785
  function queryAll(selector, options = {}) {
1744
- return function (target, propertyKey) {
1786
+ return function (_value, context) {
1745
1787
  // Default to shadow DOM only
1746
1788
  const { light = false, shadow = true } = options;
1747
- Object.defineProperty(target, propertyKey, {
1748
- get() {
1749
- // Check if this is a controller using the symbol
1750
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1751
- const root = isController && this.element ? this.element : this;
1752
- // Query in specified contexts and combine results
1753
- const results = [];
1754
- if (shadow && root.shadowRoot) {
1755
- const shadowResults = root.shadowRoot.querySelectorAll(selector);
1756
- results.push(...shadowResults);
1757
- }
1758
- if (light) {
1759
- const lightResults = root.querySelectorAll(selector);
1760
- results.push(...lightResults);
1761
- }
1762
- // Return a static NodeList-like object
1763
- return results;
1764
- },
1765
- enumerable: true,
1766
- configurable: true,
1767
- });
1789
+ const propertyKey = context.name;
1790
+ // Return a field initializer function for new decorators
1791
+ return function (initialValue) {
1792
+ // Set up the property descriptor on first access
1793
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1794
+ const descriptor = {
1795
+ get() {
1796
+ // Check if this is a controller using the symbol
1797
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1798
+ const root = isController && this.element ? this.element : this;
1799
+ // Query in specified contexts and combine results
1800
+ const results = [];
1801
+ if (shadow && root.shadowRoot) {
1802
+ const shadowResults = root.shadowRoot.querySelectorAll(selector);
1803
+ results.push(...shadowResults);
1804
+ }
1805
+ if (light) {
1806
+ const lightResults = root.querySelectorAll(selector);
1807
+ results.push(...lightResults);
1808
+ }
1809
+ // Return a static NodeList-like object
1810
+ return results;
1811
+ },
1812
+ set() {
1813
+ // Query results are read-only
1814
+ },
1815
+ configurable: true,
1816
+ enumerable: true
1817
+ };
1818
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1819
+ }
1820
+ return initialValue;
1821
+ };
1768
1822
  };
1769
1823
  }
1770
1824
  /**
@@ -1774,6 +1828,7 @@ function queryAll(selector, options = {}) {
1774
1828
  * Strings cannot contain the full-width comma character
1775
1829
  */
1776
1830
  class SimpleArray {
1831
+ static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1777
1832
  /**
1778
1833
  * Serialize array to string for attribute storage
1779
1834
  */
@@ -1823,24 +1878,25 @@ class SimpleArray {
1823
1878
  });
1824
1879
  }
1825
1880
  }
1826
- SimpleArray.SEPARATOR = ','; // U+FF0C Full-width comma
1827
1881
  function watch(...propertyNames) {
1828
- return function (target, methodName, descriptor) {
1829
- const constructor = target.constructor;
1830
- if (!constructor[PROPERTY_WATCHERS]) {
1831
- constructor[PROPERTY_WATCHERS] = new Map();
1832
- }
1833
- // Store the watcher method for each property
1834
- for (const propertyName of propertyNames) {
1835
- if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
1836
- constructor[PROPERTY_WATCHERS].set(propertyName, []);
1882
+ return function (target, context) {
1883
+ const methodName = context.name;
1884
+ context.addInitializer(function () {
1885
+ const constructor = this.constructor;
1886
+ if (!constructor[PROPERTY_WATCHERS]) {
1887
+ constructor[PROPERTY_WATCHERS] = new Map();
1888
+ }
1889
+ // Store the watcher method for each property
1890
+ for (const propertyName of propertyNames) {
1891
+ if (!constructor[PROPERTY_WATCHERS].has(propertyName)) {
1892
+ constructor[PROPERTY_WATCHERS].set(propertyName, []);
1893
+ }
1894
+ constructor[PROPERTY_WATCHERS].get(propertyName).push({
1895
+ methodName,
1896
+ method: target
1897
+ });
1837
1898
  }
1838
- constructor[PROPERTY_WATCHERS].get(propertyName).push({
1839
- methodName,
1840
- method: descriptor.value
1841
- });
1842
- }
1843
- return descriptor;
1899
+ });
1844
1900
  };
1845
1901
  }
1846
1902
  /**
@@ -1848,46 +1904,57 @@ function watch(...propertyNames) {
1848
1904
  * The context is automatically provided to page components by the router
1849
1905
  */
1850
1906
  function context() {
1851
- return function (target, propertyKey) {
1852
- // Define property getter that returns the context
1853
- Object.defineProperty(target, propertyKey, {
1854
- get() {
1855
- // First check if context is stored directly on this element
1856
- if (this[ROUTER_CONTEXT] !== undefined) {
1857
- return this[ROUTER_CONTEXT];
1858
- }
1859
- // Otherwise, request context from parent page via event
1860
- const detail = { target: this };
1861
- const event = new CustomEvent('@context/request', {
1862
- bubbles: true,
1863
- cancelable: true,
1864
- detail
1865
- });
1866
- // Dispatch event and wait for response
1867
- // Check if this is a controller using the symbol
1868
- const isController = this[IS_CONTROLLER_INSTANCE] === true;
1869
- let targetElement = isController && this.element ? this.element : this;
1870
- // If element is null (e.g., controller was detached), can't get context
1871
- if (!targetElement || !targetElement.dispatchEvent) {
1872
- return undefined;
1873
- }
1874
- // If we're in shadow DOM, dispatch on the host element to ensure proper bubbling
1875
- if (targetElement.getRootNode && targetElement.getRootNode() instanceof ShadowRoot) {
1876
- const shadowRoot = targetElement.getRootNode();
1877
- targetElement = shadowRoot.host;
1878
- }
1879
- targetElement.dispatchEvent(event);
1880
- // Check if context was provided via the event
1881
- if (detail.context !== undefined) {
1882
- // Cache it for future use
1883
- this[ROUTER_CONTEXT] = detail.context;
1884
- return detail.context;
1885
- }
1886
- return undefined;
1887
- },
1888
- enumerable: true,
1889
- configurable: true
1890
- });
1907
+ return function (_value, context) {
1908
+ const propertyKey = context.name;
1909
+ // Return a field initializer function for new decorators
1910
+ return function (initialValue) {
1911
+ // Set up the property descriptor on first access
1912
+ if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1913
+ const descriptor = {
1914
+ get() {
1915
+ // First check if context is stored directly on this element
1916
+ if (this[ROUTER_CONTEXT] !== undefined) {
1917
+ return this[ROUTER_CONTEXT];
1918
+ }
1919
+ // Otherwise, request context from parent page via event
1920
+ const detail = { target: this };
1921
+ const event = new CustomEvent('@context/request', {
1922
+ bubbles: true,
1923
+ cancelable: true,
1924
+ detail
1925
+ });
1926
+ // Dispatch event and wait for response
1927
+ // Check if this is a controller using the symbol
1928
+ const isController = this[IS_CONTROLLER_INSTANCE] === true;
1929
+ let targetElement = isController && this.element ? this.element : this;
1930
+ // If element is null (e.g., controller was detached), can't get context
1931
+ if (!targetElement || !targetElement.dispatchEvent) {
1932
+ return undefined;
1933
+ }
1934
+ // If we're in shadow DOM, dispatch on the host element to ensure proper bubbling
1935
+ if (targetElement.getRootNode && targetElement.getRootNode() instanceof ShadowRoot) {
1936
+ const shadowRoot = targetElement.getRootNode();
1937
+ targetElement = shadowRoot.host;
1938
+ }
1939
+ targetElement.dispatchEvent(event);
1940
+ // Check if context was provided via the event
1941
+ if (detail.context !== undefined) {
1942
+ // Cache it for future use
1943
+ this[ROUTER_CONTEXT] = detail.context;
1944
+ return detail.context;
1945
+ }
1946
+ return undefined;
1947
+ },
1948
+ set() {
1949
+ // Context is read-only
1950
+ },
1951
+ configurable: true,
1952
+ enumerable: true
1953
+ };
1954
+ Object.defineProperty(this.constructor.prototype, propertyKey, descriptor);
1955
+ }
1956
+ return initialValue;
1957
+ };
1891
1958
  };
1892
1959
  }
1893
1960
  /**
@@ -1896,16 +1963,18 @@ function context() {
1896
1963
  * Supports async methods
1897
1964
  */
1898
1965
  function ready() {
1899
- return function (target, methodName, descriptor) {
1900
- const constructor = target.constructor;
1901
- if (!constructor[READY_HANDLERS]) {
1902
- constructor[READY_HANDLERS] = [];
1903
- }
1904
- constructor[READY_HANDLERS].push({
1905
- methodName,
1906
- method: descriptor.value
1966
+ return function (target, context) {
1967
+ const methodName = context.name;
1968
+ context.addInitializer(function () {
1969
+ const constructor = this.constructor;
1970
+ if (!constructor[READY_HANDLERS]) {
1971
+ constructor[READY_HANDLERS] = [];
1972
+ }
1973
+ constructor[READY_HANDLERS].push({
1974
+ methodName,
1975
+ method: target
1976
+ });
1907
1977
  });
1908
- return descriptor;
1909
1978
  };
1910
1979
  }
1911
1980
  /**
@@ -1913,16 +1982,18 @@ function ready() {
1913
1982
  * Used for cleanup tasks when element is removed from DOM
1914
1983
  */
1915
1984
  function dispose() {
1916
- return function (target, methodName, descriptor) {
1917
- const constructor = target.constructor;
1918
- if (!constructor[DISPOSE_HANDLERS]) {
1919
- constructor[DISPOSE_HANDLERS] = [];
1920
- }
1921
- constructor[DISPOSE_HANDLERS].push({
1922
- methodName,
1923
- method: descriptor.value
1985
+ return function (target, context) {
1986
+ const methodName = context.name;
1987
+ context.addInitializer(function () {
1988
+ const constructor = this.constructor;
1989
+ if (!constructor[DISPOSE_HANDLERS]) {
1990
+ constructor[DISPOSE_HANDLERS] = [];
1991
+ }
1992
+ constructor[DISPOSE_HANDLERS].push({
1993
+ methodName,
1994
+ method: target
1995
+ });
1924
1996
  });
1925
- return descriptor;
1926
1997
  };
1927
1998
  }
1928
1999
  /**
@@ -1931,27 +2002,20 @@ function dispose() {
1931
2002
  * When the decorated method is called, it automatically re-renders its part
1932
2003
  */
1933
2004
  function part(partName, options = {}) {
1934
- return function (target, methodName, descriptor) {
1935
- const constructor = target.constructor;
1936
- // Handle case where descriptor might be undefined (TypeScript experimental decorators)
1937
- if (!descriptor) {
1938
- descriptor = Object.getOwnPropertyDescriptor(target, methodName) || {
1939
- value: target[methodName],
1940
- writable: true,
1941
- enumerable: true,
1942
- configurable: true
1943
- };
1944
- }
1945
- const originalMethod = descriptor.value;
1946
- if (!constructor[PARTS]) {
1947
- constructor[PARTS] = new Map();
1948
- }
1949
- constructor[PARTS].set(partName, {
1950
- methodName,
1951
- method: originalMethod
2005
+ return function (originalMethod, context) {
2006
+ const methodName = context.name;
2007
+ context.addInitializer(function () {
2008
+ const constructor = this.constructor;
2009
+ if (!constructor[PARTS]) {
2010
+ constructor[PARTS] = new Map();
2011
+ }
2012
+ constructor[PARTS].set(partName, {
2013
+ methodName,
2014
+ method: originalMethod
2015
+ });
1952
2016
  });
1953
- // Wrap the original method to automatically re-render the part when called
1954
- descriptor.value = async function (...args) {
2017
+ // Return wrapped method that automatically re-renders the part when called
2018
+ return function (...args) {
1955
2019
  // Initialize timers storage if not present
1956
2020
  if (!this[PART_TIMERS]) {
1957
2021
  this[PART_TIMERS] = new Map();
@@ -1965,58 +2029,96 @@ function part(partName, options = {}) {
1965
2029
  });
1966
2030
  }
1967
2031
  const timers = this[PART_TIMERS].get(partName);
1968
- // Create the render function
1969
- const renderPart = async () => {
1970
- // Call the original method to get the content
1971
- const result = originalMethod.apply(this, args);
1972
- const content = result instanceof Promise ? await result : result;
1973
- // Re-render the part if shadow DOM exists and content is defined
2032
+ // Call the original method first to get its result
2033
+ const result = originalMethod.apply(this, args);
2034
+ // Helper function to update DOM
2035
+ const updateDOM = (content) => {
1974
2036
  if (this.shadowRoot && content !== undefined) {
1975
2037
  const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
1976
2038
  if (partElement) {
1977
2039
  partElement.innerHTML = content;
1978
2040
  }
1979
2041
  }
1980
- return content;
1981
2042
  };
1982
- // Handle debounce (only if positive value)
1983
- if (options.debounce !== undefined && options.debounce > 0) {
1984
- if (timers.debounceTimer) {
1985
- clearTimeout(timers.debounceTimer);
1986
- }
1987
- return new Promise((resolve) => {
2043
+ // Check if result is a Promise (async method)
2044
+ if (result instanceof Promise) {
2045
+ // Handle async method
2046
+ if (options.debounce !== undefined && options.debounce > 0) {
2047
+ // Debounce: defer DOM update, return original Promise
2048
+ if (timers.debounceTimer) {
2049
+ clearTimeout(timers.debounceTimer);
2050
+ }
1988
2051
  timers.debounceTimer = setTimeout(async () => {
1989
- const result = await renderPart();
1990
- resolve(result);
2052
+ const content = await result;
2053
+ updateDOM(content);
1991
2054
  }, options.debounce);
2055
+ return result;
2056
+ }
2057
+ if (options.throttle !== undefined && options.throttle > 0) {
2058
+ // Throttle: handle timing but return original Promise
2059
+ const now = Date.now();
2060
+ if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2061
+ timers.lastThrottleCall = now;
2062
+ return result.then(content => {
2063
+ updateDOM(content);
2064
+ return content;
2065
+ });
2066
+ }
2067
+ else {
2068
+ if (!timers.throttleTimer) {
2069
+ const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2070
+ timers.throttleTimer = setTimeout(async () => {
2071
+ timers.throttleTimer = null;
2072
+ timers.lastThrottleCall = Date.now();
2073
+ const content = await result;
2074
+ updateDOM(content);
2075
+ }, remainingTime);
2076
+ }
2077
+ return result;
2078
+ }
2079
+ }
2080
+ // No timing: update DOM after Promise resolves
2081
+ return result.then(content => {
2082
+ updateDOM(content);
2083
+ return content;
1992
2084
  });
1993
2085
  }
1994
- // Handle throttle (only if positive value)
1995
- if (options.throttle !== undefined && options.throttle > 0) {
1996
- const now = Date.now();
1997
- if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
1998
- timers.lastThrottleCall = now;
1999
- return await renderPart();
2086
+ else {
2087
+ // Handle sync method
2088
+ if (options.debounce !== undefined && options.debounce > 0) {
2089
+ // Debounce: defer DOM update, return result immediately
2090
+ if (timers.debounceTimer) {
2091
+ clearTimeout(timers.debounceTimer);
2092
+ }
2093
+ timers.debounceTimer = setTimeout(() => {
2094
+ updateDOM(result);
2095
+ }, options.debounce);
2096
+ return result;
2000
2097
  }
2001
- else {
2002
- // If throttled, schedule the next call if not already scheduled
2003
- if (!timers.throttleTimer) {
2004
- const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2005
- timers.throttleTimer = setTimeout(async () => {
2006
- timers.throttleTimer = null;
2007
- timers.lastThrottleCall = Date.now();
2008
- await renderPart();
2009
- }, remainingTime);
2098
+ if (options.throttle !== undefined && options.throttle > 0) {
2099
+ // Throttle: handle timing for DOM updates
2100
+ const now = Date.now();
2101
+ if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2102
+ timers.lastThrottleCall = now;
2103
+ updateDOM(result);
2104
+ }
2105
+ else {
2106
+ if (!timers.throttleTimer) {
2107
+ const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2108
+ timers.throttleTimer = setTimeout(() => {
2109
+ timers.throttleTimer = null;
2110
+ timers.lastThrottleCall = Date.now();
2111
+ updateDOM(result);
2112
+ }, remainingTime);
2113
+ }
2010
2114
  }
2011
- // For throttled calls, don't execute the original method, just return undefined
2012
- // The actual render will happen in the scheduled timeout
2013
- return undefined;
2115
+ return result;
2014
2116
  }
2117
+ // No timing: update DOM immediately
2118
+ updateDOM(result);
2119
+ return result;
2015
2120
  }
2016
- // No throttle/debounce - render immediately
2017
- return await renderPart();
2018
2121
  };
2019
- return descriptor;
2020
2122
  };
2021
2123
  }
2022
2124
 
@@ -2788,7 +2890,16 @@ function Router(options) {
2788
2890
  * @returns A decorator function to apply to a custom element class.
2789
2891
  */
2790
2892
  function page(pageOptions) {
2791
- return function (constructor) {
2893
+ return function (constructor, context) {
2894
+ // Transfer metadata from context to constructor (for new decorators)
2895
+ if (context.metadata && context.metadata[PROPERTIES]) {
2896
+ if (!constructor[PROPERTIES]) {
2897
+ constructor[PROPERTIES] = new Map();
2898
+ }
2899
+ for (const [key, value] of context.metadata[PROPERTIES]) {
2900
+ constructor[PROPERTIES].set(key, value);
2901
+ }
2902
+ }
2792
2903
  // Apply all element functionality (properties, queries, watchers, controllers, etc.)
2793
2904
  applyElementFunctionality(constructor);
2794
2905
  // Store transition config on constructor for later use
@@ -2828,6 +2939,7 @@ function Router(options) {
2828
2939
  customElements.define(pageOptions.tag, constructor);
2829
2940
  // Register the routes with guards and layout
2830
2941
  pageOptions.routes.forEach(route => register(route, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout));
2942
+ return constructor;
2831
2943
  };
2832
2944
  }
2833
2945
  /**