@richie-router/react 0.1.2 → 0.1.4
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/dist/cjs/router.cjs +352 -107
- package/dist/cjs/router.test.cjs +574 -0
- package/dist/esm/router.mjs +353 -107
- package/dist/esm/router.test.mjs +552 -0
- package/dist/types/router.d.ts +31 -2
- package/package.json +2 -2
package/dist/esm/router.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
defaultStringifySearch,
|
|
11
11
|
isNotFound,
|
|
12
12
|
isRedirect,
|
|
13
|
+
matchPathname,
|
|
13
14
|
matchRouteTree,
|
|
14
15
|
notFound,
|
|
15
16
|
redirect,
|
|
@@ -20,16 +21,89 @@ import {
|
|
|
20
21
|
createHashHistory,
|
|
21
22
|
createMemoryHistory
|
|
22
23
|
} from "./history.mjs";
|
|
23
|
-
import {
|
|
24
|
+
import { jsx } from "react/jsx-runtime";
|
|
24
25
|
var RouterContext = React.createContext(null);
|
|
25
26
|
var RouterStateContext = React.createContext(null);
|
|
26
27
|
var OutletContext = React.createContext(null);
|
|
27
28
|
var MatchContext = React.createContext(null);
|
|
28
29
|
var MANAGED_HEAD_ATTRIBUTE = "data-richie-router-head";
|
|
29
|
-
var EMPTY_HEAD =
|
|
30
|
+
var EMPTY_HEAD = [];
|
|
31
|
+
function ensureLeadingSlash(value) {
|
|
32
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
33
|
+
}
|
|
34
|
+
function normalizeBasePath(basePath) {
|
|
35
|
+
if (!basePath) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
const trimmed = basePath.trim();
|
|
39
|
+
if (trimmed === "" || trimmed === "/") {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
const normalized = ensureLeadingSlash(trimmed).replace(/\/+$/u, "");
|
|
43
|
+
return normalized === "/" ? "" : normalized;
|
|
44
|
+
}
|
|
45
|
+
function parseHref(href) {
|
|
46
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
47
|
+
return new URL(href);
|
|
48
|
+
}
|
|
49
|
+
return new URL(ensureLeadingSlash(href), "http://richie-router.local");
|
|
50
|
+
}
|
|
51
|
+
function stripBasePathFromPathname(pathname, basePath) {
|
|
52
|
+
if (!basePath) {
|
|
53
|
+
return pathname;
|
|
54
|
+
}
|
|
55
|
+
if (pathname === basePath) {
|
|
56
|
+
return "/";
|
|
57
|
+
}
|
|
58
|
+
return pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) || "/" : pathname;
|
|
59
|
+
}
|
|
60
|
+
function stripBasePathFromHref(href, basePath) {
|
|
61
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
62
|
+
if (!normalizedBasePath) {
|
|
63
|
+
return href;
|
|
64
|
+
}
|
|
65
|
+
const url = parseHref(href);
|
|
66
|
+
return `${stripBasePathFromPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
67
|
+
}
|
|
68
|
+
function prependBasePathToPathname(pathname, basePath) {
|
|
69
|
+
if (!basePath) {
|
|
70
|
+
return pathname;
|
|
71
|
+
}
|
|
72
|
+
return pathname === "/" ? basePath : `${basePath}${ensureLeadingSlash(pathname)}`;
|
|
73
|
+
}
|
|
74
|
+
function prependBasePathToHref(href, basePath) {
|
|
75
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
76
|
+
if (!normalizedBasePath) {
|
|
77
|
+
return href;
|
|
78
|
+
}
|
|
79
|
+
const url = parseHref(href);
|
|
80
|
+
return `${prependBasePathToPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
81
|
+
}
|
|
30
82
|
function routeHasRecord(value) {
|
|
31
83
|
return typeof value === "object" && value !== null;
|
|
32
84
|
}
|
|
85
|
+
function isDeepInclusiveMatch(expected, actual) {
|
|
86
|
+
if (Array.isArray(expected)) {
|
|
87
|
+
if (!Array.isArray(actual) || expected.length !== actual.length) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return expected.every((value, index) => isDeepInclusiveMatch(value, actual[index]));
|
|
91
|
+
}
|
|
92
|
+
if (routeHasRecord(expected)) {
|
|
93
|
+
if (!routeHasRecord(actual)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return Object.entries(expected).every(([key, value]) => (key in actual) && isDeepInclusiveMatch(value, actual[key]));
|
|
97
|
+
}
|
|
98
|
+
return Object.is(expected, actual);
|
|
99
|
+
}
|
|
100
|
+
function routeHasInlineHead(route) {
|
|
101
|
+
const headOption = route.options.head;
|
|
102
|
+
return Boolean(headOption && typeof headOption !== "string");
|
|
103
|
+
}
|
|
104
|
+
function matchesHaveInlineHead(matches) {
|
|
105
|
+
return matches.some((match) => routeHasInlineHead(match.route));
|
|
106
|
+
}
|
|
33
107
|
function resolveParamsInput(input, previous) {
|
|
34
108
|
if (input === undefined) {
|
|
35
109
|
return previous;
|
|
@@ -75,6 +149,8 @@ class Router {
|
|
|
75
149
|
headCache = new Map;
|
|
76
150
|
parseSearch;
|
|
77
151
|
stringifySearch;
|
|
152
|
+
basePath;
|
|
153
|
+
initialHeadSnapshot;
|
|
78
154
|
started = false;
|
|
79
155
|
unsubscribeHistory;
|
|
80
156
|
constructor(options) {
|
|
@@ -83,6 +159,7 @@ class Router {
|
|
|
83
159
|
this.history = options.history ?? (typeof window === "undefined" ? createMemoryHistory() : createBrowserHistory());
|
|
84
160
|
this.parseSearch = options.parseSearch ?? defaultParseSearch;
|
|
85
161
|
this.stringifySearch = options.stringifySearch ?? defaultStringifySearch;
|
|
162
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
86
163
|
for (const route of collectRoutes(this.routeTree)) {
|
|
87
164
|
this.routesByFullPath.set(route.fullPath, route);
|
|
88
165
|
}
|
|
@@ -90,15 +167,22 @@ class Router {
|
|
|
90
167
|
this.routesByTo.set(branch.leaf.to, branch.leaf);
|
|
91
168
|
}
|
|
92
169
|
const location = this.readLocation();
|
|
170
|
+
const initialMatches = this.buildMatches(location);
|
|
171
|
+
const rawHistoryHref = this.history.location.href;
|
|
93
172
|
const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
|
|
94
|
-
const
|
|
173
|
+
const hasMatchingInitialHeadSnapshot = Boolean(initialHeadSnapshot && (initialHeadSnapshot.href === location.href || initialHeadSnapshot.href === rawHistoryHref));
|
|
174
|
+
const initialHead = hasMatchingInitialHeadSnapshot && initialHeadSnapshot ? initialHeadSnapshot.head : EMPTY_HEAD;
|
|
175
|
+
if (hasMatchingInitialHeadSnapshot && this.options.loadRouteHead === undefined && initialHeadSnapshot?.routeHeads !== undefined) {
|
|
176
|
+
this.seedHeadCacheFromRouteHeads(initialMatches, initialHeadSnapshot.routeHeads);
|
|
177
|
+
}
|
|
95
178
|
if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
|
|
96
179
|
delete window.__RICHIE_ROUTER_HEAD__;
|
|
97
180
|
}
|
|
181
|
+
this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
|
|
98
182
|
this.state = {
|
|
99
183
|
status: "loading",
|
|
100
184
|
location,
|
|
101
|
-
matches:
|
|
185
|
+
matches: initialMatches,
|
|
102
186
|
head: initialHead,
|
|
103
187
|
error: null
|
|
104
188
|
};
|
|
@@ -129,15 +213,17 @@ class Router {
|
|
|
129
213
|
}
|
|
130
214
|
async load(options) {
|
|
131
215
|
const nextLocation = this.readLocation();
|
|
216
|
+
const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
|
|
217
|
+
this.initialHeadSnapshot = undefined;
|
|
132
218
|
await this.commitLocation(nextLocation, {
|
|
133
219
|
request: options?.request,
|
|
134
220
|
replace: true,
|
|
135
|
-
writeHistory: false
|
|
221
|
+
writeHistory: false,
|
|
222
|
+
initialHeadSnapshot
|
|
136
223
|
});
|
|
137
224
|
}
|
|
138
225
|
async navigate(options) {
|
|
139
|
-
const
|
|
140
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
226
|
+
const location = this.buildLocation(options);
|
|
141
227
|
await this.commitLocation(location, {
|
|
142
228
|
replace: options.replace ?? false,
|
|
143
229
|
writeHistory: true,
|
|
@@ -145,8 +231,7 @@ class Router {
|
|
|
145
231
|
});
|
|
146
232
|
}
|
|
147
233
|
async preloadRoute(options) {
|
|
148
|
-
const
|
|
149
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
234
|
+
const location = this.buildLocation(options);
|
|
150
235
|
try {
|
|
151
236
|
await this.resolveLocation(location);
|
|
152
237
|
} catch {}
|
|
@@ -156,6 +241,12 @@ class Router {
|
|
|
156
241
|
await this.load();
|
|
157
242
|
}
|
|
158
243
|
buildHref(options) {
|
|
244
|
+
return prependBasePathToHref(this.buildLocationHref(options), this.basePath);
|
|
245
|
+
}
|
|
246
|
+
buildLocation(options) {
|
|
247
|
+
return createParsedLocation(this.buildLocationHref(options), options.state ?? null, this.parseSearch);
|
|
248
|
+
}
|
|
249
|
+
buildLocationHref(options) {
|
|
159
250
|
const targetRoute = this.routesByTo.get(options.to) ?? null;
|
|
160
251
|
const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
|
|
161
252
|
const previousParams = fromMatch?.params ?? {};
|
|
@@ -170,7 +261,7 @@ class Router {
|
|
|
170
261
|
}
|
|
171
262
|
readLocation() {
|
|
172
263
|
const location = this.history.location;
|
|
173
|
-
return createParsedLocation(location.href, location.state, this.parseSearch);
|
|
264
|
+
return createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
|
|
174
265
|
}
|
|
175
266
|
applyTrailingSlash(pathname, route) {
|
|
176
267
|
const trailingSlash = this.options.trailingSlash ?? "preserve";
|
|
@@ -265,29 +356,102 @@ class Router {
|
|
|
265
356
|
to: route.to
|
|
266
357
|
});
|
|
267
358
|
}
|
|
268
|
-
const head = await this.resolveLocationHead(matches, location, options?.request);
|
|
359
|
+
const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
|
|
269
360
|
return { matches, head, error: null };
|
|
270
361
|
}
|
|
271
|
-
async resolveLocationHead(matches, location, request) {
|
|
362
|
+
async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
|
|
272
363
|
const resolvedHeadByRoute = new Map;
|
|
273
|
-
|
|
274
|
-
|
|
364
|
+
const serverMatches = matches.filter((match) => match.route.serverHead);
|
|
365
|
+
if (serverMatches.length === 0) {
|
|
366
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
367
|
+
}
|
|
368
|
+
if (this.options.loadRouteHead !== undefined) {
|
|
369
|
+
for (const match of serverMatches) {
|
|
370
|
+
resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, match.params, match.search, location, request));
|
|
371
|
+
}
|
|
372
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
373
|
+
}
|
|
374
|
+
if (initialHeadSnapshot?.href === location.href && initialHeadSnapshot.routeHeads === undefined && !matchesHaveInlineHead(matches)) {
|
|
375
|
+
return initialHeadSnapshot.head;
|
|
376
|
+
}
|
|
377
|
+
let needsDocumentHeadFetch = false;
|
|
378
|
+
for (const match of serverMatches) {
|
|
379
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
380
|
+
if (cachedHead) {
|
|
381
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
275
382
|
continue;
|
|
276
383
|
}
|
|
277
|
-
|
|
384
|
+
needsDocumentHeadFetch = true;
|
|
385
|
+
}
|
|
386
|
+
if (!needsDocumentHeadFetch) {
|
|
387
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
388
|
+
}
|
|
389
|
+
const documentHead = await this.fetchDocumentHead(location);
|
|
390
|
+
if ((documentHead.routeHeads?.length ?? 0) === 0 && !matchesHaveInlineHead(matches)) {
|
|
391
|
+
return documentHead.head;
|
|
392
|
+
}
|
|
393
|
+
const routeHeadsById = this.cacheRouteHeadsFromDocument(matches, documentHead.routeHeads ?? []);
|
|
394
|
+
for (const match of serverMatches) {
|
|
395
|
+
const responseHead = routeHeadsById.get(match.route.fullPath);
|
|
396
|
+
if (responseHead) {
|
|
397
|
+
resolvedHeadByRoute.set(match.route.fullPath, responseHead);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
401
|
+
if (cachedHead) {
|
|
402
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const response = await this.fetchRouteHead(match.route, match.params, match.search);
|
|
406
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, response);
|
|
407
|
+
resolvedHeadByRoute.set(match.route.fullPath, response.head);
|
|
278
408
|
}
|
|
279
409
|
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
280
410
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
routeId
|
|
411
|
+
getRouteHeadCacheKey(routeId, params, search) {
|
|
412
|
+
return JSON.stringify({
|
|
413
|
+
routeId,
|
|
284
414
|
params,
|
|
285
415
|
search
|
|
286
416
|
});
|
|
287
|
-
|
|
417
|
+
}
|
|
418
|
+
getCachedRouteHead(routeId, params, search) {
|
|
419
|
+
const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
|
|
288
420
|
if (cached && cached.expiresAt > Date.now()) {
|
|
289
421
|
return cached.head;
|
|
290
422
|
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
setRouteHeadCache(routeId, params, search, response) {
|
|
426
|
+
this.headCache.set(this.getRouteHeadCacheKey(routeId, params, search), {
|
|
427
|
+
head: response.head,
|
|
428
|
+
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
seedHeadCacheFromRouteHeads(matches, routeHeads) {
|
|
432
|
+
this.cacheRouteHeadsFromDocument(matches, routeHeads);
|
|
433
|
+
}
|
|
434
|
+
cacheRouteHeadsFromDocument(matches, routeHeads) {
|
|
435
|
+
const routeHeadsById = new Map(routeHeads.map((entry) => [entry.routeId, entry]));
|
|
436
|
+
const resolvedHeadByRoute = new Map;
|
|
437
|
+
for (const match of matches) {
|
|
438
|
+
if (!match.route.serverHead) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const entry = routeHeadsById.get(match.route.fullPath);
|
|
442
|
+
if (!entry) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, entry);
|
|
446
|
+
resolvedHeadByRoute.set(match.route.fullPath, entry.head);
|
|
447
|
+
}
|
|
448
|
+
return resolvedHeadByRoute;
|
|
449
|
+
}
|
|
450
|
+
async loadRouteHead(route, params, search, location, request) {
|
|
451
|
+
const cachedHead = this.getCachedRouteHead(route.fullPath, params, search);
|
|
452
|
+
if (cachedHead) {
|
|
453
|
+
return cachedHead;
|
|
454
|
+
}
|
|
291
455
|
const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
|
|
292
456
|
route,
|
|
293
457
|
routeId: route.fullPath,
|
|
@@ -296,14 +460,11 @@ class Router {
|
|
|
296
460
|
location,
|
|
297
461
|
request
|
|
298
462
|
}) : await this.fetchRouteHead(route, params, search);
|
|
299
|
-
this.
|
|
300
|
-
head: response.head,
|
|
301
|
-
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
302
|
-
});
|
|
463
|
+
this.setRouteHeadCache(route.fullPath, params, search, response);
|
|
303
464
|
return response.head;
|
|
304
465
|
}
|
|
305
466
|
async fetchRouteHead(route, params, search) {
|
|
306
|
-
const basePath = this.options.headBasePath ?? "/head-api";
|
|
467
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
307
468
|
const searchParams = new URLSearchParams({
|
|
308
469
|
routeId: route.fullPath,
|
|
309
470
|
params: JSON.stringify(params),
|
|
@@ -318,6 +479,20 @@ class Router {
|
|
|
318
479
|
}
|
|
319
480
|
return await response.json();
|
|
320
481
|
}
|
|
482
|
+
async fetchDocumentHead(location) {
|
|
483
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
484
|
+
const searchParams = new URLSearchParams({
|
|
485
|
+
href: prependBasePathToHref(location.href, this.basePath)
|
|
486
|
+
});
|
|
487
|
+
const response = await fetch(`${basePath}?${searchParams.toString()}`);
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
if (response.status === 404) {
|
|
490
|
+
throw notFound();
|
|
491
|
+
}
|
|
492
|
+
throw new Error(`Failed to resolve server head for location "${location.href}"`);
|
|
493
|
+
}
|
|
494
|
+
return await response.json();
|
|
495
|
+
}
|
|
321
496
|
async commitLocation(location, options) {
|
|
322
497
|
this.state = {
|
|
323
498
|
...this.state,
|
|
@@ -327,13 +502,15 @@ class Router {
|
|
|
327
502
|
this.notify();
|
|
328
503
|
try {
|
|
329
504
|
const resolved = await this.resolveLocation(location, {
|
|
330
|
-
request: options.request
|
|
505
|
+
request: options.request,
|
|
506
|
+
initialHeadSnapshot: options.initialHeadSnapshot
|
|
331
507
|
});
|
|
508
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
332
509
|
if (options.writeHistory) {
|
|
333
510
|
if (options.replace) {
|
|
334
|
-
this.history.replace(
|
|
511
|
+
this.history.replace(historyHref, location.state);
|
|
335
512
|
} else {
|
|
336
|
-
this.history.push(
|
|
513
|
+
this.history.push(historyHref, location.state);
|
|
337
514
|
}
|
|
338
515
|
}
|
|
339
516
|
this.state = {
|
|
@@ -354,11 +531,12 @@ class Router {
|
|
|
354
531
|
return;
|
|
355
532
|
}
|
|
356
533
|
const errorMatches = this.buildMatches(location);
|
|
534
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
357
535
|
if (options.writeHistory) {
|
|
358
536
|
if (options.replace) {
|
|
359
|
-
this.history.replace(
|
|
537
|
+
this.history.replace(historyHref, location.state);
|
|
360
538
|
} else {
|
|
361
|
-
this.history.push(
|
|
539
|
+
this.history.push(historyHref, location.state);
|
|
362
540
|
}
|
|
363
541
|
}
|
|
364
542
|
this.state = {
|
|
@@ -448,61 +626,95 @@ function createManagedHeadElements(head) {
|
|
|
448
626
|
element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
|
|
449
627
|
return element;
|
|
450
628
|
};
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
629
|
+
const setAttributes = (element, attributes) => {
|
|
630
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
631
|
+
if (value === undefined || value === false) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (value === true) {
|
|
635
|
+
element.setAttribute(key, "");
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
element.setAttribute(key, value);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
for (const element of head) {
|
|
642
|
+
if (element.tag === "title") {
|
|
456
643
|
continue;
|
|
457
644
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
645
|
+
if (element.tag === "meta") {
|
|
646
|
+
const tag2 = managed(document.createElement("meta"));
|
|
647
|
+
if ("charset" in element) {
|
|
648
|
+
tag2.setAttribute("charset", element.charset);
|
|
649
|
+
} else if ("name" in element) {
|
|
650
|
+
tag2.setAttribute("name", element.name);
|
|
651
|
+
tag2.setAttribute("content", element.content);
|
|
652
|
+
} else if ("property" in element) {
|
|
653
|
+
tag2.setAttribute("property", element.property);
|
|
654
|
+
tag2.setAttribute("content", element.content);
|
|
655
|
+
} else {
|
|
656
|
+
tag2.setAttribute("http-equiv", element.httpEquiv);
|
|
657
|
+
tag2.setAttribute("content", element.content);
|
|
658
|
+
}
|
|
659
|
+
elements.push(tag2);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (element.tag === "link") {
|
|
663
|
+
const tag2 = managed(document.createElement("link"));
|
|
664
|
+
setAttributes(tag2, {
|
|
665
|
+
rel: element.rel,
|
|
666
|
+
href: element.href,
|
|
667
|
+
type: element.type,
|
|
668
|
+
media: element.media,
|
|
669
|
+
sizes: element.sizes,
|
|
670
|
+
crossorigin: element.crossorigin
|
|
671
|
+
});
|
|
672
|
+
elements.push(tag2);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (element.tag === "style") {
|
|
676
|
+
const tag2 = managed(document.createElement("style"));
|
|
677
|
+
if (element.media) {
|
|
678
|
+
tag2.setAttribute("media", element.media);
|
|
679
|
+
}
|
|
680
|
+
tag2.textContent = element.children;
|
|
681
|
+
elements.push(tag2);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (element.tag === "script") {
|
|
685
|
+
const tag2 = managed(document.createElement("script"));
|
|
686
|
+
if (element.src) {
|
|
687
|
+
tag2.setAttribute("src", element.src);
|
|
688
|
+
}
|
|
689
|
+
if (element.type) {
|
|
690
|
+
tag2.setAttribute("type", element.type);
|
|
691
|
+
}
|
|
692
|
+
if (element.async) {
|
|
693
|
+
tag2.async = true;
|
|
694
|
+
}
|
|
695
|
+
if (element.defer) {
|
|
696
|
+
tag2.defer = true;
|
|
697
|
+
}
|
|
698
|
+
if (element.children) {
|
|
699
|
+
tag2.textContent = element.children;
|
|
700
|
+
}
|
|
701
|
+
elements.push(tag2);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (element.tag === "base") {
|
|
705
|
+
const tag2 = managed(document.createElement("base"));
|
|
706
|
+
tag2.setAttribute("href", element.href);
|
|
707
|
+
if (element.target) {
|
|
708
|
+
tag2.setAttribute("target", element.target);
|
|
709
|
+
}
|
|
710
|
+
elements.push(tag2);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const tag = managed(document.createElement(element.name));
|
|
714
|
+
setAttributes(tag, element.attrs ?? {});
|
|
715
|
+
if (element.children) {
|
|
716
|
+
tag.textContent = element.children;
|
|
470
717
|
}
|
|
471
|
-
elements.push(tag);
|
|
472
|
-
}
|
|
473
|
-
for (const link of head.links ?? []) {
|
|
474
|
-
const tag = managed(document.createElement("link"));
|
|
475
|
-
tag.setAttribute("rel", link.rel);
|
|
476
|
-
tag.setAttribute("href", link.href);
|
|
477
|
-
if (link.type)
|
|
478
|
-
tag.setAttribute("type", link.type);
|
|
479
|
-
if (link.media)
|
|
480
|
-
tag.setAttribute("media", link.media);
|
|
481
|
-
if (link.sizes)
|
|
482
|
-
tag.setAttribute("sizes", link.sizes);
|
|
483
|
-
if (link.crossorigin)
|
|
484
|
-
tag.setAttribute("crossorigin", link.crossorigin);
|
|
485
|
-
elements.push(tag);
|
|
486
|
-
}
|
|
487
|
-
for (const style of head.styles ?? []) {
|
|
488
|
-
const tag = managed(document.createElement("style"));
|
|
489
|
-
if (style.media)
|
|
490
|
-
tag.setAttribute("media", style.media);
|
|
491
|
-
tag.textContent = style.children;
|
|
492
|
-
elements.push(tag);
|
|
493
|
-
}
|
|
494
|
-
for (const script of head.scripts ?? []) {
|
|
495
|
-
const tag = managed(document.createElement("script"));
|
|
496
|
-
if (script.src)
|
|
497
|
-
tag.setAttribute("src", script.src);
|
|
498
|
-
if (script.type)
|
|
499
|
-
tag.setAttribute("type", script.type);
|
|
500
|
-
if (script.async)
|
|
501
|
-
tag.async = true;
|
|
502
|
-
if (script.defer)
|
|
503
|
-
tag.defer = true;
|
|
504
|
-
if (script.children)
|
|
505
|
-
tag.textContent = script.children;
|
|
506
718
|
elements.push(tag);
|
|
507
719
|
}
|
|
508
720
|
return elements;
|
|
@@ -511,6 +723,10 @@ function reconcileDocumentHead(head) {
|
|
|
511
723
|
if (typeof document === "undefined") {
|
|
512
724
|
return;
|
|
513
725
|
}
|
|
726
|
+
const title = [...head].reverse().find((element) => element.tag === "title");
|
|
727
|
+
if (title && title.tag === "title") {
|
|
728
|
+
document.title = title.children;
|
|
729
|
+
}
|
|
514
730
|
for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
|
|
515
731
|
element.remove();
|
|
516
732
|
}
|
|
@@ -525,17 +741,17 @@ function RenderMatches({ matches, index }) {
|
|
|
525
741
|
return null;
|
|
526
742
|
}
|
|
527
743
|
const Component = match.route.options.component;
|
|
528
|
-
const outlet = /* @__PURE__ */
|
|
744
|
+
const outlet = /* @__PURE__ */ jsx(RenderMatches, {
|
|
529
745
|
matches,
|
|
530
746
|
index: index + 1
|
|
531
|
-
}
|
|
532
|
-
return /* @__PURE__ */
|
|
747
|
+
});
|
|
748
|
+
return /* @__PURE__ */ jsx(MatchContext.Provider, {
|
|
533
749
|
value: match,
|
|
534
|
-
children: /* @__PURE__ */
|
|
750
|
+
children: /* @__PURE__ */ jsx(OutletContext.Provider, {
|
|
535
751
|
value: outlet,
|
|
536
|
-
children: Component ? /* @__PURE__ */
|
|
537
|
-
}
|
|
538
|
-
}
|
|
752
|
+
children: Component ? /* @__PURE__ */ jsx(Component, {}) : outlet
|
|
753
|
+
})
|
|
754
|
+
});
|
|
539
755
|
}
|
|
540
756
|
function renderError(error, matches, router) {
|
|
541
757
|
const reversed = [...matches].reverse();
|
|
@@ -544,9 +760,9 @@ function renderError(error, matches, router) {
|
|
|
544
760
|
if (NotFoundComponent) {
|
|
545
761
|
return React.createElement(NotFoundComponent);
|
|
546
762
|
}
|
|
547
|
-
return /* @__PURE__ */
|
|
763
|
+
return /* @__PURE__ */ jsx("div", {
|
|
548
764
|
children: "Not Found"
|
|
549
|
-
}
|
|
765
|
+
});
|
|
550
766
|
}
|
|
551
767
|
const ErrorComponent = reversed.find((match) => match.route.options.errorComponent)?.route.options.errorComponent ?? router.options.defaultErrorComponent;
|
|
552
768
|
if (ErrorComponent) {
|
|
@@ -557,9 +773,9 @@ function renderError(error, matches, router) {
|
|
|
557
773
|
}
|
|
558
774
|
});
|
|
559
775
|
}
|
|
560
|
-
return /* @__PURE__ */
|
|
776
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
561
777
|
children: error instanceof Error ? error.message : "Unknown routing error"
|
|
562
|
-
}
|
|
778
|
+
});
|
|
563
779
|
}
|
|
564
780
|
function RouterProvider({ router }) {
|
|
565
781
|
const snapshot = React.useSyncExternalStore(router.subscribe, router.getSnapshot, router.getSnapshot);
|
|
@@ -572,17 +788,17 @@ function RouterProvider({ router }) {
|
|
|
572
788
|
React.useEffect(() => {
|
|
573
789
|
reconcileDocumentHead(snapshot.head);
|
|
574
790
|
}, [snapshot.head]);
|
|
575
|
-
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */
|
|
791
|
+
const content = snapshot.error ? renderError(snapshot.error, snapshot.matches, router) : /* @__PURE__ */ jsx(RenderMatches, {
|
|
576
792
|
matches: snapshot.matches,
|
|
577
793
|
index: 0
|
|
578
|
-
}
|
|
579
|
-
return /* @__PURE__ */
|
|
794
|
+
});
|
|
795
|
+
return /* @__PURE__ */ jsx(RouterContext.Provider, {
|
|
580
796
|
value: router,
|
|
581
|
-
children: /* @__PURE__ */
|
|
797
|
+
children: /* @__PURE__ */ jsx(RouterStateContext.Provider, {
|
|
582
798
|
value: snapshot,
|
|
583
799
|
children: content
|
|
584
|
-
}
|
|
585
|
-
}
|
|
800
|
+
})
|
|
801
|
+
});
|
|
586
802
|
}
|
|
587
803
|
function Outlet() {
|
|
588
804
|
return React.useContext(OutletContext);
|
|
@@ -608,6 +824,31 @@ function useNavigate() {
|
|
|
608
824
|
await router.navigate(options);
|
|
609
825
|
}, [router]);
|
|
610
826
|
}
|
|
827
|
+
function useMatchRoute() {
|
|
828
|
+
const location = useLocation();
|
|
829
|
+
return React.useCallback((options) => {
|
|
830
|
+
const matched = matchPathname(options.to, location.pathname, {
|
|
831
|
+
partial: options.fuzzy === true
|
|
832
|
+
});
|
|
833
|
+
if (!matched) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
if (options.params) {
|
|
837
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
838
|
+
if (value !== undefined && matched.params[key] !== value) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (options.includeSearch) {
|
|
844
|
+
const expectedSearch = options.search ?? {};
|
|
845
|
+
if (!isDeepInclusiveMatch(expectedSearch, location.search)) {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return matched.params;
|
|
850
|
+
}, [location.pathname, location.search]);
|
|
851
|
+
}
|
|
611
852
|
function useLocation() {
|
|
612
853
|
return useRouterStateContext().location;
|
|
613
854
|
}
|
|
@@ -651,12 +892,14 @@ function useElementScrollRestoration() {
|
|
|
651
892
|
ref: () => {}
|
|
652
893
|
};
|
|
653
894
|
}
|
|
654
|
-
function useResolvedLink(props) {
|
|
895
|
+
function useResolvedLink(props, activeOptions) {
|
|
655
896
|
const router = useRouterContext();
|
|
656
897
|
const href = router.buildHref(props);
|
|
657
898
|
const location = useLocation();
|
|
658
|
-
const
|
|
659
|
-
const isActive =
|
|
899
|
+
const targetPathname = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
900
|
+
const isActive = matchPathname(targetPathname, location.pathname, {
|
|
901
|
+
partial: activeOptions?.exact !== true
|
|
902
|
+
}) !== null;
|
|
660
903
|
return { href, isActive, router };
|
|
661
904
|
}
|
|
662
905
|
var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
@@ -672,6 +915,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
672
915
|
state,
|
|
673
916
|
mask,
|
|
674
917
|
ignoreBlocker,
|
|
918
|
+
activeOptions,
|
|
675
919
|
activeProps,
|
|
676
920
|
children,
|
|
677
921
|
onClick,
|
|
@@ -692,7 +936,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
692
936
|
mask,
|
|
693
937
|
ignoreBlocker
|
|
694
938
|
};
|
|
695
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
939
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
696
940
|
const preloadMode = preload ?? router.options.defaultPreload;
|
|
697
941
|
const preloadDelay = router.options.defaultPreloadDelay ?? 50;
|
|
698
942
|
const preloadTimeout = React.useRef(null);
|
|
@@ -717,7 +961,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
717
961
|
}
|
|
718
962
|
}, []);
|
|
719
963
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
720
|
-
return /* @__PURE__ */
|
|
964
|
+
return /* @__PURE__ */ jsx("a", {
|
|
721
965
|
...anchorProps,
|
|
722
966
|
...isActive ? activeProps : undefined,
|
|
723
967
|
ref,
|
|
@@ -745,7 +989,7 @@ var LinkComponent = React.forwardRef(function LinkInner(props, ref) {
|
|
|
745
989
|
},
|
|
746
990
|
onBlur: cancelPreload,
|
|
747
991
|
children: renderedChildren
|
|
748
|
-
}
|
|
992
|
+
});
|
|
749
993
|
});
|
|
750
994
|
var Link = LinkComponent;
|
|
751
995
|
function createLink(Component) {
|
|
@@ -762,6 +1006,7 @@ function createLink(Component) {
|
|
|
762
1006
|
state,
|
|
763
1007
|
mask,
|
|
764
1008
|
ignoreBlocker,
|
|
1009
|
+
activeOptions,
|
|
765
1010
|
activeProps,
|
|
766
1011
|
children,
|
|
767
1012
|
preload,
|
|
@@ -779,7 +1024,7 @@ function createLink(Component) {
|
|
|
779
1024
|
mask,
|
|
780
1025
|
ignoreBlocker
|
|
781
1026
|
};
|
|
782
|
-
const { href, isActive, router } = useResolvedLink(navigation);
|
|
1027
|
+
const { href, isActive, router } = useResolvedLink(navigation, activeOptions);
|
|
783
1028
|
const renderedChildren = typeof children === "function" ? children({ isActive }) : children;
|
|
784
1029
|
React.useEffect(() => {
|
|
785
1030
|
if (preload !== "render") {
|
|
@@ -787,12 +1032,12 @@ function createLink(Component) {
|
|
|
787
1032
|
}
|
|
788
1033
|
router.preloadRoute(navigation);
|
|
789
1034
|
}, [navigation, preload, router]);
|
|
790
|
-
return /* @__PURE__ */
|
|
1035
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
791
1036
|
...componentProps,
|
|
792
1037
|
...isActive ? activeProps : undefined,
|
|
793
1038
|
href,
|
|
794
1039
|
children: renderedChildren
|
|
795
|
-
}
|
|
1040
|
+
});
|
|
796
1041
|
};
|
|
797
1042
|
}
|
|
798
1043
|
function linkOptions(options) {
|
|
@@ -815,6 +1060,7 @@ export {
|
|
|
815
1060
|
useParams,
|
|
816
1061
|
useNavigate,
|
|
817
1062
|
useMatches,
|
|
1063
|
+
useMatchRoute,
|
|
818
1064
|
useMatch,
|
|
819
1065
|
useLocation,
|
|
820
1066
|
useElementScrollRestoration,
|