@tanstack/router-core 0.0.1-beta.35 → 0.0.1-beta.39
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/build/cjs/index.js +2 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/path.js +5 -7
- package/build/cjs/path.js.map +1 -1
- package/build/cjs/route.js +112 -96
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/routeConfig.js +2 -2
- package/build/cjs/routeConfig.js.map +1 -1
- package/build/cjs/routeMatch.js +107 -65
- package/build/cjs/routeMatch.js.map +1 -1
- package/build/cjs/router.js +352 -372
- package/build/cjs/router.js.map +1 -1
- package/build/cjs/searchParams.js +4 -3
- package/build/cjs/searchParams.js.map +1 -1
- package/build/cjs/sharedClone.js +122 -0
- package/build/cjs/sharedClone.js.map +1 -0
- package/build/cjs/utils.js +1 -59
- package/build/cjs/utils.js.map +1 -1
- package/build/esm/index.js +686 -614
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +183 -158
- package/build/types/index.d.ts +61 -78
- package/build/umd/index.development.js +1032 -617
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +1 -0
- package/src/link.ts +20 -12
- package/src/route.ts +160 -140
- package/src/routeConfig.ts +7 -2
- package/src/routeMatch.ts +146 -99
- package/src/router.ts +462 -523
- package/src/sharedClone.ts +118 -0
- package/src/utils.ts +0 -65
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -31
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
package/src/router.ts
CHANGED
|
@@ -12,9 +12,10 @@ import { GetFrameworkGeneric } from './frameworks'
|
|
|
12
12
|
import {
|
|
13
13
|
LinkInfo,
|
|
14
14
|
LinkOptions,
|
|
15
|
-
|
|
15
|
+
NavigateOptions,
|
|
16
16
|
ToOptions,
|
|
17
17
|
ValidFromPath,
|
|
18
|
+
ResolveRelativePath,
|
|
18
19
|
} from './link'
|
|
19
20
|
import {
|
|
20
21
|
cleanPath,
|
|
@@ -41,18 +42,20 @@ import {
|
|
|
41
42
|
RouteInfo,
|
|
42
43
|
RoutesById,
|
|
43
44
|
} from './routeInfo'
|
|
44
|
-
import { createRouteMatch, RouteMatch } from './routeMatch'
|
|
45
|
+
import { createRouteMatch, RouteMatch, RouteMatchStore } from './routeMatch'
|
|
45
46
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
47
|
+
import { createStore, batch, SetStoreFunction } from '@solidjs/reactivity'
|
|
46
48
|
import {
|
|
47
49
|
functionalUpdate,
|
|
48
50
|
last,
|
|
51
|
+
NoInfer,
|
|
49
52
|
pick,
|
|
50
53
|
PickAsRequired,
|
|
51
54
|
PickRequired,
|
|
52
|
-
replaceEqualDeep,
|
|
53
55
|
Timeout,
|
|
54
56
|
Updater,
|
|
55
57
|
} from './utils'
|
|
58
|
+
import { sharedClone } from './sharedClone'
|
|
56
59
|
|
|
57
60
|
export interface RegisterRouter {
|
|
58
61
|
// router: Router
|
|
@@ -128,9 +131,6 @@ export interface RouterOptions<
|
|
|
128
131
|
loadComponent?: (
|
|
129
132
|
component: GetFrameworkGeneric<'Component'>,
|
|
130
133
|
) => Promise<GetFrameworkGeneric<'Component'>>
|
|
131
|
-
// renderComponent?: (
|
|
132
|
-
// component: GetFrameworkGeneric<'Component'>,
|
|
133
|
-
// ) => GetFrameworkGeneric<'Element'>
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
export interface Action<
|
|
@@ -196,24 +196,22 @@ export interface LoaderState<
|
|
|
196
196
|
loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
export interface
|
|
199
|
+
export interface RouterStore<
|
|
200
200
|
TSearchObj extends AnySearchSchema = {},
|
|
201
201
|
TState extends LocationState = LocationState,
|
|
202
202
|
> {
|
|
203
203
|
status: 'idle' | 'loading'
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
latestLocation: Location<TSearchObj, TState>
|
|
205
|
+
currentMatches: RouteMatch[]
|
|
206
|
+
currentLocation: Location<TSearchObj, TState>
|
|
207
|
+
pendingMatches?: RouteMatch[]
|
|
208
|
+
pendingLocation?: Location<TSearchObj, TState>
|
|
206
209
|
lastUpdated: number
|
|
207
210
|
actions: Record<string, Action>
|
|
208
211
|
loaders: Record<string, Loader>
|
|
209
|
-
pending?: PendingState
|
|
210
212
|
isFetching: boolean
|
|
211
213
|
isPreloading: boolean
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
export interface PendingState {
|
|
215
|
-
location: Location
|
|
216
|
-
matches: RouteMatch[]
|
|
214
|
+
matchCache: Record<string, MatchCacheEntry>
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
type Listener = (router: Router<any, any, any>) => void
|
|
@@ -258,28 +256,27 @@ type LinkCurrentTargetElement = {
|
|
|
258
256
|
|
|
259
257
|
export interface DehydratedRouterState
|
|
260
258
|
extends Pick<
|
|
261
|
-
|
|
262
|
-
'status' | '
|
|
259
|
+
RouterStore,
|
|
260
|
+
'status' | 'latestLocation' | 'currentLocation' | 'lastUpdated'
|
|
263
261
|
> {
|
|
264
|
-
|
|
262
|
+
currentMatches: DehydratedRouteMatch[]
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
export interface DehydratedRouter<TRouterContext = unknown> {
|
|
268
|
-
location: Router['__location']
|
|
269
|
-
|
|
266
|
+
// location: Router['__location']
|
|
267
|
+
store: DehydratedRouterState
|
|
270
268
|
context: TRouterContext
|
|
271
269
|
}
|
|
272
270
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
| '
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
> {}
|
|
271
|
+
export type MatchCache = Record<string, MatchCacheEntry>
|
|
272
|
+
|
|
273
|
+
interface DehydratedRouteMatch {
|
|
274
|
+
matchId: string
|
|
275
|
+
store: Pick<
|
|
276
|
+
RouteMatchStore<any, any>,
|
|
277
|
+
'status' | 'routeLoaderData' | 'isInvalid' | 'invalidAt'
|
|
278
|
+
>
|
|
279
|
+
}
|
|
283
280
|
|
|
284
281
|
export interface RouterContext {}
|
|
285
282
|
|
|
@@ -300,24 +297,14 @@ export interface Router<
|
|
|
300
297
|
RouterOptions<TRouteConfig, TRouterContext>,
|
|
301
298
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
302
299
|
>
|
|
303
|
-
|
|
300
|
+
store: RouterStore<TAllRouteInfo['fullSearchSchema']>
|
|
301
|
+
setStore: SetStoreFunction<RouterStore<TAllRouteInfo['fullSearchSchema']>>
|
|
304
302
|
basepath: string
|
|
305
|
-
//
|
|
306
|
-
listeners: Listener[]
|
|
307
|
-
__location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
308
|
-
navigateTimeout?: Timeout
|
|
309
|
-
nextAction?: 'push' | 'replace'
|
|
310
|
-
state: RouterState<TAllRouteInfo['fullSearchSchema']>
|
|
303
|
+
// __location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
311
304
|
routeTree: Route<TAllRouteInfo, RouteInfo>
|
|
312
305
|
routesById: RoutesById<TAllRouteInfo>
|
|
313
|
-
navigationPromise?: Promise<void>
|
|
314
|
-
startedLoadingAt: number
|
|
315
|
-
resolveNavigation: () => void
|
|
316
|
-
subscribe: (listener: Listener) => () => void
|
|
317
306
|
reset: () => void
|
|
318
|
-
notify: () => void
|
|
319
307
|
mount: () => () => void
|
|
320
|
-
onFocus: () => void
|
|
321
308
|
update: <
|
|
322
309
|
TRouteConfig extends RouteConfig = RouteConfig,
|
|
323
310
|
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
@@ -329,7 +316,6 @@ export interface Router<
|
|
|
329
316
|
buildNext: (opts: BuildNextOptions) => Location
|
|
330
317
|
cancelMatches: () => void
|
|
331
318
|
load: (next?: Location) => Promise<void>
|
|
332
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
333
319
|
cleanMatchCache: () => void
|
|
334
320
|
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
335
321
|
id: TId,
|
|
@@ -359,7 +345,7 @@ export interface Router<
|
|
|
359
345
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
360
346
|
TTo extends string = '.',
|
|
361
347
|
>(
|
|
362
|
-
opts:
|
|
348
|
+
opts: NavigateOptions<TAllRouteInfo, TFrom, TTo>,
|
|
363
349
|
) => Promise<void>
|
|
364
350
|
matchRoute: <
|
|
365
351
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
@@ -367,7 +353,12 @@ export interface Router<
|
|
|
367
353
|
>(
|
|
368
354
|
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
369
355
|
opts?: MatchRouteOptions,
|
|
370
|
-
) =>
|
|
356
|
+
) =>
|
|
357
|
+
| false
|
|
358
|
+
| TAllRouteInfo['routeInfoById'][ResolveRelativePath<
|
|
359
|
+
TFrom,
|
|
360
|
+
NoInfer<TTo>
|
|
361
|
+
>]['allParams']
|
|
371
362
|
buildLink: <
|
|
372
363
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
373
364
|
TTo extends string = '.',
|
|
@@ -376,20 +367,6 @@ export interface Router<
|
|
|
376
367
|
) => LinkInfo
|
|
377
368
|
dehydrate: () => DehydratedRouter<TRouterContext>
|
|
378
369
|
hydrate: (dehydratedRouter: DehydratedRouter<TRouterContext>) => void
|
|
379
|
-
__: {
|
|
380
|
-
buildRouteTree: (
|
|
381
|
-
routeConfig: RouteConfig,
|
|
382
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
383
|
-
parseLocation: (
|
|
384
|
-
location: History['location'],
|
|
385
|
-
previousLocation?: Location,
|
|
386
|
-
) => Location
|
|
387
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
388
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
389
|
-
navigate: (
|
|
390
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
391
|
-
) => Promise<void>
|
|
392
|
-
}
|
|
393
370
|
}
|
|
394
371
|
|
|
395
372
|
// Detect if we're in the DOM
|
|
@@ -400,16 +377,29 @@ const isServer =
|
|
|
400
377
|
const createDefaultHistory = () =>
|
|
401
378
|
isServer ? createMemoryHistory() : createBrowserHistory()
|
|
402
379
|
|
|
403
|
-
function getInitialRouterState():
|
|
380
|
+
function getInitialRouterState(): RouterStore {
|
|
404
381
|
return {
|
|
405
382
|
status: 'idle',
|
|
406
|
-
|
|
407
|
-
|
|
383
|
+
latestLocation: null!,
|
|
384
|
+
currentLocation: null!,
|
|
385
|
+
currentMatches: [],
|
|
408
386
|
actions: {},
|
|
409
387
|
loaders: {},
|
|
410
388
|
lastUpdated: Date.now(),
|
|
411
|
-
|
|
412
|
-
|
|
389
|
+
matchCache: {},
|
|
390
|
+
get isFetching() {
|
|
391
|
+
return (
|
|
392
|
+
this.status === 'loading' ||
|
|
393
|
+
this.currentMatches.some((d) => d.store.isFetching)
|
|
394
|
+
)
|
|
395
|
+
},
|
|
396
|
+
get isPreloading() {
|
|
397
|
+
return Object.values(this.matchCache).some(
|
|
398
|
+
(d) =>
|
|
399
|
+
d.match.store.isFetching &&
|
|
400
|
+
!this.currentMatches.find((dd) => dd.matchId === d.match.matchId),
|
|
401
|
+
)
|
|
402
|
+
},
|
|
413
403
|
}
|
|
414
404
|
}
|
|
415
405
|
|
|
@@ -420,8 +410,6 @@ export function createRouter<
|
|
|
420
410
|
>(
|
|
421
411
|
userOptions?: RouterOptions<TRouteConfig, TRouterContext>,
|
|
422
412
|
): Router<TRouteConfig, TAllRouteInfo, TRouterContext> {
|
|
423
|
-
const history = userOptions?.history || createDefaultHistory()
|
|
424
|
-
|
|
425
413
|
const originalOptions = {
|
|
426
414
|
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
427
415
|
defaultLoaderMaxAge: 0,
|
|
@@ -433,168 +421,311 @@ export function createRouter<
|
|
|
433
421
|
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
434
422
|
}
|
|
435
423
|
|
|
436
|
-
|
|
424
|
+
const [store, setStore] = createStore<RouterStore>(getInitialRouterState())
|
|
425
|
+
|
|
426
|
+
let navigateTimeout: undefined | Timeout
|
|
427
|
+
let nextAction: undefined | 'push' | 'replace'
|
|
428
|
+
let navigationPromise: undefined | Promise<void>
|
|
429
|
+
|
|
430
|
+
let startedLoadingAt = Date.now()
|
|
431
|
+
let resolveNavigation = () => {}
|
|
432
|
+
|
|
433
|
+
function onFocus() {
|
|
434
|
+
router.load()
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function buildRouteTree(rootRouteConfig: RouteConfig) {
|
|
438
|
+
const recurseRoutes = (
|
|
439
|
+
routeConfigs: RouteConfig[],
|
|
440
|
+
parent?: Route<TAllRouteInfo, any, any>,
|
|
441
|
+
): Route<TAllRouteInfo, any, any>[] => {
|
|
442
|
+
return routeConfigs.map((routeConfig, i) => {
|
|
443
|
+
const routeOptions = routeConfig.options
|
|
444
|
+
const route = createRoute(routeConfig, routeOptions, i, parent, router)
|
|
445
|
+
const existingRoute = (router.routesById as any)[route.routeId]
|
|
446
|
+
|
|
447
|
+
if (existingRoute) {
|
|
448
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
449
|
+
console.warn(
|
|
450
|
+
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
451
|
+
router.routesById,
|
|
452
|
+
route,
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
throw new Error()
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
;(router.routesById as any)[route.routeId] = route
|
|
459
|
+
|
|
460
|
+
const children = routeConfig.children as RouteConfig[]
|
|
461
|
+
|
|
462
|
+
route.childRoutes = children?.length
|
|
463
|
+
? recurseRoutes(children, route)
|
|
464
|
+
: undefined
|
|
465
|
+
|
|
466
|
+
return route
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const routes = recurseRoutes([rootRouteConfig])
|
|
471
|
+
|
|
472
|
+
return routes[0]!
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function parseLocation(
|
|
476
|
+
location: History['location'],
|
|
477
|
+
previousLocation?: Location,
|
|
478
|
+
): Location {
|
|
479
|
+
const parsedSearch = router.options.parseSearch(location.search)
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
pathname: location.pathname,
|
|
483
|
+
searchStr: location.search,
|
|
484
|
+
search: sharedClone(previousLocation?.search, parsedSearch),
|
|
485
|
+
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
486
|
+
href: `${location.pathname}${location.search}${location.hash}`,
|
|
487
|
+
state: location.state as LocationState,
|
|
488
|
+
key: location.key,
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function navigate(location: BuildNextOptions & { replace?: boolean }) {
|
|
493
|
+
const next = router.buildNext(location)
|
|
494
|
+
return commitLocation(next, location.replace)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function buildLocation(dest: BuildNextOptions = {}): Location {
|
|
498
|
+
const fromPathname = dest.fromCurrent
|
|
499
|
+
? store.latestLocation.pathname
|
|
500
|
+
: dest.from ?? store.latestLocation.pathname
|
|
501
|
+
|
|
502
|
+
let pathname = resolvePath(
|
|
503
|
+
router.basepath ?? '/',
|
|
504
|
+
fromPathname,
|
|
505
|
+
`${dest.to ?? '.'}`,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
const fromMatches = router.matchRoutes(store.latestLocation.pathname, {
|
|
509
|
+
strictParseParams: true,
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
const toMatches = router.matchRoutes(pathname)
|
|
513
|
+
|
|
514
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
515
|
+
|
|
516
|
+
let nextParams =
|
|
517
|
+
(dest.params ?? true) === true
|
|
518
|
+
? prevParams
|
|
519
|
+
: functionalUpdate(dest.params!, prevParams)
|
|
520
|
+
|
|
521
|
+
if (nextParams) {
|
|
522
|
+
toMatches
|
|
523
|
+
.map((d) => d.options.stringifyParams)
|
|
524
|
+
.filter(Boolean)
|
|
525
|
+
.forEach((fn) => {
|
|
526
|
+
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
531
|
+
|
|
532
|
+
// Pre filters first
|
|
533
|
+
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
534
|
+
? dest.__preSearchFilters.reduce(
|
|
535
|
+
(prev, next) => next(prev),
|
|
536
|
+
store.latestLocation.search,
|
|
537
|
+
)
|
|
538
|
+
: store.latestLocation.search
|
|
539
|
+
|
|
540
|
+
// Then the link/navigate function
|
|
541
|
+
const destSearch =
|
|
542
|
+
dest.search === true
|
|
543
|
+
? preFilteredSearch // Preserve resolvedFrom true
|
|
544
|
+
: dest.search
|
|
545
|
+
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
546
|
+
: dest.__preSearchFilters?.length
|
|
547
|
+
? preFilteredSearch // Preserve resolvedFrom filters
|
|
548
|
+
: {}
|
|
549
|
+
|
|
550
|
+
// Then post filters
|
|
551
|
+
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
552
|
+
? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch)
|
|
553
|
+
: destSearch
|
|
554
|
+
|
|
555
|
+
const search = sharedClone(store.latestLocation.search, postFilteredSearch)
|
|
556
|
+
|
|
557
|
+
const searchStr = router.options.stringifySearch(search)
|
|
558
|
+
let hash =
|
|
559
|
+
dest.hash === true
|
|
560
|
+
? store.latestLocation.hash
|
|
561
|
+
: functionalUpdate(dest.hash!, store.latestLocation.hash)
|
|
562
|
+
hash = hash ? `#${hash}` : ''
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
pathname,
|
|
566
|
+
search,
|
|
567
|
+
searchStr,
|
|
568
|
+
state: store.latestLocation.state,
|
|
569
|
+
hash,
|
|
570
|
+
href: `${pathname}${searchStr}${hash}`,
|
|
571
|
+
key: dest.key,
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function commitLocation(next: Location, replace?: boolean): Promise<void> {
|
|
576
|
+
const id = '' + Date.now() + Math.random()
|
|
577
|
+
|
|
578
|
+
if (navigateTimeout) clearTimeout(navigateTimeout)
|
|
579
|
+
|
|
580
|
+
let nextAction: 'push' | 'replace' = 'replace'
|
|
581
|
+
|
|
582
|
+
if (!replace) {
|
|
583
|
+
nextAction = 'push'
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const isSameUrl = parseLocation(router.history.location).href === next.href
|
|
587
|
+
|
|
588
|
+
if (isSameUrl && !next.key) {
|
|
589
|
+
nextAction = 'replace'
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
router.history[nextAction](
|
|
593
|
+
{
|
|
594
|
+
pathname: next.pathname,
|
|
595
|
+
hash: next.hash,
|
|
596
|
+
search: next.searchStr,
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id,
|
|
600
|
+
...next.state,
|
|
601
|
+
},
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
return (navigationPromise = new Promise((resolve) => {
|
|
605
|
+
const previousNavigationResolve = resolveNavigation
|
|
606
|
+
|
|
607
|
+
resolveNavigation = () => {
|
|
608
|
+
previousNavigationResolve()
|
|
609
|
+
resolve()
|
|
610
|
+
}
|
|
611
|
+
}))
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const router: Router<TRouteConfig, TAllRouteInfo, TRouterContext> = {
|
|
437
615
|
types: undefined!,
|
|
438
616
|
|
|
439
617
|
// public api
|
|
440
|
-
history,
|
|
618
|
+
history: userOptions?.history || createDefaultHistory(),
|
|
619
|
+
store,
|
|
620
|
+
setStore,
|
|
441
621
|
options: originalOptions,
|
|
442
|
-
listeners: [],
|
|
443
|
-
// Resolved after construction
|
|
444
622
|
basepath: '',
|
|
445
623
|
routeTree: undefined!,
|
|
446
624
|
routesById: {} as any,
|
|
447
|
-
|
|
448
|
-
//
|
|
449
|
-
resolveNavigation: () => {},
|
|
450
|
-
matchCache: {},
|
|
451
|
-
state: getInitialRouterState(),
|
|
625
|
+
|
|
452
626
|
reset: () => {
|
|
453
|
-
|
|
454
|
-
router.notify()
|
|
455
|
-
},
|
|
456
|
-
startedLoadingAt: Date.now(),
|
|
457
|
-
subscribe: (listener: Listener): (() => void) => {
|
|
458
|
-
router.listeners.push(listener as Listener)
|
|
459
|
-
return () => {
|
|
460
|
-
router.listeners = router.listeners.filter((x) => x !== listener)
|
|
461
|
-
}
|
|
627
|
+
setStore((s) => Object.assign(s, getInitialRouterState()))
|
|
462
628
|
},
|
|
629
|
+
|
|
463
630
|
getRoute: (id) => {
|
|
464
631
|
return router.routesById[id]
|
|
465
632
|
},
|
|
466
|
-
notify: (): void => {
|
|
467
|
-
const isFetching =
|
|
468
|
-
router.state.status === 'loading' ||
|
|
469
|
-
router.state.matches.some((d) => d.isFetching)
|
|
470
|
-
|
|
471
|
-
const isPreloading = Object.values(router.matchCache).some(
|
|
472
|
-
(d) =>
|
|
473
|
-
d.match.isFetching &&
|
|
474
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
if (
|
|
478
|
-
router.state.isFetching !== isFetching ||
|
|
479
|
-
router.state.isPreloading !== isPreloading
|
|
480
|
-
) {
|
|
481
|
-
router.state = {
|
|
482
|
-
...router.state,
|
|
483
|
-
isFetching,
|
|
484
|
-
isPreloading,
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
cascadeLoaderData(router.state.matches)
|
|
489
|
-
router.listeners.forEach((listener) => listener(router))
|
|
490
|
-
},
|
|
491
633
|
|
|
492
634
|
dehydrate: () => {
|
|
493
635
|
return {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
636
|
+
store: {
|
|
637
|
+
...pick(store, [
|
|
638
|
+
'latestLocation',
|
|
639
|
+
'currentLocation',
|
|
497
640
|
'status',
|
|
498
|
-
'location',
|
|
499
641
|
'lastUpdated',
|
|
500
|
-
'location',
|
|
501
642
|
]),
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
643
|
+
currentMatches: store.currentMatches.map((match) => ({
|
|
644
|
+
matchId: match.matchId,
|
|
645
|
+
store: pick(match.store, [
|
|
505
646
|
'status',
|
|
506
647
|
'routeLoaderData',
|
|
507
|
-
'loaderData',
|
|
508
648
|
'isInvalid',
|
|
509
649
|
'invalidAt',
|
|
510
650
|
]),
|
|
511
|
-
),
|
|
651
|
+
})),
|
|
512
652
|
},
|
|
513
653
|
context: router.options.context as TRouterContext,
|
|
514
654
|
}
|
|
515
655
|
},
|
|
516
656
|
|
|
517
|
-
hydrate: (
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
// Update the context
|
|
522
|
-
router.options.context = dehydratedState.context
|
|
657
|
+
hydrate: (dehydratedRouter) => {
|
|
658
|
+
setStore((s) => {
|
|
659
|
+
// Update the context TODO: make this part of state?
|
|
660
|
+
router.options.context = dehydratedRouter.context
|
|
523
661
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const dehydratedMatch = dehydratedState.state.matches[index]
|
|
531
|
-
invariant(
|
|
532
|
-
dehydratedMatch,
|
|
533
|
-
'Oh no! Dehydrated route matches did not match the active state of the router 😬',
|
|
662
|
+
// Match the routes
|
|
663
|
+
const currentMatches = router.matchRoutes(
|
|
664
|
+
dehydratedRouter.store.latestLocation.pathname,
|
|
665
|
+
{
|
|
666
|
+
strictParseParams: true,
|
|
667
|
+
},
|
|
534
668
|
)
|
|
535
|
-
Object.assign(match, dehydratedMatch)
|
|
536
|
-
})
|
|
537
669
|
|
|
538
|
-
|
|
670
|
+
currentMatches.forEach((match, index) => {
|
|
671
|
+
const dehydratedMatch = dehydratedRouter.store.currentMatches[index]
|
|
672
|
+
invariant(
|
|
673
|
+
dehydratedMatch && dehydratedMatch.matchId === match.matchId,
|
|
674
|
+
'Oh no! There was a hydration mismatch when attempting to restore the state of the router! 😬',
|
|
675
|
+
)
|
|
676
|
+
Object.assign(match, dehydratedMatch)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
currentMatches.forEach((match) => match.__.validate())
|
|
539
680
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
...dehydratedState,
|
|
543
|
-
matches,
|
|
544
|
-
}
|
|
681
|
+
Object.assign(s, { ...dehydratedRouter.store, currentMatches })
|
|
682
|
+
})
|
|
545
683
|
},
|
|
546
684
|
|
|
547
685
|
mount: () => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
// If the current location isn't updated, trigger a navigation
|
|
555
|
-
// to the current location. Otherwise, load the current location.
|
|
556
|
-
// if (next.href !== router.__location.href) {
|
|
557
|
-
// router.__.commitLocation(next, true)
|
|
558
|
-
// }
|
|
559
|
-
|
|
560
|
-
if (!router.state.matches.length) {
|
|
561
|
-
router.load()
|
|
562
|
-
}
|
|
686
|
+
// Mount only does anything on the client
|
|
687
|
+
if (!isServer) {
|
|
688
|
+
// If the router matches are empty, load the matches
|
|
689
|
+
if (!store.currentMatches.length) {
|
|
690
|
+
router.load()
|
|
691
|
+
}
|
|
563
692
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
693
|
+
const unsub = router.history.listen((event) => {
|
|
694
|
+
router.load(parseLocation(event.location, store.latestLocation))
|
|
695
|
+
})
|
|
567
696
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
697
|
+
// addEventListener does not exist in React Native, but window does
|
|
698
|
+
// In the future, we might need to invert control here for more adapters
|
|
699
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
700
|
+
if (window.addEventListener) {
|
|
701
|
+
// Listen to visibilitychange and focus
|
|
702
|
+
window.addEventListener('visibilitychange', onFocus, false)
|
|
703
|
+
window.addEventListener('focus', onFocus, false)
|
|
704
|
+
}
|
|
575
705
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
706
|
+
return () => {
|
|
707
|
+
unsub()
|
|
708
|
+
if (window.removeEventListener) {
|
|
709
|
+
// Be sure to unsubscribe if a new handler is set
|
|
710
|
+
window.removeEventListener('visibilitychange', onFocus)
|
|
711
|
+
window.removeEventListener('focus', onFocus)
|
|
712
|
+
}
|
|
582
713
|
}
|
|
583
714
|
}
|
|
584
|
-
},
|
|
585
715
|
|
|
586
|
-
|
|
587
|
-
router.load()
|
|
716
|
+
return () => {}
|
|
588
717
|
},
|
|
589
718
|
|
|
590
719
|
update: (opts) => {
|
|
591
720
|
const newHistory = opts?.history !== router.history
|
|
592
|
-
if (!
|
|
721
|
+
if (!store.latestLocation || newHistory) {
|
|
593
722
|
if (opts?.history) {
|
|
594
723
|
router.history = opts.history
|
|
595
724
|
}
|
|
596
|
-
|
|
597
|
-
|
|
725
|
+
setStore((s) => {
|
|
726
|
+
s.latestLocation = parseLocation(router.history.location)
|
|
727
|
+
s.currentLocation = s.latestLocation
|
|
728
|
+
})
|
|
598
729
|
}
|
|
599
730
|
|
|
600
731
|
Object.assign(router.options, opts)
|
|
@@ -605,82 +736,68 @@ export function createRouter<
|
|
|
605
736
|
|
|
606
737
|
if (routeConfig) {
|
|
607
738
|
router.routesById = {} as any
|
|
608
|
-
router.routeTree =
|
|
739
|
+
router.routeTree = buildRouteTree(routeConfig)
|
|
609
740
|
}
|
|
610
741
|
|
|
611
742
|
return router as any
|
|
612
743
|
},
|
|
613
744
|
|
|
614
745
|
cancelMatches: () => {
|
|
615
|
-
;[
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
})
|
|
746
|
+
;[...store.currentMatches, ...(store.pendingMatches || [])].forEach(
|
|
747
|
+
(match) => {
|
|
748
|
+
match.cancel()
|
|
749
|
+
},
|
|
750
|
+
)
|
|
621
751
|
},
|
|
622
752
|
|
|
623
753
|
load: async (next?: Location) => {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (next) {
|
|
628
|
-
// Ingest the new location
|
|
629
|
-
router.__location = next
|
|
630
|
-
}
|
|
754
|
+
let now = Date.now()
|
|
755
|
+
const startedAt = now
|
|
756
|
+
startedLoadingAt = startedAt
|
|
631
757
|
|
|
632
758
|
// Cancel any pending matches
|
|
633
759
|
router.cancelMatches()
|
|
634
760
|
|
|
635
|
-
|
|
636
|
-
const matches = router.matchRoutes(router.__location.pathname, {
|
|
637
|
-
strictParseParams: true,
|
|
638
|
-
})
|
|
761
|
+
let matches!: RouteMatch<any, any>[]
|
|
639
762
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
},
|
|
647
|
-
status: 'loading',
|
|
648
|
-
}
|
|
649
|
-
} else {
|
|
650
|
-
router.state = {
|
|
651
|
-
...router.state,
|
|
652
|
-
matches: matches,
|
|
653
|
-
location: router.__location,
|
|
654
|
-
status: 'loading',
|
|
763
|
+
batch(() => {
|
|
764
|
+
if (next) {
|
|
765
|
+
// Ingest the new location
|
|
766
|
+
setStore((s) => {
|
|
767
|
+
s.latestLocation = next
|
|
768
|
+
})
|
|
655
769
|
}
|
|
656
|
-
}
|
|
657
770
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
match.options.beforeLoad?.({
|
|
663
|
-
router: router as any,
|
|
664
|
-
match,
|
|
665
|
-
}),
|
|
666
|
-
),
|
|
667
|
-
)
|
|
668
|
-
} catch (err: any) {
|
|
669
|
-
console.info(err)
|
|
670
|
-
invariant(false, `A route's beforeLoad middleware failed! 👆`)
|
|
671
|
-
}
|
|
771
|
+
// Match the routes
|
|
772
|
+
matches = router.matchRoutes(store.latestLocation.pathname, {
|
|
773
|
+
strictParseParams: true,
|
|
774
|
+
})
|
|
672
775
|
|
|
673
|
-
|
|
776
|
+
console.log('set loading', matches)
|
|
777
|
+
setStore((s) => {
|
|
778
|
+
s.status = 'loading'
|
|
779
|
+
s.pendingMatches = matches
|
|
780
|
+
s.pendingLocation = store.latestLocation
|
|
781
|
+
})
|
|
782
|
+
})
|
|
674
783
|
|
|
675
784
|
// Load the matches
|
|
676
|
-
|
|
785
|
+
try {
|
|
786
|
+
await router.loadMatches(matches)
|
|
787
|
+
} catch (err: any) {
|
|
788
|
+
console.log(err)
|
|
789
|
+
invariant(
|
|
790
|
+
false,
|
|
791
|
+
'Matches failed to load due to error above ☝️. Navigation cancelled!',
|
|
792
|
+
)
|
|
793
|
+
}
|
|
677
794
|
|
|
678
|
-
if (
|
|
679
|
-
// Ignore side-effects of
|
|
680
|
-
return
|
|
795
|
+
if (startedLoadingAt !== startedAt) {
|
|
796
|
+
// Ignore side-effects of outdated side-effects
|
|
797
|
+
return navigationPromise
|
|
681
798
|
}
|
|
682
799
|
|
|
683
|
-
const previousMatches =
|
|
800
|
+
const previousMatches = store.currentMatches
|
|
684
801
|
|
|
685
802
|
const exiting: RouteMatch[] = [],
|
|
686
803
|
staying: RouteMatch[] = []
|
|
@@ -697,18 +814,18 @@ export function createRouter<
|
|
|
697
814
|
return !previousMatches.find((dd) => dd.matchId === d.matchId)
|
|
698
815
|
})
|
|
699
816
|
|
|
700
|
-
|
|
817
|
+
now = Date.now()
|
|
701
818
|
|
|
702
819
|
exiting.forEach((d) => {
|
|
703
820
|
d.__.onExit?.({
|
|
704
821
|
params: d.params,
|
|
705
|
-
search: d.routeSearch,
|
|
822
|
+
search: d.store.routeSearch,
|
|
706
823
|
})
|
|
707
824
|
|
|
708
825
|
// Clear idle error states when match leaves
|
|
709
|
-
if (d.status === 'error' && !d.isFetching) {
|
|
710
|
-
d.status = 'idle'
|
|
711
|
-
d.error = undefined
|
|
826
|
+
if (d.store.status === 'error' && !d.store.isFetching) {
|
|
827
|
+
d.store.status = 'idle'
|
|
828
|
+
d.store.error = undefined
|
|
712
829
|
}
|
|
713
830
|
|
|
714
831
|
const gc = Math.max(
|
|
@@ -717,7 +834,7 @@ export function createRouter<
|
|
|
717
834
|
)
|
|
718
835
|
|
|
719
836
|
if (gc > 0) {
|
|
720
|
-
|
|
837
|
+
store.matchCache[d.matchId] = {
|
|
721
838
|
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
722
839
|
match: d,
|
|
723
840
|
}
|
|
@@ -727,19 +844,19 @@ export function createRouter<
|
|
|
727
844
|
staying.forEach((d) => {
|
|
728
845
|
d.options.onTransition?.({
|
|
729
846
|
params: d.params,
|
|
730
|
-
search: d.routeSearch,
|
|
847
|
+
search: d.store.routeSearch,
|
|
731
848
|
})
|
|
732
849
|
})
|
|
733
850
|
|
|
734
851
|
entering.forEach((d) => {
|
|
735
852
|
d.__.onExit = d.options.onLoaded?.({
|
|
736
853
|
params: d.params,
|
|
737
|
-
search: d.search,
|
|
854
|
+
search: d.store.search,
|
|
738
855
|
})
|
|
739
|
-
delete
|
|
856
|
+
delete store.matchCache[d.matchId]
|
|
740
857
|
})
|
|
741
858
|
|
|
742
|
-
if (
|
|
859
|
+
if (startedLoadingAt !== startedAt) {
|
|
743
860
|
// Ignore side-effects of match loading
|
|
744
861
|
return
|
|
745
862
|
}
|
|
@@ -747,45 +864,50 @@ export function createRouter<
|
|
|
747
864
|
matches.forEach((match) => {
|
|
748
865
|
// Clear actions
|
|
749
866
|
if (match.action) {
|
|
867
|
+
// TODO: Check reactivity here
|
|
750
868
|
match.action.current = undefined
|
|
751
869
|
match.action.submissions = []
|
|
752
870
|
}
|
|
753
871
|
})
|
|
754
872
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
873
|
+
setStore((s) => {
|
|
874
|
+
console.log('set', matches)
|
|
875
|
+
Object.assign(s, {
|
|
876
|
+
status: 'idle',
|
|
877
|
+
currentLocation: store.latestLocation,
|
|
878
|
+
currentMatches: matches,
|
|
879
|
+
pendingLocation: undefined,
|
|
880
|
+
pendingMatches: undefined,
|
|
881
|
+
})
|
|
882
|
+
})
|
|
762
883
|
|
|
763
|
-
|
|
764
|
-
router.resolveNavigation()
|
|
884
|
+
resolveNavigation()
|
|
765
885
|
},
|
|
766
886
|
|
|
767
887
|
cleanMatchCache: () => {
|
|
768
888
|
const now = Date.now()
|
|
769
889
|
|
|
770
|
-
|
|
771
|
-
|
|
890
|
+
setStore((s) => {
|
|
891
|
+
Object.keys(s.matchCache).forEach((matchId) => {
|
|
892
|
+
const entry = s.matchCache[matchId]!
|
|
772
893
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
894
|
+
// Don't remove loading matches
|
|
895
|
+
if (entry.match.store.status === 'loading') {
|
|
896
|
+
return
|
|
897
|
+
}
|
|
777
898
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
899
|
+
// Do not remove successful matches that are still valid
|
|
900
|
+
if (entry.gc > 0 && entry.gc > now) {
|
|
901
|
+
return
|
|
902
|
+
}
|
|
782
903
|
|
|
783
|
-
|
|
784
|
-
|
|
904
|
+
// Everything else gets removed
|
|
905
|
+
delete s.matchCache[matchId]
|
|
906
|
+
})
|
|
785
907
|
})
|
|
786
908
|
},
|
|
787
909
|
|
|
788
|
-
loadRoute: async (navigateOpts =
|
|
910
|
+
loadRoute: async (navigateOpts = store.latestLocation) => {
|
|
789
911
|
const next = router.buildNext(navigateOpts)
|
|
790
912
|
const matches = router.matchRoutes(next.pathname, {
|
|
791
913
|
strictParseParams: true,
|
|
@@ -794,11 +916,12 @@ export function createRouter<
|
|
|
794
916
|
return matches
|
|
795
917
|
},
|
|
796
918
|
|
|
797
|
-
preloadRoute: async (navigateOpts =
|
|
919
|
+
preloadRoute: async (navigateOpts = store.latestLocation, loaderOpts) => {
|
|
798
920
|
const next = router.buildNext(navigateOpts)
|
|
799
921
|
const matches = router.matchRoutes(next.pathname, {
|
|
800
922
|
strictParseParams: true,
|
|
801
923
|
})
|
|
924
|
+
|
|
802
925
|
await router.loadMatches(matches, {
|
|
803
926
|
preload: true,
|
|
804
927
|
maxAge:
|
|
@@ -825,8 +948,8 @@ export function createRouter<
|
|
|
825
948
|
}
|
|
826
949
|
|
|
827
950
|
const existingMatches = [
|
|
828
|
-
...
|
|
829
|
-
...(
|
|
951
|
+
...store.currentMatches,
|
|
952
|
+
...(store.pendingMatches ?? []),
|
|
830
953
|
]
|
|
831
954
|
|
|
832
955
|
const recurse = async (routes: Route<any, any>[]): Promise<void> => {
|
|
@@ -857,14 +980,6 @@ export function createRouter<
|
|
|
857
980
|
route.options.caseSensitive ?? router.options.caseSensitive,
|
|
858
981
|
})
|
|
859
982
|
|
|
860
|
-
console.log(
|
|
861
|
-
router.basepath,
|
|
862
|
-
route.fullPath,
|
|
863
|
-
fuzzy,
|
|
864
|
-
pathname,
|
|
865
|
-
matchParams,
|
|
866
|
-
)
|
|
867
|
-
|
|
868
983
|
if (matchParams) {
|
|
869
984
|
let parsedParams
|
|
870
985
|
|
|
@@ -905,7 +1020,7 @@ export function createRouter<
|
|
|
905
1020
|
|
|
906
1021
|
const match =
|
|
907
1022
|
existingMatches.find((d) => d.matchId === matchId) ||
|
|
908
|
-
|
|
1023
|
+
store.matchCache[matchId]?.match ||
|
|
909
1024
|
createRouteMatch(router, foundRoute, {
|
|
910
1025
|
parentMatch,
|
|
911
1026
|
matchId,
|
|
@@ -925,17 +1040,37 @@ export function createRouter<
|
|
|
925
1040
|
|
|
926
1041
|
recurse([router.routeTree])
|
|
927
1042
|
|
|
928
|
-
|
|
1043
|
+
linkMatches(matches)
|
|
929
1044
|
|
|
930
1045
|
return matches
|
|
931
1046
|
},
|
|
932
1047
|
|
|
933
1048
|
loadMatches: async (resolvedMatches, loaderOpts) => {
|
|
934
|
-
|
|
1049
|
+
resolvedMatches.forEach(async (match) => {
|
|
935
1050
|
// Validate the match (loads search params etc)
|
|
936
1051
|
match.__.validate()
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
// Check each match middleware to see if the route can be accessed
|
|
1055
|
+
await Promise.all(
|
|
1056
|
+
resolvedMatches.map(async (match) => {
|
|
1057
|
+
try {
|
|
1058
|
+
await match.options.beforeLoad?.({
|
|
1059
|
+
router: router as any,
|
|
1060
|
+
match,
|
|
1061
|
+
})
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
if (!loaderOpts?.preload) {
|
|
1064
|
+
match.options.onLoadError?.(err)
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
throw err
|
|
1068
|
+
}
|
|
1069
|
+
}),
|
|
1070
|
+
)
|
|
937
1071
|
|
|
938
|
-
|
|
1072
|
+
const matchPromises = resolvedMatches.map(async (match) => {
|
|
1073
|
+
const search = match.store.search as { __data?: any }
|
|
939
1074
|
|
|
940
1075
|
if (search.__data?.matchId && search.__data.matchId !== match.matchId) {
|
|
941
1076
|
return
|
|
@@ -943,14 +1078,12 @@ export function createRouter<
|
|
|
943
1078
|
|
|
944
1079
|
match.load(loaderOpts)
|
|
945
1080
|
|
|
946
|
-
if (match.status !== 'success' && match.__.loadPromise) {
|
|
1081
|
+
if (match.store.status !== 'success' && match.__.loadPromise) {
|
|
947
1082
|
// Wait for the first sign of activity from the match
|
|
948
1083
|
await match.__.loadPromise
|
|
949
1084
|
}
|
|
950
1085
|
})
|
|
951
1086
|
|
|
952
|
-
router.notify()
|
|
953
|
-
|
|
954
1087
|
await Promise.all(matchPromises)
|
|
955
1088
|
},
|
|
956
1089
|
|
|
@@ -960,9 +1093,9 @@ export function createRouter<
|
|
|
960
1093
|
(await routeMatch.options.loader?.({
|
|
961
1094
|
// parentLoaderPromise: routeMatch.parentMatch?.__.dataPromise,
|
|
962
1095
|
params: routeMatch.params,
|
|
963
|
-
search: routeMatch.routeSearch,
|
|
1096
|
+
search: routeMatch.store.routeSearch,
|
|
964
1097
|
signal: routeMatch.__.abortController.signal,
|
|
965
|
-
}))
|
|
1098
|
+
})) || {}
|
|
966
1099
|
)
|
|
967
1100
|
} else {
|
|
968
1101
|
const next = router.buildNext({
|
|
@@ -1003,18 +1136,17 @@ export function createRouter<
|
|
|
1003
1136
|
const unloadedMatchIds = router
|
|
1004
1137
|
.matchRoutes(next.pathname)
|
|
1005
1138
|
.map((d) => d.matchId)
|
|
1006
|
-
;[
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
})
|
|
1139
|
+
;[...store.currentMatches, ...(store.pendingMatches ?? [])].forEach(
|
|
1140
|
+
(match) => {
|
|
1141
|
+
if (unloadedMatchIds.includes(match.matchId)) {
|
|
1142
|
+
match.invalidate()
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
)
|
|
1014
1146
|
},
|
|
1015
1147
|
|
|
1016
1148
|
reload: () =>
|
|
1017
|
-
|
|
1149
|
+
navigate({
|
|
1018
1150
|
fromCurrent: true,
|
|
1019
1151
|
replace: true,
|
|
1020
1152
|
search: true,
|
|
@@ -1037,13 +1169,13 @@ export function createRouter<
|
|
|
1037
1169
|
const next = router.buildNext(location)
|
|
1038
1170
|
|
|
1039
1171
|
if (opts?.pending) {
|
|
1040
|
-
if (!
|
|
1172
|
+
if (!store.pendingLocation) {
|
|
1041
1173
|
return false
|
|
1042
1174
|
}
|
|
1043
1175
|
|
|
1044
1176
|
return !!matchPathname(
|
|
1045
1177
|
router.basepath,
|
|
1046
|
-
|
|
1178
|
+
store.pendingLocation.pathname,
|
|
1047
1179
|
{
|
|
1048
1180
|
...opts,
|
|
1049
1181
|
to: next.pathname,
|
|
@@ -1051,10 +1183,10 @@ export function createRouter<
|
|
|
1051
1183
|
)
|
|
1052
1184
|
}
|
|
1053
1185
|
|
|
1054
|
-
return
|
|
1186
|
+
return matchPathname(router.basepath, store.currentLocation.pathname, {
|
|
1055
1187
|
...opts,
|
|
1056
1188
|
to: next.pathname,
|
|
1057
|
-
})
|
|
1189
|
+
}) as any
|
|
1058
1190
|
},
|
|
1059
1191
|
|
|
1060
1192
|
navigate: async ({ from, to = '.', search, hash, replace, params }) => {
|
|
@@ -1078,7 +1210,7 @@ export function createRouter<
|
|
|
1078
1210
|
'Attempting to navigate to external url with router.navigate!',
|
|
1079
1211
|
)
|
|
1080
1212
|
|
|
1081
|
-
return
|
|
1213
|
+
return navigate({
|
|
1082
1214
|
from: fromString,
|
|
1083
1215
|
to: toString,
|
|
1084
1216
|
search,
|
|
@@ -1133,13 +1265,13 @@ export function createRouter<
|
|
|
1133
1265
|
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
1134
1266
|
|
|
1135
1267
|
// Compare path/hash for matches
|
|
1136
|
-
const pathIsEqual =
|
|
1137
|
-
const currentPathSplit =
|
|
1268
|
+
const pathIsEqual = store.currentLocation.pathname === next.pathname
|
|
1269
|
+
const currentPathSplit = store.currentLocation.pathname.split('/')
|
|
1138
1270
|
const nextPathSplit = next.pathname.split('/')
|
|
1139
1271
|
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
1140
1272
|
(d, i) => d === currentPathSplit[i],
|
|
1141
1273
|
)
|
|
1142
|
-
const hashIsEqual =
|
|
1274
|
+
const hashIsEqual = store.currentLocation.hash === next.hash
|
|
1143
1275
|
// Combine the matches based on user options
|
|
1144
1276
|
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
1145
1277
|
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
@@ -1161,18 +1293,23 @@ export function createRouter<
|
|
|
1161
1293
|
router.invalidateRoute(nextOpts)
|
|
1162
1294
|
}
|
|
1163
1295
|
|
|
1164
|
-
// All is well? Navigate!
|
|
1165
|
-
|
|
1296
|
+
// All is well? Navigate!
|
|
1297
|
+
navigate(nextOpts)
|
|
1166
1298
|
}
|
|
1167
1299
|
}
|
|
1168
1300
|
|
|
1169
1301
|
// The click handler
|
|
1170
1302
|
const handleFocus = (e: MouseEvent) => {
|
|
1171
1303
|
if (preload) {
|
|
1172
|
-
router
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1304
|
+
router
|
|
1305
|
+
.preloadRoute(nextOpts, {
|
|
1306
|
+
maxAge: userPreloadMaxAge,
|
|
1307
|
+
gcMaxAge: userPreloadGcMaxAge,
|
|
1308
|
+
})
|
|
1309
|
+
.catch((err) => {
|
|
1310
|
+
console.log(err)
|
|
1311
|
+
console.warn('Error preloading route! ☝️')
|
|
1312
|
+
})
|
|
1176
1313
|
}
|
|
1177
1314
|
}
|
|
1178
1315
|
|
|
@@ -1186,10 +1323,15 @@ export function createRouter<
|
|
|
1186
1323
|
|
|
1187
1324
|
target.preloadTimeout = setTimeout(() => {
|
|
1188
1325
|
target.preloadTimeout = null
|
|
1189
|
-
router
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1326
|
+
router
|
|
1327
|
+
.preloadRoute(nextOpts, {
|
|
1328
|
+
maxAge: userPreloadMaxAge,
|
|
1329
|
+
gcMaxAge: userPreloadGcMaxAge,
|
|
1330
|
+
})
|
|
1331
|
+
.catch((err) => {
|
|
1332
|
+
console.log(err)
|
|
1333
|
+
console.warn('Error preloading route! ☝️')
|
|
1334
|
+
})
|
|
1193
1335
|
}, preloadDelay)
|
|
1194
1336
|
}
|
|
1195
1337
|
}
|
|
@@ -1215,7 +1357,7 @@ export function createRouter<
|
|
|
1215
1357
|
}
|
|
1216
1358
|
},
|
|
1217
1359
|
buildNext: (opts: BuildNextOptions) => {
|
|
1218
|
-
const next =
|
|
1360
|
+
const next = buildLocation(opts)
|
|
1219
1361
|
|
|
1220
1362
|
const matches = router.matchRoutes(next.pathname)
|
|
1221
1363
|
|
|
@@ -1229,214 +1371,12 @@ export function createRouter<
|
|
|
1229
1371
|
.flat()
|
|
1230
1372
|
.filter(Boolean)
|
|
1231
1373
|
|
|
1232
|
-
return
|
|
1374
|
+
return buildLocation({
|
|
1233
1375
|
...opts,
|
|
1234
1376
|
__preSearchFilters,
|
|
1235
1377
|
__postSearchFilters,
|
|
1236
1378
|
})
|
|
1237
1379
|
},
|
|
1238
|
-
|
|
1239
|
-
__: {
|
|
1240
|
-
buildRouteTree: (rootRouteConfig: RouteConfig) => {
|
|
1241
|
-
const recurseRoutes = (
|
|
1242
|
-
routeConfigs: RouteConfig[],
|
|
1243
|
-
parent?: Route<TAllRouteInfo, any, any>,
|
|
1244
|
-
): Route<TAllRouteInfo, any, any>[] => {
|
|
1245
|
-
return routeConfigs.map((routeConfig) => {
|
|
1246
|
-
const routeOptions = routeConfig.options
|
|
1247
|
-
const route = createRoute(routeConfig, routeOptions, parent, router)
|
|
1248
|
-
const existingRoute = (router.routesById as any)[route.routeId]
|
|
1249
|
-
|
|
1250
|
-
if (existingRoute) {
|
|
1251
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1252
|
-
console.warn(
|
|
1253
|
-
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
1254
|
-
router.routesById,
|
|
1255
|
-
route,
|
|
1256
|
-
)
|
|
1257
|
-
}
|
|
1258
|
-
throw new Error()
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
;(router.routesById as any)[route.routeId] = route
|
|
1262
|
-
|
|
1263
|
-
const children = routeConfig.children as RouteConfig[]
|
|
1264
|
-
|
|
1265
|
-
route.childRoutes = children?.length
|
|
1266
|
-
? recurseRoutes(children, route)
|
|
1267
|
-
: undefined
|
|
1268
|
-
|
|
1269
|
-
return route
|
|
1270
|
-
})
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
const routes = recurseRoutes([rootRouteConfig])
|
|
1274
|
-
|
|
1275
|
-
return routes[0]!
|
|
1276
|
-
},
|
|
1277
|
-
|
|
1278
|
-
parseLocation: (
|
|
1279
|
-
location: History['location'],
|
|
1280
|
-
previousLocation?: Location,
|
|
1281
|
-
): Location => {
|
|
1282
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1283
|
-
|
|
1284
|
-
return {
|
|
1285
|
-
pathname: location.pathname,
|
|
1286
|
-
searchStr: location.search,
|
|
1287
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1288
|
-
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
1289
|
-
href: `${location.pathname}${location.search}${location.hash}`,
|
|
1290
|
-
state: location.state as LocationState,
|
|
1291
|
-
key: location.key,
|
|
1292
|
-
}
|
|
1293
|
-
},
|
|
1294
|
-
|
|
1295
|
-
navigate: (location: BuildNextOptions & { replace?: boolean }) => {
|
|
1296
|
-
const next = router.buildNext(location)
|
|
1297
|
-
return router.__.commitLocation(next, location.replace)
|
|
1298
|
-
},
|
|
1299
|
-
|
|
1300
|
-
buildLocation: (dest: BuildNextOptions = {}): Location => {
|
|
1301
|
-
const fromPathname = dest.fromCurrent
|
|
1302
|
-
? router.__location.pathname
|
|
1303
|
-
: dest.from ?? router.__location.pathname
|
|
1304
|
-
|
|
1305
|
-
let pathname = resolvePath(
|
|
1306
|
-
router.basepath ?? '/',
|
|
1307
|
-
fromPathname,
|
|
1308
|
-
`${dest.to ?? '.'}`,
|
|
1309
|
-
)
|
|
1310
|
-
|
|
1311
|
-
const fromMatches = router.matchRoutes(router.__location.pathname, {
|
|
1312
|
-
strictParseParams: true,
|
|
1313
|
-
})
|
|
1314
|
-
|
|
1315
|
-
const toMatches = router.matchRoutes(pathname)
|
|
1316
|
-
|
|
1317
|
-
const prevParams = { ...last(fromMatches)?.params }
|
|
1318
|
-
|
|
1319
|
-
let nextParams =
|
|
1320
|
-
(dest.params ?? true) === true
|
|
1321
|
-
? prevParams
|
|
1322
|
-
: functionalUpdate(dest.params!, prevParams)
|
|
1323
|
-
|
|
1324
|
-
if (nextParams) {
|
|
1325
|
-
toMatches
|
|
1326
|
-
.map((d) => d.options.stringifyParams)
|
|
1327
|
-
.filter(Boolean)
|
|
1328
|
-
.forEach((fn) => {
|
|
1329
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1330
|
-
})
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1334
|
-
|
|
1335
|
-
// Pre filters first
|
|
1336
|
-
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1337
|
-
? dest.__preSearchFilters.reduce(
|
|
1338
|
-
(prev, next) => next(prev),
|
|
1339
|
-
router.__location.search,
|
|
1340
|
-
)
|
|
1341
|
-
: router.__location.search
|
|
1342
|
-
|
|
1343
|
-
// Then the link/navigate function
|
|
1344
|
-
const destSearch =
|
|
1345
|
-
dest.search === true
|
|
1346
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1347
|
-
: dest.search
|
|
1348
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1349
|
-
: dest.__preSearchFilters?.length
|
|
1350
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1351
|
-
: {}
|
|
1352
|
-
|
|
1353
|
-
// Then post filters
|
|
1354
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1355
|
-
? dest.__postSearchFilters.reduce(
|
|
1356
|
-
(prev, next) => next(prev),
|
|
1357
|
-
destSearch,
|
|
1358
|
-
)
|
|
1359
|
-
: destSearch
|
|
1360
|
-
|
|
1361
|
-
const search = replaceEqualDeep(
|
|
1362
|
-
router.__location.search,
|
|
1363
|
-
postFilteredSearch,
|
|
1364
|
-
)
|
|
1365
|
-
|
|
1366
|
-
const searchStr = router.options.stringifySearch(search)
|
|
1367
|
-
let hash =
|
|
1368
|
-
dest.hash === true
|
|
1369
|
-
? router.__location.hash
|
|
1370
|
-
: functionalUpdate(dest.hash!, router.__location.hash)
|
|
1371
|
-
hash = hash ? `#${hash}` : ''
|
|
1372
|
-
|
|
1373
|
-
return {
|
|
1374
|
-
pathname,
|
|
1375
|
-
search,
|
|
1376
|
-
searchStr,
|
|
1377
|
-
state: router.__location.state,
|
|
1378
|
-
hash,
|
|
1379
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1380
|
-
key: dest.key,
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
|
-
|
|
1384
|
-
commitLocation: (next: Location, replace?: boolean): Promise<void> => {
|
|
1385
|
-
const id = '' + Date.now() + Math.random()
|
|
1386
|
-
|
|
1387
|
-
if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
|
|
1388
|
-
|
|
1389
|
-
let nextAction: 'push' | 'replace' = 'replace'
|
|
1390
|
-
|
|
1391
|
-
if (!replace) {
|
|
1392
|
-
nextAction = 'push'
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
const isSameUrl =
|
|
1396
|
-
router.__.parseLocation(history.location).href === next.href
|
|
1397
|
-
|
|
1398
|
-
if (isSameUrl && !next.key) {
|
|
1399
|
-
nextAction = 'replace'
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
if (nextAction === 'replace') {
|
|
1403
|
-
history.replace(
|
|
1404
|
-
{
|
|
1405
|
-
pathname: next.pathname,
|
|
1406
|
-
hash: next.hash,
|
|
1407
|
-
search: next.searchStr,
|
|
1408
|
-
},
|
|
1409
|
-
{
|
|
1410
|
-
id,
|
|
1411
|
-
...next.state,
|
|
1412
|
-
},
|
|
1413
|
-
)
|
|
1414
|
-
} else {
|
|
1415
|
-
history.push(
|
|
1416
|
-
{
|
|
1417
|
-
pathname: next.pathname,
|
|
1418
|
-
hash: next.hash,
|
|
1419
|
-
search: next.searchStr,
|
|
1420
|
-
},
|
|
1421
|
-
{
|
|
1422
|
-
id,
|
|
1423
|
-
},
|
|
1424
|
-
)
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
router.navigationPromise = new Promise((resolve) => {
|
|
1428
|
-
const previousNavigationResolve = router.resolveNavigation
|
|
1429
|
-
|
|
1430
|
-
router.resolveNavigation = () => {
|
|
1431
|
-
previousNavigationResolve()
|
|
1432
|
-
resolve()
|
|
1433
|
-
delete router.navigationPromise
|
|
1434
|
-
}
|
|
1435
|
-
})
|
|
1436
|
-
|
|
1437
|
-
return router.navigationPromise
|
|
1438
|
-
},
|
|
1439
|
-
},
|
|
1440
1380
|
}
|
|
1441
1381
|
|
|
1442
1382
|
router.update(userOptions)
|
|
@@ -1451,15 +1391,14 @@ function isCtrlEvent(e: MouseEvent) {
|
|
|
1451
1391
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1452
1392
|
}
|
|
1453
1393
|
|
|
1454
|
-
function
|
|
1394
|
+
function linkMatches(matches: RouteMatch<any, any>[]) {
|
|
1455
1395
|
matches.forEach((match, index) => {
|
|
1456
1396
|
const parent = matches[index - 1]
|
|
1457
1397
|
|
|
1458
1398
|
if (parent) {
|
|
1459
|
-
match.
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
})
|
|
1399
|
+
match.__.setParentMatch(parent)
|
|
1400
|
+
} else {
|
|
1401
|
+
match.__.setParentMatch(undefined)
|
|
1463
1402
|
}
|
|
1464
1403
|
})
|
|
1465
1404
|
}
|