@tanstack/router-core 0.0.1-beta.36 → 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 -95
- 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 +320 -351
- 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 +654 -592
- 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 +54 -68
- package/build/umd/index.development.js +1000 -595
- 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 -139
- package/src/routeMatch.ts +144 -99
- package/src/router.ts +402 -491
- 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,7 +196,7 @@ 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
|
> {
|
|
@@ -211,6 +211,7 @@ export interface RouterState<
|
|
|
211
211
|
loaders: Record<string, Loader>
|
|
212
212
|
isFetching: boolean
|
|
213
213
|
isPreloading: boolean
|
|
214
|
+
matchCache: Record<string, MatchCacheEntry>
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
type Listener = (router: Router<any, any, any>) => void
|
|
@@ -255,7 +256,7 @@ type LinkCurrentTargetElement = {
|
|
|
255
256
|
|
|
256
257
|
export interface DehydratedRouterState
|
|
257
258
|
extends Pick<
|
|
258
|
-
|
|
259
|
+
RouterStore,
|
|
259
260
|
'status' | 'latestLocation' | 'currentLocation' | 'lastUpdated'
|
|
260
261
|
> {
|
|
261
262
|
currentMatches: DehydratedRouteMatch[]
|
|
@@ -263,20 +264,19 @@ export interface DehydratedRouterState
|
|
|
263
264
|
|
|
264
265
|
export interface DehydratedRouter<TRouterContext = unknown> {
|
|
265
266
|
// location: Router['__location']
|
|
266
|
-
|
|
267
|
+
store: DehydratedRouterState
|
|
267
268
|
context: TRouterContext
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
| '
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
> {}
|
|
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
|
+
}
|
|
280
280
|
|
|
281
281
|
export interface RouterContext {}
|
|
282
282
|
|
|
@@ -297,24 +297,14 @@ export interface Router<
|
|
|
297
297
|
RouterOptions<TRouteConfig, TRouterContext>,
|
|
298
298
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
299
299
|
>
|
|
300
|
-
|
|
300
|
+
store: RouterStore<TAllRouteInfo['fullSearchSchema']>
|
|
301
|
+
setStore: SetStoreFunction<RouterStore<TAllRouteInfo['fullSearchSchema']>>
|
|
301
302
|
basepath: string
|
|
302
|
-
// Internal:
|
|
303
|
-
listeners: Listener[]
|
|
304
303
|
// __location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
305
|
-
navigateTimeout?: Timeout
|
|
306
|
-
nextAction?: 'push' | 'replace'
|
|
307
|
-
state: RouterState<TAllRouteInfo['fullSearchSchema']>
|
|
308
304
|
routeTree: Route<TAllRouteInfo, RouteInfo>
|
|
309
305
|
routesById: RoutesById<TAllRouteInfo>
|
|
310
|
-
navigationPromise?: Promise<void>
|
|
311
|
-
startedLoadingAt: number
|
|
312
|
-
resolveNavigation: () => void
|
|
313
|
-
subscribe: (listener: Listener) => () => void
|
|
314
306
|
reset: () => void
|
|
315
|
-
notify: () => void
|
|
316
307
|
mount: () => () => void
|
|
317
|
-
onFocus: () => void
|
|
318
308
|
update: <
|
|
319
309
|
TRouteConfig extends RouteConfig = RouteConfig,
|
|
320
310
|
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
@@ -326,7 +316,6 @@ export interface Router<
|
|
|
326
316
|
buildNext: (opts: BuildNextOptions) => Location
|
|
327
317
|
cancelMatches: () => void
|
|
328
318
|
load: (next?: Location) => Promise<void>
|
|
329
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
330
319
|
cleanMatchCache: () => void
|
|
331
320
|
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
332
321
|
id: TId,
|
|
@@ -356,7 +345,7 @@ export interface Router<
|
|
|
356
345
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
357
346
|
TTo extends string = '.',
|
|
358
347
|
>(
|
|
359
|
-
opts:
|
|
348
|
+
opts: NavigateOptions<TAllRouteInfo, TFrom, TTo>,
|
|
360
349
|
) => Promise<void>
|
|
361
350
|
matchRoute: <
|
|
362
351
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
@@ -364,7 +353,12 @@ export interface Router<
|
|
|
364
353
|
>(
|
|
365
354
|
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
366
355
|
opts?: MatchRouteOptions,
|
|
367
|
-
) =>
|
|
356
|
+
) =>
|
|
357
|
+
| false
|
|
358
|
+
| TAllRouteInfo['routeInfoById'][ResolveRelativePath<
|
|
359
|
+
TFrom,
|
|
360
|
+
NoInfer<TTo>
|
|
361
|
+
>]['allParams']
|
|
368
362
|
buildLink: <
|
|
369
363
|
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
370
364
|
TTo extends string = '.',
|
|
@@ -373,20 +367,6 @@ export interface Router<
|
|
|
373
367
|
) => LinkInfo
|
|
374
368
|
dehydrate: () => DehydratedRouter<TRouterContext>
|
|
375
369
|
hydrate: (dehydratedRouter: DehydratedRouter<TRouterContext>) => void
|
|
376
|
-
__: {
|
|
377
|
-
buildRouteTree: (
|
|
378
|
-
routeConfig: RouteConfig,
|
|
379
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
380
|
-
parseLocation: (
|
|
381
|
-
location: History['location'],
|
|
382
|
-
previousLocation?: Location,
|
|
383
|
-
) => Location
|
|
384
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
385
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
386
|
-
navigate: (
|
|
387
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
388
|
-
) => Promise<void>
|
|
389
|
-
}
|
|
390
370
|
}
|
|
391
371
|
|
|
392
372
|
// Detect if we're in the DOM
|
|
@@ -397,7 +377,7 @@ const isServer =
|
|
|
397
377
|
const createDefaultHistory = () =>
|
|
398
378
|
isServer ? createMemoryHistory() : createBrowserHistory()
|
|
399
379
|
|
|
400
|
-
function getInitialRouterState():
|
|
380
|
+
function getInitialRouterState(): RouterStore {
|
|
401
381
|
return {
|
|
402
382
|
status: 'idle',
|
|
403
383
|
latestLocation: null!,
|
|
@@ -406,8 +386,20 @@ function getInitialRouterState(): RouterState {
|
|
|
406
386
|
actions: {},
|
|
407
387
|
loaders: {},
|
|
408
388
|
lastUpdated: Date.now(),
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
},
|
|
411
403
|
}
|
|
412
404
|
}
|
|
413
405
|
|
|
@@ -418,8 +410,6 @@ export function createRouter<
|
|
|
418
410
|
>(
|
|
419
411
|
userOptions?: RouterOptions<TRouteConfig, TRouterContext>,
|
|
420
412
|
): Router<TRouteConfig, TAllRouteInfo, TRouterContext> {
|
|
421
|
-
const history = userOptions?.history || createDefaultHistory()
|
|
422
|
-
|
|
423
413
|
const originalOptions = {
|
|
424
414
|
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
425
415
|
defaultLoaderMaxAge: 0,
|
|
@@ -431,164 +421,311 @@ export function createRouter<
|
|
|
431
421
|
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
432
422
|
}
|
|
433
423
|
|
|
434
|
-
|
|
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> = {
|
|
435
615
|
types: undefined!,
|
|
436
616
|
|
|
437
617
|
// public api
|
|
438
|
-
history,
|
|
618
|
+
history: userOptions?.history || createDefaultHistory(),
|
|
619
|
+
store,
|
|
620
|
+
setStore,
|
|
439
621
|
options: originalOptions,
|
|
440
|
-
listeners: [],
|
|
441
|
-
// Resolved after construction
|
|
442
622
|
basepath: '',
|
|
443
623
|
routeTree: undefined!,
|
|
444
624
|
routesById: {} as any,
|
|
445
|
-
|
|
446
|
-
resolveNavigation: () => {},
|
|
447
|
-
matchCache: {},
|
|
448
|
-
state: getInitialRouterState(),
|
|
625
|
+
|
|
449
626
|
reset: () => {
|
|
450
|
-
|
|
451
|
-
router.notify()
|
|
452
|
-
},
|
|
453
|
-
startedLoadingAt: Date.now(),
|
|
454
|
-
subscribe: (listener: Listener): (() => void) => {
|
|
455
|
-
router.listeners.push(listener as Listener)
|
|
456
|
-
return () => {
|
|
457
|
-
router.listeners = router.listeners.filter((x) => x !== listener)
|
|
458
|
-
}
|
|
627
|
+
setStore((s) => Object.assign(s, getInitialRouterState()))
|
|
459
628
|
},
|
|
629
|
+
|
|
460
630
|
getRoute: (id) => {
|
|
461
631
|
return router.routesById[id]
|
|
462
632
|
},
|
|
463
|
-
notify: (): void => {
|
|
464
|
-
const isFetching =
|
|
465
|
-
router.state.status === 'loading' ||
|
|
466
|
-
router.state.currentMatches.some((d) => d.isFetching)
|
|
467
|
-
|
|
468
|
-
const isPreloading = Object.values(router.matchCache).some(
|
|
469
|
-
(d) =>
|
|
470
|
-
d.match.isFetching &&
|
|
471
|
-
!router.state.currentMatches.find(
|
|
472
|
-
(dd) => dd.matchId === d.match.matchId,
|
|
473
|
-
),
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
router.state.isFetching !== isFetching ||
|
|
478
|
-
router.state.isPreloading !== isPreloading
|
|
479
|
-
) {
|
|
480
|
-
router.state = {
|
|
481
|
-
...router.state,
|
|
482
|
-
isFetching,
|
|
483
|
-
isPreloading,
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
cascadeLoaderData(router.state.currentMatches)
|
|
488
|
-
router.listeners.forEach((listener) => listener(router))
|
|
489
|
-
},
|
|
490
633
|
|
|
491
634
|
dehydrate: () => {
|
|
492
635
|
return {
|
|
493
|
-
|
|
494
|
-
...pick(
|
|
636
|
+
store: {
|
|
637
|
+
...pick(store, [
|
|
495
638
|
'latestLocation',
|
|
496
639
|
'currentLocation',
|
|
497
640
|
'status',
|
|
498
641
|
'lastUpdated',
|
|
499
642
|
]),
|
|
500
|
-
currentMatches:
|
|
501
|
-
|
|
502
|
-
|
|
643
|
+
currentMatches: store.currentMatches.map((match) => ({
|
|
644
|
+
matchId: match.matchId,
|
|
645
|
+
store: pick(match.store, [
|
|
503
646
|
'status',
|
|
504
647
|
'routeLoaderData',
|
|
505
|
-
'loaderData',
|
|
506
648
|
'isInvalid',
|
|
507
649
|
'invalidAt',
|
|
508
650
|
]),
|
|
509
|
-
),
|
|
651
|
+
})),
|
|
510
652
|
},
|
|
511
653
|
context: router.options.context as TRouterContext,
|
|
512
654
|
}
|
|
513
655
|
},
|
|
514
656
|
|
|
515
|
-
hydrate: (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// Update the context
|
|
521
|
-
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
|
|
522
661
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
currentMatches.forEach((match, index) => {
|
|
532
|
-
const dehydratedMatch = dehydratedState.state.currentMatches[index]
|
|
533
|
-
invariant(
|
|
534
|
-
dehydratedMatch,
|
|
535
|
-
'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
|
+
},
|
|
536
668
|
)
|
|
537
|
-
Object.assign(match, dehydratedMatch)
|
|
538
|
-
})
|
|
539
669
|
|
|
540
|
-
|
|
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
|
+
})
|
|
541
678
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
...
|
|
545
|
-
|
|
546
|
-
}
|
|
679
|
+
currentMatches.forEach((match) => match.__.validate())
|
|
680
|
+
|
|
681
|
+
Object.assign(s, { ...dehydratedRouter.store, currentMatches })
|
|
682
|
+
})
|
|
547
683
|
},
|
|
548
684
|
|
|
549
685
|
mount: () => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
}
|
|
553
692
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
)
|
|
558
|
-
})
|
|
693
|
+
const unsub = router.history.listen((event) => {
|
|
694
|
+
router.load(parseLocation(event.location, store.latestLocation))
|
|
695
|
+
})
|
|
559
696
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
+
}
|
|
567
705
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
713
|
}
|
|
575
714
|
}
|
|
576
|
-
},
|
|
577
715
|
|
|
578
|
-
|
|
579
|
-
router.load()
|
|
716
|
+
return () => {}
|
|
580
717
|
},
|
|
581
718
|
|
|
582
719
|
update: (opts) => {
|
|
583
720
|
const newHistory = opts?.history !== router.history
|
|
584
|
-
if (!
|
|
721
|
+
if (!store.latestLocation || newHistory) {
|
|
585
722
|
if (opts?.history) {
|
|
586
723
|
router.history = opts.history
|
|
587
724
|
}
|
|
588
|
-
|
|
589
|
-
router.history.location
|
|
590
|
-
|
|
591
|
-
|
|
725
|
+
setStore((s) => {
|
|
726
|
+
s.latestLocation = parseLocation(router.history.location)
|
|
727
|
+
s.currentLocation = s.latestLocation
|
|
728
|
+
})
|
|
592
729
|
}
|
|
593
730
|
|
|
594
731
|
Object.assign(router.options, opts)
|
|
@@ -599,55 +736,50 @@ export function createRouter<
|
|
|
599
736
|
|
|
600
737
|
if (routeConfig) {
|
|
601
738
|
router.routesById = {} as any
|
|
602
|
-
router.routeTree =
|
|
739
|
+
router.routeTree = buildRouteTree(routeConfig)
|
|
603
740
|
}
|
|
604
741
|
|
|
605
742
|
return router as any
|
|
606
743
|
},
|
|
607
744
|
|
|
608
745
|
cancelMatches: () => {
|
|
609
|
-
;[
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
})
|
|
746
|
+
;[...store.currentMatches, ...(store.pendingMatches || [])].forEach(
|
|
747
|
+
(match) => {
|
|
748
|
+
match.cancel()
|
|
749
|
+
},
|
|
750
|
+
)
|
|
615
751
|
},
|
|
616
752
|
|
|
617
753
|
load: async (next?: Location) => {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (next) {
|
|
622
|
-
// Ingest the new location
|
|
623
|
-
router.state.latestLocation = next
|
|
624
|
-
}
|
|
754
|
+
let now = Date.now()
|
|
755
|
+
const startedAt = now
|
|
756
|
+
startedLoadingAt = startedAt
|
|
625
757
|
|
|
626
758
|
// Cancel any pending matches
|
|
627
759
|
router.cancelMatches()
|
|
628
760
|
|
|
629
|
-
|
|
630
|
-
const matches = router.matchRoutes(router.state.latestLocation.pathname, {
|
|
631
|
-
strictParseParams: true,
|
|
632
|
-
})
|
|
761
|
+
let matches!: RouteMatch<any, any>[]
|
|
633
762
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
} else {
|
|
642
|
-
router.state = {
|
|
643
|
-
...router.state,
|
|
644
|
-
status: 'loading',
|
|
645
|
-
currentMatches: matches,
|
|
646
|
-
currentLocation: router.state.latestLocation,
|
|
763
|
+
batch(() => {
|
|
764
|
+
if (next) {
|
|
765
|
+
// Ingest the new location
|
|
766
|
+
setStore((s) => {
|
|
767
|
+
s.latestLocation = next
|
|
768
|
+
})
|
|
647
769
|
}
|
|
648
|
-
}
|
|
649
770
|
|
|
650
|
-
|
|
771
|
+
// Match the routes
|
|
772
|
+
matches = router.matchRoutes(store.latestLocation.pathname, {
|
|
773
|
+
strictParseParams: true,
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
console.log('set loading', matches)
|
|
777
|
+
setStore((s) => {
|
|
778
|
+
s.status = 'loading'
|
|
779
|
+
s.pendingMatches = matches
|
|
780
|
+
s.pendingLocation = store.latestLocation
|
|
781
|
+
})
|
|
782
|
+
})
|
|
651
783
|
|
|
652
784
|
// Load the matches
|
|
653
785
|
try {
|
|
@@ -660,12 +792,12 @@ export function createRouter<
|
|
|
660
792
|
)
|
|
661
793
|
}
|
|
662
794
|
|
|
663
|
-
if (
|
|
664
|
-
// Ignore side-effects of
|
|
665
|
-
return
|
|
795
|
+
if (startedLoadingAt !== startedAt) {
|
|
796
|
+
// Ignore side-effects of outdated side-effects
|
|
797
|
+
return navigationPromise
|
|
666
798
|
}
|
|
667
799
|
|
|
668
|
-
const previousMatches =
|
|
800
|
+
const previousMatches = store.currentMatches
|
|
669
801
|
|
|
670
802
|
const exiting: RouteMatch[] = [],
|
|
671
803
|
staying: RouteMatch[] = []
|
|
@@ -682,18 +814,18 @@ export function createRouter<
|
|
|
682
814
|
return !previousMatches.find((dd) => dd.matchId === d.matchId)
|
|
683
815
|
})
|
|
684
816
|
|
|
685
|
-
|
|
817
|
+
now = Date.now()
|
|
686
818
|
|
|
687
819
|
exiting.forEach((d) => {
|
|
688
820
|
d.__.onExit?.({
|
|
689
821
|
params: d.params,
|
|
690
|
-
search: d.routeSearch,
|
|
822
|
+
search: d.store.routeSearch,
|
|
691
823
|
})
|
|
692
824
|
|
|
693
825
|
// Clear idle error states when match leaves
|
|
694
|
-
if (d.status === 'error' && !d.isFetching) {
|
|
695
|
-
d.status = 'idle'
|
|
696
|
-
d.error = undefined
|
|
826
|
+
if (d.store.status === 'error' && !d.store.isFetching) {
|
|
827
|
+
d.store.status = 'idle'
|
|
828
|
+
d.store.error = undefined
|
|
697
829
|
}
|
|
698
830
|
|
|
699
831
|
const gc = Math.max(
|
|
@@ -702,7 +834,7 @@ export function createRouter<
|
|
|
702
834
|
)
|
|
703
835
|
|
|
704
836
|
if (gc > 0) {
|
|
705
|
-
|
|
837
|
+
store.matchCache[d.matchId] = {
|
|
706
838
|
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
707
839
|
match: d,
|
|
708
840
|
}
|
|
@@ -712,19 +844,19 @@ export function createRouter<
|
|
|
712
844
|
staying.forEach((d) => {
|
|
713
845
|
d.options.onTransition?.({
|
|
714
846
|
params: d.params,
|
|
715
|
-
search: d.routeSearch,
|
|
847
|
+
search: d.store.routeSearch,
|
|
716
848
|
})
|
|
717
849
|
})
|
|
718
850
|
|
|
719
851
|
entering.forEach((d) => {
|
|
720
852
|
d.__.onExit = d.options.onLoaded?.({
|
|
721
853
|
params: d.params,
|
|
722
|
-
search: d.search,
|
|
854
|
+
search: d.store.search,
|
|
723
855
|
})
|
|
724
|
-
delete
|
|
856
|
+
delete store.matchCache[d.matchId]
|
|
725
857
|
})
|
|
726
858
|
|
|
727
|
-
if (
|
|
859
|
+
if (startedLoadingAt !== startedAt) {
|
|
728
860
|
// Ignore side-effects of match loading
|
|
729
861
|
return
|
|
730
862
|
}
|
|
@@ -732,46 +864,50 @@ export function createRouter<
|
|
|
732
864
|
matches.forEach((match) => {
|
|
733
865
|
// Clear actions
|
|
734
866
|
if (match.action) {
|
|
867
|
+
// TODO: Check reactivity here
|
|
735
868
|
match.action.current = undefined
|
|
736
869
|
match.action.submissions = []
|
|
737
870
|
}
|
|
738
871
|
})
|
|
739
872
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
+
})
|
|
748
883
|
|
|
749
|
-
|
|
750
|
-
router.resolveNavigation()
|
|
884
|
+
resolveNavigation()
|
|
751
885
|
},
|
|
752
886
|
|
|
753
887
|
cleanMatchCache: () => {
|
|
754
888
|
const now = Date.now()
|
|
755
889
|
|
|
756
|
-
|
|
757
|
-
|
|
890
|
+
setStore((s) => {
|
|
891
|
+
Object.keys(s.matchCache).forEach((matchId) => {
|
|
892
|
+
const entry = s.matchCache[matchId]!
|
|
758
893
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
894
|
+
// Don't remove loading matches
|
|
895
|
+
if (entry.match.store.status === 'loading') {
|
|
896
|
+
return
|
|
897
|
+
}
|
|
763
898
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
899
|
+
// Do not remove successful matches that are still valid
|
|
900
|
+
if (entry.gc > 0 && entry.gc > now) {
|
|
901
|
+
return
|
|
902
|
+
}
|
|
768
903
|
|
|
769
|
-
|
|
770
|
-
|
|
904
|
+
// Everything else gets removed
|
|
905
|
+
delete s.matchCache[matchId]
|
|
906
|
+
})
|
|
771
907
|
})
|
|
772
908
|
},
|
|
773
909
|
|
|
774
|
-
loadRoute: async (navigateOpts =
|
|
910
|
+
loadRoute: async (navigateOpts = store.latestLocation) => {
|
|
775
911
|
const next = router.buildNext(navigateOpts)
|
|
776
912
|
const matches = router.matchRoutes(next.pathname, {
|
|
777
913
|
strictParseParams: true,
|
|
@@ -780,10 +916,7 @@ export function createRouter<
|
|
|
780
916
|
return matches
|
|
781
917
|
},
|
|
782
918
|
|
|
783
|
-
preloadRoute: async (
|
|
784
|
-
navigateOpts = router.state.latestLocation,
|
|
785
|
-
loaderOpts,
|
|
786
|
-
) => {
|
|
919
|
+
preloadRoute: async (navigateOpts = store.latestLocation, loaderOpts) => {
|
|
787
920
|
const next = router.buildNext(navigateOpts)
|
|
788
921
|
const matches = router.matchRoutes(next.pathname, {
|
|
789
922
|
strictParseParams: true,
|
|
@@ -815,8 +948,8 @@ export function createRouter<
|
|
|
815
948
|
}
|
|
816
949
|
|
|
817
950
|
const existingMatches = [
|
|
818
|
-
...
|
|
819
|
-
...(
|
|
951
|
+
...store.currentMatches,
|
|
952
|
+
...(store.pendingMatches ?? []),
|
|
820
953
|
]
|
|
821
954
|
|
|
822
955
|
const recurse = async (routes: Route<any, any>[]): Promise<void> => {
|
|
@@ -847,14 +980,6 @@ export function createRouter<
|
|
|
847
980
|
route.options.caseSensitive ?? router.options.caseSensitive,
|
|
848
981
|
})
|
|
849
982
|
|
|
850
|
-
// console.log(
|
|
851
|
-
// router.basepath,
|
|
852
|
-
// route.fullPath,
|
|
853
|
-
// fuzzy,
|
|
854
|
-
// pathname,
|
|
855
|
-
// matchParams,
|
|
856
|
-
// )
|
|
857
|
-
|
|
858
983
|
if (matchParams) {
|
|
859
984
|
let parsedParams
|
|
860
985
|
|
|
@@ -895,7 +1020,7 @@ export function createRouter<
|
|
|
895
1020
|
|
|
896
1021
|
const match =
|
|
897
1022
|
existingMatches.find((d) => d.matchId === matchId) ||
|
|
898
|
-
|
|
1023
|
+
store.matchCache[matchId]?.match ||
|
|
899
1024
|
createRouteMatch(router, foundRoute, {
|
|
900
1025
|
parentMatch,
|
|
901
1026
|
matchId,
|
|
@@ -915,7 +1040,7 @@ export function createRouter<
|
|
|
915
1040
|
|
|
916
1041
|
recurse([router.routeTree])
|
|
917
1042
|
|
|
918
|
-
|
|
1043
|
+
linkMatches(matches)
|
|
919
1044
|
|
|
920
1045
|
return matches
|
|
921
1046
|
},
|
|
@@ -945,7 +1070,7 @@ export function createRouter<
|
|
|
945
1070
|
)
|
|
946
1071
|
|
|
947
1072
|
const matchPromises = resolvedMatches.map(async (match) => {
|
|
948
|
-
const search = match.search as { __data?: any }
|
|
1073
|
+
const search = match.store.search as { __data?: any }
|
|
949
1074
|
|
|
950
1075
|
if (search.__data?.matchId && search.__data.matchId !== match.matchId) {
|
|
951
1076
|
return
|
|
@@ -953,14 +1078,12 @@ export function createRouter<
|
|
|
953
1078
|
|
|
954
1079
|
match.load(loaderOpts)
|
|
955
1080
|
|
|
956
|
-
if (match.status !== 'success' && match.__.loadPromise) {
|
|
1081
|
+
if (match.store.status !== 'success' && match.__.loadPromise) {
|
|
957
1082
|
// Wait for the first sign of activity from the match
|
|
958
1083
|
await match.__.loadPromise
|
|
959
1084
|
}
|
|
960
1085
|
})
|
|
961
1086
|
|
|
962
|
-
router.notify()
|
|
963
|
-
|
|
964
1087
|
await Promise.all(matchPromises)
|
|
965
1088
|
},
|
|
966
1089
|
|
|
@@ -970,9 +1093,9 @@ export function createRouter<
|
|
|
970
1093
|
(await routeMatch.options.loader?.({
|
|
971
1094
|
// parentLoaderPromise: routeMatch.parentMatch?.__.dataPromise,
|
|
972
1095
|
params: routeMatch.params,
|
|
973
|
-
search: routeMatch.routeSearch,
|
|
1096
|
+
search: routeMatch.store.routeSearch,
|
|
974
1097
|
signal: routeMatch.__.abortController.signal,
|
|
975
|
-
}))
|
|
1098
|
+
})) || {}
|
|
976
1099
|
)
|
|
977
1100
|
} else {
|
|
978
1101
|
const next = router.buildNext({
|
|
@@ -1013,18 +1136,17 @@ export function createRouter<
|
|
|
1013
1136
|
const unloadedMatchIds = router
|
|
1014
1137
|
.matchRoutes(next.pathname)
|
|
1015
1138
|
.map((d) => d.matchId)
|
|
1016
|
-
;[
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
})
|
|
1139
|
+
;[...store.currentMatches, ...(store.pendingMatches ?? [])].forEach(
|
|
1140
|
+
(match) => {
|
|
1141
|
+
if (unloadedMatchIds.includes(match.matchId)) {
|
|
1142
|
+
match.invalidate()
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
)
|
|
1024
1146
|
},
|
|
1025
1147
|
|
|
1026
1148
|
reload: () =>
|
|
1027
|
-
|
|
1149
|
+
navigate({
|
|
1028
1150
|
fromCurrent: true,
|
|
1029
1151
|
replace: true,
|
|
1030
1152
|
search: true,
|
|
@@ -1047,13 +1169,13 @@ export function createRouter<
|
|
|
1047
1169
|
const next = router.buildNext(location)
|
|
1048
1170
|
|
|
1049
1171
|
if (opts?.pending) {
|
|
1050
|
-
if (!
|
|
1172
|
+
if (!store.pendingLocation) {
|
|
1051
1173
|
return false
|
|
1052
1174
|
}
|
|
1053
1175
|
|
|
1054
1176
|
return !!matchPathname(
|
|
1055
1177
|
router.basepath,
|
|
1056
|
-
|
|
1178
|
+
store.pendingLocation.pathname,
|
|
1057
1179
|
{
|
|
1058
1180
|
...opts,
|
|
1059
1181
|
to: next.pathname,
|
|
@@ -1061,14 +1183,10 @@ export function createRouter<
|
|
|
1061
1183
|
)
|
|
1062
1184
|
}
|
|
1063
1185
|
|
|
1064
|
-
return
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
...opts,
|
|
1069
|
-
to: next.pathname,
|
|
1070
|
-
},
|
|
1071
|
-
)
|
|
1186
|
+
return matchPathname(router.basepath, store.currentLocation.pathname, {
|
|
1187
|
+
...opts,
|
|
1188
|
+
to: next.pathname,
|
|
1189
|
+
}) as any
|
|
1072
1190
|
},
|
|
1073
1191
|
|
|
1074
1192
|
navigate: async ({ from, to = '.', search, hash, replace, params }) => {
|
|
@@ -1092,7 +1210,7 @@ export function createRouter<
|
|
|
1092
1210
|
'Attempting to navigate to external url with router.navigate!',
|
|
1093
1211
|
)
|
|
1094
1212
|
|
|
1095
|
-
return
|
|
1213
|
+
return navigate({
|
|
1096
1214
|
from: fromString,
|
|
1097
1215
|
to: toString,
|
|
1098
1216
|
search,
|
|
@@ -1147,14 +1265,13 @@ export function createRouter<
|
|
|
1147
1265
|
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
1148
1266
|
|
|
1149
1267
|
// Compare path/hash for matches
|
|
1150
|
-
const pathIsEqual =
|
|
1151
|
-
|
|
1152
|
-
const currentPathSplit = router.state.currentLocation.pathname.split('/')
|
|
1268
|
+
const pathIsEqual = store.currentLocation.pathname === next.pathname
|
|
1269
|
+
const currentPathSplit = store.currentLocation.pathname.split('/')
|
|
1153
1270
|
const nextPathSplit = next.pathname.split('/')
|
|
1154
1271
|
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
1155
1272
|
(d, i) => d === currentPathSplit[i],
|
|
1156
1273
|
)
|
|
1157
|
-
const hashIsEqual =
|
|
1274
|
+
const hashIsEqual = store.currentLocation.hash === next.hash
|
|
1158
1275
|
// Combine the matches based on user options
|
|
1159
1276
|
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
1160
1277
|
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
@@ -1176,8 +1293,8 @@ export function createRouter<
|
|
|
1176
1293
|
router.invalidateRoute(nextOpts)
|
|
1177
1294
|
}
|
|
1178
1295
|
|
|
1179
|
-
// All is well? Navigate!
|
|
1180
|
-
|
|
1296
|
+
// All is well? Navigate!
|
|
1297
|
+
navigate(nextOpts)
|
|
1181
1298
|
}
|
|
1182
1299
|
}
|
|
1183
1300
|
|
|
@@ -1240,7 +1357,7 @@ export function createRouter<
|
|
|
1240
1357
|
}
|
|
1241
1358
|
},
|
|
1242
1359
|
buildNext: (opts: BuildNextOptions) => {
|
|
1243
|
-
const next =
|
|
1360
|
+
const next = buildLocation(opts)
|
|
1244
1361
|
|
|
1245
1362
|
const matches = router.matchRoutes(next.pathname)
|
|
1246
1363
|
|
|
@@ -1254,217 +1371,12 @@ export function createRouter<
|
|
|
1254
1371
|
.flat()
|
|
1255
1372
|
.filter(Boolean)
|
|
1256
1373
|
|
|
1257
|
-
return
|
|
1374
|
+
return buildLocation({
|
|
1258
1375
|
...opts,
|
|
1259
1376
|
__preSearchFilters,
|
|
1260
1377
|
__postSearchFilters,
|
|
1261
1378
|
})
|
|
1262
1379
|
},
|
|
1263
|
-
|
|
1264
|
-
__: {
|
|
1265
|
-
buildRouteTree: (rootRouteConfig: RouteConfig) => {
|
|
1266
|
-
const recurseRoutes = (
|
|
1267
|
-
routeConfigs: RouteConfig[],
|
|
1268
|
-
parent?: Route<TAllRouteInfo, any, any>,
|
|
1269
|
-
): Route<TAllRouteInfo, any, any>[] => {
|
|
1270
|
-
return routeConfigs.map((routeConfig) => {
|
|
1271
|
-
const routeOptions = routeConfig.options
|
|
1272
|
-
const route = createRoute(routeConfig, routeOptions, parent, router)
|
|
1273
|
-
const existingRoute = (router.routesById as any)[route.routeId]
|
|
1274
|
-
|
|
1275
|
-
if (existingRoute) {
|
|
1276
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1277
|
-
console.warn(
|
|
1278
|
-
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
1279
|
-
router.routesById,
|
|
1280
|
-
route,
|
|
1281
|
-
)
|
|
1282
|
-
}
|
|
1283
|
-
throw new Error()
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
;(router.routesById as any)[route.routeId] = route
|
|
1287
|
-
|
|
1288
|
-
const children = routeConfig.children as RouteConfig[]
|
|
1289
|
-
|
|
1290
|
-
route.childRoutes = children?.length
|
|
1291
|
-
? recurseRoutes(children, route)
|
|
1292
|
-
: undefined
|
|
1293
|
-
|
|
1294
|
-
return route
|
|
1295
|
-
})
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
const routes = recurseRoutes([rootRouteConfig])
|
|
1299
|
-
|
|
1300
|
-
return routes[0]!
|
|
1301
|
-
},
|
|
1302
|
-
|
|
1303
|
-
parseLocation: (
|
|
1304
|
-
location: History['location'],
|
|
1305
|
-
previousLocation?: Location,
|
|
1306
|
-
): Location => {
|
|
1307
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1308
|
-
|
|
1309
|
-
return {
|
|
1310
|
-
pathname: location.pathname,
|
|
1311
|
-
searchStr: location.search,
|
|
1312
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1313
|
-
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
1314
|
-
href: `${location.pathname}${location.search}${location.hash}`,
|
|
1315
|
-
state: location.state as LocationState,
|
|
1316
|
-
key: location.key,
|
|
1317
|
-
}
|
|
1318
|
-
},
|
|
1319
|
-
|
|
1320
|
-
navigate: (location: BuildNextOptions & { replace?: boolean }) => {
|
|
1321
|
-
const next = router.buildNext(location)
|
|
1322
|
-
return router.__.commitLocation(next, location.replace)
|
|
1323
|
-
},
|
|
1324
|
-
|
|
1325
|
-
buildLocation: (dest: BuildNextOptions = {}): Location => {
|
|
1326
|
-
const fromPathname = dest.fromCurrent
|
|
1327
|
-
? router.state.latestLocation.pathname
|
|
1328
|
-
: dest.from ?? router.state.latestLocation.pathname
|
|
1329
|
-
|
|
1330
|
-
let pathname = resolvePath(
|
|
1331
|
-
router.basepath ?? '/',
|
|
1332
|
-
fromPathname,
|
|
1333
|
-
`${dest.to ?? '.'}`,
|
|
1334
|
-
)
|
|
1335
|
-
|
|
1336
|
-
const fromMatches = router.matchRoutes(
|
|
1337
|
-
router.state.latestLocation.pathname,
|
|
1338
|
-
{
|
|
1339
|
-
strictParseParams: true,
|
|
1340
|
-
},
|
|
1341
|
-
)
|
|
1342
|
-
|
|
1343
|
-
const toMatches = router.matchRoutes(pathname)
|
|
1344
|
-
|
|
1345
|
-
const prevParams = { ...last(fromMatches)?.params }
|
|
1346
|
-
|
|
1347
|
-
let nextParams =
|
|
1348
|
-
(dest.params ?? true) === true
|
|
1349
|
-
? prevParams
|
|
1350
|
-
: functionalUpdate(dest.params!, prevParams)
|
|
1351
|
-
|
|
1352
|
-
if (nextParams) {
|
|
1353
|
-
toMatches
|
|
1354
|
-
.map((d) => d.options.stringifyParams)
|
|
1355
|
-
.filter(Boolean)
|
|
1356
|
-
.forEach((fn) => {
|
|
1357
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1358
|
-
})
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1362
|
-
|
|
1363
|
-
// Pre filters first
|
|
1364
|
-
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1365
|
-
? dest.__preSearchFilters.reduce(
|
|
1366
|
-
(prev, next) => next(prev),
|
|
1367
|
-
router.state.latestLocation.search,
|
|
1368
|
-
)
|
|
1369
|
-
: router.state.latestLocation.search
|
|
1370
|
-
|
|
1371
|
-
// Then the link/navigate function
|
|
1372
|
-
const destSearch =
|
|
1373
|
-
dest.search === true
|
|
1374
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1375
|
-
: dest.search
|
|
1376
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1377
|
-
: dest.__preSearchFilters?.length
|
|
1378
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1379
|
-
: {}
|
|
1380
|
-
|
|
1381
|
-
// Then post filters
|
|
1382
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1383
|
-
? dest.__postSearchFilters.reduce(
|
|
1384
|
-
(prev, next) => next(prev),
|
|
1385
|
-
destSearch,
|
|
1386
|
-
)
|
|
1387
|
-
: destSearch
|
|
1388
|
-
|
|
1389
|
-
const search = replaceEqualDeep(
|
|
1390
|
-
router.state.latestLocation.search,
|
|
1391
|
-
postFilteredSearch,
|
|
1392
|
-
)
|
|
1393
|
-
|
|
1394
|
-
const searchStr = router.options.stringifySearch(search)
|
|
1395
|
-
let hash =
|
|
1396
|
-
dest.hash === true
|
|
1397
|
-
? router.state.latestLocation.hash
|
|
1398
|
-
: functionalUpdate(dest.hash!, router.state.latestLocation.hash)
|
|
1399
|
-
hash = hash ? `#${hash}` : ''
|
|
1400
|
-
|
|
1401
|
-
return {
|
|
1402
|
-
pathname,
|
|
1403
|
-
search,
|
|
1404
|
-
searchStr,
|
|
1405
|
-
state: router.state.latestLocation.state,
|
|
1406
|
-
hash,
|
|
1407
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1408
|
-
key: dest.key,
|
|
1409
|
-
}
|
|
1410
|
-
},
|
|
1411
|
-
|
|
1412
|
-
commitLocation: (next: Location, replace?: boolean): Promise<void> => {
|
|
1413
|
-
const id = '' + Date.now() + Math.random()
|
|
1414
|
-
|
|
1415
|
-
if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
|
|
1416
|
-
|
|
1417
|
-
let nextAction: 'push' | 'replace' = 'replace'
|
|
1418
|
-
|
|
1419
|
-
if (!replace) {
|
|
1420
|
-
nextAction = 'push'
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
const isSameUrl =
|
|
1424
|
-
router.__.parseLocation(history.location).href === next.href
|
|
1425
|
-
|
|
1426
|
-
if (isSameUrl && !next.key) {
|
|
1427
|
-
nextAction = 'replace'
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
if (nextAction === 'replace') {
|
|
1431
|
-
history.replace(
|
|
1432
|
-
{
|
|
1433
|
-
pathname: next.pathname,
|
|
1434
|
-
hash: next.hash,
|
|
1435
|
-
search: next.searchStr,
|
|
1436
|
-
},
|
|
1437
|
-
{
|
|
1438
|
-
id,
|
|
1439
|
-
...next.state,
|
|
1440
|
-
},
|
|
1441
|
-
)
|
|
1442
|
-
} else {
|
|
1443
|
-
history.push(
|
|
1444
|
-
{
|
|
1445
|
-
pathname: next.pathname,
|
|
1446
|
-
hash: next.hash,
|
|
1447
|
-
search: next.searchStr,
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
id,
|
|
1451
|
-
},
|
|
1452
|
-
)
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
router.navigationPromise = new Promise((resolve) => {
|
|
1456
|
-
const previousNavigationResolve = router.resolveNavigation
|
|
1457
|
-
|
|
1458
|
-
router.resolveNavigation = () => {
|
|
1459
|
-
previousNavigationResolve()
|
|
1460
|
-
resolve()
|
|
1461
|
-
delete router.navigationPromise
|
|
1462
|
-
}
|
|
1463
|
-
})
|
|
1464
|
-
|
|
1465
|
-
return router.navigationPromise
|
|
1466
|
-
},
|
|
1467
|
-
},
|
|
1468
1380
|
}
|
|
1469
1381
|
|
|
1470
1382
|
router.update(userOptions)
|
|
@@ -1479,15 +1391,14 @@ function isCtrlEvent(e: MouseEvent) {
|
|
|
1479
1391
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1480
1392
|
}
|
|
1481
1393
|
|
|
1482
|
-
function
|
|
1394
|
+
function linkMatches(matches: RouteMatch<any, any>[]) {
|
|
1483
1395
|
matches.forEach((match, index) => {
|
|
1484
1396
|
const parent = matches[index - 1]
|
|
1485
1397
|
|
|
1486
1398
|
if (parent) {
|
|
1487
|
-
match.
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
})
|
|
1399
|
+
match.__.setParentMatch(parent)
|
|
1400
|
+
} else {
|
|
1401
|
+
match.__.setParentMatch(undefined)
|
|
1491
1402
|
}
|
|
1492
1403
|
})
|
|
1493
1404
|
}
|