@ilha/router 0.11.3 → 0.11.5
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 +75 -66
- package/dist/codegen.d.ts +3 -3
- package/dist/hash.d.ts +13 -9
- package/dist/head-BLtm0xtK.js +342 -0
- package/dist/head.d.ts +42 -28
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +52 -55
- package/dist/index.js +339 -305
- package/dist/plugin-DCdQ9yO8.js +1140 -0
- package/dist/plugin.d.ts +15 -15
- package/dist/request-scope.d.ts +3 -5
- package/dist/route-error.d.ts +6 -0
- package/dist/route-match.d.ts +3 -3
- package/dist/rsbuild.d.ts +3 -3
- package/dist/rsbuild.js +2 -4
- package/dist/server-island.d.ts +26 -15
- package/dist/server-island.js +166 -136
- package/dist/server-islands.d.ts +15 -14
- package/dist/snapshot-XpAkSV8s.js +57 -0
- package/dist/snapshot.d.ts +15 -2
- package/dist/{ssr-CV_vNpS3.js → ssr-Dvq_qXs4.js} +170 -142
- package/dist/ssr.d.ts +38 -39
- package/dist/ssr.js +2 -2
- package/dist/vite.d.ts +3 -3
- package/dist/vite.js +2 -4
- package/oxlint.cjs +201 -122
- package/package.json +5 -5
- package/dist/head-DpYV3bHH.js +0 -285
- package/dist/plugin-Bfnn44nu.js +0 -1034
- package/dist/snapshot-C0E2OGwL.js +0 -55
package/dist/index.js
CHANGED
|
@@ -1,98 +1,74 @@
|
|
|
1
1
|
import { t as REQUEST_ALS_KEY } from "./als-key-CVxbuM3z.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as withHeadStore, i as serializeHead, n as head, r as openBrowserHead, t as applyHeadEntriesToDocument } from "./head-BLtm0xtK.js";
|
|
3
3
|
import { h, mount, renderToString } from "ilha";
|
|
4
4
|
|
|
5
5
|
//#region src/hash.ts
|
|
6
|
-
const isBrowser$1 =
|
|
6
|
+
const isBrowser$1 = globalThis.window !== void 0 && globalThis.document !== void 0;
|
|
7
|
+
const noop$1 = () => void 0;
|
|
8
|
+
const HTTP_SCHEME = /^(?:http:|https:)$/u;
|
|
7
9
|
const historyAdapter = {
|
|
10
|
+
extractLogicalPath(anchor) {
|
|
11
|
+
const href = anchor.getAttribute("href");
|
|
12
|
+
if (!href) return null;
|
|
13
|
+
if (anchor.protocol && !HTTP_SCHEME.test(anchor.protocol)) return null;
|
|
14
|
+
if (href.startsWith("#")) return null;
|
|
15
|
+
if (!!anchor.hostname && (anchor.hostname !== location.hostname || anchor.protocol !== location.protocol)) return null;
|
|
16
|
+
return anchor.pathname + anchor.search + anchor.hash;
|
|
17
|
+
},
|
|
18
|
+
onChange(handler) {
|
|
19
|
+
if (!isBrowser$1) return noop$1;
|
|
20
|
+
window.addEventListener("popstate", handler);
|
|
21
|
+
return () => window.removeEventListener("popstate", handler);
|
|
22
|
+
},
|
|
23
|
+
push(to, state) {
|
|
24
|
+
if (!isBrowser$1) return;
|
|
25
|
+
history.pushState(state ?? null, "", to);
|
|
26
|
+
},
|
|
8
27
|
readLocation() {
|
|
9
28
|
if (!isBrowser$1) return {
|
|
29
|
+
hash: "",
|
|
10
30
|
pathname: "/",
|
|
11
|
-
search: ""
|
|
12
|
-
hash: ""
|
|
31
|
+
search: ""
|
|
13
32
|
};
|
|
14
33
|
return {
|
|
34
|
+
hash: location.hash,
|
|
15
35
|
pathname: location.pathname,
|
|
16
|
-
search: location.search
|
|
17
|
-
hash: location.hash
|
|
36
|
+
search: location.search
|
|
18
37
|
};
|
|
19
38
|
},
|
|
20
|
-
push(to, state) {
|
|
21
|
-
if (!isBrowser$1) return;
|
|
22
|
-
history.pushState(state ?? null, "", to);
|
|
23
|
-
},
|
|
24
39
|
replace(to, state) {
|
|
25
40
|
if (!isBrowser$1) return;
|
|
26
41
|
history.replaceState(state ?? null, "", to);
|
|
27
42
|
},
|
|
28
|
-
onChange(handler) {
|
|
29
|
-
if (!isBrowser$1) return () => {};
|
|
30
|
-
window.addEventListener("popstate", handler);
|
|
31
|
-
return () => window.removeEventListener("popstate", handler);
|
|
32
|
-
},
|
|
33
43
|
toLinkHref(p) {
|
|
34
44
|
return p;
|
|
35
|
-
},
|
|
36
|
-
extractLogicalPath(anchor) {
|
|
37
|
-
const href = anchor.getAttribute("href");
|
|
38
|
-
if (!href) return null;
|
|
39
|
-
if (anchor.protocol && !/^(http:|https:)$/.test(anchor.protocol)) return null;
|
|
40
|
-
if (href.startsWith("#")) return null;
|
|
41
|
-
if (!!anchor.hostname && (anchor.hostname !== location.hostname || anchor.protocol !== location.protocol)) return null;
|
|
42
|
-
return anchor.pathname + anchor.search + anchor.hash;
|
|
43
45
|
}
|
|
44
46
|
};
|
|
45
|
-
|
|
47
|
+
const parseHash = (rawHash) => {
|
|
46
48
|
const stripped = rawHash.startsWith("#") ? rawHash.slice(1) : rawHash;
|
|
47
|
-
|
|
49
|
+
let path;
|
|
50
|
+
if (stripped === "") path = "/";
|
|
51
|
+
else if (stripped.startsWith("/")) path = stripped;
|
|
52
|
+
else path = `/${stripped}`;
|
|
48
53
|
const u = new URL(path, "http://_");
|
|
49
54
|
return {
|
|
55
|
+
hash: u.hash,
|
|
50
56
|
pathname: u.pathname,
|
|
51
|
-
search: u.search
|
|
52
|
-
hash: u.hash
|
|
57
|
+
search: u.search
|
|
53
58
|
};
|
|
54
|
-
}
|
|
59
|
+
};
|
|
55
60
|
const hashAdapter = {
|
|
56
|
-
readLocation() {
|
|
57
|
-
if (!isBrowser$1) return {
|
|
58
|
-
pathname: "/",
|
|
59
|
-
search: "",
|
|
60
|
-
hash: ""
|
|
61
|
-
};
|
|
62
|
-
return parseHash(location.hash);
|
|
63
|
-
},
|
|
64
|
-
push(to, state) {
|
|
65
|
-
if (!isBrowser$1) return;
|
|
66
|
-
history.pushState(state ?? null, "", to.startsWith("#") ? to : "#" + to);
|
|
67
|
-
},
|
|
68
|
-
replace(to, state) {
|
|
69
|
-
if (!isBrowser$1) return;
|
|
70
|
-
history.replaceState(state ?? null, "", to.startsWith("#") ? to : "#" + to);
|
|
71
|
-
},
|
|
72
|
-
onChange(handler) {
|
|
73
|
-
if (!isBrowser$1) return () => {};
|
|
74
|
-
window.addEventListener("popstate", handler);
|
|
75
|
-
window.addEventListener("hashchange", handler);
|
|
76
|
-
return () => {
|
|
77
|
-
window.removeEventListener("popstate", handler);
|
|
78
|
-
window.removeEventListener("hashchange", handler);
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
toLinkHref(p) {
|
|
82
|
-
if (p.startsWith("#")) return p;
|
|
83
|
-
return "#" + p;
|
|
84
|
-
},
|
|
85
61
|
extractLogicalPath(anchor) {
|
|
86
62
|
const href = anchor.getAttribute("href");
|
|
87
63
|
if (!href) return null;
|
|
88
64
|
if (href.startsWith("//")) return null;
|
|
89
|
-
if (anchor.protocol &&
|
|
65
|
+
if (anchor.protocol && !HTTP_SCHEME.test(anchor.protocol)) return null;
|
|
90
66
|
if (href.startsWith("#")) {
|
|
91
67
|
const inner = href.slice(1);
|
|
92
68
|
if (inner === "" || !inner.startsWith("/")) return null;
|
|
93
69
|
return inner;
|
|
94
70
|
}
|
|
95
|
-
if (/^https?:\/\//
|
|
71
|
+
if (/^https?:\/\//iu.test(href)) try {
|
|
96
72
|
const u = new URL(href);
|
|
97
73
|
if (u.origin !== location.origin) return null;
|
|
98
74
|
if (!u.hash || u.hash === "#") return null;
|
|
@@ -103,6 +79,35 @@ const hashAdapter = {
|
|
|
103
79
|
return null;
|
|
104
80
|
}
|
|
105
81
|
return href;
|
|
82
|
+
},
|
|
83
|
+
onChange(handler) {
|
|
84
|
+
if (!isBrowser$1) return noop$1;
|
|
85
|
+
window.addEventListener("popstate", handler);
|
|
86
|
+
window.addEventListener("hashchange", handler);
|
|
87
|
+
return () => {
|
|
88
|
+
window.removeEventListener("popstate", handler);
|
|
89
|
+
window.removeEventListener("hashchange", handler);
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
push(to, state) {
|
|
93
|
+
if (!isBrowser$1) return;
|
|
94
|
+
history.pushState(state ?? null, "", to.startsWith("#") ? to : `#${to}`);
|
|
95
|
+
},
|
|
96
|
+
readLocation() {
|
|
97
|
+
if (!isBrowser$1) return {
|
|
98
|
+
hash: "",
|
|
99
|
+
pathname: "/",
|
|
100
|
+
search: ""
|
|
101
|
+
};
|
|
102
|
+
return parseHash(location.hash);
|
|
103
|
+
},
|
|
104
|
+
replace(to, state) {
|
|
105
|
+
if (!isBrowser$1) return;
|
|
106
|
+
history.replaceState(state ?? null, "", to.startsWith("#") ? to : `#${to}`);
|
|
107
|
+
},
|
|
108
|
+
toLinkHref(p) {
|
|
109
|
+
if (p.startsWith("#")) return p;
|
|
110
|
+
return `#${p}`;
|
|
106
111
|
}
|
|
107
112
|
};
|
|
108
113
|
let _mode = "history";
|
|
@@ -120,17 +125,13 @@ let _adapter = historyAdapter;
|
|
|
120
125
|
* until they're re-attached (typically by unmounting and remounting
|
|
121
126
|
* the router).
|
|
122
127
|
*/
|
|
123
|
-
|
|
128
|
+
const setHistoryMode = (mode) => {
|
|
124
129
|
_mode = mode;
|
|
125
130
|
_adapter = mode === "hash" ? hashAdapter : historyAdapter;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return _mode;
|
|
129
|
-
}
|
|
131
|
+
};
|
|
132
|
+
const getHistoryMode = () => _mode;
|
|
130
133
|
/** Internal — used by index.ts. Not part of the public API. */
|
|
131
|
-
|
|
132
|
-
return _adapter;
|
|
133
|
-
}
|
|
134
|
+
const getAdapter = () => _adapter;
|
|
134
135
|
|
|
135
136
|
//#endregion
|
|
136
137
|
//#region src/http.ts
|
|
@@ -141,82 +142,109 @@ function getAdapter() {
|
|
|
141
142
|
* a low-level helper — prefer {@link RouterBuilder.respond} for the full
|
|
142
143
|
* render+head+headers pipeline.
|
|
143
144
|
*/
|
|
144
|
-
|
|
145
|
+
const httpResponse = (body, options = {}) => {
|
|
145
146
|
const headers = new Headers(options.headers);
|
|
146
|
-
if (body
|
|
147
|
+
if (body !== null && !headers.has("content-type")) headers.set("content-type", "text/html; charset=utf-8");
|
|
147
148
|
if (!headers.has("x-content-type-options")) headers.set("x-content-type-options", "nosniff");
|
|
148
149
|
if (!headers.has("referrer-policy")) headers.set("referrer-policy", "no-referrer");
|
|
149
150
|
if (!headers.has("cache-control")) headers.set("cache-control", "no-store");
|
|
150
151
|
const csp = options.contentSecurityPolicy ?? (options.cspNonce ? `default-src 'self'; script-src 'self' 'nonce-${options.cspNonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'` : void 0);
|
|
151
152
|
if (csp) headers.set("content-security-policy", csp);
|
|
152
153
|
return new Response(body, {
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
headers,
|
|
155
|
+
status: options.status ?? 200
|
|
155
156
|
});
|
|
156
|
-
}
|
|
157
|
+
};
|
|
157
158
|
const EMPTY_HEAD = {
|
|
159
|
+
bodyAttrs: "",
|
|
158
160
|
headTags: "",
|
|
159
|
-
htmlAttrs: ""
|
|
160
|
-
bodyAttrs: ""
|
|
161
|
+
htmlAttrs: ""
|
|
161
162
|
};
|
|
162
163
|
/**
|
|
163
164
|
* Options for {@link RouterBuilder.respond}.
|
|
164
165
|
*/
|
|
165
166
|
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/route-error.ts
|
|
169
|
+
var RouteError = class {
|
|
170
|
+
__ilhaRouteError = true;
|
|
171
|
+
status;
|
|
172
|
+
message;
|
|
173
|
+
constructor(status, message) {
|
|
174
|
+
this.message = message;
|
|
175
|
+
this.status = status >= 400 && status <= 599 ? status : 500;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
166
179
|
//#endregion
|
|
167
180
|
//#region src/route-match.ts
|
|
168
|
-
|
|
181
|
+
const segmentKind = (segment) => {
|
|
182
|
+
if (segment.startsWith("*")) return 0;
|
|
183
|
+
if (segment.startsWith(":")) return 1;
|
|
184
|
+
return 2;
|
|
185
|
+
};
|
|
186
|
+
const parsePattern = (pattern) => {
|
|
169
187
|
const segments = pattern.split("/").filter(Boolean);
|
|
170
188
|
return {
|
|
171
|
-
segments,
|
|
172
|
-
|
|
189
|
+
kinds: segments.map(segmentKind),
|
|
190
|
+
segments
|
|
173
191
|
};
|
|
174
|
-
}
|
|
192
|
+
};
|
|
175
193
|
/**
|
|
176
194
|
* Match `segments` against a pathname. Returns raw (still-encoded) captured
|
|
177
195
|
* params, or `null` when the path doesn't match — the caller decides whether to
|
|
178
196
|
* decode via {@link safeDecode}.
|
|
179
197
|
*/
|
|
180
|
-
|
|
198
|
+
const matchSegments = (segments, pathname) => {
|
|
181
199
|
const pathSegments = pathname.split("/").filter(Boolean);
|
|
182
200
|
const params = {};
|
|
183
201
|
let cursor = 0;
|
|
184
|
-
for (let i = 0; i < segments.length; i
|
|
202
|
+
for (let i = 0; i < segments.length; i += 1) {
|
|
185
203
|
const segment = segments[i];
|
|
204
|
+
if (segment === void 0) return null;
|
|
186
205
|
const isLast = i === segments.length - 1;
|
|
187
206
|
if (segment.startsWith("**") && isLast) {
|
|
188
|
-
const name = segment.slice(2).replace(
|
|
207
|
+
const name = segment.slice(2).replace(/^:/u, "");
|
|
189
208
|
if (name) params[name] = pathSegments.slice(cursor).join("/");
|
|
190
209
|
cursor = pathSegments.length;
|
|
191
210
|
break;
|
|
192
211
|
}
|
|
193
212
|
if (segment.startsWith("*")) {
|
|
194
213
|
if (pathSegments[cursor] === void 0) return null;
|
|
195
|
-
cursor
|
|
214
|
+
cursor += 1;
|
|
196
215
|
continue;
|
|
197
216
|
}
|
|
198
217
|
if (segment.startsWith(":")) {
|
|
199
218
|
const value = pathSegments[cursor];
|
|
200
219
|
if (value === void 0) return null;
|
|
201
220
|
params[segment.slice(1)] = value;
|
|
202
|
-
cursor
|
|
203
|
-
} else if (pathSegments[cursor] === segment) cursor
|
|
221
|
+
cursor += 1;
|
|
222
|
+
} else if (pathSegments[cursor] === segment) cursor += 1;
|
|
204
223
|
else return null;
|
|
205
224
|
}
|
|
206
225
|
return cursor === pathSegments.length ? params : null;
|
|
207
|
-
}
|
|
226
|
+
};
|
|
208
227
|
/** Decode a route-param value, tolerating malformed percent-encoding. */
|
|
209
|
-
|
|
228
|
+
const safeDecode = (value) => {
|
|
210
229
|
try {
|
|
211
230
|
return decodeURIComponent(value);
|
|
212
231
|
} catch {
|
|
213
232
|
return value;
|
|
214
233
|
}
|
|
215
|
-
}
|
|
234
|
+
};
|
|
216
235
|
|
|
217
236
|
//#endregion
|
|
218
237
|
//#region src/index.ts
|
|
219
|
-
const
|
|
238
|
+
const objectTag = (value) => Object.prototype.toString.call(value);
|
|
239
|
+
const isString = (value) => objectTag(value) === "[object String]";
|
|
240
|
+
const isNumber = (value) => objectTag(value) === "[object Number]";
|
|
241
|
+
const isFunction = (value) => {
|
|
242
|
+
const tag = objectTag(value);
|
|
243
|
+
return tag === "[object Function]" || tag === "[object AsyncFunction]" || tag === "[object GeneratorFunction]";
|
|
244
|
+
};
|
|
245
|
+
const isObject = (value) => value !== null && objectTag(value) === "[object Object]";
|
|
246
|
+
const isBrowser = globalThis.window !== void 0 && globalThis.document !== void 0;
|
|
247
|
+
const noop = () => void 0;
|
|
220
248
|
var Redirect = class {
|
|
221
249
|
__ilhaRedirect = true;
|
|
222
250
|
to;
|
|
@@ -226,58 +254,26 @@ var Redirect = class {
|
|
|
226
254
|
this.status = status >= 300 && status <= 399 ? status : 302;
|
|
227
255
|
}
|
|
228
256
|
};
|
|
229
|
-
|
|
230
|
-
__ilhaRouteError = true;
|
|
231
|
-
status;
|
|
232
|
-
message;
|
|
233
|
-
constructor(status, message) {
|
|
234
|
-
this.message = message;
|
|
235
|
-
this.status = status >= 400 && status <= 599 ? status : 500;
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
function redirect(to, status = 302) {
|
|
257
|
+
const redirect = (to, status = 302) => {
|
|
239
258
|
throw new Redirect(to, status);
|
|
240
|
-
}
|
|
241
|
-
|
|
259
|
+
};
|
|
260
|
+
const throwAppError = (status, message) => {
|
|
242
261
|
throw new RouteError(status, message);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
function wrapError(handler, page) {
|
|
250
|
-
return async function Wrapped() {
|
|
251
|
-
try {
|
|
252
|
-
const out = page();
|
|
253
|
-
return out instanceof Promise ? await out : out;
|
|
254
|
-
} catch (e) {
|
|
255
|
-
const view = handler(e instanceof RouteError ? {
|
|
256
|
-
message: e.message,
|
|
257
|
-
status: e.status
|
|
258
|
-
} : { message: e instanceof Error ? e.message : String(e) }, snapshot());
|
|
259
|
-
return typeof view === "function" ? view() : view;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
function defineLayout(layout) {
|
|
264
|
-
return layout;
|
|
265
|
-
}
|
|
266
|
-
function createRouteRegistry() {
|
|
267
|
-
return [];
|
|
268
|
-
}
|
|
269
|
-
function addRouteEntry(registry, pattern, data) {
|
|
262
|
+
};
|
|
263
|
+
const defineLayout = (layout) => layout;
|
|
264
|
+
const createRouteRegistry = () => [];
|
|
265
|
+
const addRouteEntry = (registry, pattern, data) => {
|
|
270
266
|
registry.push({
|
|
271
|
-
|
|
267
|
+
data,
|
|
272
268
|
parsed: parsePattern(pattern),
|
|
273
|
-
|
|
269
|
+
pattern
|
|
274
270
|
});
|
|
275
|
-
}
|
|
276
|
-
|
|
271
|
+
};
|
|
272
|
+
const matchRoute = (registry, pathname) => {
|
|
277
273
|
let best = null;
|
|
278
274
|
let bestScore = -1;
|
|
279
275
|
for (const entry of registry) {
|
|
280
|
-
if (matchSegments(entry.parsed.segments, pathname)
|
|
276
|
+
if (matchSegments(entry.parsed.segments, pathname) === null) continue;
|
|
281
277
|
const score = entry.parsed.kinds.reduce((s, k) => s + k, 0);
|
|
282
278
|
if (score > bestScore) {
|
|
283
279
|
best = entry;
|
|
@@ -285,14 +281,14 @@ function matchRoute(registry, pathname) {
|
|
|
285
281
|
}
|
|
286
282
|
}
|
|
287
283
|
return best;
|
|
288
|
-
}
|
|
289
|
-
|
|
284
|
+
};
|
|
285
|
+
const extractParams = (match, pathname) => {
|
|
290
286
|
if (!match) return {};
|
|
291
287
|
const raw = matchSegments(match.parsed.segments, pathname) ?? {};
|
|
292
288
|
const out = {};
|
|
293
289
|
for (const [k, v] of Object.entries(raw)) out[k] = safeDecode(v);
|
|
294
290
|
return out;
|
|
295
|
-
}
|
|
291
|
+
};
|
|
296
292
|
let _routes = [];
|
|
297
293
|
let _path = "/";
|
|
298
294
|
let _params = {};
|
|
@@ -304,123 +300,133 @@ let _lastNavKey = 0;
|
|
|
304
300
|
const _scrollPositions = /* @__PURE__ */ new Map();
|
|
305
301
|
const _beforeNavigateHooks = [];
|
|
306
302
|
const _afterNavigateHooks = [];
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
function useRoute() {
|
|
331
|
-
return {
|
|
332
|
-
path: routePath,
|
|
333
|
-
params: routeParams,
|
|
334
|
-
search: routeSearch,
|
|
335
|
-
hash: routeHash,
|
|
336
|
-
navigating
|
|
303
|
+
const snapshot = () => ({
|
|
304
|
+
hash: _hash,
|
|
305
|
+
params: _params,
|
|
306
|
+
path: _path,
|
|
307
|
+
search: _search
|
|
308
|
+
});
|
|
309
|
+
const wrapLayout = (layout, page) => {
|
|
310
|
+
const Wrapped = () => h(layout, null, h(page, null));
|
|
311
|
+
return Wrapped;
|
|
312
|
+
};
|
|
313
|
+
const wrapError = (handler, page) => {
|
|
314
|
+
const Wrapped = async () => {
|
|
315
|
+
try {
|
|
316
|
+
const out = page();
|
|
317
|
+
return out instanceof Promise ? await out : out;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
const view = handler(error instanceof RouteError ? {
|
|
320
|
+
message: error.message,
|
|
321
|
+
status: error.status
|
|
322
|
+
} : { message: error instanceof Error ? error.message : String(error) }, snapshot());
|
|
323
|
+
return isFunction(view) ? view() : view;
|
|
324
|
+
}
|
|
337
325
|
};
|
|
338
|
-
|
|
339
|
-
|
|
326
|
+
return Wrapped;
|
|
327
|
+
};
|
|
328
|
+
const routePath = () => _path;
|
|
329
|
+
const routeParams = () => _params;
|
|
330
|
+
const routeSearch = () => _search;
|
|
331
|
+
const routeHash = () => _hash;
|
|
332
|
+
const navigating = () => _navigating;
|
|
333
|
+
const useRoute = () => ({
|
|
334
|
+
hash: routeHash,
|
|
335
|
+
navigating,
|
|
336
|
+
params: routeParams,
|
|
337
|
+
path: routePath,
|
|
338
|
+
search: routeSearch
|
|
339
|
+
});
|
|
340
|
+
const useContext = () => {
|
|
340
341
|
return { request: globalThis[REQUEST_ALS_KEY]?.getStore?.() };
|
|
341
|
-
}
|
|
342
|
-
|
|
342
|
+
};
|
|
343
|
+
const syncFromParts = (pathname, search, hash, routes = _routes) => {
|
|
343
344
|
_path = pathname || "/";
|
|
344
345
|
_search = search;
|
|
345
346
|
_hash = hash;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
const match = matchRoute(routes, _path);
|
|
348
|
+
_params = extractParams(match, _path);
|
|
349
|
+
};
|
|
350
|
+
const syncRouteFromLocation = () => {
|
|
349
351
|
const loc = getAdapter().readLocation();
|
|
350
352
|
syncFromParts(loc.pathname, loc.search, loc.hash);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
+
};
|
|
354
|
+
const prime = () => {
|
|
353
355
|
if (isBrowser) syncRouteFromLocation();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
+
};
|
|
357
|
+
const beforeNavigate = (fn) => {
|
|
356
358
|
_beforeNavigateHooks.push(fn);
|
|
357
359
|
return () => {
|
|
358
360
|
const i = _beforeNavigateHooks.indexOf(fn);
|
|
359
|
-
if (i
|
|
361
|
+
if (i !== -1) _beforeNavigateHooks.splice(i, 1);
|
|
360
362
|
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
+
};
|
|
364
|
+
const afterNavigate = (fn) => {
|
|
363
365
|
_afterNavigateHooks.push(fn);
|
|
364
366
|
return () => {
|
|
365
367
|
const i = _afterNavigateHooks.indexOf(fn);
|
|
366
|
-
if (i
|
|
368
|
+
if (i !== -1) _afterNavigateHooks.splice(i, 1);
|
|
367
369
|
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
+
};
|
|
371
|
+
const runAfterNavigateHooks = (nav) => {
|
|
370
372
|
for (const fn of _afterNavigateHooks) try {
|
|
371
373
|
fn(nav);
|
|
372
|
-
} catch (
|
|
373
|
-
console.error("[ilha-router] afterNavigate hook threw:",
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error("[ilha-router] afterNavigate hook threw:", error);
|
|
374
376
|
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const state = isBrowser ? history
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
377
|
+
};
|
|
378
|
+
const currentNavKey = () => {
|
|
379
|
+
const { state } = isBrowser ? history : { state: null };
|
|
380
|
+
if (!state || !isObject(state)) return 0;
|
|
381
|
+
const { __ilhaNavKey: key } = state;
|
|
382
|
+
return isNumber(key) ? key : 0;
|
|
383
|
+
};
|
|
384
|
+
const saveScrollPosition = () => {
|
|
382
385
|
if (!isBrowser) return;
|
|
383
386
|
_scrollPositions.set(_lastNavKey, {
|
|
384
387
|
x: window.scrollX,
|
|
385
388
|
y: window.scrollY
|
|
386
389
|
});
|
|
387
|
-
}
|
|
388
|
-
|
|
390
|
+
};
|
|
391
|
+
const scrollAfterNavigate = (hash) => {
|
|
389
392
|
if (!isBrowser) return;
|
|
390
393
|
if (hash && hash.length > 1) {
|
|
391
394
|
const id = hash.slice(1);
|
|
392
|
-
const el = document.
|
|
395
|
+
const el = document.querySelector(`[id=${JSON.stringify(id)}]`);
|
|
393
396
|
if (el) {
|
|
394
397
|
el.scrollIntoView();
|
|
395
398
|
return;
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
401
|
window.scrollTo(0, 0);
|
|
399
|
-
}
|
|
400
|
-
|
|
402
|
+
};
|
|
403
|
+
const restoreScrollPosition = () => {
|
|
401
404
|
const pos = _scrollPositions.get(currentNavKey());
|
|
402
405
|
if (!pos) return;
|
|
403
406
|
requestAnimationFrame(() => window.scrollTo(pos.x, pos.y));
|
|
404
|
-
}
|
|
405
|
-
|
|
407
|
+
};
|
|
408
|
+
const navigate = (to, opts = {}) => {
|
|
406
409
|
if (!isBrowser) return;
|
|
407
410
|
const adapter = getAdapter();
|
|
408
411
|
const cur = adapter.readLocation();
|
|
409
412
|
const current = cur.pathname + cur.search + cur.hash;
|
|
410
413
|
if (to === current) return;
|
|
411
414
|
const type = opts.replace ? "replace" : "push";
|
|
412
|
-
|
|
415
|
+
const cancelState = { cancelled: false };
|
|
416
|
+
const cancelNav = () => {
|
|
417
|
+
cancelState.cancelled = true;
|
|
418
|
+
};
|
|
413
419
|
for (const fn of _beforeNavigateHooks) try {
|
|
414
420
|
fn({
|
|
421
|
+
cancel: cancelNav,
|
|
415
422
|
from: current,
|
|
416
423
|
to,
|
|
417
|
-
type
|
|
418
|
-
cancel: () => cancelled = true
|
|
424
|
+
type
|
|
419
425
|
});
|
|
420
|
-
} catch (
|
|
421
|
-
console.error("[ilha-router] beforeNavigate hook threw:",
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error("[ilha-router] beforeNavigate hook threw:", error);
|
|
422
428
|
}
|
|
423
|
-
if (cancelled) return;
|
|
429
|
+
if (cancelState.cancelled) return;
|
|
424
430
|
if (opts.replace) adapter.replace(to, { __ilhaNavKey: currentNavKey() });
|
|
425
431
|
else {
|
|
426
432
|
saveScrollPosition();
|
|
@@ -434,23 +440,33 @@ function navigate(to, opts = {}) {
|
|
|
434
440
|
const dest = adapter.readLocation();
|
|
435
441
|
const fromMatch = matchRoute(_routes, prevPath);
|
|
436
442
|
const destMatch = matchRoute(_routes, dest.pathname);
|
|
437
|
-
if (!(fromMatch?.data.page
|
|
443
|
+
if (!(fromMatch?.data.page !== null && fromMatch?.data.page !== void 0 && destMatch?.data.page === fromMatch.data.page) || dest.hash && dest.hash !== "#") scrollAfterNavigate(dest.hash);
|
|
438
444
|
}
|
|
439
445
|
runAfterNavigateHooks({
|
|
440
446
|
from: current,
|
|
441
447
|
to,
|
|
442
448
|
type
|
|
443
449
|
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
450
|
+
};
|
|
451
|
+
const NON_NAV_SCHEME = /^(?:mailto|tel|javascript):/iu;
|
|
452
|
+
const resolveClickTarget = (_href, url) => {
|
|
453
|
+
if (getHistoryMode() === "hash") {
|
|
454
|
+
if (url.hash.startsWith("#/")) return url.hash.slice(1);
|
|
455
|
+
return url.pathname + url.search;
|
|
456
|
+
}
|
|
457
|
+
return url.pathname + url.search + url.hash;
|
|
458
|
+
};
|
|
459
|
+
const enableLinkInterception = (root = document, _options = {}) => {
|
|
460
|
+
if (!isBrowser) return noop;
|
|
447
461
|
const clickHandler = (event) => {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
const
|
|
451
|
-
if (!
|
|
452
|
-
const
|
|
453
|
-
if (!
|
|
462
|
+
const mouseEvent = event;
|
|
463
|
+
if (mouseEvent.defaultPrevented || mouseEvent.button !== 0 || mouseEvent.metaKey || mouseEvent.ctrlKey || mouseEvent.shiftKey || mouseEvent.altKey) return;
|
|
464
|
+
const { target } = mouseEvent;
|
|
465
|
+
if (!(target instanceof Element)) return;
|
|
466
|
+
const anchor = target.closest("a");
|
|
467
|
+
if (!anchor || anchor.hasAttribute("download") || anchor.getAttribute("target") === "_blank" || anchor.matches("[data-no-intercept]")) return;
|
|
468
|
+
const href = anchor.getAttribute("href");
|
|
469
|
+
if (!href || NON_NAV_SCHEME.test(href)) return;
|
|
454
470
|
if (href.startsWith("#") && getHistoryMode() !== "hash") return;
|
|
455
471
|
if (href.startsWith("#") && !href.startsWith("#/")) return;
|
|
456
472
|
let url;
|
|
@@ -460,22 +476,25 @@ function enableLinkInterception(root = document, _options = {}) {
|
|
|
460
476
|
return;
|
|
461
477
|
}
|
|
462
478
|
if (url.origin !== location.origin) return;
|
|
463
|
-
|
|
464
|
-
navigate(
|
|
479
|
+
mouseEvent.preventDefault();
|
|
480
|
+
navigate(resolveClickTarget(href, url));
|
|
465
481
|
};
|
|
466
482
|
root.addEventListener("click", clickHandler);
|
|
467
483
|
return () => root.removeEventListener("click", clickHandler);
|
|
468
|
-
}
|
|
469
|
-
|
|
484
|
+
};
|
|
485
|
+
const isActive = (pattern, options = {}) => {
|
|
470
486
|
if (options.end) return _path === pattern;
|
|
471
487
|
if (pattern === "/") return _path === "/";
|
|
472
|
-
return _path === pattern || _path.startsWith(pattern.endsWith("/") ? pattern : pattern
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
|
|
488
|
+
return _path === pattern || _path.startsWith(pattern.endsWith("/") ? pattern : `${pattern}/`);
|
|
489
|
+
};
|
|
490
|
+
const resolveRedirectTarget = (to, base, allowExternal) => {
|
|
491
|
+
for (const ch of to) {
|
|
492
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
493
|
+
if (code <= 32 || code === 92) return { ok: false };
|
|
494
|
+
}
|
|
476
495
|
try {
|
|
477
496
|
const u = new URL(to, base);
|
|
478
|
-
if (!/^https
|
|
497
|
+
if (!/^https?:$/u.test(u.protocol)) return { ok: false };
|
|
479
498
|
if (u.origin === base.origin) return {
|
|
480
499
|
ok: true,
|
|
481
500
|
to: u.pathname + u.search + u.hash
|
|
@@ -487,24 +506,28 @@ function resolveRedirectTarget(to, base, allowExternal) {
|
|
|
487
506
|
} catch {
|
|
488
507
|
return { ok: false };
|
|
489
508
|
}
|
|
490
|
-
}
|
|
491
|
-
|
|
509
|
+
};
|
|
510
|
+
const resolveRequestUrl = (urlOrRequest) => {
|
|
492
511
|
try {
|
|
493
512
|
if (urlOrRequest instanceof Request) return new URL(urlOrRequest.url);
|
|
494
|
-
|
|
513
|
+
if (isString(urlOrRequest)) return new URL(urlOrRequest, "http://localhost");
|
|
514
|
+
return urlOrRequest;
|
|
495
515
|
} catch {
|
|
496
516
|
return new URL("http://localhost/");
|
|
497
517
|
}
|
|
498
|
-
}
|
|
499
|
-
|
|
518
|
+
};
|
|
519
|
+
const pageForPath = (pathname, routes, notFound) => {
|
|
500
520
|
const match = matchRoute(routes, pathname);
|
|
501
521
|
if (match) return match.data.page;
|
|
502
522
|
return notFound;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
523
|
+
};
|
|
524
|
+
const renderPage = (page, opts) => renderToString(page, opts);
|
|
525
|
+
const buildRespondBody = (result, respondOpts, head) => {
|
|
526
|
+
const { html } = result;
|
|
527
|
+
if (respondOpts.shell) return respondOpts.shell(head, html);
|
|
528
|
+
return html;
|
|
529
|
+
};
|
|
530
|
+
const router = (options = {}) => {
|
|
508
531
|
const mode = options.mode ?? "spa";
|
|
509
532
|
const defaultInterceptLinks = options.interceptLinks !== false;
|
|
510
533
|
const allowExternalRedirects = options.allowExternalRedirects === true;
|
|
@@ -517,19 +540,6 @@ function router(options = {}) {
|
|
|
517
540
|
let navChangeCleanup = null;
|
|
518
541
|
let linkCleanup = null;
|
|
519
542
|
const builder = {
|
|
520
|
-
route(pattern, page) {
|
|
521
|
-
const data = {
|
|
522
|
-
page,
|
|
523
|
-
pattern
|
|
524
|
-
};
|
|
525
|
-
records.push({
|
|
526
|
-
pattern,
|
|
527
|
-
page
|
|
528
|
-
});
|
|
529
|
-
addRouteEntry(routes, pattern, data);
|
|
530
|
-
patternToData.set(pattern, data);
|
|
531
|
-
return builder;
|
|
532
|
-
},
|
|
533
543
|
errorBoundary(pattern, handler) {
|
|
534
544
|
const data = patternToData.get(pattern);
|
|
535
545
|
if (!data) {
|
|
@@ -539,23 +549,27 @@ function router(options = {}) {
|
|
|
539
549
|
data.errorHandler = handler;
|
|
540
550
|
return builder;
|
|
541
551
|
},
|
|
542
|
-
|
|
543
|
-
|
|
552
|
+
hydrate(hydrateOpts = {}) {
|
|
553
|
+
const root = hydrateOpts.root ?? (isBrowser ? document.body : null);
|
|
554
|
+
if (!root) return noop;
|
|
555
|
+
return builder.mount(root, {
|
|
556
|
+
hydrate: true,
|
|
557
|
+
interceptLinks: hydrateOpts.interceptLinks
|
|
558
|
+
});
|
|
544
559
|
},
|
|
545
|
-
prime,
|
|
546
560
|
mount(target, { hydrate = false, interceptLinks: mountInterceptLinks } = {}) {
|
|
547
561
|
if (!isBrowser) {
|
|
548
562
|
console.warn("[ilha-router] mount() called in a non-browser environment");
|
|
549
|
-
return
|
|
563
|
+
return noop;
|
|
550
564
|
}
|
|
551
|
-
const host =
|
|
565
|
+
const host = isString(target) ? document.querySelector(target) : target;
|
|
552
566
|
if (!host) {
|
|
553
567
|
console.warn(`[ilha-router] No element found for selector "${target}"`);
|
|
554
|
-
return
|
|
568
|
+
return noop;
|
|
555
569
|
}
|
|
556
570
|
if (mode === "static") {
|
|
557
571
|
console.warn("[ilha-router] router.mount() is a no-op in static mode.");
|
|
558
|
-
return
|
|
572
|
+
return noop;
|
|
559
573
|
}
|
|
560
574
|
syncRouteFromLocation();
|
|
561
575
|
_lastNavKey = currentNavKey();
|
|
@@ -563,6 +577,25 @@ function router(options = {}) {
|
|
|
563
577
|
let mounted = true;
|
|
564
578
|
const prevScroll = "scrollRestoration" in history ? history.scrollRestoration : null;
|
|
565
579
|
if (prevScroll !== null) history.scrollRestoration = "manual";
|
|
580
|
+
let unmountView = null;
|
|
581
|
+
let headSession = null;
|
|
582
|
+
const remount = () => {
|
|
583
|
+
unmountView?.();
|
|
584
|
+
headSession?.close();
|
|
585
|
+
headSession = null;
|
|
586
|
+
const page = pageForPath(_path, routes, notFound);
|
|
587
|
+
if (!page) {
|
|
588
|
+
unmountView = null;
|
|
589
|
+
applyHeadEntriesToDocument([]);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const session = openBrowserHead();
|
|
593
|
+
headSession = session;
|
|
594
|
+
unmountView = mount(host, page, { hydrate: unmountView === null && hydrate });
|
|
595
|
+
queueMicrotask(() => {
|
|
596
|
+
session.flush();
|
|
597
|
+
});
|
|
598
|
+
};
|
|
566
599
|
const popHandler = () => {
|
|
567
600
|
if (!mounted) return;
|
|
568
601
|
const prevPath = _path + _search + _hash;
|
|
@@ -582,16 +615,6 @@ function router(options = {}) {
|
|
|
582
615
|
};
|
|
583
616
|
navChangeCleanup = getAdapter().onChange(popHandler);
|
|
584
617
|
linkCleanup = mountInterceptLinks ?? defaultInterceptLinks ? enableLinkInterception(document) : null;
|
|
585
|
-
let unmountView = null;
|
|
586
|
-
const remount = () => {
|
|
587
|
-
unmountView?.();
|
|
588
|
-
const page = pageForPath(_path, routes, notFound);
|
|
589
|
-
if (!page) {
|
|
590
|
-
unmountView = null;
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
unmountView = mount(host, page, { hydrate: unmountView == null && hydrate });
|
|
594
|
-
};
|
|
595
618
|
remount();
|
|
596
619
|
const offNav = afterNavigate(() => {
|
|
597
620
|
if (mounted) remount();
|
|
@@ -600,18 +623,21 @@ function router(options = {}) {
|
|
|
600
623
|
mounted = false;
|
|
601
624
|
offNav();
|
|
602
625
|
unmountView?.();
|
|
626
|
+
headSession?.close();
|
|
627
|
+
headSession = null;
|
|
603
628
|
navChangeCleanup?.();
|
|
604
629
|
linkCleanup?.();
|
|
605
630
|
if (prevScroll !== null) history.scrollRestoration = prevScroll;
|
|
606
631
|
};
|
|
607
632
|
},
|
|
608
|
-
|
|
609
|
-
|
|
633
|
+
prime,
|
|
634
|
+
async render(url, renderOpts) {
|
|
635
|
+
const result = await builder.renderResponse(url, renderOpts);
|
|
610
636
|
if (result.kind === "html") return result.html;
|
|
611
637
|
if (result.kind === "redirect") return "";
|
|
612
638
|
return result.html;
|
|
613
639
|
},
|
|
614
|
-
async renderResponse(url,
|
|
640
|
+
async renderResponse(url, renderOpts) {
|
|
615
641
|
const parsed = resolveRequestUrl(url);
|
|
616
642
|
syncFromParts(parsed.pathname, parsed.search, parsed.hash, routes);
|
|
617
643
|
const store = { entries: [] };
|
|
@@ -622,80 +648,88 @@ function router(options = {}) {
|
|
|
622
648
|
const wrapped = async () => {
|
|
623
649
|
try {
|
|
624
650
|
return await Promise.resolve(page());
|
|
625
|
-
} catch (
|
|
626
|
-
captured =
|
|
627
|
-
throw
|
|
651
|
+
} catch (error) {
|
|
652
|
+
captured = error;
|
|
653
|
+
throw error;
|
|
628
654
|
}
|
|
629
655
|
};
|
|
630
|
-
const html = await withHeadStore(store, () => renderPage(wrapped,
|
|
656
|
+
const html = await withHeadStore(store, () => renderPage(wrapped, renderOpts));
|
|
631
657
|
if (captured instanceof Redirect || captured instanceof RouteError) throw captured;
|
|
632
658
|
return {
|
|
633
|
-
|
|
659
|
+
head: serializeHead(store.entries),
|
|
634
660
|
html,
|
|
635
|
-
|
|
636
|
-
|
|
661
|
+
kind: "html",
|
|
662
|
+
status: 200
|
|
637
663
|
};
|
|
638
|
-
} catch (
|
|
639
|
-
if (
|
|
640
|
-
const safe = resolveRedirectTarget(
|
|
664
|
+
} catch (error) {
|
|
665
|
+
if (error instanceof Redirect) {
|
|
666
|
+
const safe = resolveRedirectTarget(error.to, parsed, allowExternalRedirects);
|
|
641
667
|
if (!safe.ok) return {
|
|
668
|
+
head: EMPTY_HEAD,
|
|
669
|
+
html: "",
|
|
642
670
|
kind: "error",
|
|
643
|
-
status: 500,
|
|
644
671
|
message: "unsafe redirect target",
|
|
645
|
-
|
|
646
|
-
head: EMPTY_HEAD
|
|
672
|
+
status: 500
|
|
647
673
|
};
|
|
648
674
|
return {
|
|
649
675
|
kind: "redirect",
|
|
650
|
-
|
|
651
|
-
|
|
676
|
+
status: error.status,
|
|
677
|
+
to: safe.to
|
|
652
678
|
};
|
|
653
679
|
}
|
|
654
|
-
if (
|
|
680
|
+
if (error instanceof RouteError) {
|
|
655
681
|
const boundary = matchRoute(routes, parsed.pathname)?.data.errorHandler;
|
|
656
682
|
let html = "";
|
|
657
683
|
if (boundary) {
|
|
658
684
|
const view = boundary({
|
|
659
|
-
message:
|
|
660
|
-
status:
|
|
685
|
+
message: error.message,
|
|
686
|
+
status: error.status
|
|
661
687
|
}, snapshot());
|
|
662
|
-
html = await renderPage(
|
|
688
|
+
html = await renderPage(isFunction(view) ? view : () => view, renderOpts);
|
|
663
689
|
}
|
|
664
690
|
return {
|
|
665
|
-
|
|
666
|
-
status: e.status,
|
|
667
|
-
message: e.message,
|
|
691
|
+
head: serializeHead(store.entries),
|
|
668
692
|
html,
|
|
669
|
-
|
|
693
|
+
kind: "error",
|
|
694
|
+
message: error.message,
|
|
695
|
+
status: error.status
|
|
670
696
|
};
|
|
671
697
|
}
|
|
672
|
-
throw
|
|
698
|
+
throw error;
|
|
673
699
|
}
|
|
674
700
|
},
|
|
675
|
-
async respond(url,
|
|
676
|
-
const result = await builder.renderResponse(url,
|
|
701
|
+
async respond(url, respondOpts = {}) {
|
|
702
|
+
const result = await builder.renderResponse(url, respondOpts);
|
|
677
703
|
if (result.kind === "redirect") return Response.redirect(result.to, result.status);
|
|
678
704
|
const head = result.head ?? EMPTY_HEAD;
|
|
679
705
|
if (isBrowser && result.kind === "html") applyHeadEntriesToDocument([]);
|
|
680
|
-
const body =
|
|
706
|
+
const body = buildRespondBody(result, respondOpts, head);
|
|
681
707
|
return httpResponse(body, {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
708
|
+
contentSecurityPolicy: respondOpts.contentSecurityPolicy,
|
|
709
|
+
cspNonce: respondOpts.cspNonce,
|
|
710
|
+
headers: respondOpts.headers,
|
|
711
|
+
status: result.kind === "error" ? result.status : result.status ?? 200
|
|
686
712
|
});
|
|
687
713
|
},
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
714
|
+
route(pattern, page) {
|
|
715
|
+
const data = {
|
|
716
|
+
page,
|
|
717
|
+
pattern
|
|
718
|
+
};
|
|
719
|
+
records.push({
|
|
720
|
+
page,
|
|
721
|
+
pattern
|
|
694
722
|
});
|
|
723
|
+
addRouteEntry(routes, pattern, data);
|
|
724
|
+
patternToData.set(pattern, data);
|
|
725
|
+
return builder;
|
|
726
|
+
},
|
|
727
|
+
routes() {
|
|
728
|
+
return records.map((r) => ({ ...r }));
|
|
695
729
|
}
|
|
696
730
|
};
|
|
697
731
|
return builder;
|
|
698
|
-
}
|
|
732
|
+
};
|
|
699
733
|
|
|
700
734
|
//#endregion
|
|
701
|
-
export { EMPTY_HEAD, Redirect, RouteError, afterNavigate, beforeNavigate, defineLayout, enableLinkInterception, error, getHistoryMode, head, httpResponse, isActive, navigate, navigating, prime, redirect, resolveRedirectTarget, routeHash, routeParams, routePath, routeSearch, router, serializeHead, setHistoryMode, useContext, useRoute, wrapError, wrapLayout };
|
|
735
|
+
export { EMPTY_HEAD, Redirect, RouteError, afterNavigate, beforeNavigate, defineLayout, enableLinkInterception, throwAppError as error, getHistoryMode, head, httpResponse, isActive, navigate, navigating, prime, redirect, resolveRedirectTarget, routeHash, routeParams, routePath, routeSearch, router, serializeHead, setHistoryMode, useContext, useRoute, wrapError, wrapLayout };
|