sibujs 4.13.1 → 4.14.1

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