@tanstack/router-core 1.133.25 → 1.133.28
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/path.cjs +15 -21
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +23 -3
- package/dist/cjs/process-route-tree.cjs +1 -1
- package/dist/cjs/process-route-tree.cjs.map +1 -1
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/rewrite.cjs.map +1 -1
- package/dist/cjs/rewrite.d.cts +4 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +14 -0
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/searchParams.d.cts +2 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +23 -0
- package/dist/esm/path.d.ts +23 -3
- package/dist/esm/path.js +15 -21
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/process-route-tree.js +2 -2
- package/dist/esm/process-route-tree.js.map +1 -1
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/rewrite.d.ts +4 -0
- package/dist/esm/rewrite.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +14 -0
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.d.ts +2 -0
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/utils.d.ts +23 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/path.ts +38 -37
- package/src/process-route-tree.ts +2 -2
- package/src/qss.ts +1 -0
- package/src/rewrite.ts +4 -0
- package/src/scroll-restoration.ts +14 -0
- package/src/searchParams.ts +2 -0
- package/src/utils.ts +23 -0
package/src/path.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { rootRouteId } from './root'
|
|
2
1
|
import { last } from './utils'
|
|
3
2
|
import type { LRUCache } from './lru-cache'
|
|
4
3
|
import type { MatchLocation } from './RouterProvider'
|
|
@@ -22,6 +21,7 @@ export interface Segment {
|
|
|
22
21
|
readonly hasStaticAfter?: boolean
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
/** Join path segments, cleaning duplicate slashes between parts. */
|
|
25
25
|
/** Join path segments, cleaning duplicate slashes between parts. */
|
|
26
26
|
export function joinPaths(paths: Array<string | undefined>) {
|
|
27
27
|
return cleanPath(
|
|
@@ -33,22 +33,26 @@ export function joinPaths(paths: Array<string | undefined>) {
|
|
|
33
33
|
)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** Remove repeated slashes from a path string. */
|
|
36
37
|
/** Remove repeated slashes from a path string. */
|
|
37
38
|
export function cleanPath(path: string) {
|
|
38
39
|
// remove double slashes
|
|
39
40
|
return path.replace(/\/{2,}/g, '/')
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/** Trim leading slashes (except preserving root '/'). */
|
|
42
44
|
/** Trim leading slashes (except preserving root '/'). */
|
|
43
45
|
export function trimPathLeft(path: string) {
|
|
44
46
|
return path === '/' ? path : path.replace(/^\/{1,}/, '')
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
/** Trim trailing slashes (except preserving root '/'). */
|
|
47
50
|
/** Trim trailing slashes (except preserving root '/'). */
|
|
48
51
|
export function trimPathRight(path: string) {
|
|
49
52
|
return path === '/' ? path : path.replace(/\/{1,}$/, '')
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
/** Trim both leading and trailing slashes. */
|
|
52
56
|
/** Trim both leading and trailing slashes. */
|
|
53
57
|
export function trimPath(path: string) {
|
|
54
58
|
return trimPathRight(trimPathLeft(path))
|
|
@@ -66,6 +70,10 @@ export function removeTrailingSlash(value: string, basepath: string): string {
|
|
|
66
70
|
// see the usage in the isActive under useLinkProps
|
|
67
71
|
// /sample/path1 = /sample/path1/
|
|
68
72
|
// /sample/path1/some <> /sample/path1
|
|
73
|
+
/**
|
|
74
|
+
* Compare two pathnames for exact equality after normalizing trailing slashes
|
|
75
|
+
* relative to the provided `basepath`.
|
|
76
|
+
*/
|
|
69
77
|
/**
|
|
70
78
|
* Compare two pathnames for exact equality after normalizing trailing slashes
|
|
71
79
|
* relative to the provided `basepath`.
|
|
@@ -169,8 +177,8 @@ export function resolvePath({
|
|
|
169
177
|
trailingSlash = 'never',
|
|
170
178
|
parseCache,
|
|
171
179
|
}: ResolvePathOptions) {
|
|
172
|
-
let baseSegments =
|
|
173
|
-
const toSegments =
|
|
180
|
+
let baseSegments = parsePathname(base, parseCache).slice()
|
|
181
|
+
const toSegments = parsePathname(to, parseCache)
|
|
174
182
|
|
|
175
183
|
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
176
184
|
baseSegments.pop()
|
|
@@ -216,16 +224,10 @@ export function resolvePath({
|
|
|
216
224
|
|
|
217
225
|
export type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>
|
|
218
226
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
export const parseRoutePathSegments = (
|
|
225
|
-
pathname?: string,
|
|
226
|
-
cache?: ParsePathnameCache,
|
|
227
|
-
): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)
|
|
228
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Parse a pathname into an array of typed segments used by the router's
|
|
229
|
+
* matcher. Results are optionally cached via an LRU cache.
|
|
230
|
+
*/
|
|
229
231
|
/**
|
|
230
232
|
* Parse a pathname into an array of typed segments used by the router's
|
|
231
233
|
* matcher. Results are optionally cached via an LRU cache.
|
|
@@ -233,12 +235,11 @@ export const parseRoutePathSegments = (
|
|
|
233
235
|
export const parsePathname = (
|
|
234
236
|
pathname?: string,
|
|
235
237
|
cache?: ParsePathnameCache,
|
|
236
|
-
basePathValues?: boolean,
|
|
237
238
|
): ReadonlyArray<Segment> => {
|
|
238
239
|
if (!pathname) return []
|
|
239
240
|
const cached = cache?.get(pathname)
|
|
240
241
|
if (cached) return cached
|
|
241
|
-
const parsed = baseParsePathname(pathname
|
|
242
|
+
const parsed = baseParsePathname(pathname)
|
|
242
243
|
cache?.set(pathname, parsed)
|
|
243
244
|
return parsed
|
|
244
245
|
}
|
|
@@ -268,10 +269,7 @@ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
|
|
|
268
269
|
* - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
|
|
269
270
|
* - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
|
|
270
271
|
*/
|
|
271
|
-
function baseParsePathname(
|
|
272
|
-
pathname: string,
|
|
273
|
-
basePathValues?: boolean,
|
|
274
|
-
): ReadonlyArray<Segment> {
|
|
272
|
+
function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
|
|
275
273
|
pathname = cleanPath(pathname)
|
|
276
274
|
|
|
277
275
|
const segments: Array<Segment> = []
|
|
@@ -293,14 +291,8 @@ function baseParsePathname(
|
|
|
293
291
|
|
|
294
292
|
segments.push(
|
|
295
293
|
...split.map((part): Segment => {
|
|
296
|
-
// strip tailing underscore for non-nested paths
|
|
297
|
-
const partToMatch =
|
|
298
|
-
!basePathValues && part !== rootRouteId && part.slice(-1) === '_'
|
|
299
|
-
? part.slice(0, -1)
|
|
300
|
-
: part
|
|
301
|
-
|
|
302
294
|
// Check for wildcard with curly braces: prefix{$}suffix
|
|
303
|
-
const wildcardBracesMatch =
|
|
295
|
+
const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)
|
|
304
296
|
if (wildcardBracesMatch) {
|
|
305
297
|
const prefix = wildcardBracesMatch[1]
|
|
306
298
|
const suffix = wildcardBracesMatch[2]
|
|
@@ -313,7 +305,7 @@ function baseParsePathname(
|
|
|
313
305
|
}
|
|
314
306
|
|
|
315
307
|
// Check for optional parameter format: prefix{-$paramName}suffix
|
|
316
|
-
const optionalParamBracesMatch =
|
|
308
|
+
const optionalParamBracesMatch = part.match(
|
|
317
309
|
OPTIONAL_PARAM_W_CURLY_BRACES_RE,
|
|
318
310
|
)
|
|
319
311
|
if (optionalParamBracesMatch) {
|
|
@@ -329,7 +321,7 @@ function baseParsePathname(
|
|
|
329
321
|
}
|
|
330
322
|
|
|
331
323
|
// Check for the new parameter format: prefix{$paramName}suffix
|
|
332
|
-
const paramBracesMatch =
|
|
324
|
+
const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)
|
|
333
325
|
if (paramBracesMatch) {
|
|
334
326
|
const prefix = paramBracesMatch[1]
|
|
335
327
|
const paramName = paramBracesMatch[2]
|
|
@@ -343,8 +335,8 @@ function baseParsePathname(
|
|
|
343
335
|
}
|
|
344
336
|
|
|
345
337
|
// Check for bare parameter format: $paramName (without curly braces)
|
|
346
|
-
if (PARAM_RE.test(
|
|
347
|
-
const paramName =
|
|
338
|
+
if (PARAM_RE.test(part)) {
|
|
339
|
+
const paramName = part.substring(1)
|
|
348
340
|
return {
|
|
349
341
|
type: SEGMENT_TYPE_PARAM,
|
|
350
342
|
value: '$' + paramName,
|
|
@@ -354,7 +346,7 @@ function baseParsePathname(
|
|
|
354
346
|
}
|
|
355
347
|
|
|
356
348
|
// Check for bare wildcard: $ (without curly braces)
|
|
357
|
-
if (WILDCARD_RE.test(
|
|
349
|
+
if (WILDCARD_RE.test(part)) {
|
|
358
350
|
return {
|
|
359
351
|
type: SEGMENT_TYPE_WILDCARD,
|
|
360
352
|
value: '$',
|
|
@@ -366,12 +358,12 @@ function baseParsePathname(
|
|
|
366
358
|
// Handle regular pathname segment
|
|
367
359
|
return {
|
|
368
360
|
type: SEGMENT_TYPE_PATHNAME,
|
|
369
|
-
value:
|
|
370
|
-
?
|
|
361
|
+
value: part.includes('%25')
|
|
362
|
+
? part
|
|
371
363
|
.split('%25')
|
|
372
364
|
.map((segment) => decodeURI(segment))
|
|
373
365
|
.join('%25')
|
|
374
|
-
: decodeURI(
|
|
366
|
+
: decodeURI(part),
|
|
375
367
|
}
|
|
376
368
|
}),
|
|
377
369
|
)
|
|
@@ -409,6 +401,10 @@ type InterPolatePathResult = {
|
|
|
409
401
|
* - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards
|
|
410
402
|
* - Optionally leaves placeholders or wildcards in place
|
|
411
403
|
*/
|
|
404
|
+
/**
|
|
405
|
+
* Interpolate params and wildcards into a route path template.
|
|
406
|
+
* Encodes safely and supports optional params and custom decode char maps.
|
|
407
|
+
*/
|
|
412
408
|
export function interpolatePath({
|
|
413
409
|
path,
|
|
414
410
|
params,
|
|
@@ -417,7 +413,7 @@ export function interpolatePath({
|
|
|
417
413
|
decodeCharMap,
|
|
418
414
|
parseCache,
|
|
419
415
|
}: InterpolatePathOptions): InterPolatePathResult {
|
|
420
|
-
const interpolatedPathSegments =
|
|
416
|
+
const interpolatedPathSegments = parsePathname(path, parseCache)
|
|
421
417
|
|
|
422
418
|
function encodeParam(key: string): any {
|
|
423
419
|
const value = params[key]
|
|
@@ -535,6 +531,10 @@ function encodePathParam(value: string, decodeCharMap?: Map<string, string>) {
|
|
|
535
531
|
return encoded
|
|
536
532
|
}
|
|
537
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Match a pathname against a route destination and return extracted params
|
|
536
|
+
* or `undefined`. Uses the same parsing as the router for consistency.
|
|
537
|
+
*/
|
|
538
538
|
/**
|
|
539
539
|
* Match a pathname against a route destination and return extracted params
|
|
540
540
|
* or `undefined`. Uses the same parsing as the router for consistency.
|
|
@@ -554,6 +554,7 @@ export function matchPathname(
|
|
|
554
554
|
return pathParams ?? {}
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
/** Low-level matcher that compares two path strings and extracts params. */
|
|
557
558
|
/** Low-level matcher that compares two path strings and extracts params. */
|
|
558
559
|
export function matchByPath(
|
|
559
560
|
from: string,
|
|
@@ -567,11 +568,11 @@ export function matchByPath(
|
|
|
567
568
|
const stringTo = to as string
|
|
568
569
|
|
|
569
570
|
// Parse the from and to
|
|
570
|
-
const baseSegments =
|
|
571
|
+
const baseSegments = parsePathname(
|
|
571
572
|
from.startsWith('/') ? from : `/${from}`,
|
|
572
573
|
parseCache,
|
|
573
574
|
)
|
|
574
|
-
const routeSegments =
|
|
575
|
+
const routeSegments = parsePathname(
|
|
575
576
|
stringTo.startsWith('/') ? stringTo : `/${stringTo}`,
|
|
576
577
|
parseCache,
|
|
577
578
|
)
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
SEGMENT_TYPE_OPTIONAL_PARAM,
|
|
4
4
|
SEGMENT_TYPE_PARAM,
|
|
5
5
|
SEGMENT_TYPE_PATHNAME,
|
|
6
|
-
|
|
6
|
+
parsePathname,
|
|
7
7
|
trimPathLeft,
|
|
8
8
|
trimPathRight,
|
|
9
9
|
} from './path'
|
|
@@ -70,7 +70,7 @@ function sortRoutes<TRouteLike extends RouteLike>(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const trimmed = trimPathLeft(d.fullPath)
|
|
73
|
-
let parsed =
|
|
73
|
+
let parsed = parsePathname(trimmed)
|
|
74
74
|
|
|
75
75
|
// Removes the leading slash if it is not the only remaining segment
|
|
76
76
|
let skip = 0
|
package/src/qss.ts
CHANGED
package/src/rewrite.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { joinPaths, trimPath } from './path'
|
|
2
2
|
import type { LocationRewrite } from './router'
|
|
3
3
|
|
|
4
|
+
/** Compose multiple rewrite pairs into a single in/out rewrite. */
|
|
4
5
|
/** Compose multiple rewrite pairs into a single in/out rewrite. */
|
|
5
6
|
export function composeRewrites(rewrites: Array<LocationRewrite>) {
|
|
6
7
|
return {
|
|
@@ -19,6 +20,7 @@ export function composeRewrites(rewrites: Array<LocationRewrite>) {
|
|
|
19
20
|
} satisfies LocationRewrite
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/** Create a rewrite pair that strips/adds a basepath on input/output. */
|
|
22
24
|
/** Create a rewrite pair that strips/adds a basepath on input/output. */
|
|
23
25
|
export function rewriteBasepath(opts: {
|
|
24
26
|
basepath: string
|
|
@@ -56,6 +58,7 @@ export function rewriteBasepath(opts: {
|
|
|
56
58
|
} satisfies LocationRewrite
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
/** Execute a location input rewrite if provided. */
|
|
59
62
|
/** Execute a location input rewrite if provided. */
|
|
60
63
|
export function executeRewriteInput(
|
|
61
64
|
rewrite: LocationRewrite | undefined,
|
|
@@ -72,6 +75,7 @@ export function executeRewriteInput(
|
|
|
72
75
|
return url
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/** Execute a location output rewrite if provided. */
|
|
75
79
|
/** Execute a location output rewrite if provided. */
|
|
76
80
|
export function executeRewriteOutput(
|
|
77
81
|
rewrite: LocationRewrite | undefined,
|
|
@@ -35,6 +35,7 @@ function getSafeSessionStorage() {
|
|
|
35
35
|
|
|
36
36
|
/** SessionStorage key used to persist scroll restoration state. */
|
|
37
37
|
/** SessionStorage key used to store scroll positions across navigations. */
|
|
38
|
+
/** SessionStorage key used to store scroll positions across navigations. */
|
|
38
39
|
export const storageKey = 'tsr-scroll-restoration-v1_3'
|
|
39
40
|
|
|
40
41
|
const throttle = (fn: (...args: Array<any>) => void, wait: number) => {
|
|
@@ -72,6 +73,7 @@ function createScrollRestorationCache(): ScrollRestorationCache | null {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/** In-memory handle to the persisted scroll restoration cache. */
|
|
75
77
|
/** In-memory handle to the persisted scroll restoration cache. */
|
|
76
78
|
/** In-memory handle to the persisted scroll restoration cache. */
|
|
77
79
|
export const scrollRestorationCache = createScrollRestorationCache()
|
|
@@ -89,6 +91,9 @@ export const scrollRestorationCache = createScrollRestorationCache()
|
|
|
89
91
|
/**
|
|
90
92
|
* Default scroll restoration cache key: location state key or full href.
|
|
91
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* Default scroll restoration cache key: location state key or full href.
|
|
96
|
+
*/
|
|
92
97
|
export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
|
|
93
98
|
return location.state.__TSR_key! || location.href
|
|
94
99
|
}
|
|
@@ -112,6 +117,9 @@ let ignoreScroll = false
|
|
|
112
117
|
// unless they are passed in as arguments. Why? Because we need to be able to
|
|
113
118
|
// toString() it into a script tag to execute as early as possible in the browser
|
|
114
119
|
// during SSR. Additionally, we also call it from within the router lifecycle
|
|
120
|
+
/**
|
|
121
|
+
* Restore scroll positions for window/elements based on cached entries.
|
|
122
|
+
*/
|
|
115
123
|
/**
|
|
116
124
|
* Restore scroll positions for window/elements based on cached entries.
|
|
117
125
|
*/
|
|
@@ -214,6 +222,7 @@ export function restoreScroll({
|
|
|
214
222
|
ignoreScroll = false
|
|
215
223
|
}
|
|
216
224
|
|
|
225
|
+
/** Setup global listeners and hooks to support scroll restoration. */
|
|
217
226
|
/** Setup global listeners and hooks to support scroll restoration. */
|
|
218
227
|
export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
219
228
|
if (!scrollRestorationCache && !router.isServer) {
|
|
@@ -388,6 +397,11 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
388
397
|
* Provides hash scrolling for programmatic navigation when default browser handling is prevented.
|
|
389
398
|
* @param router The router instance containing current location and state
|
|
390
399
|
*/
|
|
400
|
+
/**
|
|
401
|
+
* @private
|
|
402
|
+
* Handles hash-based scrolling after navigation completes.
|
|
403
|
+
* To be used in framework-specific Transitioners.
|
|
404
|
+
*/
|
|
391
405
|
export function handleHashScroll(router: AnyRouter) {
|
|
392
406
|
if (typeof document !== 'undefined' && (document as any).querySelector) {
|
|
393
407
|
const hashScrollIntoViewOptions =
|
package/src/searchParams.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { decode, encode } from './qss'
|
|
2
2
|
import type { AnySchema } from './validators'
|
|
3
3
|
|
|
4
|
+
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
4
5
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
5
6
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
6
7
|
export const defaultParseSearch = parseSearchWith(JSON.parse)
|
|
8
|
+
/** Default `stringifySearch` using JSON.stringify for complex values. */
|
|
7
9
|
export const defaultStringifySearch = stringifySearchWith(
|
|
8
10
|
JSON.stringify,
|
|
9
11
|
JSON.parse,
|
package/src/utils.ts
CHANGED
|
@@ -184,6 +184,10 @@ export type LooseAsyncReturnType<T> = T extends (
|
|
|
184
184
|
: TReturn
|
|
185
185
|
: never
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Return the last element of an array.
|
|
189
|
+
* Intended for non-empty arrays used within router internals.
|
|
190
|
+
*/
|
|
187
191
|
export function last<T>(arr: Array<T>) {
|
|
188
192
|
return arr[arr.length - 1]
|
|
189
193
|
}
|
|
@@ -192,6 +196,10 @@ function isFunction(d: any): d is Function {
|
|
|
192
196
|
return typeof d === 'function'
|
|
193
197
|
}
|
|
194
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Apply a value-or-updater to a previous value.
|
|
201
|
+
* Accepts either a literal value or a function of the previous value.
|
|
202
|
+
*/
|
|
195
203
|
export function functionalUpdate<TPrevious, TResult = TPrevious>(
|
|
196
204
|
updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,
|
|
197
205
|
previous: TPrevious,
|
|
@@ -311,10 +319,17 @@ function hasObjectPrototype(o: any) {
|
|
|
311
319
|
return Object.prototype.toString.call(o) === '[object Object]'
|
|
312
320
|
}
|
|
313
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Check if a value is a "plain" array (no extra enumerable keys).
|
|
324
|
+
*/
|
|
314
325
|
export function isPlainArray(value: unknown): value is Array<unknown> {
|
|
315
326
|
return Array.isArray(value) && value.length === Object.keys(value).length
|
|
316
327
|
}
|
|
317
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Perform a deep equality check with options for partial comparison and
|
|
331
|
+
* ignoring `undefined` values. Optimized for router state comparisons.
|
|
332
|
+
*/
|
|
318
333
|
export function deepEqual(
|
|
319
334
|
a: any,
|
|
320
335
|
b: any,
|
|
@@ -407,6 +422,10 @@ export type ControlledPromise<T> = Promise<T> & {
|
|
|
407
422
|
value?: T
|
|
408
423
|
}
|
|
409
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Create a promise with exposed resolve/reject and status fields.
|
|
427
|
+
* Useful for coordinating async router lifecycle operations.
|
|
428
|
+
*/
|
|
410
429
|
export function createControlledPromise<T>(onResolve?: (value: T) => void) {
|
|
411
430
|
let resolveLoadPromise!: (value: T) => void
|
|
412
431
|
let rejectLoadPromise!: (value: any) => void
|
|
@@ -433,6 +452,10 @@ export function createControlledPromise<T>(onResolve?: (value: T) => void) {
|
|
|
433
452
|
return controlledPromise
|
|
434
453
|
}
|
|
435
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Heuristically detect dynamic import "module not found" errors
|
|
457
|
+
* across major browsers for lazy route component handling.
|
|
458
|
+
*/
|
|
436
459
|
export function isModuleNotFoundError(error: any): boolean {
|
|
437
460
|
// chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
|
|
438
461
|
// firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
|