snice 7.2.0 → 7.4.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 (232) hide show
  1. package/.agents/skills/snice/SKILL.md +36 -13
  2. package/README.md +11 -6
  3. package/adapters/react/SniceProvider.js +1 -1
  4. package/adapters/react/SniceRouter.d.ts +2 -0
  5. package/adapters/react/SniceRouter.js +3 -2
  6. package/adapters/react/SniceRouter.js.map +1 -1
  7. package/adapters/react/matchRoute.d.ts +4 -1
  8. package/adapters/react/matchRoute.js +13 -3
  9. package/adapters/react/matchRoute.js.map +1 -1
  10. package/adapters/react/useRequestHandler.js +1 -1
  11. package/bin/analyzer-contracts.js +4 -1
  12. package/bin/project-analyzer.js +521 -20
  13. package/bin/snice.js +77 -13
  14. package/bin/templates/AI_GUIDANCE.md +6 -1
  15. package/bin/templates/default/README.md +1 -1
  16. package/bin/templates/default/src/components/app-header.ts +11 -16
  17. package/bin/templates/default/src/components/notification-badge.ts +5 -14
  18. package/bin/templates/default/src/components/search-bar.ts +21 -65
  19. package/bin/templates/default/src/pages/dashboard.ts +15 -11
  20. package/bin/templates/default/src/pages/data.ts +10 -13
  21. package/bin/templates/default/src/pages/notifications.ts +4 -9
  22. package/bin/templates/default/src/pages/profile.ts +2 -8
  23. package/bin/templates/default/src/pages/settings.ts +16 -16
  24. package/dist/app-context.d.ts +10 -10
  25. package/dist/cdn/accordion/snice-accordion.js +1 -1
  26. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  27. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  28. package/dist/cdn/alert/snice-alert.js +1 -1
  29. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  30. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  31. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  32. package/dist/cdn/availability/snice-availability.js +1 -1
  33. package/dist/cdn/avatar/snice-avatar.js +1 -1
  34. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  35. package/dist/cdn/badge/snice-badge.js +1 -1
  36. package/dist/cdn/banner/snice-banner.js +1 -1
  37. package/dist/cdn/binpack/snice-binpack.js +1 -1
  38. package/dist/cdn/book/snice-book.js +1 -1
  39. package/dist/cdn/booking/snice-booking.js +1 -1
  40. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  41. package/dist/cdn/button/snice-button.js +1 -1
  42. package/dist/cdn/calendar/snice-calendar.js +1 -1
  43. package/dist/cdn/camera/snice-camera.js +1 -1
  44. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  45. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  46. package/dist/cdn/card/snice-card.js +1 -1
  47. package/dist/cdn/carousel/snice-carousel.js +1 -1
  48. package/dist/cdn/cart/snice-cart.js +1 -1
  49. package/dist/cdn/chart/snice-chart.js +1 -1
  50. package/dist/cdn/chat/snice-chat.js +1 -1
  51. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  52. package/dist/cdn/chip/snice-chip.js +1 -1
  53. package/dist/cdn/code-block/snice-code-block.js +1 -1
  54. package/dist/cdn/color-display/snice-color-display.js +1 -1
  55. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  56. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  57. package/dist/cdn/comments/snice-comments.js +1 -1
  58. package/dist/cdn/countdown/snice-countdown.js +1 -1
  59. package/dist/cdn/cropper/snice-cropper.js +1 -1
  60. package/dist/cdn/data-card/snice-data-card.js +1 -1
  61. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  62. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  63. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  64. package/dist/cdn/diff/snice-diff.js +1 -1
  65. package/dist/cdn/divider/snice-divider.js +1 -1
  66. package/dist/cdn/doc/snice-doc.js +1 -1
  67. package/dist/cdn/draw/snice-draw.js +1 -1
  68. package/dist/cdn/drawer/snice-drawer.js +1 -1
  69. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  70. package/dist/cdn/estimate/snice-estimate.js +1 -1
  71. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  72. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  73. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  74. package/dist/cdn/flow/snice-flow.js +1 -1
  75. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  76. package/dist/cdn/funnel/snice-funnel.js +1 -1
  77. package/dist/cdn/gantt/snice-gantt.js +1 -1
  78. package/dist/cdn/gauge/snice-gauge.js +1 -1
  79. package/dist/cdn/grid/snice-grid.js +1 -1
  80. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  81. package/dist/cdn/image/snice-image.js +1 -1
  82. package/dist/cdn/input/snice-input.js +1 -1
  83. package/dist/cdn/invoice/snice-invoice.js +1 -1
  84. package/dist/cdn/kanban/snice-kanban.js +1 -1
  85. package/dist/cdn/key-value/snice-key-value.js +1 -1
  86. package/dist/cdn/kpi/snice-kpi.js +1 -1
  87. package/dist/cdn/layout/snice-layout.js +1 -1
  88. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  89. package/dist/cdn/link/snice-link.js +1 -1
  90. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  91. package/dist/cdn/list/snice-list.js +1 -1
  92. package/dist/cdn/location/snice-location.js +1 -1
  93. package/dist/cdn/login/snice-login.js +1 -1
  94. package/dist/cdn/map/snice-map.js +1 -1
  95. package/dist/cdn/markdown/snice-markdown.js +1 -1
  96. package/dist/cdn/masonry/snice-masonry.js +1 -1
  97. package/dist/cdn/menu/snice-menu.js +1 -1
  98. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  99. package/dist/cdn/modal/snice-modal.js +1 -1
  100. package/dist/cdn/music-player/snice-music-player.js +1 -1
  101. package/dist/cdn/nav/snice-nav.js +1 -1
  102. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  103. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  104. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  105. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  106. package/dist/cdn/pagination/snice-pagination.js +1 -1
  107. package/dist/cdn/paint/snice-paint.js +1 -1
  108. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  109. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  110. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  111. package/dist/cdn/popover/snice-popover.js +1 -1
  112. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  113. package/dist/cdn/product-card/snice-product-card.js +1 -1
  114. package/dist/cdn/progress/snice-progress.js +1 -1
  115. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  116. package/dist/cdn/qr-code/snice-qr-code.js +1 -1
  117. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  118. package/dist/cdn/radio/snice-radio.js +1 -1
  119. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  120. package/dist/cdn/rating/snice-rating.js +1 -1
  121. package/dist/cdn/receipt/snice-receipt.js +1 -1
  122. package/dist/cdn/recipe/snice-recipe.js +1 -1
  123. package/dist/cdn/runtime/README.md +2 -2
  124. package/dist/cdn/runtime/snice-runtime.esm.js +333 -192
  125. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  126. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  127. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  128. package/dist/cdn/runtime/snice-runtime.js +333 -191
  129. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  130. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  131. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  132. package/dist/cdn/sankey/snice-sankey.js +1 -1
  133. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  134. package/dist/cdn/select/snice-select.js +1 -1
  135. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  136. package/dist/cdn/slider/snice-slider.js +1 -1
  137. package/dist/cdn/sortable/snice-sortable.js +1 -1
  138. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  139. package/dist/cdn/spinner/snice-spinner.js +1 -1
  140. package/dist/cdn/split-button/snice-split-button.js +1 -1
  141. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  142. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  143. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  144. package/dist/cdn/step-input/snice-step-input.js +1 -1
  145. package/dist/cdn/stepper/snice-stepper.js +1 -1
  146. package/dist/cdn/switch/snice-switch.js +1 -1
  147. package/dist/cdn/table/snice-table.esm.js +1 -1
  148. package/dist/cdn/table/snice-table.js +1 -1
  149. package/dist/cdn/tabs/snice-tabs.js +1 -1
  150. package/dist/cdn/tag/snice-tag.js +1 -1
  151. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  152. package/dist/cdn/terminal/snice-terminal.js +1 -1
  153. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  154. package/dist/cdn/textarea/snice-textarea.js +1 -1
  155. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  156. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  157. package/dist/cdn/timeline/snice-timeline.js +1 -1
  158. package/dist/cdn/timer/snice-timer.js +1 -1
  159. package/dist/cdn/toast/snice-toast.js +1 -1
  160. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  161. package/dist/cdn/tree/snice-tree.js +1 -1
  162. package/dist/cdn/treemap/snice-treemap.js +1 -1
  163. package/dist/cdn/user-card/snice-user-card.js +1 -1
  164. package/dist/cdn/video-player/snice-video-player.js +1 -1
  165. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  166. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  167. package/dist/cdn/weather/snice-weather.js +1 -1
  168. package/dist/cdn/work-order/snice-work-order.js +1 -1
  169. package/dist/components/pagination/snice-pagination.types.d.ts +5 -0
  170. package/dist/context-provider.d.ts +11 -1
  171. package/dist/context.d.ts +5 -6
  172. package/dist/index.cjs +330 -188
  173. package/dist/index.cjs.map +1 -1
  174. package/dist/index.d.ts +2 -1
  175. package/dist/index.esm.js +330 -189
  176. package/dist/index.esm.js.map +1 -1
  177. package/dist/index.iife.js +330 -188
  178. package/dist/index.iife.js.map +1 -1
  179. package/dist/react/SniceProvider.js +1 -1
  180. package/dist/react/SniceRouter.d.ts +2 -0
  181. package/dist/react/SniceRouter.js +3 -2
  182. package/dist/react/SniceRouter.js.map +1 -1
  183. package/dist/react/index.js +1 -1
  184. package/dist/react/matchRoute.d.ts +4 -1
  185. package/dist/react/matchRoute.js +13 -3
  186. package/dist/react/matchRoute.js.map +1 -1
  187. package/dist/react/useRequestHandler.js +1 -1
  188. package/dist/symbols.cjs +1 -1
  189. package/dist/symbols.esm.js +1 -1
  190. package/dist/testing-dom.cjs +48 -0
  191. package/dist/testing-dom.cjs.map +1 -0
  192. package/dist/testing-dom.d.ts +23 -0
  193. package/dist/testing-dom.esm.js +45 -0
  194. package/dist/testing-dom.esm.js.map +1 -0
  195. package/dist/transitions.cjs +1 -1
  196. package/dist/transitions.esm.js +1 -1
  197. package/dist/types/context.d.ts +8 -8
  198. package/dist/types/page-options.d.ts +15 -1
  199. package/dist/types/router-instance.d.ts +2 -1
  200. package/dist/utils.d.ts +3 -1
  201. package/docs/ai/README.md +6 -4
  202. package/docs/ai/api.md +13 -2
  203. package/docs/ai/architecture.md +28 -11
  204. package/docs/ai/bindings.md +1 -1
  205. package/docs/ai/cli.md +26 -1
  206. package/docs/ai/components/badge.md +4 -0
  207. package/docs/ai/components/pagination.md +4 -2
  208. package/docs/ai/components/segmented-control.md +5 -1
  209. package/docs/ai/controllers.md +41 -12
  210. package/docs/ai/decorators.md +3 -3
  211. package/docs/ai/events.md +8 -2
  212. package/docs/ai/fetcher.md +32 -1
  213. package/docs/ai/lifecycle.md +4 -1
  214. package/docs/ai/patterns.md +9 -4
  215. package/docs/ai/react-integration.md +1 -0
  216. package/docs/ai/request-response.md +20 -6
  217. package/docs/ai/routing.md +33 -4
  218. package/docs/ai/testing.md +71 -0
  219. package/docs/bindings.md +1 -1
  220. package/docs/cli.md +27 -2
  221. package/docs/components/badge.md +4 -0
  222. package/docs/components/pagination.md +8 -5
  223. package/docs/components/segmented-control.md +5 -1
  224. package/docs/controllers.md +46 -15
  225. package/docs/events.md +11 -2
  226. package/docs/fetcher.md +32 -1
  227. package/docs/lifecycle.md +5 -1
  228. package/docs/react-integration.md +1 -0
  229. package/docs/request-response.md +25 -7
  230. package/docs/routing.md +39 -3
  231. package/docs/testing.md +71 -0
  232. package/package.json +6 -1
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.1.1
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -230,7 +230,7 @@ let contextIdCounter = 0;
230
230
  */
231
231
  class Context {
232
232
  constructor(context = {}, placards = [], currentRoute = '', routeParams = {}, fetcher) {
233
- // Stores the SET of wrapped-method names per element so that classes with
233
+ // Stores the SET of wrapped-method names per subscriber so that classes with
234
234
  // multiple @context() handlers register every method, not just the last one.
235
235
  this[_a] = new WeakMap();
236
236
  this[_b] = new Set();
@@ -254,7 +254,7 @@ class Context {
254
254
  }
255
255
  }
256
256
  /**
257
- * Register an element to receive context updates
257
+ * Register an element or controller to receive context updates
258
258
  * @internal Used by @context decorator
259
259
  */
260
260
  [(_a = REGISTERED_ELEMENTS, _b = REGISTERED_ELEMENTS_SET, _c = IS_UPDATING, CONTEXT_REGISTER)](element, methodName) {
@@ -268,10 +268,10 @@ class Context {
268
268
  this[REGISTERED_ELEMENTS_SET].add(element);
269
269
  }
270
270
  /**
271
- * Unregister an element from receiving context updates. With a methodName,
271
+ * Unregister a subscriber from context updates. With a methodName,
272
272
  * removes only that one handler (so a `once` handler tearing itself down
273
273
  * doesn't kill the element's other @context handlers); without one, removes
274
- * the element entirely (used on disconnect).
274
+ * the subscriber entirely (used on disconnect/detach).
275
275
  * @internal Used by @context decorator cleanup
276
276
  */
277
277
  [CONTEXT_UNREGISTER](element, methodName) {
@@ -312,7 +312,7 @@ class Context {
312
312
  return;
313
313
  }
314
314
  this[IS_UPDATING] = true;
315
- // Notify all registered elements by calling EVERY registered method
315
+ // Notify all registered subscribers by calling EVERY registered method
316
316
  // name on each. Classes with multiple @context() methods get all fired.
317
317
  const elementsSet = this[REGISTERED_ELEMENTS_SET];
318
318
  const elementsMap = this[REGISTERED_ELEMENTS];
@@ -334,7 +334,7 @@ class Context {
334
334
  this[IS_UPDATING] = false;
335
335
  }
336
336
  /**
337
- * Notify a specific element of the current context state
337
+ * Notify a specific subscriber of the current context state
338
338
  * @internal Used by @context decorator
339
339
  */
340
340
  [CONTEXT_NOTIFY_ELEMENT](element) {
@@ -343,7 +343,12 @@ class Context {
343
343
  return;
344
344
  for (const methodName of names) {
345
345
  if (typeof element[methodName] === 'function') {
346
- element[methodName](this);
346
+ try {
347
+ element[methodName](this);
348
+ }
349
+ catch (error) {
350
+ console.error(`Error calling @context method ${methodName}:`, error);
351
+ }
347
352
  }
348
353
  }
349
354
  }
@@ -675,11 +680,17 @@ function invokeImmediateWatchers(instance, constructor) {
675
680
  */
676
681
  function createDebounced(fn, delay) {
677
682
  let timer = null;
678
- return function (...args) {
683
+ const debounced = function (...args) {
679
684
  if (timer !== null)
680
685
  clearTimeout(timer);
681
686
  timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay);
682
687
  };
688
+ debounced.cancel = () => {
689
+ if (timer !== null)
690
+ clearTimeout(timer);
691
+ timer = null;
692
+ };
693
+ return debounced;
683
694
  }
684
695
  /**
685
696
  * Create a throttled version of a function (leading + trailing edge)
@@ -1136,7 +1147,7 @@ function contextTarget(source) {
1136
1147
  * boundary. Public callers use provideContext(), which also activates the
1137
1148
  * daemon instances declared by the context.
1138
1149
  */
1139
- function installContextProvider(root, context) {
1150
+ function installContextProvider(root, context, fetch, navigation) {
1140
1151
  if (!root || typeof root.addEventListener !== 'function' || typeof root.removeEventListener !== 'function') {
1141
1152
  throw new TypeError('provideContext() requires an EventTarget root.');
1142
1153
  }
@@ -1148,9 +1159,26 @@ function installContextProvider(root, context) {
1148
1159
  }
1149
1160
  const listener = (event) => {
1150
1161
  const request = event;
1151
- if (!request.detail || request.detail.context !== undefined)
1162
+ if (!request.detail)
1163
+ return;
1164
+ if (request.detail.kind === 'application') {
1165
+ if (request.detail.context !== undefined)
1166
+ return;
1167
+ request.detail.context = context;
1168
+ }
1169
+ else if (request.detail.kind === 'fetch') {
1170
+ if (!fetch || request.detail.fetch !== undefined)
1171
+ return;
1172
+ request.detail.fetch = fetch;
1173
+ }
1174
+ else if (request.detail.kind === 'navigation') {
1175
+ if (!navigation || request.detail.navigation !== undefined)
1176
+ return;
1177
+ request.detail.navigation = navigation;
1178
+ }
1179
+ else {
1152
1180
  return;
1153
- request.detail.context = context;
1181
+ }
1154
1182
  request.preventDefault();
1155
1183
  request.stopPropagation();
1156
1184
  };
@@ -1180,7 +1208,7 @@ function getContext(source) {
1180
1208
  }
1181
1209
  const target = contextTarget(source);
1182
1210
  if (target) {
1183
- const detail = {};
1211
+ const detail = { kind: 'application' };
1184
1212
  target.dispatchEvent(new CustomEvent(CONTEXT_REQUEST_EVENT, {
1185
1213
  bubbles: true,
1186
1214
  composed: true,
@@ -1199,6 +1227,39 @@ function getContext(source) {
1199
1227
  }
1200
1228
  return undefined;
1201
1229
  }
1230
+ /**
1231
+ * Resolve the fetch function supplied by the nearest context provider.
1232
+ * Router providers expose their ContextAwareFetcher-bound function here;
1233
+ * explicit providers may supply the same dependency for non-router apps and
1234
+ * tests without adding a reserved property to AppContext.
1235
+ */
1236
+ function getContextFetch(source) {
1237
+ const target = contextTarget(source);
1238
+ if (!target)
1239
+ return undefined;
1240
+ const detail = { kind: 'fetch' };
1241
+ target.dispatchEvent(new CustomEvent(CONTEXT_REQUEST_EVENT, {
1242
+ bubbles: true,
1243
+ composed: true,
1244
+ cancelable: true,
1245
+ detail,
1246
+ }));
1247
+ return detail.fetch;
1248
+ }
1249
+ /** @internal Resolve the router Context visible to an element or controller. */
1250
+ function getNavigationContext(source) {
1251
+ const target = contextTarget(source);
1252
+ if (!target)
1253
+ return undefined;
1254
+ const detail = { kind: 'navigation' };
1255
+ target.dispatchEvent(new CustomEvent(CONTEXT_REQUEST_EVENT, {
1256
+ bubbles: true,
1257
+ composed: true,
1258
+ cancelable: true,
1259
+ detail,
1260
+ }));
1261
+ return detail.navigation;
1262
+ }
1202
1263
 
1203
1264
  function isDaemonInstance(value) {
1204
1265
  return !!value
@@ -1862,12 +1923,13 @@ function setupEventHandlers(instance, targetElement) {
1862
1923
  }
1863
1924
  // Initialize cleanup object if needed
1864
1925
  if (!instance[CLEANUP]) {
1865
- instance[CLEANUP] = { events: [], channels: [], observers: [] };
1926
+ instance[CLEANUP] = { events: [], eventCancels: [], channels: [], observers: [] };
1866
1927
  }
1867
- else if (!instance[CLEANUP].events) {
1928
+ if (!instance[CLEANUP].events)
1868
1929
  instance[CLEANUP].events = [];
1869
- }
1870
- else if (instance[CLEANUP].events.length > 0) {
1930
+ if (!instance[CLEANUP].eventCancels)
1931
+ instance[CLEANUP].eventCancels = [];
1932
+ if (instance[CLEANUP].events.length > 0 || instance[CLEANUP].eventCancels.length > 0) {
1871
1933
  // Events already set up - clean them up first to avoid duplicates
1872
1934
  cleanupEventHandlers(instance);
1873
1935
  }
@@ -1897,7 +1959,9 @@ function setupEventHandlers(instance, targetElement) {
1897
1959
  warnIfModifierMisuse(handler.eventName);
1898
1960
  // Apply debounce (takes precedence over throttle)
1899
1961
  if (handlerOptions.debounce && handlerOptions.debounce > 0) {
1900
- boundMethod = createDebounced(boundMethod, handlerOptions.debounce);
1962
+ const debounced = createDebounced(boundMethod, handlerOptions.debounce);
1963
+ instance[CLEANUP].eventCancels.push(() => debounced.cancel());
1964
+ boundMethod = debounced;
1901
1965
  }
1902
1966
  else if (handlerOptions.throttle && handlerOptions.throttle > 0) {
1903
1967
  boundMethod = throttle$1(boundMethod, handlerOptions.throttle);
@@ -2070,12 +2134,190 @@ function setupEventHandlers(instance, targetElement) {
2070
2134
  function cleanupEventHandlers(instance) {
2071
2135
  if (!instance[CLEANUP]?.events)
2072
2136
  return;
2137
+ for (const cancel of instance[CLEANUP].eventCancels ?? [])
2138
+ cancel();
2139
+ instance[CLEANUP].eventCancels = [];
2073
2140
  for (const { target, eventName, handler, options } of instance[CLEANUP].events) {
2074
2141
  target.removeEventListener(eventName, handler, options);
2075
2142
  }
2076
2143
  instance[CLEANUP].events = [];
2077
2144
  }
2078
2145
 
2146
+ /**
2147
+ * @context decorator for receiving router context updates
2148
+ */
2149
+ const CONTEXT_HANDLERS = getSymbol('context-handlers');
2150
+ /**
2151
+ * @context decorator for receiving router context updates
2152
+ *
2153
+ * @example
2154
+ * ```typescript
2155
+ * @element('my-layout')
2156
+ * class MyLayout extends HTMLElement {
2157
+ * @context
2158
+ * handleContext(ctx: Context) {
2159
+ * this.renderNav(ctx.placards, ctx.currentRoute);
2160
+ * }
2161
+ *
2162
+ * @context({ debounce: 300 })
2163
+ * handleContextDebounced(ctx: Context) {
2164
+ * // Called after 300ms of no updates
2165
+ * }
2166
+ * }
2167
+ * ```
2168
+ */
2169
+ function context$1(options = {}) {
2170
+ return function (originalMethod, context) {
2171
+ const methodName = context.name;
2172
+ context.addInitializer(function () {
2173
+ const constructor = this.constructor;
2174
+ // hasOwnProperty guards so subclasses don't mutate parent state via
2175
+ // the prototype chain.
2176
+ if (!Object.prototype.hasOwnProperty.call(constructor, CONTEXT_METHODS)) {
2177
+ constructor[CONTEXT_METHODS] = new Set();
2178
+ }
2179
+ if (constructor[CONTEXT_METHODS].has(originalMethod))
2180
+ return;
2181
+ constructor[CONTEXT_METHODS].add(originalMethod);
2182
+ if (!Object.prototype.hasOwnProperty.call(constructor, CONTEXT_HANDLERS)) {
2183
+ const inherited = constructor[CONTEXT_HANDLERS];
2184
+ constructor[CONTEXT_HANDLERS] = inherited ? [...inherited] : [];
2185
+ }
2186
+ constructor[CONTEXT_HANDLERS].push({
2187
+ methodName,
2188
+ method: originalMethod,
2189
+ options,
2190
+ });
2191
+ });
2192
+ return originalMethod;
2193
+ };
2194
+ }
2195
+ /**
2196
+ * Set up context handlers for an element or attached controller.
2197
+ * Called automatically by the corresponding managed lifecycle.
2198
+ */
2199
+ function setupContextHandler(element, catchUp = false) {
2200
+ const handlers = element.constructor[CONTEXT_HANDLERS];
2201
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2202
+ return;
2203
+ }
2204
+ // Get the Context instance from the router
2205
+ const ctx = element[CONTEXT_HANDLER] ?? getNavigationContext(element);
2206
+ if (!ctx) {
2207
+ return;
2208
+ }
2209
+ // Store the Context instance for cleanup
2210
+ element[NAVIGATION_CONTEXT_INSTANCE] = ctx;
2211
+ // A controller can finish attaching after the Router has already announced
2212
+ // the current navigation. Track whether an update arrives during this turn;
2213
+ // if none does, catch it up once from the Context's current state.
2214
+ let receivedContextUpdate = false;
2215
+ // Register each handler with the Context
2216
+ for (const handler of handlers) {
2217
+ const { methodName, method, options } = handler;
2218
+ const wrappedMethodName = `__wrapped_${methodName}`;
2219
+ // Create wrapped method with timing controls
2220
+ element[wrappedMethodName] = function (context) {
2221
+ receivedContextUpdate = true;
2222
+ // `once` is tracked PER method (not a single element-level flag) so
2223
+ // multiple once handlers on the same element each fire.
2224
+ const called = element[CONTEXT_CALLED] ||
2225
+ (element[CONTEXT_CALLED] = new Set());
2226
+ if (options.once && called.has(methodName)) {
2227
+ return;
2228
+ }
2229
+ const callMethod = () => {
2230
+ method.call(element, context);
2231
+ // Handle once option
2232
+ if (options.once) {
2233
+ called.add(methodName);
2234
+ // Unregister ONLY this handler, not the whole element — otherwise the
2235
+ // element's other @context handlers would stop receiving updates.
2236
+ const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2237
+ if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2238
+ ctx[CONTEXT_UNREGISTER](element, wrappedMethodName);
2239
+ }
2240
+ }
2241
+ };
2242
+ // Per-handler timer slot to avoid debounce/throttle handlers on the same
2243
+ // element overwriting each other's state.
2244
+ const timerSlot = element[CONTEXT_TIMER] ||
2245
+ (element[CONTEXT_TIMER] = {});
2246
+ const timerKey = methodName;
2247
+ if (options.debounce) {
2248
+ clearTimeout(timerSlot[timerKey]?.timeout);
2249
+ timerSlot[timerKey] = {
2250
+ timeout: setTimeout(callMethod, options.debounce),
2251
+ };
2252
+ return;
2253
+ }
2254
+ if (options.throttle) {
2255
+ const now = Date.now();
2256
+ const lastCall = timerSlot[timerKey]?.lastCall || 0;
2257
+ if (now - lastCall >= options.throttle) {
2258
+ timerSlot[timerKey] = { lastCall: now };
2259
+ callMethod();
2260
+ }
2261
+ return;
2262
+ }
2263
+ callMethod();
2264
+ };
2265
+ // Register with the Context using the wrapped method name
2266
+ if (typeof ctx[CONTEXT_REGISTER] === 'function') {
2267
+ ctx[CONTEXT_REGISTER](element, wrappedMethodName);
2268
+ }
2269
+ }
2270
+ if (catchUp && typeof ctx[CONTEXT_NOTIFY_ELEMENT] === 'function') {
2271
+ return new Promise((resolve) => {
2272
+ const notifyIfNeeded = () => {
2273
+ try {
2274
+ if (!receivedContextUpdate
2275
+ && element[NAVIGATION_CONTEXT_INSTANCE] === ctx) {
2276
+ ctx[CONTEXT_NOTIFY_ELEMENT](element);
2277
+ }
2278
+ }
2279
+ finally {
2280
+ resolve();
2281
+ }
2282
+ };
2283
+ if (catchUp === 'task')
2284
+ setTimeout(notifyIfNeeded, 0);
2285
+ else
2286
+ queueMicrotask(notifyIfNeeded);
2287
+ });
2288
+ }
2289
+ }
2290
+ /**
2291
+ * Clean up context handlers for an element or attached controller.
2292
+ */
2293
+ function cleanupContextHandler(element) {
2294
+ const handlers = element.constructor[CONTEXT_HANDLERS];
2295
+ if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2296
+ return;
2297
+ }
2298
+ // Clear any pending debounce timers (per-handler slots)
2299
+ const timerSlot = element[CONTEXT_TIMER];
2300
+ if (timerSlot && typeof timerSlot === 'object') {
2301
+ for (const key of Object.keys(timerSlot)) {
2302
+ if (timerSlot[key]?.timeout)
2303
+ clearTimeout(timerSlot[key].timeout);
2304
+ }
2305
+ delete element[CONTEXT_TIMER];
2306
+ }
2307
+ for (const handler of handlers) {
2308
+ // Clean up wrapped method
2309
+ const wrappedMethodName = `__wrapped_${handler.methodName}`;
2310
+ delete element[wrappedMethodName];
2311
+ }
2312
+ // Unregister from Context if available
2313
+ const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2314
+ if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2315
+ ctx[CONTEXT_UNREGISTER](element);
2316
+ }
2317
+ delete element[NAVIGATION_CONTEXT_INSTANCE];
2318
+ delete element[CONTEXT_CALLED];
2319
+ }
2320
+
2079
2321
  // Controller-scoped cleanup registry
2080
2322
  class ControllerScope {
2081
2323
  constructor() {
@@ -2409,6 +2651,10 @@ async function attachController(element, controller) {
2409
2651
  await scope.runOperation(async () => {
2410
2652
  await controllerInstance.attach(element);
2411
2653
  });
2654
+ // @context has the same managed lifecycle on controllers as @on,
2655
+ // @observe and @respond. Catch up controllers whose async attach completed
2656
+ // after the Router announced the current navigation.
2657
+ await setupContextHandler(controllerInstance, 'microtask');
2412
2658
  // Setup @observe observers for controller
2413
2659
  setupObservers(controllerInstance, element);
2414
2660
  // Setup @channel handlers for controller
@@ -2474,6 +2720,8 @@ async function detachController(element) {
2474
2720
  cleanupResponseHandlers(controllerInstance);
2475
2721
  // Cleanup @on event handlers for controller
2476
2722
  cleanupEventHandlers(controllerInstance);
2723
+ // Cleanup @context handlers for controller
2724
+ cleanupContextHandler(controllerInstance);
2477
2725
  // Cleanup the controller scope
2478
2726
  if (scope) {
2479
2727
  await scope.cleanup();
@@ -2607,158 +2855,6 @@ function useNativeElementControllers() {
2607
2855
  globalThis.sniceNativeControllerObserver = observer;
2608
2856
  }
2609
2857
 
2610
- /**
2611
- * @context decorator for receiving router context updates
2612
- */
2613
- const CONTEXT_HANDLERS = getSymbol('context-handlers');
2614
- /**
2615
- * @context decorator for receiving router context updates
2616
- *
2617
- * @example
2618
- * ```typescript
2619
- * @element('my-layout')
2620
- * class MyLayout extends HTMLElement {
2621
- * @context
2622
- * handleContext(ctx: Context) {
2623
- * this.renderNav(ctx.placards, ctx.currentRoute);
2624
- * }
2625
- *
2626
- * @context({ debounce: 300 })
2627
- * handleContextDebounced(ctx: Context) {
2628
- * // Called after 300ms of no updates
2629
- * }
2630
- * }
2631
- * ```
2632
- */
2633
- function context$1(options = {}) {
2634
- return function (originalMethod, context) {
2635
- const methodName = context.name;
2636
- context.addInitializer(function () {
2637
- const constructor = this.constructor;
2638
- // hasOwnProperty guards so subclasses don't mutate parent state via
2639
- // the prototype chain.
2640
- if (!Object.prototype.hasOwnProperty.call(constructor, CONTEXT_METHODS)) {
2641
- constructor[CONTEXT_METHODS] = new Set();
2642
- }
2643
- if (constructor[CONTEXT_METHODS].has(originalMethod))
2644
- return;
2645
- constructor[CONTEXT_METHODS].add(originalMethod);
2646
- if (!Object.prototype.hasOwnProperty.call(constructor, CONTEXT_HANDLERS)) {
2647
- const inherited = constructor[CONTEXT_HANDLERS];
2648
- constructor[CONTEXT_HANDLERS] = inherited ? [...inherited] : [];
2649
- }
2650
- constructor[CONTEXT_HANDLERS].push({
2651
- methodName,
2652
- method: originalMethod,
2653
- options,
2654
- });
2655
- });
2656
- return originalMethod;
2657
- };
2658
- }
2659
- /**
2660
- * Setup context handler for an element instance
2661
- * Called automatically during element connection
2662
- */
2663
- function setupContextHandler(element) {
2664
- const handlers = element.constructor[CONTEXT_HANDLERS];
2665
- if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2666
- return;
2667
- }
2668
- // Get the Context instance from the router
2669
- const ctx = element[CONTEXT_HANDLER];
2670
- if (!ctx) {
2671
- return;
2672
- }
2673
- // Store the Context instance for cleanup
2674
- element[NAVIGATION_CONTEXT_INSTANCE] = ctx;
2675
- // Register each handler with the Context
2676
- for (const handler of handlers) {
2677
- const { methodName, method, options } = handler;
2678
- const wrappedMethodName = `__wrapped_${methodName}`;
2679
- // Create wrapped method with timing controls
2680
- element[wrappedMethodName] = function (context) {
2681
- // `once` is tracked PER method (not a single element-level flag) so
2682
- // multiple once handlers on the same element each fire.
2683
- const called = element[CONTEXT_CALLED] ||
2684
- (element[CONTEXT_CALLED] = new Set());
2685
- if (options.once && called.has(methodName)) {
2686
- return;
2687
- }
2688
- const callMethod = () => {
2689
- method.call(element, context);
2690
- // Handle once option
2691
- if (options.once) {
2692
- called.add(methodName);
2693
- // Unregister ONLY this handler, not the whole element — otherwise the
2694
- // element's other @context handlers would stop receiving updates.
2695
- const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2696
- if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2697
- ctx[CONTEXT_UNREGISTER](element, wrappedMethodName);
2698
- }
2699
- }
2700
- };
2701
- // Per-handler timer slot to avoid debounce/throttle handlers on the same
2702
- // element overwriting each other's state.
2703
- const timerSlot = element[CONTEXT_TIMER] ||
2704
- (element[CONTEXT_TIMER] = {});
2705
- const timerKey = methodName;
2706
- if (options.debounce) {
2707
- clearTimeout(timerSlot[timerKey]?.timeout);
2708
- timerSlot[timerKey] = {
2709
- timeout: setTimeout(callMethod, options.debounce),
2710
- };
2711
- return;
2712
- }
2713
- if (options.throttle) {
2714
- const now = Date.now();
2715
- const lastCall = timerSlot[timerKey]?.lastCall || 0;
2716
- if (now - lastCall >= options.throttle) {
2717
- timerSlot[timerKey] = { lastCall: now };
2718
- callMethod();
2719
- }
2720
- return;
2721
- }
2722
- callMethod();
2723
- };
2724
- // Register with the Context using the wrapped method name
2725
- if (typeof ctx[CONTEXT_REGISTER] === 'function') {
2726
- ctx[CONTEXT_REGISTER](element, wrappedMethodName);
2727
- }
2728
- }
2729
- }
2730
- /**
2731
- * Cleanup context handler for an element instance
2732
- * Called automatically during element disconnection
2733
- */
2734
- function cleanupContextHandler(element) {
2735
- const handlers = element.constructor[CONTEXT_HANDLERS];
2736
- if (!handlers || !Array.isArray(handlers) || handlers.length === 0) {
2737
- return;
2738
- }
2739
- // Clear any pending debounce timers (per-handler slots)
2740
- const timerSlot = element[CONTEXT_TIMER];
2741
- if (timerSlot && typeof timerSlot === 'object') {
2742
- for (const key of Object.keys(timerSlot)) {
2743
- if (timerSlot[key]?.timeout)
2744
- clearTimeout(timerSlot[key].timeout);
2745
- }
2746
- delete element[CONTEXT_TIMER];
2747
- }
2748
- for (const handler of handlers) {
2749
- // Clean up wrapped method
2750
- const wrappedMethodName = `__wrapped_${handler.methodName}`;
2751
- delete element[wrappedMethodName];
2752
- }
2753
- // Unregister from Context if available
2754
- const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
2755
- if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
2756
- ctx[CONTEXT_UNREGISTER](element);
2757
- }
2758
- delete element[NAVIGATION_CONTEXT_INSTANCE];
2759
- delete element[CONTEXT_CALLED];
2760
- }
2761
-
2762
2858
  /**
2763
2859
  * Template system for Snice v3.0.0
2764
2860
  * Provides html`` and css`` tagged template processors with differential rendering
@@ -2994,14 +3090,32 @@ const NOT_COMMITTED = Symbol('not-committed');
2994
3090
  function markPreUpgradePropertyBinding(element, propertyName) {
2995
3091
  const tagName = element.localName;
2996
3092
  if (!tagName?.includes('-'))
2997
- return;
3093
+ return false;
2998
3094
  const registry = element.ownerDocument?.defaultView?.customElements ?? globalThis.customElements;
2999
3095
  if (registry?.get(tagName))
3000
- return;
3096
+ return false;
3001
3097
  const target = element;
3002
3098
  if (!target[PRE_UPGRADE_PROPERTY_BINDINGS])
3003
3099
  target[PRE_UPGRADE_PROPERTY_BINDINGS] = new Set();
3004
3100
  target[PRE_UPGRADE_PROPERTY_BINDINGS].add(propertyName);
3101
+ return true;
3102
+ }
3103
+ function commitPropertyValue(element, propertyName, value) {
3104
+ if (!markPreUpgradePropertyBinding(element, propertyName)) {
3105
+ element[propertyName] = value;
3106
+ return;
3107
+ }
3108
+ // An undefined custom element can inherit a native IDL setter with the same
3109
+ // name. Assignment would invoke that setter (for example HTMLElement.role),
3110
+ // stringify structured data into an attribute, and leave no own value for
3111
+ // @property's upgrade adoption. Park every pre-upgrade binding as the own,
3112
+ // configurable value that the initializer already knows how to replay.
3113
+ Object.defineProperty(element, propertyName, {
3114
+ configurable: true,
3115
+ enumerable: true,
3116
+ writable: true,
3117
+ value,
3118
+ });
3005
3119
  }
3006
3120
  // noChange sentinel - preserves the currently committed value
3007
3121
  const noChange = Symbol.for('snice:no-change');
@@ -4638,15 +4752,13 @@ class SpreadPart extends Part {
4638
4752
  commitProperties(next) {
4639
4753
  for (const key of Object.keys(this.committed)) {
4640
4754
  if (!Object.prototype.hasOwnProperty.call(next, key)) {
4641
- markPreUpgradePropertyBinding(this.element, key);
4642
- this.element[key] = undefined;
4755
+ commitPropertyValue(this.element, key, undefined);
4643
4756
  }
4644
4757
  }
4645
4758
  for (const [key, value] of Object.entries(next)) {
4646
4759
  if (Object.is(this.committed[key], value))
4647
4760
  continue;
4648
- markPreUpgradePropertyBinding(this.element, key);
4649
- this.element[key] = value === nothing ? undefined : value;
4761
+ commitPropertyValue(this.element, key, value === nothing ? undefined : value);
4650
4762
  }
4651
4763
  }
4652
4764
  commitAttributes(next) {
@@ -4773,8 +4885,7 @@ class PropertyPart extends Part {
4773
4885
  return;
4774
4886
  }
4775
4887
  this._committedValue = value;
4776
- markPreUpgradePropertyBinding(this.element, this.name);
4777
- this.element[this.name] = value === nothing ? undefined : value;
4888
+ commitPropertyValue(this.element, this.name, value === nothing ? undefined : value);
4778
4889
  }
4779
4890
  clear() {
4780
4891
  // This part is being destroyed with its owning template. The element is
@@ -6806,7 +6917,7 @@ function applyElementFunctionality(constructor) {
6806
6917
  // Re-establish handlers that get cleaned up on disconnect
6807
6918
  setupEventHandlers(this, this);
6808
6919
  setupResponseHandlers(this, this);
6809
- setupContextHandler(this);
6920
+ void setupContextHandler(this, 'task');
6810
6921
  // Re-establish observers that get cleaned up on disconnect
6811
6922
  try {
6812
6923
  setupObservers(this, this);
@@ -6931,7 +7042,7 @@ function applyElementFunctionality(constructor) {
6931
7042
  }
6932
7043
  setupEventHandlers(this, this);
6933
7044
  setupResponseHandlers(this, this);
6934
- setupContextHandler(this);
7045
+ void setupContextHandler(this, 'task');
6935
7046
  this[INITIALIZED] = true;
6936
7047
  // One-time (per class) sanity check: a @watch name with no matching
6937
7048
  // @property never fires — surface the typo instead of staying silent.
@@ -8636,10 +8747,11 @@ function bindContextDaemons(context) {
8636
8747
  *
8637
8748
  * Daemon instances must already exist in context.daemons. The returned cleanup
8638
8749
  * removes the provider and deactivates framework communication for daemons no
8639
- * longer provided by any context.
8750
+ * longer provided by any context. An optional fetch function is exposed on the
8751
+ * same boundary through getContextFetch() without modifying the context object.
8640
8752
  */
8641
- function provideContext(root, context) {
8642
- const releaseProvider = installContextProvider(root, context);
8753
+ function provideContextBoundary(root, context, options, navigation) {
8754
+ const releaseProvider = installContextProvider(root, context, options.fetch, navigation);
8643
8755
  let releaseDaemons;
8644
8756
  try {
8645
8757
  releaseDaemons = bindContextDaemons(context);
@@ -8657,6 +8769,15 @@ function provideContext(root, context) {
8657
8769
  releaseDaemons?.();
8658
8770
  };
8659
8771
  }
8772
+ function provideContext(root, context, options = {}) {
8773
+ return provideContextBoundary(root, context, options);
8774
+ }
8775
+ /** @internal Router-owned provider including live navigation state. */
8776
+ function provideRouterContext(root, navigation) {
8777
+ return provideContextBoundary(root, navigation.application, {
8778
+ fetch: navigation.fetch,
8779
+ }, navigation);
8780
+ }
8660
8781
 
8661
8782
  // Router historically allows a later Router instance to take over the same
8662
8783
  // target. Track only Router-owned provider releases so that handoff remains
@@ -8680,6 +8801,7 @@ function Router(options) {
8680
8801
  const win = options.window ?? window;
8681
8802
  const doc = options.document ?? document;
8682
8803
  const routes = [];
8804
+ let nextRegistrationOrder = 0;
8683
8805
  let is_sorted = false;
8684
8806
  let placards = []; // Store collected placards
8685
8807
  let _404; // the 404 page
@@ -8699,7 +8821,7 @@ function Router(options) {
8699
8821
  return;
8700
8822
  releaseContextProvider?.();
8701
8823
  routerContextProviders.get(target)?.();
8702
- const releaseProvidedContext = provideContext(target, context);
8824
+ const releaseProvidedContext = provideRouterContext(target, navigationContext);
8703
8825
  let released = false;
8704
8826
  const release = () => {
8705
8827
  if (released)
@@ -8770,7 +8892,11 @@ function Router(options) {
8770
8892
  win.customElements.define(pageOptions.tag, constructor);
8771
8893
  }
8772
8894
  // Register the routes with guards, layout, and placard
8773
- pageOptions.routes.forEach(route => register(route, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout, pageOptions.placard));
8895
+ pageOptions.routes.forEach(route => {
8896
+ const path = typeof route === 'string' ? route : route.path;
8897
+ const order = typeof route === 'string' ? undefined : route.order;
8898
+ register(path, pageOptions.tag, pageOptions.transition, pageOptions.guards, pageOptions.layout, pageOptions.placard, order);
8899
+ });
8774
8900
  return constructor;
8775
8901
  };
8776
8902
  }
@@ -8778,11 +8904,24 @@ function Router(options) {
8778
8904
  * Registers a new route with the router.
8779
8905
  * @param {string} route - The route path.
8780
8906
  * @param {string} tag - The custom element tag associated with the route.
8907
+ * @param {number} order - Optional lower-first tie-break after specificity.
8781
8908
  * @example
8782
8909
  * register('/custom-route', 'custom-element');
8783
8910
  */
8784
- function register(route, tag, transition, guards, layout, placard) {
8785
- routes.push({ route: new Route(route), tag, transition, guards, layout, placard });
8911
+ function register(route, tag, transition, guards, layout, placard, order = 0) {
8912
+ if (!Number.isFinite(order)) {
8913
+ throw new TypeError(`Route order for "${route}" must be a finite number.`);
8914
+ }
8915
+ routes.push({
8916
+ route: new Route(route),
8917
+ tag,
8918
+ transition,
8919
+ guards,
8920
+ layout,
8921
+ placard,
8922
+ order,
8923
+ registrationOrder: nextRegistrationOrder++
8924
+ });
8786
8925
  is_sorted = false;
8787
8926
  switch (route) {
8788
8927
  case '/404':
@@ -8843,7 +8982,9 @@ function Router(options) {
8843
8982
  ensureContextProvider(target);
8844
8983
  const needsSorting = !is_sorted;
8845
8984
  if (needsSorting) {
8846
- routes.sort((a, b) => routeSpecificity(b.route.spec) - routeSpecificity(a.route.spec));
8985
+ routes.sort((a, b) => routeSpecificity(b.route.spec) - routeSpecificity(a.route.spec)
8986
+ || a.order - b.order
8987
+ || a.registrationOrder - b.registrationOrder);
8847
8988
  is_sorted = true;
8848
8989
  }
8849
8990
  // Collect placards from registered routes
@@ -9566,5 +9707,5 @@ if (typeof document !== 'undefined') {
9566
9707
  // Supports event delegation, keyboard modifiers, debounce/throttle - see docs/events.md
9567
9708
  // Template event syntax (@click=${handler}) is also available as an alternative.
9568
9709
 
9569
- export { Context, ContextAwareFetcher, IS_CONTROLLER_INSTANCE, Router, SimpleArray, SniceElement, TransitionMode, adopted, applyElementFunctionality, attachController, classMap, clearDebounceTimers, clearMemoizeCache, clearThrottleTimers, context$1 as context, context as contextProperty, controller, createRequestHandler, css, daemon, debounce, detachController, dispatch, dispose, element, escapeAttr, escapeHtml, getBodyScrollLockCount, getContext, getController, getSymbol, html, isSafeUrl, layout, live, lockBodyScroll, memoize, moved, noChange, nothing, observe, on, once, parseDuration, property, provideContext, query, queryAll, ready, reconnect, render, repeat, request, resetOnce, respond, setDisableElementReadyWarnings, setStrictRenderErrors, state, styleMap, styles, svg, throttle, trackRenders, unlockBodyScroll, unsafeHTML, useNativeElementControllers, waitForAllCustomElements, waitForElementDefined, waitForElementReady, watch };
9710
+ export { Context, ContextAwareFetcher, IS_CONTROLLER_INSTANCE, Router, SimpleArray, SniceElement, TransitionMode, adopted, applyElementFunctionality, attachController, classMap, clearDebounceTimers, clearMemoizeCache, clearThrottleTimers, context$1 as context, context as contextProperty, controller, createRequestHandler, css, daemon, debounce, detachController, dispatch, dispose, element, escapeAttr, escapeHtml, getBodyScrollLockCount, getContext, getContextFetch, getController, getSymbol, html, isSafeUrl, layout, live, lockBodyScroll, memoize, moved, noChange, nothing, observe, on, once, parseDuration, property, provideContext, query, queryAll, ready, reconnect, render, repeat, request, resetOnce, respond, setDisableElementReadyWarnings, setStrictRenderErrors, state, styleMap, styles, svg, throttle, trackRenders, unlockBodyScroll, unsafeHTML, useNativeElementControllers, waitForAllCustomElements, waitForElementDefined, waitForElementReady, watch };
9570
9711
  //# sourceMappingURL=index.esm.js.map