@adukiorg/anza 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +215 -0
  3. package/bin/anza.js +63 -0
  4. package/bin/create.js +150 -0
  5. package/importmap.json +72 -0
  6. package/package.json +100 -0
  7. package/src/core/animations/index.js +55 -0
  8. package/src/core/animations/play.js +111 -0
  9. package/src/core/animations/registry.js +54 -0
  10. package/src/core/animations/scroll.js +50 -0
  11. package/src/core/animations/tokens.js +58 -0
  12. package/src/core/animations/usage.md +301 -0
  13. package/src/core/animations/waapi.js +86 -0
  14. package/src/core/api/cache.js +120 -0
  15. package/src/core/api/caches/glob.js +24 -0
  16. package/src/core/api/caches/index.js +118 -0
  17. package/src/core/api/events/index.js +75 -0
  18. package/src/core/api/fetch.js +99 -0
  19. package/src/core/api/index.js +158 -0
  20. package/src/core/api/pipeline.js +98 -0
  21. package/src/core/api/plan.md +209 -0
  22. package/src/core/api/prefixes/index.js +66 -0
  23. package/src/core/api/retry.js +69 -0
  24. package/src/core/api/stream.js +127 -0
  25. package/src/core/api/upload.js +180 -0
  26. package/src/core/api/usage.md +206 -0
  27. package/src/core/events/bus.js +38 -0
  28. package/src/core/events/delegate.js +79 -0
  29. package/src/core/events/index.js +26 -0
  30. package/src/core/events/listen.js +50 -0
  31. package/src/core/events/missing.md +103 -0
  32. package/src/core/events/once.js +49 -0
  33. package/src/core/events/plan.md +177 -0
  34. package/src/core/events/types/index.js +34 -0
  35. package/src/core/events/usage.md +107 -0
  36. package/src/core/offline/bridge.js +51 -0
  37. package/src/core/offline/clock.js +100 -0
  38. package/src/core/offline/connectivity.js +116 -0
  39. package/src/core/offline/index.js +41 -0
  40. package/src/core/offline/missing.md +89 -0
  41. package/src/core/offline/plan.md +143 -0
  42. package/src/core/offline/queue.js +168 -0
  43. package/src/core/offline/state.js +18 -0
  44. package/src/core/offline/sync.js +106 -0
  45. package/src/core/offline/usage.md +273 -0
  46. package/src/core/platform/guard.js +104 -0
  47. package/src/core/platform/index.js +42 -0
  48. package/src/core/platform/missing.md +119 -0
  49. package/src/core/platform/platform.d.ts +88 -0
  50. package/src/core/platform/polyfills/anchor.js +79 -0
  51. package/src/core/platform/polyfills/navigation.js +142 -0
  52. package/src/core/platform/polyfills/popover.js +142 -0
  53. package/src/core/platform/polyfills/scheduler.js +194 -0
  54. package/src/core/platform/polyfills/shadow.js +35 -0
  55. package/src/core/platform/polyfills/urlpattern.js +119 -0
  56. package/src/core/platform/supports.js +186 -0
  57. package/src/core/platform/usage.md +287 -0
  58. package/src/core/router/cache.js +95 -0
  59. package/src/core/router/container.js +146 -0
  60. package/src/core/router/handler.js +52 -0
  61. package/src/core/router/history.js +120 -0
  62. package/src/core/router/index.js +158 -0
  63. package/src/core/router/intercept.js +376 -0
  64. package/src/core/router/match.js +145 -0
  65. package/src/core/router/missing.md +716 -0
  66. package/src/core/router/outlet.js +139 -0
  67. package/src/core/router/plan.md +370 -0
  68. package/src/core/router/sync/index.js +16 -0
  69. package/src/core/router/sync/tab.js +115 -0
  70. package/src/core/router/sync/transport.js +139 -0
  71. package/src/core/router/transitions.js +59 -0
  72. package/src/core/router/usage.md +773 -0
  73. package/src/core/security/crypto.js +159 -0
  74. package/src/core/security/index.js +49 -0
  75. package/src/core/security/missing.md +97 -0
  76. package/src/core/security/permissions.js +64 -0
  77. package/src/core/security/sanitize.js +100 -0
  78. package/src/core/security/usage.md +283 -0
  79. package/src/core/state/derived.js +117 -0
  80. package/src/core/state/index.js +23 -0
  81. package/src/core/state/missing.md +165 -0
  82. package/src/core/state/persist.js +284 -0
  83. package/src/core/state/store.js +308 -0
  84. package/src/core/state/sync.js +46 -0
  85. package/src/core/state/usage.md +440 -0
  86. package/src/core/storage/cache.js +83 -0
  87. package/src/core/storage/idb.js +196 -0
  88. package/src/core/storage/index.js +373 -0
  89. package/src/core/storage/lru.js +107 -0
  90. package/src/core/storage/missing.md +165 -0
  91. package/src/core/storage/opfs.js +190 -0
  92. package/src/core/storage/plan.md +69 -0
  93. package/src/core/storage/quota.js +69 -0
  94. package/src/core/storage/usage.md +226 -0
  95. package/src/core/ui/base.js +50 -0
  96. package/src/core/ui/define/container.js +82 -0
  97. package/src/core/ui/define/define.js +12 -0
  98. package/src/core/ui/define/element.js +390 -0
  99. package/src/core/ui/define/index.js +9 -0
  100. package/src/core/ui/define/orchestrator.js +105 -0
  101. package/src/core/ui/define/proxy.js +644 -0
  102. package/src/core/ui/define/state.js +6 -0
  103. package/src/core/ui/define/utils.js +134 -0
  104. package/src/core/ui/implementation.md +170 -0
  105. package/src/core/ui/index.js +41 -0
  106. package/src/core/ui/observe.js +117 -0
  107. package/src/core/ui/plan.md +510 -0
  108. package/src/core/ui/schedule.js +60 -0
  109. package/src/core/ui/template.js +37 -0
  110. package/src/core/ui/transitions.js +37 -0
  111. package/src/core/ui/ui.types.md +890 -0
  112. package/src/core/ui/usage.md +1124 -0
  113. package/src/core/ui/watch.md +346 -0
  114. package/src/core/workers/broadcast.js +138 -0
  115. package/src/core/workers/dedicated.js +153 -0
  116. package/src/core/workers/index.js +169 -0
  117. package/src/core/workers/locks.js +160 -0
  118. package/src/core/workers/offscreen.js +166 -0
  119. package/src/core/workers/plan.md +381 -0
  120. package/src/core/workers/pool.js +267 -0
  121. package/src/core/workers/shared.js +137 -0
  122. package/src/core/workers/usage.md +622 -0
  123. package/src/elements/base.js +12 -0
  124. package/src/elements/data/card/index.html +9 -0
  125. package/src/elements/data/card/index.js +19 -0
  126. package/src/elements/data/card/index.tags.json +1 -0
  127. package/src/elements/data/card/style.css +46 -0
  128. package/src/elements/data/chart/index.html +1 -0
  129. package/src/elements/data/chart/index.js +143 -0
  130. package/src/elements/data/chart/index.tags.json +1 -0
  131. package/src/elements/data/chart/style.css +13 -0
  132. package/src/elements/data/list/index.html +3 -0
  133. package/src/elements/data/list/index.js +19 -0
  134. package/src/elements/data/list/index.tags.json +1 -0
  135. package/src/elements/data/list/style.css +39 -0
  136. package/src/elements/data/stat/index.html +9 -0
  137. package/src/elements/data/stat/index.js +19 -0
  138. package/src/elements/data/stat/index.tags.json +1 -0
  139. package/src/elements/data/stat/style.css +50 -0
  140. package/src/elements/data/table/index.html +1 -0
  141. package/src/elements/data/table/index.js +16 -0
  142. package/src/elements/data/table/index.tags.json +1 -0
  143. package/src/elements/data/table/style.css +50 -0
  144. package/src/elements/feedback/alert/index.html +11 -0
  145. package/src/elements/feedback/alert/index.js +28 -0
  146. package/src/elements/feedback/alert/index.tags.json +1 -0
  147. package/src/elements/feedback/alert/style.css +75 -0
  148. package/src/elements/feedback/empty/index.html +13 -0
  149. package/src/elements/feedback/empty/index.js +34 -0
  150. package/src/elements/feedback/empty/index.tags.json +1 -0
  151. package/src/elements/feedback/empty/style.css +45 -0
  152. package/src/elements/feedback/progress/index.html +7 -0
  153. package/src/elements/feedback/progress/index.js +46 -0
  154. package/src/elements/feedback/progress/index.tags.json +1 -0
  155. package/src/elements/feedback/progress/style.css +36 -0
  156. package/src/elements/feedback/skeleton/index.html +1 -0
  157. package/src/elements/feedback/skeleton/index.js +78 -0
  158. package/src/elements/feedback/skeleton/index.tags.json +1 -0
  159. package/src/elements/feedback/skeleton/style.css +28 -0
  160. package/src/elements/feedback/toast/index.html +3 -0
  161. package/src/elements/feedback/toast/index.js +65 -0
  162. package/src/elements/feedback/toast/index.tags.json +1 -0
  163. package/src/elements/feedback/toast/style.css +36 -0
  164. package/src/elements/forms/checkbox/index.html +7 -0
  165. package/src/elements/forms/checkbox/index.js +104 -0
  166. package/src/elements/forms/checkbox/index.tags.json +1 -0
  167. package/src/elements/forms/checkbox/style.css +86 -0
  168. package/src/elements/forms/field/index.html +13 -0
  169. package/src/elements/forms/field/index.js +42 -0
  170. package/src/elements/forms/field/index.tags.json +1 -0
  171. package/src/elements/forms/field/style.css +42 -0
  172. package/src/elements/forms/form/index.html +3 -0
  173. package/src/elements/forms/form/index.js +122 -0
  174. package/src/elements/forms/form/index.tags.json +1 -0
  175. package/src/elements/forms/form/style.css +11 -0
  176. package/src/elements/forms/input/index.html +4 -0
  177. package/src/elements/forms/input/index.js +103 -0
  178. package/src/elements/forms/input/index.tags.json +1 -0
  179. package/src/elements/forms/input/style.css +39 -0
  180. package/src/elements/forms/radio/index.html +4 -0
  181. package/src/elements/forms/radio/index.js +109 -0
  182. package/src/elements/forms/radio/index.tags.json +1 -0
  183. package/src/elements/forms/radio/style.css +65 -0
  184. package/src/elements/forms/select/index.html +9 -0
  185. package/src/elements/forms/select/index.js +114 -0
  186. package/src/elements/forms/select/index.tags.json +1 -0
  187. package/src/elements/forms/select/style.css +95 -0
  188. package/src/elements/forms/textarea/index.html +4 -0
  189. package/src/elements/forms/textarea/index.js +115 -0
  190. package/src/elements/forms/textarea/index.tags.json +1 -0
  191. package/src/elements/forms/textarea/style.css +46 -0
  192. package/src/elements/forms/toggle/index.html +4 -0
  193. package/src/elements/forms/toggle/index.js +89 -0
  194. package/src/elements/forms/toggle/index.tags.json +1 -0
  195. package/src/elements/forms/toggle/style.css +63 -0
  196. package/src/elements/forms/upload/index.html +13 -0
  197. package/src/elements/forms/upload/index.js +120 -0
  198. package/src/elements/forms/upload/index.tags.json +1 -0
  199. package/src/elements/forms/upload/style.css +61 -0
  200. package/src/elements/index.js +71 -0
  201. package/src/elements/layout/app/index.html +7 -0
  202. package/src/elements/layout/app/index.js +16 -0
  203. package/src/elements/layout/app/index.tags.json +1 -0
  204. package/src/elements/layout/app/style.css +41 -0
  205. package/src/elements/layout/grid/index.html +3 -0
  206. package/src/elements/layout/grid/index.js +41 -0
  207. package/src/elements/layout/grid/index.tags.json +1 -0
  208. package/src/elements/layout/grid/style.css +12 -0
  209. package/src/elements/layout/header/index.html +8 -0
  210. package/src/elements/layout/header/index.js +16 -0
  211. package/src/elements/layout/header/index.tags.json +1 -0
  212. package/src/elements/layout/header/style.css +28 -0
  213. package/src/elements/layout/scroll/index.html +3 -0
  214. package/src/elements/layout/scroll/index.js +19 -0
  215. package/src/elements/layout/scroll/index.tags.json +1 -0
  216. package/src/elements/layout/scroll/style.css +24 -0
  217. package/src/elements/layout/sidebar/index.html +3 -0
  218. package/src/elements/layout/sidebar/index.js +24 -0
  219. package/src/elements/layout/sidebar/index.tags.json +1 -0
  220. package/src/elements/layout/sidebar/style.css +30 -0
  221. package/src/elements/layout/split/index.html +3 -0
  222. package/src/elements/layout/split/index.js +18 -0
  223. package/src/elements/layout/split/index.tags.json +1 -0
  224. package/src/elements/layout/split/style.css +28 -0
  225. package/src/elements/layout/stack/index.html +3 -0
  226. package/src/elements/layout/stack/index.js +31 -0
  227. package/src/elements/layout/stack/index.tags.json +1 -0
  228. package/src/elements/layout/stack/style.css +15 -0
  229. package/src/elements/layout/surface/index.html +3 -0
  230. package/src/elements/layout/surface/index.js +19 -0
  231. package/src/elements/layout/surface/index.tags.json +1 -0
  232. package/src/elements/layout/surface/style.css +29 -0
  233. package/src/elements/navigation/breadcrumb/index.html +5 -0
  234. package/src/elements/navigation/breadcrumb/index.js +16 -0
  235. package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
  236. package/src/elements/navigation/breadcrumb/style.css +36 -0
  237. package/src/elements/navigation/nav/index.html +3 -0
  238. package/src/elements/navigation/nav/index.js +24 -0
  239. package/src/elements/navigation/nav/index.tags.json +1 -0
  240. package/src/elements/navigation/nav/style.css +38 -0
  241. package/src/elements/navigation/pagination/index.html +3 -0
  242. package/src/elements/navigation/pagination/index.js +94 -0
  243. package/src/elements/navigation/pagination/index.tags.json +1 -0
  244. package/src/elements/navigation/pagination/style.css +39 -0
  245. package/src/elements/navigation/steps/index.html +6 -0
  246. package/src/elements/navigation/steps/index.js +64 -0
  247. package/src/elements/navigation/steps/index.tags.json +1 -0
  248. package/src/elements/navigation/steps/style.css +78 -0
  249. package/src/elements/navigation/tabs/index.html +6 -0
  250. package/src/elements/navigation/tabs/index.js +132 -0
  251. package/src/elements/navigation/tabs/index.tags.json +1 -0
  252. package/src/elements/navigation/tabs/style.css +52 -0
  253. package/src/elements/overlay/dialog/index.html +5 -0
  254. package/src/elements/overlay/dialog/index.js +57 -0
  255. package/src/elements/overlay/dialog/index.tags.json +1 -0
  256. package/src/elements/overlay/dialog/style.css +31 -0
  257. package/src/elements/overlay/drawer/index.html +3 -0
  258. package/src/elements/overlay/drawer/index.js +56 -0
  259. package/src/elements/overlay/drawer/index.tags.json +1 -0
  260. package/src/elements/overlay/drawer/style.css +48 -0
  261. package/src/elements/overlay/menu/index.html +3 -0
  262. package/src/elements/overlay/menu/index.js +107 -0
  263. package/src/elements/overlay/menu/index.tags.json +1 -0
  264. package/src/elements/overlay/menu/style.css +43 -0
  265. package/src/elements/overlay/popover/index.html +3 -0
  266. package/src/elements/overlay/popover/index.js +44 -0
  267. package/src/elements/overlay/popover/index.tags.json +1 -0
  268. package/src/elements/overlay/popover/style.css +21 -0
  269. package/src/elements/overlay/sheet/index.html +8 -0
  270. package/src/elements/overlay/sheet/index.js +105 -0
  271. package/src/elements/overlay/sheet/index.tags.json +1 -0
  272. package/src/elements/overlay/sheet/style.css +64 -0
  273. package/src/elements/overlay/tooltip/index.html +6 -0
  274. package/src/elements/overlay/tooltip/index.js +16 -0
  275. package/src/elements/overlay/tooltip/index.tags.json +1 -0
  276. package/src/elements/overlay/tooltip/style.css +41 -0
  277. package/src/elements/primitives/avatar/index.html +2 -0
  278. package/src/elements/primitives/avatar/index.js +79 -0
  279. package/src/elements/primitives/avatar/index.tags.json +1 -0
  280. package/src/elements/primitives/avatar/style.css +36 -0
  281. package/src/elements/primitives/badge/index.html +3 -0
  282. package/src/elements/primitives/badge/index.js +20 -0
  283. package/src/elements/primitives/badge/index.tags.json +1 -0
  284. package/src/elements/primitives/badge/style.css +67 -0
  285. package/src/elements/primitives/button/index.html +3 -0
  286. package/src/elements/primitives/button/index.js +61 -0
  287. package/src/elements/primitives/button/index.tags.json +1 -0
  288. package/src/elements/primitives/button/style.css +66 -0
  289. package/src/elements/primitives/divider/index.html +1 -0
  290. package/src/elements/primitives/divider/index.js +43 -0
  291. package/src/elements/primitives/divider/index.tags.json +1 -0
  292. package/src/elements/primitives/divider/style.css +39 -0
  293. package/src/elements/primitives/icon/index.html +3 -0
  294. package/src/elements/primitives/icon/index.js +66 -0
  295. package/src/elements/primitives/icon/index.tags.json +1 -0
  296. package/src/elements/primitives/icon/style.css +20 -0
  297. package/src/elements/primitives/link/index.html +3 -0
  298. package/src/elements/primitives/link/index.js +129 -0
  299. package/src/elements/primitives/link/index.tags.json +1 -0
  300. package/src/elements/primitives/link/style.css +40 -0
  301. package/src/elements/primitives/spinner/index.html +1 -0
  302. package/src/elements/primitives/spinner/index.js +62 -0
  303. package/src/elements/primitives/spinner/index.tags.json +1 -0
  304. package/src/elements/primitives/spinner/style.css +20 -0
  305. package/src/elements/primitives/text/index.html +1 -0
  306. package/src/elements/primitives/text/index.js +79 -0
  307. package/src/elements/primitives/text/index.tags.json +1 -0
  308. package/src/elements/primitives/text/style.css +25 -0
  309. package/src/index.js +23 -0
  310. package/src/styles/base.css +66 -0
  311. package/src/styles/index.css +10 -0
  312. package/src/styles/layers.css +9 -0
  313. package/src/styles/reset.css +66 -0
  314. package/src/sw/activate.js +51 -0
  315. package/src/sw/expire.js +47 -0
  316. package/src/sw/index.js +28 -0
  317. package/src/sw/install.js +35 -0
  318. package/src/sw/push.js +58 -0
  319. package/src/sw/queue.js +60 -0
  320. package/src/sw/routes.js +71 -0
  321. package/src/sw/strategies.js +247 -0
  322. package/src/sw/sync.js +80 -0
  323. package/src/tokens/index.css +26 -0
  324. package/src/tokens/primitives/colors.css +54 -0
  325. package/src/tokens/primitives/motion.css +34 -0
  326. package/src/tokens/primitives/radius.css +16 -0
  327. package/src/tokens/primitives/shadow.css +34 -0
  328. package/src/tokens/primitives/spacing.css +27 -0
  329. package/src/tokens/primitives/typography.css +46 -0
  330. package/src/tokens/primitives/zindex.css +18 -0
  331. package/src/tokens/registered/colors.css +133 -0
  332. package/src/tokens/registered/dimensions.css +31 -0
  333. package/src/tokens/semantic/components.css +125 -0
  334. package/src/tokens/semantic/contrast.css +33 -0
  335. package/src/tokens/semantic/dark.css +61 -0
  336. package/src/tokens/semantic/light.css +64 -0
  337. package/types/core/animations/index.d.ts +52 -0
  338. package/types/core/api/index.d.ts +68 -0
  339. package/types/core/events/index.d.ts +50 -0
  340. package/types/core/offline/index.d.ts +68 -0
  341. package/types/core/platform/index.d.ts +60 -0
  342. package/types/core/router/index.d.ts +203 -0
  343. package/types/core/security/index.d.ts +33 -0
  344. package/types/core/state/index.d.ts +68 -0
  345. package/types/core/storage/index.d.ts +40 -0
  346. package/types/core/ui/index.d.ts +446 -0
  347. package/types/core/workers/index.d.ts +221 -0
  348. package/types/elements/index.d.ts +150 -0
  349. package/types/index.d.ts +18 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * src/core/router/intercept.js
3
+ *
4
+ * Core navigation interceptor loop.
5
+ * Attaches listeners to the Navigation API 'navigate' event, evaluates security
6
+ * guards, manages loading indicators, and performs updates wrapped in view transitions.
7
+ *
8
+ * Source: doc 09 — Routing §2, §5, §9, §13
9
+ */
10
+
11
+ import { match } from './match.js';
12
+ import { transitions } from './transitions.js';
13
+ import { getContainer } from './container.js';
14
+ import { isCallback, runCallback } from './handler.js';
15
+
16
+ let guards = [];
17
+ let notFoundHandler = null;
18
+ let ready = false;
19
+
20
+ // Navigation API listener references for teardown
21
+ let navListener = null;
22
+ let successListener = null;
23
+ let errorListener = null;
24
+
25
+ const listeners = {
26
+ found: new Set(),
27
+ notfound: new Set(),
28
+ error: new Set()
29
+ };
30
+
31
+ /**
32
+ * Registers an event listener on the router.
33
+ * Supported events: 'found', 'notfound', 'error'.
34
+ * Returns a disposer. Supports auto-cleanup via AbortSignal.
35
+ */
36
+ export function on(type, callback, signal) {
37
+ if (!listeners[type]) return () => {};
38
+
39
+ listeners[type].add(callback);
40
+
41
+ const dispose = () => {
42
+ listeners[type].delete(callback);
43
+ signal?.removeEventListener('abort', dispose);
44
+ };
45
+
46
+ if (signal) {
47
+ signal.addEventListener('abort', dispose, { once: true });
48
+ }
49
+
50
+ return dispose;
51
+ }
52
+
53
+ /**
54
+ * Emits an event to registered router listeners.
55
+ */
56
+ export function emit(type, detail) {
57
+ if (!listeners[type]) return;
58
+ for (const callback of Array.from(listeners[type])) {
59
+ try {
60
+ callback(detail);
61
+ } catch (err) {
62
+ console.error(`Error in router event listener for "${type}":`, err);
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Registers a global navigation guard. Returns a disposer.
69
+ * Guards receive (destination, controller) and return a redirect URL if blocked,
70
+ * or null/undefined to allow.
71
+ */
72
+ export function addGuard(guardFn) {
73
+ guards.push(guardFn);
74
+ return () => {
75
+ const idx = guards.indexOf(guardFn);
76
+ if (idx !== -1) guards.splice(idx, 1);
77
+ };
78
+ }
79
+
80
+ /** Grouped guard API. */
81
+ export const guardsApi = {
82
+ add: addGuard,
83
+ clear() { guards = []; }
84
+ };
85
+
86
+ /**
87
+ * Sets the default handler for unmatched routes (404 page). Returns a disposer.
88
+ */
89
+ export function setNotFound(handler) {
90
+ notFoundHandler = handler;
91
+ return () => {
92
+ if (notFoundHandler === handler) notFoundHandler = null;
93
+ };
94
+ }
95
+
96
+ /** Grouped miss API. */
97
+ export const missApi = {
98
+ set: setNotFound,
99
+ clear() { notFoundHandler = null; }
100
+ };
101
+
102
+ /**
103
+ * Attaches the global window.navigation navigate listener.
104
+ * Idempotent — safe to call multiple times.
105
+ */
106
+ export function setup() {
107
+ if (ready) return;
108
+ if (typeof window === 'undefined' || !window.navigation) return;
109
+ ready = true;
110
+
111
+ navListener = (event) => {
112
+ // Skip cross-origin navigations, file downloads, or same-document hash scrolls
113
+ if (!event.canIntercept || event.hashChange || event.downloadRequest) {
114
+ return;
115
+ }
116
+
117
+ const url = event.destination.url;
118
+ let precommitted = false; // Scoped precommitted guard state (RT-02)
119
+
120
+ event.intercept({
121
+ /**
122
+ * Runs guards before URL changes.
123
+ * Supports atomic redirection before URL commit (Chrome & Firefox).
124
+ */
125
+ async precommitHandler(controller) {
126
+ precommitted = true;
127
+ const destination = event.destination;
128
+ for (const guardFn of guards) {
129
+ const redirectUrl = await guardFn(destination, controller);
130
+ if (redirectUrl) {
131
+ controller.redirect(redirectUrl);
132
+ return;
133
+ }
134
+ }
135
+ },
136
+
137
+ /**
138
+ * Executes DOM mutations, layout changes, and provides fallbacks for Safari.
139
+ */
140
+ async handler() {
141
+ const destination = event.destination;
142
+ let routeMatch = null;
143
+ try {
144
+ routeMatch = await match(destination.url);
145
+ } catch (err) {
146
+ emit('error', { error: err, url: destination.url, route: null, phase: 'match' });
147
+ return;
148
+ }
149
+
150
+ // Strict Layout Resolution Guard: prevent blind mounts if required container is inactive
151
+ if (routeMatch?.route?.meta?.container) {
152
+ const containerName = routeMatch.route.meta.container;
153
+ if (!getContainer(containerName)) {
154
+ const err = new Error(`RouteError: Required layout container '${containerName}' is not active in the DOM.`);
155
+ emit('error', { error: err, url: destination.url, route: routeMatch.route, phase: 'container' });
156
+ throw err;
157
+ }
158
+ }
159
+
160
+ // Graceful Safari Fallback: Evaluate guards inside post-commit handler if precommit is ignored.
161
+ if (!precommitted) {
162
+ for (const guardFn of guards) {
163
+ let redirectUrl;
164
+ try {
165
+ redirectUrl = await guardFn(destination, null);
166
+ } catch (err) {
167
+ emit('error', { error: err, url: destination.url, route: routeMatch?.route ?? null, phase: 'guard' });
168
+ return;
169
+ }
170
+ if (redirectUrl) {
171
+ window.navigation.navigate(redirectUrl, { history: 'replace' });
172
+ return;
173
+ }
174
+ }
175
+ }
176
+
177
+ await transitions.run(async () => {
178
+ if (routeMatch) {
179
+ // Run callback handlers exactly once, here (never during match()).
180
+ if (isCallback(routeMatch.route.handler)) {
181
+ try {
182
+ await runCallback(routeMatch.route.handler, routeMatch.params, event);
183
+ } catch (err) {
184
+ emit('error', { error: err, url: destination.url, route: routeMatch.route, phase: 'handler' });
185
+ return;
186
+ }
187
+ }
188
+
189
+ emit('found', {
190
+ tag: routeMatch.tag,
191
+ params: routeMatch.params,
192
+ query: routeMatch.query,
193
+ hash: routeMatch.hash,
194
+ chain: routeMatch.chain,
195
+ url: destination.url,
196
+ direction: event.navigationType
197
+ });
198
+ } else {
199
+ emit('notfound', { url: destination.url });
200
+
201
+ if (notFoundHandler) {
202
+ await notFoundHandler(event);
203
+ } else {
204
+ console.error(`Route matching failed and no not-found boundary handler was configured: ${destination.url}`);
205
+ }
206
+ }
207
+ });
208
+ }
209
+ });
210
+ };
211
+
212
+ successListener = () => {
213
+ const url = window.navigation.currentEntry?.url;
214
+ if (url) {
215
+ import('./sync/index.js').then(({ coordinateConnections }) => {
216
+ coordinateConnections(url);
217
+ }).catch(() => {});
218
+ }
219
+ };
220
+
221
+ errorListener = (event) => {
222
+ const error = event.error;
223
+
224
+ // Silence aborted/superseded navigation actions
225
+ if (error && (error.name === 'AbortError' || error.message?.includes('aborted'))) {
226
+ return;
227
+ }
228
+
229
+ console.error('Navigation error caught globally:', error);
230
+ emit('error', { error, url: window.navigation.currentEntry?.url ?? null, route: null, phase: 'navigation' });
231
+
232
+ import('../events/index.js').then(({ events }) => {
233
+ events.emit('core:error', {
234
+ code: 'NAVIGATION_FAILED',
235
+ message: error?.message || 'Navigation failed',
236
+ cause: error,
237
+ context: { url: window.navigation.currentEntry?.url },
238
+ recoverable: true
239
+ });
240
+ }).catch(() => {});
241
+ };
242
+
243
+ window.navigation.addEventListener('navigate', navListener);
244
+ window.navigation.addEventListener('navigatesuccess', successListener);
245
+ window.navigation.addEventListener('navigateerror', errorListener);
246
+
247
+ // Trigger initial on-boot matching and emit initial events once setup is completed
248
+ Promise.resolve().then(async () => {
249
+ const url = window.navigation.currentEntry?.url || window.location.href;
250
+ const routeMatch = await match(url);
251
+ if (routeMatch) {
252
+ emit('found', {
253
+ tag: routeMatch.tag,
254
+ params: routeMatch.params,
255
+ query: routeMatch.query,
256
+ hash: routeMatch.hash,
257
+ chain: routeMatch.chain,
258
+ url,
259
+ direction: 'load'
260
+ });
261
+ } else {
262
+ emit('notfound', { url });
263
+ }
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Tears down all navigation listeners and resets router state.
269
+ * Useful for test isolation and SSR teardown.
270
+ */
271
+ export function destroy() {
272
+ if (!ready) return;
273
+ ready = false;
274
+
275
+ if (typeof window !== 'undefined' && window.navigation) {
276
+ if (navListener) window.navigation.removeEventListener('navigate', navListener);
277
+ if (successListener) window.navigation.removeEventListener('navigatesuccess', successListener);
278
+ if (errorListener) window.navigation.removeEventListener('navigateerror', errorListener);
279
+ }
280
+
281
+ navListener = null;
282
+ successListener = null;
283
+ errorListener = null;
284
+
285
+ guards = [];
286
+ notFoundHandler = null;
287
+
288
+ for (const set of Object.values(listeners)) {
289
+ set.clear();
290
+ }
291
+
292
+ // Close sync channel via dynamic import (avoids circular dep at module load)
293
+ import('./sync/tab.js').then(m => m.close?.()).catch(() => {});
294
+ }
295
+
296
+ class TransitionController {
297
+ constructor(url, navigationPromise) {
298
+ this.url = url;
299
+ this.promise = navigationPromise;
300
+ this.listeners = {
301
+ found: [],
302
+ notfound: [],
303
+ error: []
304
+ };
305
+
306
+ const getPath = (u) => {
307
+ try { return new URL(u, window.location.href).pathname; } catch (_) { return u; }
308
+ };
309
+
310
+ // Coordinate with Navigation events using standard on() mechanism (RT-01)
311
+ const disposeFound = on('found', (detail) => {
312
+ if (getPath(detail.url) === getPath(this.url)) {
313
+ cleanup();
314
+ this._dispatch('found', detail);
315
+ }
316
+ });
317
+
318
+ const disposeNotFound = on('notfound', (detail) => {
319
+ if (getPath(detail.url) === getPath(this.url)) {
320
+ cleanup();
321
+ this._dispatch('notfound', detail);
322
+ }
323
+ });
324
+
325
+ const disposeError = on('error', (detail) => {
326
+ cleanup();
327
+ this._dispatch('error', detail.error);
328
+ });
329
+
330
+ const cleanup = () => {
331
+ disposeFound();
332
+ disposeNotFound();
333
+ disposeError();
334
+ };
335
+
336
+ this.promise.catch((err) => {
337
+ cleanup();
338
+ this._dispatch('error', err);
339
+ });
340
+ }
341
+
342
+ on(event, callback) {
343
+ if (this.listeners[event]) {
344
+ this.listeners[event].push(callback);
345
+ }
346
+ return this;
347
+ }
348
+
349
+ _dispatch(event, payload) {
350
+ for (const cb of this.listeners[event]) {
351
+ try {
352
+ cb(payload);
353
+ } catch (err) {
354
+ console.error(`Error inside fluent navigation "${event}" handler:`, err);
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ let navigateCallback = null;
361
+
362
+ export function registerNavigator(cb) {
363
+ navigateCallback = cb;
364
+ }
365
+
366
+ export const nav = {
367
+ to(url, options) {
368
+ if (!navigateCallback) {
369
+ console.warn('[Router] Navigator is not registered yet. Falling back to dynamic import.');
370
+ return new TransitionController(url, import('./history.js').then(m => m.navigate(url, options)));
371
+ }
372
+ const result = navigateCallback(url, options);
373
+ const p = result instanceof Promise ? result : Promise.resolve(result);
374
+ return new TransitionController(url, p);
375
+ }
376
+ };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * src/core/router/match.js
3
+ *
4
+ * Route table registration and URLPattern matcher.
5
+ * Compiles patterns lazily and extracts named capture group parameters on success.
6
+ *
7
+ * Source: doc 09 — Routing §3, §4
8
+ */
9
+
10
+ import { guard } from '../platform/index.js';
11
+ import { resolveTag } from './handler.js';
12
+
13
+ const routes = [];
14
+ // Resolve Pattern class at module load if available natively (RT-03)
15
+ let Pattern = typeof URLPattern !== 'undefined' ? URLPattern : null;
16
+
17
+ // Pre-resolve polyfill in background if not native
18
+ if (!Pattern) {
19
+ guard.urlPattern().then(cls => {
20
+ Pattern = cls;
21
+ }).catch(() => {});
22
+ }
23
+
24
+ async function getURLPattern() {
25
+ if (!Pattern) {
26
+ Pattern = await guard.urlPattern();
27
+ }
28
+ return Pattern;
29
+ }
30
+
31
+ function getSpecificity(patternStr) {
32
+ if (patternStr === '*') return 0;
33
+ const hasWildcard = patternStr.includes('*');
34
+ const hasParam = patternStr.includes(':');
35
+
36
+ if (!hasWildcard && !hasParam) return 3; // Static: highest
37
+ if (!hasWildcard && hasParam) return 2; // Params: medium
38
+ return 1; // Wildcard: low
39
+ }
40
+
41
+ /**
42
+ * Registers a route mapping.
43
+ */
44
+ export function register(patternStr, handler, meta = {}) {
45
+ routes.push({
46
+ patternStr,
47
+ handler,
48
+ meta,
49
+ pattern: null
50
+ });
51
+
52
+ // Sort routes by specificity (descending) and length (longer first) at registration time (RT-04)
53
+ routes.sort((a, b) => {
54
+ const specA = getSpecificity(a.patternStr);
55
+ const specB = getSpecificity(b.patternStr);
56
+ if (specA !== specB) return specB - specA;
57
+ return b.patternStr.length - a.patternStr.length;
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Matches a URL against the registered routes.
63
+ */
64
+ export async function match(url) {
65
+ // Use pre-resolved Pattern class synchronously on hot path (RT-03)
66
+ const P = Pattern || (await getURLPattern());
67
+ const targetUrl = new URL(url, globalThis.location?.href || 'http://localhost');
68
+
69
+ for (const route of routes) {
70
+ if (!route.pattern) {
71
+ if (route.patternStr.startsWith('http://') || route.patternStr.startsWith('https://')) {
72
+ route.pattern = new P(route.patternStr);
73
+ } else {
74
+ route.pattern = new P({ pathname: route.patternStr });
75
+ }
76
+ }
77
+
78
+ const result = route.pattern.exec(targetUrl.href);
79
+ if (result) {
80
+ // Resolve to a tag without ever invoking callback handlers (see handler.js).
81
+ const tag = await resolveTag(route.handler);
82
+
83
+ const query = Object.fromEntries(targetUrl.searchParams.entries());
84
+ const hash = targetUrl.hash;
85
+
86
+ const chain = [];
87
+ let currentRoute = route;
88
+ while (currentRoute) {
89
+ const currentTag = await resolveTag(currentRoute.handler);
90
+ chain.unshift({
91
+ route: currentRoute,
92
+ tag: currentTag,
93
+ params: result.pathname.groups || {}
94
+ });
95
+
96
+ const parentPattern = currentRoute.meta?.parent;
97
+ if (parentPattern) {
98
+ const parentRoute = routes.find(r => r.patternStr === parentPattern);
99
+ currentRoute = parentRoute;
100
+ } else {
101
+ currentRoute = null;
102
+ }
103
+ }
104
+
105
+ return {
106
+ route,
107
+ tag,
108
+ params: result.pathname.groups || {},
109
+ query,
110
+ hash,
111
+ chain,
112
+ result
113
+ };
114
+ }
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Clears the route registry.
122
+ */
123
+ export function clear() {
124
+ routes.length = 0;
125
+ }
126
+
127
+ /**
128
+ * Returns all currently registered routes.
129
+ */
130
+ export function getRoutes() {
131
+ return routes;
132
+ }
133
+
134
+ /**
135
+ * Bulk registers routes from a JSON array or object.
136
+ * Accepts an array of { pattern, handler, meta } objects.
137
+ */
138
+ export function load(routesData) {
139
+ const routesArray = Array.isArray(routesData) ? routesData : [routesData];
140
+ for (const { pattern, handler, meta } of routesArray) {
141
+ if (pattern && handler) {
142
+ register(pattern, handler, meta || {});
143
+ }
144
+ }
145
+ }