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