sibujs 4.13.0 → 4.14.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 (107) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +15 -2
  3. package/dist/browser.js +9 -7
  4. package/dist/build.cjs +880 -281
  5. package/dist/build.d.cts +68 -3
  6. package/dist/build.d.ts +68 -3
  7. package/dist/build.js +88 -30
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +13 -13
  10. package/dist/cdn.full.global.js +12 -12
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-7K7W4AA5.js → chunk-2TTRW2TE.js} +6 -6
  13. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  14. package/dist/chunk-3HZOSMPX.js +90 -0
  15. package/dist/{chunk-GNSRXK34.js → chunk-4FJSAUKB.js} +11 -3
  16. package/dist/{chunk-IV74C2YX.js → chunk-4TQDTWWN.js} +6 -5
  17. package/dist/{chunk-L3QHS4O4.js → chunk-7ILEHTVX.js} +1 -1
  18. package/dist/{chunk-CK5F2IR5.js → chunk-A454RAEK.js} +4 -4
  19. package/dist/{chunk-BRRRVLHQ.js → chunk-AE32RGWX.js} +10 -6
  20. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  21. package/dist/{chunk-F4W7WYXY.js → chunk-BKZ5N6NK.js} +8 -6
  22. package/dist/{chunk-MNEITZ27.js → chunk-BNXRWZZV.js} +49 -8
  23. package/dist/chunk-BOYKGGVD.js +2015 -0
  24. package/dist/{chunk-G6366I2A.js → chunk-CCKUNHVU.js} +2 -2
  25. package/dist/{chunk-346BYZL2.js → chunk-CJ7KS3DF.js} +16 -10
  26. package/dist/chunk-CNPT6NKV.js +1859 -0
  27. package/dist/{chunk-ENFRHCMK.js → chunk-F67F77IA.js} +2 -2
  28. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  29. package/dist/chunk-GVAMIU6K.js +195 -0
  30. package/dist/{chunk-HDKDBC6V.js → chunk-HQIAL7F2.js} +8 -6
  31. package/dist/{chunk-ZE5LF44R.js → chunk-JTFQ7IL4.js} +8 -8
  32. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  33. package/dist/{chunk-HVVRXRGO.js → chunk-MIRKTVH4.js} +7 -14
  34. package/dist/{chunk-YJ57VR6Y.js → chunk-N7A6FKWW.js} +5 -3
  35. package/dist/chunk-NT3ZYB5S.js +167 -0
  36. package/dist/{chunk-Z3EZX6DZ.js → chunk-QS6GMWDD.js} +151 -86
  37. package/dist/chunk-R5U7XKVJ.js +16 -0
  38. package/dist/{chunk-3C56VNCK.js → chunk-RVR5NPFL.js} +3 -3
  39. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  40. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  41. package/dist/{chunk-QV5WEOFD.js → chunk-SVULV7O4.js} +119 -26
  42. package/dist/{chunk-C2FAO6VQ.js → chunk-TLS4NVIB.js} +5 -5
  43. package/dist/{chunk-M25RIH6Y.js → chunk-UUIKJJIK.js} +10 -8
  44. package/dist/chunk-UYTYNCMZ.js +199 -0
  45. package/dist/chunk-VM2U5LEL.js +2596 -0
  46. package/dist/{chunk-24L5RDPS.js → chunk-Y4LUM2XS.js} +8 -6
  47. package/dist/data.cjs +265 -35
  48. package/dist/data.d.cts +31 -1
  49. package/dist/data.d.ts +31 -1
  50. package/dist/data.js +16 -10
  51. package/dist/devtools.cjs +15 -2
  52. package/dist/devtools.js +9 -7
  53. package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
  54. package/dist/ecosystem.cjs +62 -23
  55. package/dist/ecosystem.js +14 -12
  56. package/dist/extras.cjs +3520 -270
  57. package/dist/extras.d.cts +3 -3
  58. package/dist/extras.d.ts +3 -3
  59. package/dist/extras.js +39 -29
  60. package/dist/index.cjs +442 -253
  61. package/dist/index.d.cts +93 -283
  62. package/dist/index.d.ts +93 -283
  63. package/dist/index.js +26 -16
  64. package/dist/motion.cjs +12 -1
  65. package/dist/motion.js +6 -4
  66. package/dist/patterns.cjs +15 -2
  67. package/dist/patterns.js +10 -8
  68. package/dist/performance.cjs +36 -2
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +14 -10
  72. package/dist/plugins.cjs +5225 -1807
  73. package/dist/plugins.d.cts +8 -834
  74. package/dist/plugins.d.ts +8 -834
  75. package/dist/plugins.js +64 -2329
  76. package/dist/prerenderServer-MXJEVWA3.js +350 -0
  77. package/dist/renderRoute-DP20XwMS.d.cts +86 -0
  78. package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
  79. package/dist/routeHead-CE9SV-cH.d.cts +995 -0
  80. package/dist/routeHead-DzVxPviY.d.ts +995 -0
  81. package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
  82. package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
  83. package/dist/ssr-JpGpHnpC.d.cts +841 -0
  84. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  85. package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
  86. package/dist/ssr-dom.cjs +1862 -0
  87. package/dist/ssr-dom.d.cts +341 -0
  88. package/dist/ssr-dom.d.ts +341 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7630 -2064
  91. package/dist/ssr.d.cts +63 -5
  92. package/dist/ssr.d.ts +63 -5
  93. package/dist/ssr.js +136 -14
  94. package/dist/testing.cjs +6543 -87
  95. package/dist/testing.d.cts +80 -1
  96. package/dist/testing.d.ts +80 -1
  97. package/dist/testing.js +117 -4
  98. package/dist/ui.cjs +36 -2
  99. package/dist/ui.js +17 -14
  100. package/dist/widgets.cjs +14 -1
  101. package/dist/widgets.js +11 -9
  102. package/package.json +9 -2
  103. package/dist/chunk-2F4PSJ5Z.js +0 -765
  104. package/dist/chunk-7XHATCIH.js +0 -121
  105. package/dist/chunk-B36ADEIR.js +0 -21
  106. package/dist/ssr-Db0Sgdu3.d.cts +0 -245
  107. package/dist/ssr-Db0Sgdu3.d.ts +0 -245
package/dist/plugins.js CHANGED
@@ -1,6 +1,44 @@
1
1
  import {
2
- prerenderWait
3
- } from "./chunk-B36ADEIR.js";
2
+ KeepAliveRoute,
3
+ Outlet,
4
+ Route,
5
+ RouterLink,
6
+ SibuRouter,
7
+ Suspense,
8
+ __getNavigationEpoch,
9
+ __removeRouterPagehideHandler,
10
+ addRoute,
11
+ afterEach,
12
+ back,
13
+ beforeEach,
14
+ beforeResolve,
15
+ buildURL,
16
+ createMemoryRouter,
17
+ createRouter,
18
+ destroyRouter,
19
+ forward,
20
+ getRouteInfo,
21
+ getRouteTransition,
22
+ go,
23
+ hasRoute,
24
+ jsonLdScript,
25
+ lazy,
26
+ navigate,
27
+ preloadRoute,
28
+ push,
29
+ removeRoute,
30
+ replace,
31
+ resolveRouteHead,
32
+ route,
33
+ routeHeadToHTML,
34
+ router,
35
+ routerPlugin,
36
+ routerReady,
37
+ routerState,
38
+ serializeJsonLd,
39
+ setRouteTransition,
40
+ setRoutes
41
+ } from "./chunk-VM2U5LEL.js";
4
42
  import {
5
43
  MigrationStorageError,
6
44
  VERSION,
@@ -22,10 +60,8 @@ import {
22
60
  preloadCritical,
23
61
  prerenderRoutes,
24
62
  satisfies
25
- } from "./chunk-IV74C2YX.js";
26
- import {
27
- acquireTitle
28
- } from "./chunk-52XFPGSN.js";
63
+ } from "./chunk-4TQDTWWN.js";
64
+ import "./chunk-52XFPGSN.js";
29
65
  import {
30
66
  PluginInstallCancelledError,
31
67
  createPlugin,
@@ -38,61 +74,43 @@ import {
38
74
  triggerPluginError,
39
75
  triggerPluginMount,
40
76
  triggerPluginUnmount
41
- } from "./chunk-J6FW5TV6.js";
77
+ } from "./chunk-3HEX6QJQ.js";
42
78
  import {
43
79
  span
44
- } from "./chunk-L3QHS4O4.js";
80
+ } from "./chunk-7ILEHTVX.js";
45
81
  import {
46
- STATIC_VALUE_RESOLVER,
47
82
  escapeScriptJson,
48
- planHeadElementEntry,
49
- planMetaEntry,
50
83
  renderToString,
51
84
  serializeHeadEntry
52
- } from "./chunk-2F4PSJ5Z.js";
53
- import "./chunk-AM6DZLBM.js";
85
+ } from "./chunk-BOYKGGVD.js";
86
+ import "./chunk-CNPT6NKV.js";
87
+ import "./chunk-LJMX4SQ7.js";
88
+ import "./chunk-3HZOSMPX.js";
54
89
  import {
55
90
  isUnsafeKey,
56
91
  readOwnGlobal
57
92
  } from "./chunk-62BDYGAG.js";
58
- import {
59
- appendChildren,
60
- resolveClassValue
61
- } from "./chunk-346BYZL2.js";
62
- import {
63
- setSafeAttribute
64
- } from "./chunk-HDKDBC6V.js";
65
- import "./chunk-F2UWOZ6J.js";
93
+ import "./chunk-CJ7KS3DF.js";
94
+ import "./chunk-HQIAL7F2.js";
95
+ import "./chunk-RVWJGWCL.js";
66
96
  import {
67
97
  globalSingleton
68
98
  } from "./chunk-7ZHH77QA.js";
99
+ import "./chunk-AE32RGWX.js";
69
100
  import {
70
- effect
71
- } from "./chunk-BRRRVLHQ.js";
72
- import {
73
- sanitizeUrl,
74
- stripControlChars
75
- } from "./chunk-EXXSN3BW.js";
101
+ sanitizeUrl
102
+ } from "./chunk-B3HOT4TK.js";
76
103
  import {
77
104
  getRequestStore
78
- } from "./chunk-7XHATCIH.js";
105
+ } from "./chunk-UYTYNCMZ.js";
79
106
  import "./chunk-5HZXGZ6T.js";
80
107
  import {
81
108
  signal
82
- } from "./chunk-GNSRXK34.js";
83
- import {
84
- bindOwnerScope,
85
- getOwnerScope,
86
- runWithOwnerScope,
87
- track,
88
- untracked
89
- } from "./chunk-HVVRXRGO.js";
90
- import {
91
- devWarn,
92
- dispose,
93
- isDev,
94
- registerDisposer
95
- } from "./chunk-NYNYSPK7.js";
109
+ } from "./chunk-4FJSAUKB.js";
110
+ import "./chunk-MIRKTVH4.js";
111
+ import "./chunk-S37PZD6I.js";
112
+ import "./chunk-NT3ZYB5S.js";
113
+ import "./chunk-R5U7XKVJ.js";
96
114
 
97
115
  // src/plugins/i18n.ts
98
116
  var _i18n = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.i18n.v1"), () => ({
@@ -167,2291 +185,6 @@ function getAvailableLocales() {
167
185
  return Object.keys(locales);
168
186
  }
169
187
 
170
- // src/plugins/routeHead.ts
171
- var OG_FIELDS = [
172
- ["title", "og:title"],
173
- ["description", "og:description"],
174
- ["image", "og:image"],
175
- ["url", "og:url"],
176
- ["type", "og:type"],
177
- ["siteName", "og:site_name"]
178
- ];
179
- var OG_URL_PROPERTIES = /* @__PURE__ */ new Set(["og:image", "og:url"]);
180
- var ROUTE_HEAD_ATTR = "data-sibu-head";
181
- var BASE_TITLE_ATTR = "data-sibu-base-title";
182
- function routeHasHead(matched, defaults) {
183
- if (defaults) return true;
184
- for (const record of matched) if (record.head) return true;
185
- return false;
186
- }
187
- function resolveValue(value, route2) {
188
- if (value == null) return null;
189
- const v = typeof value === "function" ? value(route2) : value;
190
- return v == null ? null : String(v);
191
- }
192
- function mergeHeads(route2, defaults) {
193
- const layers = [];
194
- if (defaults) layers.push(defaults);
195
- for (const record of route2.matched) {
196
- const head = record.head;
197
- if (head) layers.push(head);
198
- }
199
- const merged = {};
200
- const og = {};
201
- const meta = /* @__PURE__ */ new Map();
202
- for (const layer of layers) {
203
- if (layer.title !== void 0) merged.title = layer.title;
204
- if (layer.description !== void 0) merged.description = layer.description;
205
- if (layer.canonical !== void 0) merged.canonical = layer.canonical;
206
- if (layer.og) {
207
- for (const [field] of OG_FIELDS) if (layer.og[field] !== void 0) og[field] = layer.og[field];
208
- }
209
- for (const entry of layer.meta ?? []) {
210
- const id = metaIdentity(entry);
211
- if (id) meta.set(id, entry);
212
- }
213
- }
214
- merged.og = og;
215
- merged.meta = Array.from(meta.values());
216
- return merged;
217
- }
218
- function metaIdentity(entry) {
219
- const name = typeof entry.name === "string" ? entry.name : null;
220
- if (name) return `name:${name.toLowerCase()}`;
221
- const property = typeof entry.property === "string" ? entry.property : null;
222
- if (property) return `property:${property.toLowerCase()}`;
223
- return null;
224
- }
225
- function applyTemplate(title, template) {
226
- if (!template) return title;
227
- return typeof template === "function" ? template(title) : template.replace("%s", title);
228
- }
229
- function resolveRouteHead(route2, defaults) {
230
- const merged = mergeHeads(route2, defaults);
231
- const ownTitle = resolveValue(merged.title, route2);
232
- const routeSetTitle = route2.matched.some((r) => r.head?.title != null);
233
- const title = ownTitle === null ? null : routeSetTitle ? applyTemplate(ownTitle, defaults?.titleTemplate) : ownTitle;
234
- const description = resolveValue(merged.description, route2);
235
- const canonical = resolveValue(merged.canonical, route2);
236
- const tags = [];
237
- const push2 = (key, tag, attrs) => {
238
- const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
239
- if (plan.kind === "drop") return;
240
- tags.push({ key, tag, attrs: Object.fromEntries(plan.attributes) });
241
- };
242
- if (description !== null) push2("meta:name:description", "meta", { name: "description", content: description });
243
- if (canonical !== null) push2("link:rel:canonical", "link", { rel: "canonical", href: canonical });
244
- const og = merged.og ?? {};
245
- for (const [field, property] of OG_FIELDS) {
246
- let value = og[field] === void 0 ? void 0 : resolveValue(og[field], route2);
247
- if (value === void 0) {
248
- if (field === "title") value = ownTitle;
249
- else if (field === "description") value = description;
250
- else if (field === "url") value = canonical;
251
- else value = null;
252
- }
253
- if (value === null) continue;
254
- if (OG_URL_PROPERTIES.has(property)) {
255
- value = sanitizeUrl(value);
256
- if (value === "") continue;
257
- }
258
- push2(`meta:property:${property}`, "meta", { property, content: value });
259
- }
260
- for (const entry of merged.meta ?? []) {
261
- const id = metaIdentity(entry);
262
- if (!id) continue;
263
- const attrs = {};
264
- for (const [name, v] of Object.entries(entry)) {
265
- const resolved = resolveValue(v, route2);
266
- if (resolved !== null) attrs[name] = resolved;
267
- }
268
- push2(`meta:${id}`, "meta", attrs);
269
- }
270
- return { title, tags };
271
- }
272
- function escapeText(value) {
273
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
274
- }
275
- function routeHeadToHTML(head) {
276
- const parts = [];
277
- if (head.title !== null) parts.push(`<title>${escapeText(head.title)}</title>`);
278
- for (const t2 of head.tags) {
279
- const html = serializeHeadEntry(t2.tag, t2.attrs);
280
- if (html) parts.push(html.replace(/ \/>$/, ` ${ROUTE_HEAD_ATTR}="" />`));
281
- }
282
- return parts.join("\n");
283
- }
284
- function selectorFor(key) {
285
- const [tag, attr, ...rest] = key.split(":");
286
- const value = rest.join(":").replace(/["\\]/g, "\\$&");
287
- return `${tag}[${attr}="${value}" i]`;
288
- }
289
- function createRouteHeadController(defaults) {
290
- const [current, setCurrent] = signal(null);
291
- const owned = /* @__PURE__ */ new Map();
292
- const initial = new Set(Array.from(document.head.children));
293
- const titleEl = document.head.querySelector("title");
294
- const baseTitle = titleEl?.getAttribute(BASE_TITLE_ATTR) ?? document.title;
295
- let titleLease = null;
296
- let firstApply = true;
297
- const publish = (tag) => {
298
- const existing = owned.get(tag.key);
299
- if (existing && sameAttrs(existing, tag.attrs)) return;
300
- if (!existing) {
301
- for (const el of Array.from(document.head.querySelectorAll(selectorFor(tag.key)))) {
302
- if (initial.has(el)) {
303
- el.remove();
304
- initial.delete(el);
305
- }
306
- }
307
- }
308
- const next = document.createElement(tag.tag);
309
- for (const [name, value] of Object.entries(tag.attrs)) next.setAttribute(name, value);
310
- next.setAttribute(ROUTE_HEAD_ATTR, "");
311
- if (existing?.parentNode) existing.replaceWith(next);
312
- else document.head.appendChild(next);
313
- owned.set(tag.key, next);
314
- };
315
- const apply = (head) => {
316
- if (firstApply) {
317
- firstApply = false;
318
- for (const el of Array.from(document.head.querySelectorAll(`[${ROUTE_HEAD_ATTR}]`))) {
319
- if (initial.has(el) && !head.tags.some((t2) => el.matches(selectorFor(t2.key)))) {
320
- el.remove();
321
- initial.delete(el);
322
- }
323
- }
324
- }
325
- const keep = /* @__PURE__ */ new Set();
326
- for (const tag of head.tags) {
327
- keep.add(tag.key);
328
- publish(tag);
329
- }
330
- for (const [key, el] of owned) {
331
- if (keep.has(key)) continue;
332
- el.remove();
333
- owned.delete(key);
334
- }
335
- const title = head.title ?? baseTitle;
336
- if (titleLease) titleLease.set(title);
337
- else titleLease = acquireTitle(title);
338
- };
339
- const stop = effect(() => {
340
- const route2 = current();
341
- if (route2 === null) return;
342
- const head = resolveRouteHead(route2, defaults);
343
- untracked(() => apply(head));
344
- });
345
- return {
346
- update(route2) {
347
- setCurrent(route2);
348
- },
349
- dispose() {
350
- stop();
351
- for (const el of owned.values()) el.remove();
352
- owned.clear();
353
- titleLease?.release();
354
- titleLease = null;
355
- }
356
- };
357
- }
358
- function sameAttrs(el, attrs) {
359
- const names = Object.keys(attrs);
360
- if (el.attributes.length !== names.length + 1) return false;
361
- for (const name of names) if (el.getAttribute(name) !== attrs[name]) return false;
362
- return true;
363
- }
364
-
365
- // src/plugins/router.ts
366
- function splitRouteUrl(url) {
367
- const hashIndex = url.indexOf("#");
368
- const beforeHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
369
- const hash = hashIndex === -1 ? "" : url.slice(hashIndex + 1);
370
- const queryIndex = beforeHash.indexOf("?");
371
- const path = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
372
- const query = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
373
- return { path, query, hash };
374
- }
375
- function classifyNavigationTarget(path) {
376
- if (path === "") return "internal";
377
- const normalized = stripControlChars(path).replace(/\\/g, "/");
378
- if (normalized === "") return "internal";
379
- if (normalized.startsWith("//")) return "unsafe";
380
- if (!/^[a-z][a-z0-9+.-]*:/i.test(normalized)) return "internal";
381
- return sanitizeUrl(normalized) === "" ? "unsafe" : "external";
382
- }
383
- function isRouterNavigable(path) {
384
- return classifyNavigationTarget(path) === "internal";
385
- }
386
- function normalizePathname(path) {
387
- if (!path) return "/";
388
- const trimmed = path.length > 1 ? path.replace(/\/+$/, "") : path;
389
- return trimmed || "/";
390
- }
391
- function parseQuery(query) {
392
- return Object.fromEntries(new URLSearchParams(query));
393
- }
394
- function parseQueryAll(query) {
395
- const all = /* @__PURE__ */ new Map();
396
- for (const [key, value] of new URLSearchParams(query)) {
397
- const values = all.get(key);
398
- if (values) values.push(value);
399
- else all.set(key, [value]);
400
- }
401
- return Object.fromEntries(all);
402
- }
403
- function serializeQuery(query) {
404
- const search = new URLSearchParams();
405
- for (const [key, value] of Object.entries(query)) {
406
- if (Array.isArray(value)) for (const v of value) search.append(key, String(v));
407
- else search.append(key, String(value));
408
- }
409
- return search.toString();
410
- }
411
- function paramsIdentity(params) {
412
- return JSON.stringify(Object.entries(params).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
413
- }
414
- function queryIdentity(query) {
415
- return paramsIdentity(typeof query === "string" ? parseQueryAll(query) : query);
416
- }
417
- function isPathnameAncestor(target, current) {
418
- if (target === current) return true;
419
- if (target === "/") return false;
420
- return current.startsWith(`${target}/`);
421
- }
422
- var ABORT_ROUTER_DESTROYED = "sibu:router-destroyed";
423
- var LRUCache = class {
424
- constructor(maxSize = 100) {
425
- this.cache = /* @__PURE__ */ new Map();
426
- this.maxSize = Math.max(1, maxSize);
427
- }
428
- get(key) {
429
- const value = this.cache.get(key);
430
- if (value !== void 0) {
431
- this.cache.delete(key);
432
- this.cache.set(key, value);
433
- }
434
- return value;
435
- }
436
- set(key, value) {
437
- if (this.cache.has(key)) {
438
- this.cache.delete(key);
439
- } else if (this.cache.size >= this.maxSize) {
440
- const firstKey = this.cache.keys().next().value;
441
- if (firstKey !== void 0) {
442
- this.cache.delete(firstKey);
443
- }
444
- }
445
- this.cache.set(key, value);
446
- }
447
- has(key) {
448
- return this.cache.has(key);
449
- }
450
- delete(key) {
451
- return this.cache.delete(key);
452
- }
453
- clear() {
454
- this.cache.clear();
455
- }
456
- get size() {
457
- return this.cache.size;
458
- }
459
- };
460
- var NavigationController = class {
461
- constructor() {
462
- this.currentController = null;
463
- }
464
- async navigate(navigationFn) {
465
- if (this.currentController) {
466
- this.currentController.abort();
467
- }
468
- this.currentController = new AbortController();
469
- const signal2 = this.currentController.signal;
470
- try {
471
- await navigationFn(signal2);
472
- } catch (error) {
473
- if (error instanceof Error && error.name === "AbortError") {
474
- return;
475
- }
476
- throw error;
477
- } finally {
478
- if (this.currentController?.signal === signal2) {
479
- this.currentController = null;
480
- }
481
- }
482
- }
483
- abort(reason) {
484
- if (this.currentController) {
485
- this.currentController.abort(reason);
486
- this.currentController = null;
487
- }
488
- }
489
- get isNavigating() {
490
- return this.currentController !== null;
491
- }
492
- };
493
- var RouteMatcher = class _RouteMatcher {
494
- constructor(routes) {
495
- this.routeTrie = /* @__PURE__ */ new Map();
496
- this.parentChain = /* @__PURE__ */ new Map();
497
- this.namedRoutes = /* @__PURE__ */ new Map();
498
- this.compiledPatterns = new LRUCache(50);
499
- // Pattern routes (those with params/wildcards) ordered most-specific-first,
500
- // rebuilt lazily after any mutation. Static full paths are served by the
501
- // exact-match map and excluded here.
502
- this.patternOrder = null;
503
- this.buildIndex(routes);
504
- }
505
- // Specificity per segment: static (2) > param `:x` (1) > wildcard `*` (0).
506
- static specificity(path) {
507
- return path.split("/").filter(Boolean).map((seg) => seg.startsWith(":") ? 1 : seg.includes("*") ? 0 : 2);
508
- }
509
- getPatternOrder() {
510
- if (this.patternOrder) return this.patternOrder;
511
- const patterns = [...this.routeTrie].filter(([p]) => p.includes(":") || p.includes("*"));
512
- patterns.sort(([a], [b]) => {
513
- const ka = _RouteMatcher.specificity(a);
514
- const kb = _RouteMatcher.specificity(b);
515
- const len = Math.max(ka.length, kb.length);
516
- for (let i = 0; i < len; i++) {
517
- const va = ka[i] ?? -1;
518
- const vb = kb[i] ?? -1;
519
- if (va !== vb) return vb - va;
520
- }
521
- return 0;
522
- });
523
- this.patternOrder = patterns;
524
- return patterns;
525
- }
526
- buildIndex(routes, parentPath = "", ancestors = []) {
527
- for (const route2 of routes) {
528
- const fullPath = parentPath + route2.path;
529
- const chain = [...ancestors, route2];
530
- this.routeTrie.set(fullPath, route2);
531
- this.parentChain.set(fullPath, chain);
532
- if (route2.name) {
533
- this.namedRoutes.set(route2.name, route2);
534
- }
535
- if (route2.alias) {
536
- const aliases = Array.isArray(route2.alias) ? route2.alias : [route2.alias];
537
- for (const alias of aliases) {
538
- this.routeTrie.set(parentPath + alias, route2);
539
- this.parentChain.set(parentPath + alias, chain);
540
- }
541
- }
542
- if (route2.children?.length) {
543
- this.buildIndex(route2.children, fullPath, chain);
544
- }
545
- }
546
- }
547
- match(path) {
548
- const exactMatch = this.routeTrie.get(path);
549
- if (exactMatch) {
550
- return { route: exactMatch, params: {}, matched: this.parentChain.get(path) || [exactMatch] };
551
- }
552
- for (const [routePath, route2] of this.getPatternOrder()) {
553
- const match = this.matchPattern(path, routePath);
554
- if (match) {
555
- return { route: route2, params: match.params, matched: this.parentChain.get(routePath) || [route2] };
556
- }
557
- }
558
- return null;
559
- }
560
- findByName(name) {
561
- return this.namedRoutes.get(name) || null;
562
- }
563
- matchPattern(path, routePath) {
564
- if (routePath === "*") {
565
- return { params: { pathMatch: path } };
566
- }
567
- if (routePath.endsWith("/*")) {
568
- const basePath = routePath.slice(0, -2);
569
- if (path === basePath || path.startsWith(`${basePath}/`)) {
570
- return { params: { pathMatch: path.slice(basePath.length) } };
571
- }
572
- return null;
573
- }
574
- let compiled = this.compiledPatterns.get(routePath);
575
- if (!compiled) {
576
- compiled = this.compileRoute(routePath);
577
- this.compiledPatterns.set(routePath, compiled);
578
- }
579
- const match = path.match(compiled.regex);
580
- if (match) {
581
- const params = {};
582
- compiled.keys.forEach((key, i) => {
583
- const raw = match[i + 1];
584
- if (raw !== void 0) {
585
- try {
586
- params[key] = decodeURIComponent(raw);
587
- } catch {
588
- params[key] = raw;
589
- }
590
- }
591
- });
592
- return { params };
593
- }
594
- return null;
595
- }
596
- compileRoute(routePath) {
597
- const keys = [];
598
- const escapeRegex = (s) => s.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
599
- const parts = routePath.split(/(\/:[^/]+\??)/);
600
- let pattern = "";
601
- for (const part of parts) {
602
- if (part.startsWith("/:")) {
603
- if (part.endsWith("?")) {
604
- keys.push(part.slice(2, -1));
605
- pattern += "(?:/([^/]+))?";
606
- } else {
607
- keys.push(part.slice(2));
608
- pattern += "/([^/]+)";
609
- }
610
- } else {
611
- pattern += escapeRegex(part);
612
- }
613
- }
614
- return {
615
- regex: new RegExp(`^${pattern}$`),
616
- keys
617
- };
618
- }
619
- rebuild(routes) {
620
- this.routeTrie.clear();
621
- this.parentChain.clear();
622
- this.namedRoutes.clear();
623
- this.compiledPatterns.clear();
624
- this.patternOrder = null;
625
- this.buildIndex(routes);
626
- }
627
- addRoute(route2, parentPath = "") {
628
- this.patternOrder = null;
629
- const fullPath = parentPath + route2.path;
630
- const parentAncestors = this.parentChain.get(parentPath) || [];
631
- const chain = [...parentAncestors, route2];
632
- this.routeTrie.set(fullPath, route2);
633
- this.parentChain.set(fullPath, chain);
634
- if (route2.name) {
635
- this.namedRoutes.set(route2.name, route2);
636
- }
637
- if (route2.children?.length) {
638
- this.buildIndex(route2.children, fullPath, chain);
639
- }
640
- }
641
- removeRoute(path) {
642
- this.compiledPatterns.clear();
643
- this.patternOrder = null;
644
- const root = this.routeTrie.get(path);
645
- if (!root) return;
646
- const removed = /* @__PURE__ */ new Set();
647
- const collect = (r) => {
648
- removed.add(r);
649
- if (r.children) for (const child of r.children) collect(child);
650
- };
651
- collect(root);
652
- for (const [key, route2] of [...this.routeTrie]) {
653
- if (removed.has(route2)) this.routeTrie.delete(key);
654
- }
655
- for (const [key, chain] of [...this.parentChain]) {
656
- if (chain.length > 0 && removed.has(chain[chain.length - 1])) this.parentChain.delete(key);
657
- }
658
- for (const [name, route2] of [...this.namedRoutes]) {
659
- if (removed.has(route2)) this.namedRoutes.delete(name);
660
- }
661
- }
662
- };
663
- var GuardManager = class {
664
- constructor(timeout = 5e3) {
665
- this.beforeEachGuards = [];
666
- this.beforeResolveGuards = [];
667
- this.afterEachHooks = [];
668
- this.timeout = timeout;
669
- }
670
- async runBeforeEach(to, from, signal2) {
671
- if (signal2.aborted) throw new Error("Navigation aborted");
672
- for (const guard of this.beforeEachGuards) {
673
- if (signal2.aborted) throw new Error("Navigation aborted");
674
- const result = await this.runGuard(guard, to, from, signal2);
675
- if (result !== true) return result;
676
- }
677
- return true;
678
- }
679
- async runBeforeResolve(to, from, signal2) {
680
- if (signal2.aborted) throw new Error("Navigation aborted");
681
- for (const guard of this.beforeResolveGuards) {
682
- if (signal2.aborted) throw new Error("Navigation aborted");
683
- const result = await this.runGuard(guard, to, from, signal2);
684
- if (result !== true) return result;
685
- }
686
- return true;
687
- }
688
- runAfterEach(to, from) {
689
- for (const hook of this.afterEachHooks) {
690
- try {
691
- hook(to, from);
692
- } catch (error) {
693
- console.error("[Router] AfterEach hook error:", error);
694
- }
695
- }
696
- }
697
- runGuard(guard, to, from, signal2) {
698
- return new Promise((resolve, reject) => {
699
- if (signal2.aborted) {
700
- reject(new Error("Navigation aborted"));
701
- return;
702
- }
703
- let resolved = false;
704
- const next = (result) => {
705
- if (resolved || signal2.aborted) return;
706
- resolved = true;
707
- clearTimeout(timeoutId);
708
- signal2.removeEventListener("abort", abortHandler);
709
- if (result instanceof Error) {
710
- reject(result);
711
- } else if (result === false) {
712
- resolve(false);
713
- } else if (typeof result === "string") {
714
- resolve(result);
715
- } else {
716
- resolve(true);
717
- }
718
- };
719
- const abortHandler = () => {
720
- if (!resolved) {
721
- resolved = true;
722
- clearTimeout(timeoutId);
723
- reject(new Error("Navigation aborted"));
724
- }
725
- };
726
- const timeoutId = setTimeout(() => {
727
- if (!resolved) {
728
- resolved = true;
729
- signal2.removeEventListener("abort", abortHandler);
730
- reject(new Error("Guard timeout"));
731
- }
732
- }, this.timeout);
733
- signal2.addEventListener("abort", abortHandler);
734
- try {
735
- guard(to, from, next);
736
- } catch (error) {
737
- clearTimeout(timeoutId);
738
- signal2.removeEventListener("abort", abortHandler);
739
- if (!resolved) {
740
- resolved = true;
741
- reject(error instanceof Error ? error : new Error(String(error)));
742
- }
743
- }
744
- });
745
- }
746
- addBeforeEach(guard) {
747
- this.beforeEachGuards.push(guard);
748
- return () => {
749
- const index = this.beforeEachGuards.indexOf(guard);
750
- if (index > -1) this.beforeEachGuards.splice(index, 1);
751
- };
752
- }
753
- addBeforeResolve(guard) {
754
- this.beforeResolveGuards.push(guard);
755
- return () => {
756
- const index = this.beforeResolveGuards.indexOf(guard);
757
- if (index > -1) this.beforeResolveGuards.splice(index, 1);
758
- };
759
- }
760
- addAfterEach(hook) {
761
- this.afterEachHooks.push(hook);
762
- return () => {
763
- const index = this.afterEachHooks.indexOf(hook);
764
- if (index > -1) this.afterEachHooks.splice(index, 1);
765
- };
766
- }
767
- clear() {
768
- this.beforeEachGuards = [];
769
- this.beforeResolveGuards = [];
770
- this.afterEachHooks = [];
771
- }
772
- };
773
- var _ComponentLoader = class _ComponentLoader {
774
- constructor(cacheSize = 50, retryDelay = 1e3) {
775
- this.errorCache = /* @__PURE__ */ new Map();
776
- this.loadingPromises = /* @__PURE__ */ new Map();
777
- // In-flight *module* resolutions for deferred plans, so concurrent first
778
- // mounts of a lazy route do not each run the loader arrow.
779
- this.moduleLoads = /* @__PURE__ */ new Map();
780
- // Stable per-route-definition id. Caching by the RESOLVED path (e.g.
781
- // /users/123) gave the cache one entry per visited URL, so parameterized
782
- // routes thrashed/evicted and the component was reloaded every navigation.
783
- // Keying by route-definition identity makes the cache effective again.
784
- this.routeKeys = /* @__PURE__ */ new WeakMap();
785
- this.keyCounter = 0;
786
- this.planCache = new LRUCache(cacheSize);
787
- this.retryDelay = retryDelay;
788
- }
789
- keyFor(route2) {
790
- let key = this.routeKeys.get(route2);
791
- if (key === void 0) {
792
- key = `route#${this.keyCounter++}`;
793
- this.routeKeys.set(route2, key);
794
- }
795
- return key;
796
- }
797
- /**
798
- * Resolve the route's **plan** — how to produce an instance — without ever
799
- * instantiating a component to inspect it.
800
- *
801
- * Loading a route component and instantiating one are separate operations.
802
- * The loader never invokes a user component factory merely to validate its
803
- * return type; validation happens on the instance that will actually be
804
- * mounted. (LOAD-001)
805
- */
806
- async loadPlan(route2, routePath) {
807
- if (!("component" in route2)) {
808
- throw new Error(`Route ${routePath} does not have a component`);
809
- }
810
- const comp = route2.component;
811
- const cacheKey = this.keyFor(route2);
812
- const cached = this.planCache.get(cacheKey);
813
- if (cached) return cached;
814
- const existingPromise = this.loadingPromises.get(cacheKey);
815
- if (existingPromise) return existingPromise;
816
- this.assertNotHot(cacheKey);
817
- const loadingPromise = this.doLoadPlan(comp, routePath);
818
- this.loadingPromises.set(cacheKey, loadingPromise);
819
- try {
820
- const plan = await loadingPromise;
821
- this.planCache.set(cacheKey, plan);
822
- this.errorCache.delete(cacheKey);
823
- return plan;
824
- } catch (error) {
825
- this.noteError(cacheKey);
826
- throw error;
827
- } finally {
828
- this.loadingPromises.delete(cacheKey);
829
- }
830
- }
831
- /** Reject early while a recent load failure for this route is still hot. */
832
- assertNotHot(cacheKey) {
833
- const errorInfo = this.errorCache.get(cacheKey);
834
- if (errorInfo && Date.now() - errorInfo.timestamp < this.retryDelay) {
835
- throw new Error(`Component loading failed recently, retry in ${this.retryDelay}ms`);
836
- }
837
- }
838
- /**
839
- * Record a load failure. Keyed by route-definition id, so the map is bounded
840
- * by the number of route definitions; still capped defensively, evicting the
841
- * oldest.
842
- */
843
- noteError(cacheKey) {
844
- const currentError = this.errorCache.get(cacheKey) || { timestamp: 0, count: 0 };
845
- if (!this.errorCache.has(cacheKey) && this.errorCache.size >= _ComponentLoader.MAX_ERROR_ENTRIES) {
846
- const oldest = this.errorCache.keys().next().value;
847
- if (oldest !== void 0) this.errorCache.delete(oldest);
848
- }
849
- this.errorCache.set(cacheKey, { timestamp: Date.now(), count: currentError.count + 1 });
850
- }
851
- /**
852
- * Classify without instantiating.
853
- *
854
- * The `lazy()` brand decides, and nothing else: a branded loader is
855
- * `deferred`, everything else is a `factory`. No inspection of the function's
856
- * type or source is involved, so an unbranded factory can never become
857
- * preload-executable. (LOAD-004)
858
- *
859
- * An unbranded factory that happens to resolve to a module — an un-marked
860
- * `async () => ({ default: Page })` — still works: that is unwrapped during
861
- * real instantiation, where the produced value is used rather than discarded.
862
- */
863
- async doLoadPlan(comp, routePath) {
864
- if (typeof comp !== "function") {
865
- throw new Error(`Component for route "${routePath}" must be a function, got ${typeof comp}`);
866
- }
867
- if (!this.isDeferredModule(comp)) {
868
- return { kind: "factory", component: comp };
869
- }
870
- return { kind: "deferred", load: comp };
871
- }
872
- /**
873
- * Create **one** real route component instance, validating what will actually
874
- * be mounted.
875
- *
876
- * `route` is used only to upgrade a `deferred` plan once a lazy module has
877
- * resolved, so the import happens once and later mounts call the module's
878
- * factory directly.
879
- */
880
- async instantiate(plan, route2, routePath, scope) {
881
- if (plan.kind === "factory") {
882
- return this.instantiateFactory(plan.component, route2, routePath, scope);
883
- }
884
- const factory = await this.resolveModuleFactory(plan, route2, routePath);
885
- return this.instantiateFactory(factory, route2, routePath, scope);
886
- }
887
- /**
888
- * Resolve a deferred plan's module and cache the factory it exports —
889
- * **without invoking that factory**.
890
- *
891
- * Shared by instantiation and preloading, so a preload racing a navigation to
892
- * the same route imports the module once. A deferred plan is only ever a
893
- * module loader (see `isDeferredModule`), so a resolution that is not
894
- * module-shaped is a configuration error and is reported as one.
895
- */
896
- async resolveModuleFactory(plan, route2, routePath) {
897
- const cacheKey = this.keyFor(route2);
898
- const cached = this.planCache.get(cacheKey);
899
- if (cached && cached.kind === "factory") return cached.component;
900
- this.assertNotHot(cacheKey);
901
- let pending = this.moduleLoads.get(cacheKey);
902
- const owner = pending === void 0;
903
- if (pending === void 0) {
904
- pending = plan.load();
905
- this.moduleLoads.set(cacheKey, pending);
906
- }
907
- let resolved;
908
- try {
909
- resolved = await pending;
910
- } catch (error) {
911
- this.noteError(cacheKey);
912
- throw error;
913
- } finally {
914
- if (owner) this.moduleLoads.delete(cacheKey);
915
- }
916
- const factory = this.extractFactory(resolved, routePath);
917
- this.planCache.set(cacheKey, { kind: "factory", component: factory });
918
- return factory;
919
- }
920
- /**
921
- * Invoke a factory exactly once and validate the instance it produced.
922
- *
923
- * A factory may turn out to resolve to a *module* rather than an Element —
924
- * an un-marked `async () => ({ default: Page })` is indistinguishable from a
925
- * component factory until it runs. That is settled here, on the real
926
- * invocation, and the plan is upgraded so later mounts skip the extra hop.
927
- * `depth` bounds that unwrapping; a module whose default is itself a module
928
- * is a configuration error, not something to chase.
929
- */
930
- async instantiateFactory(component, route2, routePath, scope, depth = 0) {
931
- const result = scope ? runWithOwnerScope(scope, component) : component();
932
- const value = this.isThenable(result) ? await result : result;
933
- if (this.isElement(value)) return value;
934
- if (depth === 0 && this.isFactoryLike(value)) {
935
- const factory = this.extractFactory(value, routePath);
936
- this.planCache.set(this.keyFor(route2), { kind: "factory", component: factory });
937
- return this.instantiateFactory(factory, route2, routePath, scope, depth + 1);
938
- }
939
- throw new Error(`Component for route "${routePath}" must return Element, got ${typeof value}`);
940
- }
941
- /**
942
- * Something `extractFactory` can turn into a component factory: a bare
943
- * callable, or a module namespace carrying a callable `default`. Both are
944
- * long-supported shapes for an async loader's resolution.
945
- */
946
- isFactoryLike(value) {
947
- if (typeof value === "function") return true;
948
- return typeof value === "object" && value !== null && typeof value.default === "function";
949
- }
950
- /**
951
- * Pull a factory out of a resolved lazy module **without calling it**. Only
952
- * what can be checked without running user code is checked here: that a
953
- * default export exists and is callable.
954
- */
955
- extractFactory(result, routePath) {
956
- if (typeof result === "function") return result;
957
- if (typeof result === "object" && result !== null && typeof result.default === "function") {
958
- return result.default;
959
- }
960
- throw new Error(`Invalid component module for route "${routePath}"`);
961
- }
962
- /**
963
- * Prepare explicitly deferred route code without creating a component
964
- * instance or any DOM.
965
- *
966
- * Only routes explicitly branded as deferred — a loader wrapped in `lazy()` —
967
- * are resolved during preload. Direct component factories, whether
968
- * synchronous, `async`, or Promise-returning, are `factory` plans and
969
- * therefore require no preload work at all.
970
- *
971
- * ```text
972
- * factory plan → preload no-op
973
- * deferred plan → resolve module → cache factory → no component invocation
974
- * ```
975
- *
976
- * Deferred module resolution caches the component factory the module exports
977
- * **without invoking it**. (LOAD-004)
978
- */
979
- async preloadPlan(route2, routePath) {
980
- const plan = await this.loadPlan(route2, routePath);
981
- if (plan.kind === "factory") return;
982
- await this.resolveModuleFactory(plan, route2, routePath);
983
- }
984
- isThenable(value) {
985
- return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
986
- }
987
- /**
988
- * Is this route component an **explicitly declared module loader**?
989
- *
990
- * Preload is the only place SibuJS executes a route function before the
991
- * router needs an instance, so permission to do that must be explicit. The
992
- * brand stamped by `lazy()` is the sole authority.
993
- *
994
- * Everything the loader once inferred is deliberately *not* a signal:
995
- *
996
- * - `constructor.name === "AsyncFunction"` — cannot distinguish
997
- * `async () => import("./Page")` from `async () => div()`. Treating the
998
- * latter as a module loader is what made preload invoke user
999
- * components. (LOAD-003)
1000
- * - `toString().includes("import(")` — source text is a representation, not
1001
- * semantics. An ordinary component with `"import('./x')"` in a string, a
1002
- * template literal, or a comment was executed during preload; and the same
1003
- * text can vanish under a bundler that rewrites dynamic imports into its
1004
- * own chunk-loader calls. Both directions are wrong, and no better regex
1005
- * fixes that — the defect is source inspection itself. (LOAD-004)
1006
- *
1007
- * A brand is metadata: it survives TypeScript, every bundler, and
1008
- * minification, because it is part of runtime behaviour rather than syntax.
1009
- */
1010
- isDeferredModule(comp) {
1011
- return comp[LAZY_MARKER] === true;
1012
- }
1013
- isElement(value) {
1014
- return value instanceof Element;
1015
- }
1016
- clearErrors() {
1017
- this.errorCache.clear();
1018
- }
1019
- clearCache() {
1020
- this.planCache.clear();
1021
- this.errorCache.clear();
1022
- this.loadingPromises.clear();
1023
- this.moduleLoads.clear();
1024
- }
1025
- };
1026
- _ComponentLoader.MAX_ERROR_ENTRIES = 256;
1027
- var ComponentLoader = _ComponentLoader;
1028
- var _SibuRouter = class _SibuRouter {
1029
- constructor(routes, options = {}) {
1030
- // Event listeners cleanup
1031
- this.cleanup = [];
1032
- // Set by destroy(). Lets a navigation still in flight report
1033
- // "router-destroyed" rather than the generic "superseded".
1034
- this.destroyed = false;
1035
- // Monotonic navigation generation. Bumped by every committed navigation that
1036
- // can acquire DOM ownership, and by destroy(). Async work that wants to
1037
- // commit into the DOM captures this first and re-checks before committing:
1038
- // unlike a URL comparison it distinguishes "/b" generation 10 from "/b"
1039
- // generation 12, so an A→B→A round trip can never restore commit permission
1040
- // to superseded work. The router's own initial location resolution is
1041
- // excluded — it establishes the bootstrap route rather than superseding it.
1042
- this.navEpoch = 0;
1043
- // Created the first time a committed route needs a managed <head>.
1044
- this.headController = null;
1045
- // `readyPromise` plus the matched components loaded; created on the first
1046
- // `routerReady()` call only, so an app that never asks pays nothing and its
1047
- // outlets load exactly as before.
1048
- this.loadedPromise = null;
1049
- this.readyPromise = new Promise((resolve) => {
1050
- this.resolveReady = resolve;
1051
- });
1052
- this.options = {
1053
- mode: "history",
1054
- base: "",
1055
- linkActiveClass: "router-link-active",
1056
- linkExactActiveClass: "router-link-exact-active",
1057
- fallback: true,
1058
- guardTimeout: 5e3,
1059
- cacheSize: 50,
1060
- errorRetryDelay: 1e3,
1061
- preloadStrategy: "none",
1062
- ...options
1063
- };
1064
- const [currentRouteState, setCurrentRouteState] = signal(this.createInitialRoute());
1065
- const [isReadyState, setIsReadyState] = signal(false);
1066
- this.currentRouteGetter = currentRouteState;
1067
- this.currentRouteSetter = setCurrentRouteState;
1068
- this.isReadyGetter = isReadyState;
1069
- this.isReadySetter = setIsReadyState;
1070
- this.matcher = new RouteMatcher(routes);
1071
- this.guards = new GuardManager(this.options.guardTimeout);
1072
- this.loader = new ComponentLoader(this.options.cacheSize, this.options.errorRetryDelay);
1073
- this.navigator = new NavigationController();
1074
- this.initialize();
1075
- }
1076
- initialize() {
1077
- if (typeof window !== "undefined") {
1078
- if (this.options.mode === "history") {
1079
- const popstateHandler = () => this.handleLocationChange(true);
1080
- window.addEventListener("popstate", popstateHandler);
1081
- this.cleanup.push(() => window.removeEventListener("popstate", popstateHandler));
1082
- } else {
1083
- const hashHandler = () => this.handleLocationChange(true);
1084
- window.addEventListener("hashchange", hashHandler);
1085
- this.cleanup.push(() => window.removeEventListener("hashchange", hashHandler));
1086
- }
1087
- }
1088
- queueMicrotask(() => {
1089
- let initial = Promise.resolve();
1090
- if (!this.navigator.isNavigating) {
1091
- initial = this.handleLocationChange(true, true);
1092
- }
1093
- this.isReadySetter(true);
1094
- prerenderWait(initial.then(this.resolveReady, this.resolveReady));
1095
- });
1096
- }
1097
- /** Load the component of every record the current route matched, without instantiating any. */
1098
- async preloadMatched() {
1099
- const current = untracked(() => this.currentRouteGetter());
1100
- for (const record of current.matched) {
1101
- if ("redirect" in record) continue;
1102
- try {
1103
- await this.loader.preloadPlan(record, current.path);
1104
- } catch {
1105
- this.loader.clearErrors();
1106
- }
1107
- }
1108
- }
1109
- /** Settles after the initial navigation and its component loads; see `routerReady()`. */
1110
- whenReady() {
1111
- this.loadedPromise ?? (this.loadedPromise = this.readyPromise.then(() => this.destroyed ? void 0 : this.preloadMatched()));
1112
- return this.loadedPromise;
1113
- }
1114
- createInitialRoute() {
1115
- return {
1116
- path: "/",
1117
- params: {},
1118
- query: {},
1119
- queryAll: {},
1120
- hash: "",
1121
- meta: {},
1122
- matched: []
1123
- };
1124
- }
1125
- handleLocationChange(skipHistory = true, initialResolution = false) {
1126
- const path = this.getCurrentPath();
1127
- return this.navigate(path, { replace: true, skipHistory, initialResolution }).catch((err) => {
1128
- console.error("[Router] Error during location change navigation:", err);
1129
- });
1130
- }
1131
- getCurrentPath() {
1132
- const { mode, base } = this.options;
1133
- if (typeof window === "undefined") return "/";
1134
- if (mode === "hash") {
1135
- return window.location.hash.slice(1) || "/";
1136
- }
1137
- let path = window.location.pathname;
1138
- if (base && (path === base || path.startsWith(`${base}/`))) {
1139
- path = path.slice(base.length);
1140
- }
1141
- return (path || "/") + window.location.search + window.location.hash;
1142
- }
1143
- createRouteContext(fullPath) {
1144
- const { path, query: queryString, hash } = splitRouteUrl(fullPath);
1145
- const query = parseQuery(queryString);
1146
- const queryAll = parseQueryAll(queryString);
1147
- const match = this.matcher.match(path || "/");
1148
- const params = match?.params || {};
1149
- const meta = match?.route.meta || {};
1150
- const matched = match?.matched || [];
1151
- return {
1152
- path: path || "/",
1153
- params,
1154
- query,
1155
- queryAll,
1156
- hash,
1157
- meta,
1158
- matched
1159
- };
1160
- }
1161
- // Public API
1162
- async navigate(to, options = {}) {
1163
- try {
1164
- await this.navigator.navigate(async (signal2) => {
1165
- const targetPath = this.resolvePath(to);
1166
- if (!isRouterNavigable(targetPath)) {
1167
- const from2 = this.currentRouteGetter();
1168
- const toContext2 = this.createRouteContext(targetPath);
1169
- throw new NavigationFailureError("aborted", from2, toContext2, void 0, "unsafe-target");
1170
- }
1171
- const from = this.currentRouteGetter();
1172
- const toContext = this.createRouteContext(targetPath);
1173
- if (this.isSameRoute(from, toContext)) {
1174
- throw new NavigationFailureError("duplicated", from, toContext, void 0, "duplicate");
1175
- }
1176
- await this.performNavigation(toContext, from, options, signal2);
1177
- });
1178
- return { success: true, route: this.currentRouteGetter() };
1179
- } catch (error) {
1180
- if (error instanceof NavigationFailureError) {
1181
- const failure2 = error.toFailure();
1182
- return { success: false, type: failure2.type, reason: failure2.reason, failure: failure2 };
1183
- }
1184
- const isAbort = error instanceof Error && error.message === "Navigation aborted";
1185
- const reason = isAbort ? this.destroyed ? "router-destroyed" : "superseded" : "error";
1186
- const failure = {
1187
- type: "aborted",
1188
- reason,
1189
- from: this.currentRouteGetter(),
1190
- to: this.createRouteContext(this.resolvePath(to)),
1191
- error: error instanceof Error ? error : new Error(String(error))
1192
- };
1193
- return { success: false, type: failure.type, reason, failure };
1194
- }
1195
- }
1196
- async performNavigation(to, from, options, signal2, depth = 0, redirectChain = []) {
1197
- const chain = depth === 0 ? [to.path] : redirectChain;
1198
- if (depth >= _SibuRouter.MAX_REDIRECT_DEPTH) {
1199
- if (isDev()) {
1200
- devWarn(
1201
- `Router: redirect loop detected \u2014 navigation aborted after ${_SibuRouter.MAX_REDIRECT_DEPTH} hops.
1202
- ${chain.map((p) => `"${p}"`).join(" -> ")}`
1203
- );
1204
- }
1205
- throw new NavigationFailureError("aborted", from, to, void 0, "redirect-loop");
1206
- }
1207
- const beforeEachResult = await this.guards.runBeforeEach(to, from, signal2);
1208
- if (beforeEachResult !== true) {
1209
- if (typeof beforeEachResult === "string") {
1210
- if (!isRouterNavigable(beforeEachResult)) {
1211
- throw new NavigationFailureError("aborted", from, to, void 0, "unsafe-target");
1212
- }
1213
- return this.performNavigation(this.createRouteContext(beforeEachResult), from, options, signal2, depth + 1, [
1214
- ...chain,
1215
- beforeEachResult
1216
- ]);
1217
- }
1218
- throw new NavigationFailureError("aborted", from, to, void 0, "guard");
1219
- }
1220
- const match = this.matcher.match(to.path);
1221
- if (match) {
1222
- const { route: route2 } = match;
1223
- for (const matchedRoute of match.matched) {
1224
- if ("beforeEnter" in matchedRoute && matchedRoute.beforeEnter) {
1225
- const guards = Array.isArray(matchedRoute.beforeEnter) ? matchedRoute.beforeEnter : [matchedRoute.beforeEnter];
1226
- for (const guard of guards) {
1227
- if (signal2.aborted) throw new Error("Navigation aborted");
1228
- const result = await guard(to, from);
1229
- if (signal2.aborted) {
1230
- throw new NavigationFailureError("aborted", from, to, void 0, abortReasonOf(signal2));
1231
- }
1232
- if (result !== true) {
1233
- if (typeof result === "string") {
1234
- if (!isRouterNavigable(result)) {
1235
- throw new NavigationFailureError("aborted", from, to, void 0, "unsafe-target");
1236
- }
1237
- return this.performNavigation(this.createRouteContext(result), from, options, signal2, depth + 1, [
1238
- ...chain,
1239
- result
1240
- ]);
1241
- }
1242
- throw new NavigationFailureError("aborted", from, to, void 0, "guard");
1243
- }
1244
- }
1245
- }
1246
- }
1247
- if ("redirect" in route2) {
1248
- const redirectPath = typeof route2.redirect === "function" ? route2.redirect(to) : route2.redirect;
1249
- if (typeof redirectPath === "string" && !isRouterNavigable(redirectPath)) {
1250
- if (classifyNavigationTarget(redirectPath) === "external" && typeof console !== "undefined") {
1251
- console.error(
1252
- `[SibuJS Router] Refusing absolute/protocol-relative redirect "${redirectPath}" \u2014 open-redirect risk.`
1253
- );
1254
- }
1255
- throw new NavigationFailureError("aborted", from, to, void 0, "unsafe-target");
1256
- }
1257
- return this.performNavigation(this.createRouteContext(redirectPath), from, options, signal2, depth + 1, [
1258
- ...chain,
1259
- redirectPath
1260
- ]);
1261
- }
1262
- }
1263
- const beforeResolveResult = await this.guards.runBeforeResolve(to, from, signal2);
1264
- if (beforeResolveResult !== true) {
1265
- if (typeof beforeResolveResult === "string") {
1266
- if (!isRouterNavigable(beforeResolveResult)) {
1267
- throw new NavigationFailureError("aborted", from, to, void 0, "unsafe-target");
1268
- }
1269
- return this.performNavigation(this.createRouteContext(beforeResolveResult), from, options, signal2, depth + 1, [
1270
- ...chain,
1271
- beforeResolveResult
1272
- ]);
1273
- }
1274
- throw new NavigationFailureError("aborted", from, to, void 0, "guard");
1275
- }
1276
- if (signal2.aborted) {
1277
- throw new NavigationFailureError("aborted", from, to, void 0, abortReasonOf(signal2));
1278
- }
1279
- if (!options.skipHistory) {
1280
- this.updateHistory(to, options);
1281
- }
1282
- if (!options.initialResolution) this.navEpoch++;
1283
- this.currentRouteSetter(to);
1284
- this.updateHead(to);
1285
- this.guards.runAfterEach(to, from);
1286
- this.handleScrollBehavior(to, from);
1287
- }
1288
- updateHead(to) {
1289
- if (typeof document === "undefined" || this.destroyed) return;
1290
- if (this.headController === null) {
1291
- if (!routeHasHead(to.matched, this.options.head)) return;
1292
- this.headController = createRouteHeadController(this.options.head);
1293
- }
1294
- this.headController.update(to);
1295
- }
1296
- resolvePath(to) {
1297
- if (typeof to === "string") return to;
1298
- let path = to.path || "";
1299
- if (to.name && !path) {
1300
- const namedRoute = this.matcher.findByName(to.name);
1301
- if (namedRoute) {
1302
- path = namedRoute.path;
1303
- }
1304
- }
1305
- if (to.params) {
1306
- for (const [key, value] of Object.entries(to.params)) {
1307
- path = path.replace(new RegExp(`:${key}(?=[/?#]|$)`, "g"), encodeURIComponent(value));
1308
- }
1309
- }
1310
- const search = to.query ? serializeQuery(to.query) : "";
1311
- if (search) {
1312
- path += `?${search}`;
1313
- }
1314
- if (to.hash) {
1315
- path += `#${to.hash}`;
1316
- }
1317
- return path;
1318
- }
1319
- isSameRoute(from, to) {
1320
- if (from.matched.length === 0 && to.matched.length > 0) return false;
1321
- return from.path === to.path && paramsIdentity(from.params) === paramsIdentity(to.params) && queryIdentity(from.queryAll) === queryIdentity(to.queryAll) && from.hash === to.hash;
1322
- }
1323
- updateHistory(to, options) {
1324
- const search = serializeQuery(to.queryAll);
1325
- const routeUrl = to.path + (search ? `?${search}` : "") + (to.hash ? `#${to.hash}` : "");
1326
- const loc = globalThis.location;
1327
- const fullPath = this.options.mode === "hash" ? `${loc ? loc.pathname + loc.search : ""}#${routeUrl}` : this.options.base + routeUrl;
1328
- const h = globalThis.history;
1329
- if (!h) return;
1330
- if (options.replace) {
1331
- h.replaceState(options.state || {}, "", fullPath);
1332
- } else {
1333
- h.pushState(options.state || {}, "", fullPath);
1334
- }
1335
- }
1336
- /**
1337
- * Apply the configured `scrollBehavior`, if the runtime can actually scroll.
1338
- *
1339
- * This runs *after* the route has been committed, so an exception here does
1340
- * not merely skip scrolling — it propagates out of `navigateInternal` and the
1341
- * navigation is reported as `success: false` while the route state says it
1342
- * succeeded. In a DOM-less runtime the bare `requestAnimationFrame` did
1343
- * exactly that: a legal `scrollBehavior` option made every navigation report
1344
- * failure under SSR and in memory-router-style tests. (MEM-001)
1345
- *
1346
- * Policy A, consistent with the history write in `updateHistory()` (NODE-001):
1347
- * the route still commits, and only the browser-only side effect is skipped.
1348
- *
1349
- * Both primitives are probed, not just `window` — `requestAnimationFrame` and
1350
- * `scrollTo` can be missing independently (jsdom without a rAF polyfill, a
1351
- * partial DOM shim). `requestAnimationFrame` is invoked as a method on the
1352
- * global object rather than through a detached reference, since browsers
1353
- * reject a bare call with "Illegal invocation".
1354
- */
1355
- handleScrollBehavior(to, from) {
1356
- const scrollBehavior = this.options.scrollBehavior;
1357
- if (!scrollBehavior) return;
1358
- const g = globalThis;
1359
- if (typeof g.requestAnimationFrame !== "function") return;
1360
- if (typeof window === "undefined" || typeof window.scrollTo !== "function") return;
1361
- let position;
1362
- try {
1363
- position = scrollBehavior(to, from, null);
1364
- } catch (error) {
1365
- if (typeof console !== "undefined") console.error("[router] scrollBehavior failed:", error);
1366
- return;
1367
- }
1368
- if (!position) return;
1369
- const { x, y } = position;
1370
- g.requestAnimationFrame(() => {
1371
- try {
1372
- window.scrollTo(x, y);
1373
- } catch (error) {
1374
- if (typeof console !== "undefined") console.error("[router] scroll failed:", error);
1375
- }
1376
- });
1377
- }
1378
- // Component loading
1379
- /**
1380
- * Resolve how this route's component will be produced, without instantiating
1381
- * anything. Pair with {@link instantiateComponent} at the commit boundary.
1382
- */
1383
- async loadPlan(route2, routePath) {
1384
- return prerenderWait(this.loader.loadPlan(route2, routePath));
1385
- }
1386
- /**
1387
- * Create exactly one real instance from an already-resolved plan. With a
1388
- * `scope`, the factory runs in it (see `RouteScope`).
1389
- */
1390
- async instantiateComponent(plan, route2, routePath, scope) {
1391
- return prerenderWait(this.loader.instantiate(plan, route2, routePath, scope));
1392
- }
1393
- /** Resolve a route's module/factory without creating any component instance. */
1394
- async preloadPlan(route2, routePath) {
1395
- return this.loader.preloadPlan(route2, routePath);
1396
- }
1397
- // Guards API
1398
- beforeEach(guard) {
1399
- return this.guards.addBeforeEach(guard);
1400
- }
1401
- beforeResolve(guard) {
1402
- return this.guards.addBeforeResolve(guard);
1403
- }
1404
- afterEach(hook) {
1405
- return this.guards.addAfterEach(hook);
1406
- }
1407
- // Utility methods
1408
- push(to) {
1409
- return this.navigate(to);
1410
- }
1411
- replace(to) {
1412
- return this.navigate(to, { replace: true });
1413
- }
1414
- go(delta) {
1415
- history.go(delta);
1416
- }
1417
- back() {
1418
- history.back();
1419
- }
1420
- forward() {
1421
- history.forward();
1422
- }
1423
- // State getters
1424
- get currentRoute() {
1425
- return this.currentRouteGetter();
1426
- }
1427
- get isReady() {
1428
- return this.isReadyGetter();
1429
- }
1430
- /** @internal Navigation generation — see `navEpoch`. Not a public API. */
1431
- get navigationEpoch() {
1432
- return this.navEpoch;
1433
- }
1434
- get isNavigating() {
1435
- return this.navigator.isNavigating;
1436
- }
1437
- // Cleanup
1438
- destroy() {
1439
- this.destroyed = true;
1440
- this.navEpoch++;
1441
- this.navigator.abort(ABORT_ROUTER_DESTROYED);
1442
- for (const fn of this.cleanup) fn();
1443
- this.cleanup = [];
1444
- this.guards.clear();
1445
- this.loader.clearCache();
1446
- this.isReadySetter(false);
1447
- this.headController?.dispose();
1448
- this.headController = null;
1449
- this.resolveReady();
1450
- }
1451
- // Cache management
1452
- clearCache() {
1453
- this.loader.clearCache();
1454
- }
1455
- clearErrorCache() {
1456
- this.loader.clearErrors();
1457
- }
1458
- // Route management
1459
- updateRoutes(routes) {
1460
- this.matcher.rebuild(routes);
1461
- this.clearCache();
1462
- }
1463
- /**
1464
- * Add a route dynamically at runtime.
1465
- */
1466
- addRoute(route2, parentPath) {
1467
- this.matcher.addRoute(route2, parentPath);
1468
- this.clearCache();
1469
- }
1470
- /**
1471
- * Remove a route by path.
1472
- */
1473
- removeRoute(path) {
1474
- this.matcher.removeRoute(path);
1475
- this.clearCache();
1476
- }
1477
- /**
1478
- * Get the reactive route getter for tracking.
1479
- */
1480
- get routeGetter() {
1481
- return this.currentRouteGetter;
1482
- }
1483
- };
1484
- _SibuRouter.MAX_REDIRECT_DEPTH = 10;
1485
- var SibuRouter = _SibuRouter;
1486
- var NavigationFailureError = class extends Error {
1487
- constructor(type, from, to, error, reason) {
1488
- super(`Navigation ${type}${reason ? ` (${reason})` : ""}: from ${from.path} to ${to.path}`);
1489
- this.name = "NavigationFailureError";
1490
- this.type = type;
1491
- this.reason = reason;
1492
- this.from = from;
1493
- this.to = to;
1494
- if (error) {
1495
- this.cause = error;
1496
- }
1497
- }
1498
- toFailure() {
1499
- return {
1500
- type: this.type,
1501
- reason: this.reason,
1502
- from: this.from,
1503
- to: this.to,
1504
- error: this.cause instanceof Error ? this.cause : void 0
1505
- };
1506
- }
1507
- };
1508
- function abortReasonOf(signal2) {
1509
- return signal2.reason === ABORT_ROUTER_DESTROYED ? "router-destroyed" : "superseded";
1510
- }
1511
- var ROUTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.router.v1");
1512
- var _routerRef = globalThis[ROUTER_KEY] ?? (globalThis[ROUTER_KEY] = { current: null });
1513
- var [routerRevision, setRouterRevision] = signal(0);
1514
- var awaitingParent = /* @__PURE__ */ new Map();
1515
- var parentObserver = null;
1516
- function stopParentObserverIfIdle() {
1517
- if (parentObserver && awaitingParent.size === 0) {
1518
- parentObserver.disconnect();
1519
- parentObserver = null;
1520
- }
1521
- }
1522
- function whenAnchorAttached(anchor, run) {
1523
- let cancelled = false;
1524
- queueMicrotask(() => {
1525
- if (cancelled) return;
1526
- if (anchor.parentNode) {
1527
- cancelled = true;
1528
- run();
1529
- return;
1530
- }
1531
- if (typeof MutationObserver === "undefined" || typeof document === "undefined") return;
1532
- awaitingParent.set(anchor, () => {
1533
- cancelled = true;
1534
- run();
1535
- });
1536
- if (!parentObserver) {
1537
- parentObserver = new MutationObserver(() => {
1538
- for (const [node, cb] of awaitingParent) {
1539
- if (!node.parentNode) continue;
1540
- awaitingParent.delete(node);
1541
- cb();
1542
- }
1543
- stopParentObserverIfIdle();
1544
- });
1545
- parentObserver.observe(document, { childList: true, subtree: true });
1546
- }
1547
- });
1548
- return () => {
1549
- if (cancelled) return;
1550
- cancelled = true;
1551
- awaitingParent.delete(anchor);
1552
- stopParentObserverIfIdle();
1553
- };
1554
- }
1555
- function attachRetry(anchor, rerun) {
1556
- const run = bindOwnerScope(rerun);
1557
- let cancelWait = null;
1558
- return {
1559
- request: () => {
1560
- if (cancelWait) return;
1561
- cancelWait = whenAnchorAttached(anchor, () => {
1562
- cancelWait = null;
1563
- run();
1564
- });
1565
- },
1566
- cancel: () => {
1567
- cancelWait?.();
1568
- cancelWait = null;
1569
- }
1570
- };
1571
- }
1572
- function normalizeRoutes(routes) {
1573
- return routes.map((route2) => {
1574
- const normalizedChildren = route2.children && route2.children.length > 0 ? normalizeRoutes(route2.children) : route2.children;
1575
- if ("lazy" in route2 && typeof route2.lazy === "function") {
1576
- const { lazy: importFn, ...rest } = route2;
1577
- const asyncRoute = {
1578
- ...rest,
1579
- component: lazy(importFn),
1580
- children: normalizedChildren
1581
- };
1582
- return asyncRoute;
1583
- }
1584
- if (normalizedChildren !== route2.children) {
1585
- return { ...route2, children: normalizedChildren };
1586
- }
1587
- return route2;
1588
- });
1589
- }
1590
- function createRouter(routesOrOptions, options = {}) {
1591
- if (_routerRef.current) {
1592
- _routerRef.current.destroy();
1593
- }
1594
- let routes;
1595
- if (Array.isArray(routesOrOptions)) {
1596
- routes = normalizeRoutes(routesOrOptions);
1597
- } else {
1598
- options = routesOrOptions;
1599
- routes = [];
1600
- }
1601
- _routerRef.current = new SibuRouter(routes, options);
1602
- ensureRouterPagehide();
1603
- setRouterRevision((n) => n + 1);
1604
- return _routerRef.current;
1605
- }
1606
- function setRoutes(routes) {
1607
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1608
- _routerRef.current.updateRoutes(normalizeRoutes(routes));
1609
- }
1610
- var RouteScope = class {
1611
- constructor(route2) {
1612
- this.released = false;
1613
- const [get, set] = signal(route2);
1614
- this.get = get;
1615
- this.set = set;
1616
- }
1617
- /** The instance's route, or `null` once released. Reactive. */
1618
- read() {
1619
- return this.get();
1620
- }
1621
- /** Hand a kept instance the route of the navigation that kept it. */
1622
- update(route2) {
1623
- if (!this.released) this.set(route2);
1624
- }
1625
- /**
1626
- * Called when the instance is disposed. The switch to "no scope" is
1627
- * published a microtask later, once the whole dispose has finished: a
1628
- * teardown the instance registered after this one (an `onMount` effect, say)
1629
- * may still be alive at this point, and must not re-run against the
1630
- * router-global route on its way out.
1631
- */
1632
- release() {
1633
- if (this.released) return;
1634
- this.released = true;
1635
- queueMicrotask(() => this.set(null));
1636
- }
1637
- };
1638
- function activeRouteScope() {
1639
- const scope = getOwnerScope();
1640
- return scope instanceof RouteScope ? scope : null;
1641
- }
1642
- function scopedRoute(router2) {
1643
- const scope = activeRouteScope();
1644
- if (scope) {
1645
- const committed = scope.read();
1646
- if (committed !== null) return committed;
1647
- }
1648
- return router2.currentRoute;
1649
- }
1650
- var instanceScopes = /* @__PURE__ */ new WeakMap();
1651
- async function instantiateScoped(router2, plan, routeDef, routePath, route2) {
1652
- const scope = new RouteScope(route2);
1653
- const node = await router2.instantiateComponent(plan, routeDef, routePath, scope);
1654
- if (node) {
1655
- instanceScopes.set(node, scope);
1656
- registerDisposer(node, () => scope.release());
1657
- }
1658
- return node;
1659
- }
1660
- function updateInstanceRoute(node, route2) {
1661
- if (node) instanceScopes.get(node)?.update(route2);
1662
- }
1663
- function disposeOwned(node) {
1664
- const scope = instanceScopes.get(node);
1665
- if (scope) runWithOwnerScope(scope, () => dispose(node));
1666
- else dispose(node);
1667
- }
1668
- function route() {
1669
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1670
- return scopedRoute(_routerRef.current);
1671
- }
1672
- function router() {
1673
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1674
- return {
1675
- currentRoute: scopedRoute(_routerRef.current),
1676
- isReady: _routerRef.current.isReady,
1677
- isNavigating: _routerRef.current.isNavigating,
1678
- push: (to) => _routerRef.current?.push(to),
1679
- replace: (to) => _routerRef.current?.replace(to),
1680
- go: (delta) => _routerRef.current?.go(delta),
1681
- back: () => _routerRef.current?.back(),
1682
- forward: () => _routerRef.current?.forward(),
1683
- beforeEach: (guard) => _routerRef.current?.beforeEach(guard),
1684
- beforeResolve: (guard) => _routerRef.current?.beforeResolve(guard),
1685
- afterEach: (hook) => _routerRef.current?.afterEach(hook)
1686
- };
1687
- }
1688
- function routerReady() {
1689
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1690
- return _routerRef.current.whenReady();
1691
- }
1692
- function navigate(to, options) {
1693
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1694
- return _routerRef.current.navigate(to, options);
1695
- }
1696
- function push(to) {
1697
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1698
- return _routerRef.current.push(to);
1699
- }
1700
- function replace(to) {
1701
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1702
- return _routerRef.current.replace(to);
1703
- }
1704
- function go(delta) {
1705
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1706
- _routerRef.current.go(delta);
1707
- }
1708
- function back() {
1709
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1710
- _routerRef.current.back();
1711
- }
1712
- function forward() {
1713
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1714
- _routerRef.current.forward();
1715
- }
1716
- function beforeEach(guard) {
1717
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1718
- return _routerRef.current.beforeEach(guard);
1719
- }
1720
- function beforeResolve(guard) {
1721
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1722
- return _routerRef.current.beforeResolve(guard);
1723
- }
1724
- function afterEach(hook) {
1725
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1726
- return _routerRef.current.afterEach(hook);
1727
- }
1728
- var routeCleanups = [];
1729
- var ownParamNamesCache = /* @__PURE__ */ new WeakMap();
1730
- function ownParamNames(record) {
1731
- const cached = ownParamNamesCache.get(record);
1732
- if (cached) return cached;
1733
- const aliases = record.alias == null ? [] : Array.isArray(record.alias) ? record.alias : [record.alias];
1734
- const names = /* @__PURE__ */ new Set();
1735
- for (const pattern of [record.path, ...aliases]) {
1736
- for (const m of pattern.matchAll(/\/:([^/]+)/g)) {
1737
- names.add(m[1].endsWith("?") ? m[1].slice(0, -1) : m[1]);
1738
- }
1739
- }
1740
- const result = [...names];
1741
- ownParamNamesCache.set(record, result);
1742
- return result;
1743
- }
1744
- function instanceKey(owner, records, route2) {
1745
- const custom = owner.key;
1746
- if (custom) return untracked(() => String(custom(route2)));
1747
- const parts = [];
1748
- for (const record of records) {
1749
- for (const name of ownParamNames(record)) parts.push(name, route2.params[name] ?? null);
1750
- }
1751
- return JSON.stringify(parts);
1752
- }
1753
- function Route() {
1754
- const anchor = document.createComment("route-outlet");
1755
- let currentNode = null;
1756
- let loadingNode = null;
1757
- let errorNode = null;
1758
- let navSeq = 0;
1759
- let currentTopRoute = null;
1760
- let currentKey = null;
1761
- let routeTorn = false;
1762
- const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
1763
- const retryIfDetached = (seq) => {
1764
- if (!routeTorn && seq === navSeq && !anchor.parentNode) attachWait.request();
1765
- };
1766
- const release = (node) => {
1767
- if (!node) return;
1768
- disposeOwned(node);
1769
- node.parentNode?.removeChild(node);
1770
- };
1771
- const cleanupNodes = () => {
1772
- [currentNode, loadingNode, errorNode].forEach((node) => {
1773
- if (!node) return;
1774
- disposeOwned(node);
1775
- if (node.parentNode) {
1776
- node.parentNode.removeChild(node);
1777
- }
1778
- });
1779
- currentNode = null;
1780
- loadingNode = null;
1781
- errorNode = null;
1782
- };
1783
- const showLoading = () => {
1784
- if (!loadingNode && anchor.parentNode) {
1785
- loadingNode = document.createElement("div");
1786
- loadingNode.className = "route-loading";
1787
- loadingNode.setAttribute("role", "status");
1788
- loadingNode.setAttribute("aria-label", "Loading route");
1789
- const spinner = document.createElement("div");
1790
- spinner.className = "route-loading-spinner";
1791
- spinner.setAttribute("aria-hidden", "true");
1792
- const text = document.createElement("span");
1793
- text.textContent = "Loading...";
1794
- text.className = "route-loading-text";
1795
- loadingNode.appendChild(spinner);
1796
- loadingNode.appendChild(text);
1797
- anchor.parentNode.insertBefore(loadingNode, anchor.nextSibling);
1798
- }
1799
- };
1800
- const hideLoading = () => {
1801
- if (loadingNode?.parentNode) {
1802
- loadingNode.parentNode.removeChild(loadingNode);
1803
- loadingNode = null;
1804
- }
1805
- };
1806
- const showError = (error, routeDef) => {
1807
- if (!anchor.parentNode) return;
1808
- cleanupNodes();
1809
- errorNode = document.createElement("div");
1810
- errorNode.className = "route-error";
1811
- errorNode.setAttribute("role", "alert");
1812
- errorNode.setAttribute("aria-live", "assertive");
1813
- if (routeDef && "component" in routeDef) {
1814
- const src = routeDef.component.toString();
1815
- const importMatch = src.match(/import\(["']([^"']+)["']\)/);
1816
- if (importMatch) {
1817
- errorNode.setAttribute("data-component-source", importMatch[1]);
1818
- }
1819
- if (routeDef.component.name) {
1820
- errorNode.setAttribute("data-component-name", routeDef.component.name);
1821
- }
1822
- }
1823
- errorNode.__routeError = error;
1824
- const title = document.createElement("h3");
1825
- title.textContent = "Route Error";
1826
- title.className = "route-error-title";
1827
- const message = document.createElement("p");
1828
- message.textContent = error.message || "Failed to load route component";
1829
- message.className = "route-error-message";
1830
- const retryButton = document.createElement("button");
1831
- retryButton.textContent = "Retry";
1832
- retryButton.className = "route-error-retry";
1833
- retryButton.type = "button";
1834
- const onRetryClick = () => {
1835
- if (_routerRef.current) {
1836
- _routerRef.current.clearErrorCache();
1837
- update();
1838
- }
1839
- };
1840
- retryButton.addEventListener("click", onRetryClick);
1841
- registerDisposer(retryButton, () => retryButton.removeEventListener("click", onRetryClick));
1842
- errorNode.appendChild(title);
1843
- errorNode.appendChild(message);
1844
- errorNode.appendChild(retryButton);
1845
- anchor.parentNode.insertBefore(errorNode, anchor.nextSibling);
1846
- };
1847
- const update = async () => {
1848
- routerRevision();
1849
- if (routeTorn || !_routerRef.current) return;
1850
- const seq = ++navSeq;
1851
- const route2 = scopedRoute(_routerRef.current);
1852
- try {
1853
- const match = _routerRef.current["matcher"].match(route2.path);
1854
- if (!match) {
1855
- currentTopRoute = null;
1856
- currentKey = null;
1857
- cleanupNodes();
1858
- return;
1859
- }
1860
- const routeDef = match.matched[0] || match.route;
1861
- const key = instanceKey(routeDef, [routeDef], route2);
1862
- if (routeDef === currentTopRoute && key === currentKey && currentNode) {
1863
- updateInstanceRoute(currentNode, route2);
1864
- return;
1865
- }
1866
- if ("redirect" in routeDef) {
1867
- const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
1868
- queueMicrotask(() => {
1869
- _routerRef.current?.navigate(redirectPath).catch((err) => {
1870
- if (typeof console !== "undefined") console.error("[router] redirect failed:", err);
1871
- });
1872
- });
1873
- return;
1874
- }
1875
- if ("component" in routeDef) {
1876
- try {
1877
- const isAsync = routeDef.component.constructor.name === "AsyncFunction" || routeDef.component.toString().includes("import(");
1878
- if (isAsync) {
1879
- showLoading();
1880
- }
1881
- const plan = await _routerRef.current.loadPlan(routeDef, route2.path);
1882
- if (!commitTarget(seq)) {
1883
- retryIfDetached(seq);
1884
- return;
1885
- }
1886
- const node = await instantiateScoped(_routerRef.current, plan, routeDef, route2.path, route2);
1887
- const parent = commitTarget(seq);
1888
- if (!parent) {
1889
- release(node);
1890
- retryIfDetached(seq);
1891
- return;
1892
- }
1893
- if (node) {
1894
- currentTopRoute = routeDef;
1895
- currentKey = key;
1896
- cleanupNodes();
1897
- parent.insertBefore(node, anchor.nextSibling);
1898
- currentNode = node;
1899
- }
1900
- } catch (error) {
1901
- if (routeTorn || seq !== navSeq) return;
1902
- hideLoading();
1903
- console.error("[Route] Component error:", error);
1904
- fail(seq, error, routeDef);
1905
- }
1906
- }
1907
- } catch (error) {
1908
- if (seq !== navSeq) return;
1909
- if (routeTorn) return;
1910
- console.error("[Route] Update failed:", error);
1911
- fail(seq, error);
1912
- }
1913
- };
1914
- let unshownFailure = null;
1915
- const fail = (seq, error, routeDef) => {
1916
- const err = error instanceof Error ? error : new Error(String(error));
1917
- if (anchor.parentNode) {
1918
- showError(err, routeDef);
1919
- return;
1920
- }
1921
- unshownFailure = { seq, error: err, routeDef };
1922
- attachWait.request();
1923
- };
1924
- const onAttached = () => {
1925
- const failure = unshownFailure;
1926
- unshownFailure = null;
1927
- if (failure && failure.seq === navSeq) showError(failure.error, failure.routeDef);
1928
- else update();
1929
- };
1930
- let routeInitialized = false;
1931
- const originalUpdate = update;
1932
- const wrappedUpdate = async () => {
1933
- await originalUpdate();
1934
- routeInitialized = true;
1935
- };
1936
- const attachWait = attachRetry(anchor, onAttached);
1937
- const routeTeardown = track(wrappedUpdate);
1938
- if (!routeInitialized) {
1939
- queueMicrotask(
1940
- bindOwnerScope(() => {
1941
- if (!routeInitialized && anchor.parentNode) wrappedUpdate();
1942
- })
1943
- );
1944
- }
1945
- const routeCleanup = () => {
1946
- if (routeTorn) return;
1947
- routeTorn = true;
1948
- navSeq++;
1949
- routeTeardown();
1950
- attachWait.cancel();
1951
- unshownFailure = null;
1952
- cleanupNodes();
1953
- currentTopRoute = null;
1954
- currentKey = null;
1955
- };
1956
- registerDisposer(anchor, routeCleanup);
1957
- routeCleanups.push(routeCleanup);
1958
- return anchor;
1959
- }
1960
- function KeepAliveRoute(options) {
1961
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1962
- const anchor = document.createComment("keep-alive-route");
1963
- const cache = /* @__PURE__ */ new Map();
1964
- const lruOrder = [];
1965
- const routerOpts = _routerRef.current["options"];
1966
- const keepAliveOpt = routerOpts.keepAlive;
1967
- const maxCache = options?.max ?? (typeof keepAliveOpt === "number" ? keepAliveOpt : 20);
1968
- const includeNames = options?.include ?? (Array.isArray(keepAliveOpt) ? keepAliveOpt : void 0);
1969
- let currentNode = null;
1970
- let currentKey = "";
1971
- let currentCached = false;
1972
- let updateSeq = 0;
1973
- let kaTorn = false;
1974
- const update = async () => {
1975
- routerRevision();
1976
- if (kaTorn || !_routerRef.current) return;
1977
- const seq = ++updateSeq;
1978
- const router2 = _routerRef.current;
1979
- const route2 = scopedRoute(router2);
1980
- const match = router2["matcher"].match(route2.path);
1981
- if (!match) return;
1982
- const { route: routeDef } = match;
1983
- if ("redirect" in routeDef) {
1984
- const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
1985
- queueMicrotask(() => {
1986
- _routerRef.current?.navigate(redirectPath).catch((err) => {
1987
- if (typeof console !== "undefined") console.error("[router] redirect failed:", err);
1988
- });
1989
- });
1990
- return;
1991
- }
1992
- if (!("component" in routeDef)) return;
1993
- const cacheKey = JSON.stringify([route2.path, queryIdentity(route2.queryAll), route2.hash]);
1994
- const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
1995
- if (cacheKey === currentKey && currentNode) {
1996
- updateInstanceRoute(currentNode, route2);
1997
- return;
1998
- }
1999
- if (!anchor.parentNode) {
2000
- attachWait.request();
2001
- return;
2002
- }
2003
- let node = null;
2004
- let fromCache = false;
2005
- if (shouldCache && cache.has(cacheKey)) {
2006
- node = cache.get(cacheKey);
2007
- fromCache = true;
2008
- } else {
2009
- try {
2010
- const plan = await router2.loadPlan(routeDef, route2.path);
2011
- if (kaTorn || seq !== updateSeq) return;
2012
- node = await instantiateScoped(router2, plan, routeDef, route2.path, route2);
2013
- } catch (error) {
2014
- if (kaTorn || seq !== updateSeq) return;
2015
- console.error("[KeepAliveRoute] Component error:", error);
2016
- return;
2017
- }
2018
- if (!node) return;
2019
- if (kaTorn || seq !== updateSeq) {
2020
- disposeOwned(node);
2021
- return;
2022
- }
2023
- }
2024
- const parent = anchor.parentNode;
2025
- if (!parent) {
2026
- if (!fromCache) disposeOwned(node);
2027
- attachWait.request();
2028
- return;
2029
- }
2030
- if (currentNode && currentNode !== node) {
2031
- if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
2032
- if (!currentCached) disposeOwned(currentNode);
2033
- }
2034
- if (fromCache) {
2035
- updateInstanceRoute(node, route2);
2036
- const idx = lruOrder.indexOf(cacheKey);
2037
- if (idx !== -1) lruOrder.splice(idx, 1);
2038
- lruOrder.push(cacheKey);
2039
- } else if (shouldCache) {
2040
- cache.set(cacheKey, node);
2041
- lruOrder.push(cacheKey);
2042
- while (lruOrder.length > maxCache) {
2043
- const evictKey = lruOrder.shift();
2044
- if (evictKey === cacheKey) {
2045
- lruOrder.push(evictKey);
2046
- break;
2047
- }
2048
- const evictNode = cache.get(evictKey);
2049
- if (evictNode) {
2050
- disposeOwned(evictNode);
2051
- if (evictNode.parentNode) evictNode.parentNode.removeChild(evictNode);
2052
- cache.delete(evictKey);
2053
- }
2054
- }
2055
- }
2056
- currentNode = node;
2057
- currentCached = fromCache || shouldCache;
2058
- currentKey = cacheKey;
2059
- parent.insertBefore(node, anchor.nextSibling);
2060
- };
2061
- let initialized = false;
2062
- const wrappedUpdate = async () => {
2063
- await update();
2064
- initialized = true;
2065
- };
2066
- const attachWait = attachRetry(anchor, update);
2067
- const kaTeardown = track(wrappedUpdate);
2068
- if (!initialized) {
2069
- queueMicrotask(
2070
- bindOwnerScope(() => {
2071
- if (!initialized && anchor.parentNode) wrappedUpdate();
2072
- })
2073
- );
2074
- }
2075
- const kaCleanup = () => {
2076
- if (kaTorn) return;
2077
- kaTorn = true;
2078
- updateSeq++;
2079
- kaTeardown();
2080
- attachWait.cancel();
2081
- for (const node of cache.values()) {
2082
- disposeOwned(node);
2083
- if (node.parentNode) node.parentNode.removeChild(node);
2084
- }
2085
- cache.clear();
2086
- lruOrder.length = 0;
2087
- if (currentNode?.parentNode) currentNode.parentNode.removeChild(currentNode);
2088
- if (currentNode && !currentCached) disposeOwned(currentNode);
2089
- currentNode = null;
2090
- currentKey = "";
2091
- currentCached = false;
2092
- };
2093
- registerDisposer(anchor, kaCleanup);
2094
- routeCleanups.push(kaCleanup);
2095
- return anchor;
2096
- }
2097
- function RouterLink(props, children) {
2098
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2099
- const { to, replace: replace2 = false, activeClass, exactActiveClass, nodes, target, rel, class: classAttr, ...attrs } = props;
2100
- const content = children !== void 0 ? children : nodes;
2101
- const routeGetter = _routerRef.current.routeGetter;
2102
- const rawHref = _routerRef.current["resolvePath"](to);
2103
- const kind = classifyNavigationTarget(rawHref);
2104
- const href = kind === "unsafe" ? "#" : rawHref;
2105
- const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
2106
- const targetPath = normalizePathname(hrefPathRaw);
2107
- const targetQuery = queryIdentity(hrefQueryRaw);
2108
- const link = document.createElement("a");
2109
- link.href = kind === "internal" && _routerRef.current["options"].mode === "hash" ? `#${href}` : href;
2110
- if (target) {
2111
- link.target = target;
2112
- if (target === "_blank") {
2113
- link.rel = rel ? `${rel} noopener noreferrer` : "noopener noreferrer";
2114
- } else if (rel) {
2115
- link.rel = rel;
2116
- }
2117
- } else if (rel) {
2118
- link.rel = rel;
2119
- }
2120
- const options = _routerRef.current["options"];
2121
- const effectCleanup = effect(() => {
2122
- const route2 = routeGetter();
2123
- const currentPath = normalizePathname(route2.path);
2124
- const isActive = kind === "internal" && isPathnameAncestor(targetPath, currentPath);
2125
- const isExactActive = kind === "internal" && currentPath === targetPath && queryIdentity(route2.queryAll) === targetQuery && route2.hash === hrefHash;
2126
- const classes = [];
2127
- if (isActive) {
2128
- if (activeClass) classes.push(activeClass);
2129
- else if (options.linkActiveClass) classes.push(options.linkActiveClass);
2130
- }
2131
- if (isExactActive) {
2132
- if (exactActiveClass) classes.push(exactActiveClass);
2133
- else if (options.linkExactActiveClass) classes.push(options.linkExactActiveClass);
2134
- }
2135
- link.className = [resolveClassValue(classAttr), ...classes].filter(Boolean).join(" ");
2136
- });
2137
- registerDisposer(link, effectCleanup);
2138
- Object.entries(attrs).forEach(([key, value]) => {
2139
- const lkey = key.toLowerCase();
2140
- if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
2141
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2142
- setSafeAttribute(link, key, String(value), { syncValueProperty: false, label: "RouterLink" });
2143
- }
2144
- });
2145
- if (content !== void 0) appendChildren(link, content);
2146
- const onLinkClick = (e) => {
2147
- if (e.defaultPrevented || target || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
2148
- return;
2149
- }
2150
- if (link.hasAttribute("download")) return;
2151
- if (kind === "external") return;
2152
- e.preventDefault();
2153
- if (kind === "unsafe") return;
2154
- _routerRef.current?.navigate(to, { replace: replace2 }).catch((err) => {
2155
- if (typeof console !== "undefined") console.error("[router] link navigate failed:", err);
2156
- });
2157
- };
2158
- link.addEventListener("click", onLinkClick);
2159
- registerDisposer(link, () => {
2160
- link.removeEventListener("click", onLinkClick);
2161
- });
2162
- return link;
2163
- }
2164
- function Suspense(props) {
2165
- const anchor = document.createComment("suspense-boundary");
2166
- let currentNode = null;
2167
- let fallbackNode = null;
2168
- let isLoading = false;
2169
- let generation = 0;
2170
- let tornDown = false;
2171
- const release = (node) => {
2172
- if (!node) return;
2173
- dispose(node);
2174
- node.parentNode?.removeChild(node);
2175
- };
2176
- const commitTarget = (myGeneration) => tornDown || myGeneration !== generation ? null : anchor.parentNode;
2177
- const cleanupNodes = () => {
2178
- release(currentNode);
2179
- release(fallbackNode);
2180
- currentNode = null;
2181
- fallbackNode = null;
2182
- };
2183
- const showFallback = (myGeneration) => {
2184
- if (fallbackNode || !props.fallback || tornDown) return;
2185
- if (!anchor.parentNode) return;
2186
- let fallback;
2187
- try {
2188
- fallback = typeof props.fallback === "function" ? props.fallback() : props.fallback;
2189
- } catch (error) {
2190
- console.error("[Suspense] Fallback error:", error);
2191
- return;
2192
- }
2193
- if (!fallback || fallback.nodeType !== 1) return;
2194
- const parent = commitTarget(myGeneration);
2195
- if (!parent) {
2196
- release(fallback);
2197
- return;
2198
- }
2199
- fallbackNode = fallback;
2200
- parent.insertBefore(fallbackNode, anchor.nextSibling);
2201
- };
2202
- const render = async () => {
2203
- if (tornDown || isLoading) return;
2204
- isLoading = true;
2205
- const myGeneration = ++generation;
2206
- try {
2207
- const result = props.nodes();
2208
- let element;
2209
- const isNode = typeof result?.nodeType === "number";
2210
- if (!isNode && result != null && typeof result.then === "function") {
2211
- showFallback(myGeneration);
2212
- element = await result;
2213
- } else {
2214
- element = result;
2215
- }
2216
- const parent = commitTarget(myGeneration);
2217
- if (!parent) {
2218
- release(element);
2219
- cleanupNodes();
2220
- return;
2221
- }
2222
- cleanupNodes();
2223
- parent.insertBefore(element, anchor.nextSibling);
2224
- currentNode = element;
2225
- } catch (error) {
2226
- console.error("[Suspense] Nodes error:", error);
2227
- const parent = commitTarget(myGeneration);
2228
- cleanupNodes();
2229
- if (!parent) return;
2230
- const errorElement = document.createElement("div");
2231
- errorElement.className = "suspense-error";
2232
- errorElement.textContent = error instanceof Error ? error.message : "Failed to load";
2233
- parent.insertBefore(errorElement, anchor.nextSibling);
2234
- currentNode = errorElement;
2235
- } finally {
2236
- isLoading = false;
2237
- }
2238
- };
2239
- registerDisposer(anchor, () => {
2240
- tornDown = true;
2241
- generation++;
2242
- cleanupNodes();
2243
- });
2244
- queueMicrotask(bindOwnerScope(render));
2245
- return anchor;
2246
- }
2247
- var LAZY_MARKER = /* @__PURE__ */ Symbol.for("sibujs:lazy");
2248
- function lazy(importFn) {
2249
- importFn[LAZY_MARKER] = true;
2250
- return importFn;
2251
- }
2252
- async function preloadRoute(to) {
2253
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2254
- const path = _routerRef.current["resolvePath"](to);
2255
- const match = _routerRef.current["matcher"].match(path.split("?")[0].split("#")[0]);
2256
- if (match && "component" in match.route) {
2257
- try {
2258
- await _routerRef.current.preloadPlan(match.route, path);
2259
- } catch (error) {
2260
- console.warn("[Router] Preload failed:", error);
2261
- }
2262
- }
2263
- }
2264
- function __getNavigationEpoch() {
2265
- return _routerRef.current?.navigationEpoch ?? -1;
2266
- }
2267
- function hasRoute(name) {
2268
- if (!_routerRef.current) return false;
2269
- return _routerRef.current["matcher"].findByName(name) !== null;
2270
- }
2271
- function getRouteInfo(name) {
2272
- if (!_routerRef.current) return null;
2273
- return _routerRef.current["matcher"].findByName(name);
2274
- }
2275
- function buildURL(to) {
2276
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2277
- return _routerRef.current["resolvePath"](to);
2278
- }
2279
- function destroyRouter() {
2280
- for (const fn of routeCleanups) fn();
2281
- routeCleanups.length = 0;
2282
- if (_routerRef.current) {
2283
- _routerRef.current.destroy();
2284
- _routerRef.current = null;
2285
- }
2286
- }
2287
- var _routerPagehideHandler = null;
2288
- function ensureRouterPagehide() {
2289
- if (_routerPagehideHandler || typeof window === "undefined") return;
2290
- _routerPagehideHandler = (event) => {
2291
- if (event.persisted === false) {
2292
- destroyRouter();
2293
- }
2294
- };
2295
- window.addEventListener("pagehide", _routerPagehideHandler);
2296
- }
2297
- function __removeRouterPagehideHandler() {
2298
- if (_routerPagehideHandler && typeof window !== "undefined") {
2299
- window.removeEventListener("pagehide", _routerPagehideHandler);
2300
- _routerPagehideHandler = null;
2301
- }
2302
- }
2303
- function Outlet() {
2304
- const anchor = document.createComment("route-outlet-nested");
2305
- let currentNode = null;
2306
- let currentChild = null;
2307
- let currentKey = null;
2308
- let navSeq = 0;
2309
- let outletTorn = false;
2310
- const clearCurrent = () => {
2311
- if (currentNode) {
2312
- disposeOwned(currentNode);
2313
- if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
2314
- currentNode = null;
2315
- }
2316
- currentChild = null;
2317
- currentKey = null;
2318
- };
2319
- const commitTarget = (seq) => outletTorn || seq !== navSeq ? null : anchor.parentNode;
2320
- const retryIfDetached = (seq) => {
2321
- if (!outletTorn && seq === navSeq && !anchor.parentNode) attachWait.request();
2322
- };
2323
- const release = (node) => {
2324
- if (!node) return;
2325
- disposeOwned(node);
2326
- node.parentNode?.removeChild(node);
2327
- };
2328
- const update = async () => {
2329
- routerRevision();
2330
- if (outletTorn || !_routerRef.current) return;
2331
- const seq = ++navSeq;
2332
- const route2 = scopedRoute(_routerRef.current);
2333
- if (route2.matched.length < 2) {
2334
- clearCurrent();
2335
- return;
2336
- }
2337
- const childRoute = route2.matched[route2.matched.length - 1];
2338
- if (!childRoute || !("component" in childRoute)) {
2339
- clearCurrent();
2340
- return;
2341
- }
2342
- const key = instanceKey(childRoute, route2.matched.slice(1), route2);
2343
- if (childRoute === currentChild && key === currentKey && currentNode) {
2344
- updateInstanceRoute(currentNode, route2);
2345
- return;
2346
- }
2347
- try {
2348
- const cacheKey = `${route2.path}\0${childRoute.path}`;
2349
- const plan = await _routerRef.current.loadPlan(childRoute, cacheKey);
2350
- if (!commitTarget(seq)) {
2351
- retryIfDetached(seq);
2352
- return;
2353
- }
2354
- const node = await instantiateScoped(_routerRef.current, plan, childRoute, cacheKey, route2);
2355
- const parent = commitTarget(seq);
2356
- if (!parent) {
2357
- release(node);
2358
- retryIfDetached(seq);
2359
- return;
2360
- }
2361
- if (!node) return;
2362
- clearCurrent();
2363
- parent.insertBefore(node, anchor.nextSibling);
2364
- currentNode = node;
2365
- currentChild = childRoute;
2366
- currentKey = key;
2367
- } catch (error) {
2368
- if (outletTorn || seq !== navSeq) return;
2369
- console.error("[Outlet] Failed to render child route:", error);
2370
- }
2371
- };
2372
- const attachWait = attachRetry(anchor, update);
2373
- const outletTeardown = track(update);
2374
- if (!anchor.parentNode) {
2375
- queueMicrotask(
2376
- bindOwnerScope(() => {
2377
- if (anchor.parentNode) update();
2378
- })
2379
- );
2380
- }
2381
- const outletCleanup = () => {
2382
- if (outletTorn) return;
2383
- outletTorn = true;
2384
- navSeq++;
2385
- outletTeardown();
2386
- attachWait.cancel();
2387
- if (currentNode) {
2388
- disposeOwned(currentNode);
2389
- if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
2390
- currentNode = null;
2391
- }
2392
- currentChild = null;
2393
- currentKey = null;
2394
- };
2395
- registerDisposer(anchor, outletCleanup);
2396
- routeCleanups.push(outletCleanup);
2397
- return anchor;
2398
- }
2399
- function addRoute(route2, parentPath) {
2400
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2401
- _routerRef.current.addRoute(route2, parentPath);
2402
- }
2403
- function removeRoute(path) {
2404
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2405
- _routerRef.current.removeRoute(path);
2406
- }
2407
- function routerState() {
2408
- if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
2409
- const router2 = _routerRef.current;
2410
- return {
2411
- currentPath: () => scopedRoute(router2).path,
2412
- params: () => scopedRoute(router2).params,
2413
- query: () => scopedRoute(router2).query,
2414
- queryAll: () => scopedRoute(router2).queryAll,
2415
- hash: () => scopedRoute(router2).hash,
2416
- meta: () => scopedRoute(router2).meta,
2417
- isNavigating: () => router2.isNavigating,
2418
- isReady: () => router2.isReady
2419
- };
2420
- }
2421
- var routerPlugins = [];
2422
- function routerPlugin(plugin2) {
2423
- routerPlugins.push(plugin2);
2424
- if (_routerRef.current?.isReady && plugin2.onReady) {
2425
- plugin2.onReady();
2426
- }
2427
- let removeGuard = null;
2428
- if (_routerRef.current && plugin2.onNavigate) {
2429
- removeGuard = _routerRef.current.afterEach((to, from) => {
2430
- plugin2.onNavigate?.(to, from);
2431
- });
2432
- }
2433
- return () => {
2434
- const idx = routerPlugins.indexOf(plugin2);
2435
- if (idx !== -1) routerPlugins.splice(idx, 1);
2436
- removeGuard?.();
2437
- };
2438
- }
2439
- var _routeTransitionOptions = null;
2440
- function setRouteTransition(options) {
2441
- _routeTransitionOptions = options;
2442
- }
2443
- function getRouteTransition() {
2444
- return _routeTransitionOptions;
2445
- }
2446
- function createMemoryRouter(routes, _initialPath = "/") {
2447
- const router2 = createRouter(routes, { mode: "hash" });
2448
- return {
2449
- router: router2,
2450
- currentPath: () => router2.currentRoute.path,
2451
- push: (path) => router2.push(path)
2452
- };
2453
- }
2454
-
2455
188
  // src/plugins/routerSSR.ts
2456
189
  function safeDecode(raw) {
2457
190
  try {
@@ -2697,8 +430,8 @@ function hydrateRouter(routes, options) {
2697
430
  const liveUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`;
2698
431
  const resolved = resolveServerRoute(liveUrl, routes);
2699
432
  const bootstrapEpoch = __getNavigationEpoch();
2700
- import("./ssr-VBM6J5SJ.js").then(async ({ hydrate }) => {
2701
- const { replaceChildrenSafely } = await import("./dispose-GEIG2KOF.js");
433
+ import("./ssr-NECEU4X4.js").then(async ({ hydrate }) => {
434
+ const { replaceChildrenSafely } = await import("./dispose-EIHHHDC7.js");
2702
435
  if (__getNavigationEpoch() !== bootstrapEpoch) return;
2703
436
  if (resolved.component) {
2704
437
  hydrate(resolved.component, container);
@@ -2779,6 +512,7 @@ export {
2779
512
  hydrateRouter,
2780
513
  inject,
2781
514
  isPluginInstallCancelledError,
515
+ jsonLdScript,
2782
516
  lazy,
2783
517
  lazyModule,
2784
518
  navigate,
@@ -2804,6 +538,7 @@ export {
2804
538
  routerReady,
2805
539
  routerState,
2806
540
  satisfies,
541
+ serializeJsonLd,
2807
542
  serializeRouteState,
2808
543
  setDefaultPluginRegistry,
2809
544
  setLocale,