@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.
Files changed (38) hide show
  1. package/build/cjs/index.js +2 -1
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/cjs/path.js +5 -7
  4. package/build/cjs/path.js.map +1 -1
  5. package/build/cjs/route.js +112 -96
  6. package/build/cjs/route.js.map +1 -1
  7. package/build/cjs/routeConfig.js +2 -2
  8. package/build/cjs/routeConfig.js.map +1 -1
  9. package/build/cjs/routeMatch.js +107 -65
  10. package/build/cjs/routeMatch.js.map +1 -1
  11. package/build/cjs/router.js +352 -372
  12. package/build/cjs/router.js.map +1 -1
  13. package/build/cjs/searchParams.js +4 -3
  14. package/build/cjs/searchParams.js.map +1 -1
  15. package/build/cjs/sharedClone.js +122 -0
  16. package/build/cjs/sharedClone.js.map +1 -0
  17. package/build/cjs/utils.js +1 -59
  18. package/build/cjs/utils.js.map +1 -1
  19. package/build/esm/index.js +686 -614
  20. package/build/esm/index.js.map +1 -1
  21. package/build/stats-html.html +1 -1
  22. package/build/stats-react.json +183 -158
  23. package/build/types/index.d.ts +61 -78
  24. package/build/umd/index.development.js +1032 -617
  25. package/build/umd/index.development.js.map +1 -1
  26. package/build/umd/index.production.js +1 -1
  27. package/build/umd/index.production.js.map +1 -1
  28. package/package.json +2 -1
  29. package/src/index.ts +1 -0
  30. package/src/link.ts +20 -12
  31. package/src/route.ts +160 -140
  32. package/src/routeConfig.ts +7 -2
  33. package/src/routeMatch.ts +146 -99
  34. package/src/router.ts +462 -523
  35. package/src/sharedClone.ts +118 -0
  36. package/src/utils.ts +0 -65
  37. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -31
  38. 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
- NavigateOptionsAbsolute,
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 RouterState<
199
+ export interface RouterStore<
200
200
  TSearchObj extends AnySearchSchema = {},
201
201
  TState extends LocationState = LocationState,
202
202
  > {
203
203
  status: 'idle' | 'loading'
204
- location: Location<TSearchObj, TState>
205
- matches: RouteMatch[]
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
- RouterState,
262
- 'status' | 'location' | 'lastUpdated' | 'location'
259
+ RouterStore,
260
+ 'status' | 'latestLocation' | 'currentLocation' | 'lastUpdated'
263
261
  > {
264
- matches: DehydratedRouteMatch[]
262
+ currentMatches: DehydratedRouteMatch[]
265
263
  }
266
264
 
267
265
  export interface DehydratedRouter<TRouterContext = unknown> {
268
- location: Router['__location']
269
- state: DehydratedRouterState
266
+ // location: Router['__location']
267
+ store: DehydratedRouterState
270
268
  context: TRouterContext
271
269
  }
272
270
 
273
- interface DehydratedRouteMatch
274
- extends Pick<
275
- RouteMatch<any, any>,
276
- | 'matchId'
277
- | 'status'
278
- | 'routeLoaderData'
279
- | 'loaderData'
280
- | 'isInvalid'
281
- | 'invalidAt'
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
- // Computed in this.update()
300
+ store: RouterStore<TAllRouteInfo['fullSearchSchema']>
301
+ setStore: SetStoreFunction<RouterStore<TAllRouteInfo['fullSearchSchema']>>
304
302
  basepath: string
305
- // Internal:
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: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
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
- ) => boolean
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(): RouterState {
380
+ function getInitialRouterState(): RouterStore {
404
381
  return {
405
382
  status: 'idle',
406
- location: null!,
407
- matches: [],
383
+ latestLocation: null!,
384
+ currentLocation: null!,
385
+ currentMatches: [],
408
386
  actions: {},
409
387
  loaders: {},
410
388
  lastUpdated: Date.now(),
411
- isFetching: false,
412
- isPreloading: false,
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
- let router: Router<TRouteConfig, TAllRouteInfo, TRouterContext> = {
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
- __location: undefined!,
448
- //
449
- resolveNavigation: () => {},
450
- matchCache: {},
451
- state: getInitialRouterState(),
625
+
452
626
  reset: () => {
453
- router.state = getInitialRouterState()
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
- location: router.__location,
495
- state: {
496
- ...pick(router.state, [
636
+ store: {
637
+ ...pick(store, [
638
+ 'latestLocation',
639
+ 'currentLocation',
497
640
  'status',
498
- 'location',
499
641
  'lastUpdated',
500
- 'location',
501
642
  ]),
502
- matches: router.state.matches.map((match) =>
503
- pick(match, [
504
- 'matchId',
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: (dehydratedState) => {
518
- // Update the location
519
- router.__location = dehydratedState.location
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
- // Match the routes
525
- const matches = router.matchRoutes(router.__location.pathname, {
526
- strictParseParams: true,
527
- })
528
-
529
- matches.forEach((match, index) => {
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
- matches.forEach((match) => match.__.validate())
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
- router.state = {
541
- ...router.state,
542
- ...dehydratedState,
543
- matches,
544
- }
681
+ Object.assign(s, { ...dehydratedRouter.store, currentMatches })
682
+ })
545
683
  },
546
684
 
547
685
  mount: () => {
548
- const next = router.__.buildLocation({
549
- to: '.',
550
- search: true,
551
- hash: true,
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
- const unsub = router.history.listen((event) => {
565
- router.load(router.__.parseLocation(event.location, router.__location))
566
- })
693
+ const unsub = router.history.listen((event) => {
694
+ router.load(parseLocation(event.location, store.latestLocation))
695
+ })
567
696
 
568
- // addEventListener does not exist in React Native, but window does
569
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
570
- if (!isServer && window.addEventListener) {
571
- // Listen to visibillitychange and focus
572
- window.addEventListener('visibilitychange', router.onFocus, false)
573
- window.addEventListener('focus', router.onFocus, false)
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
- return () => {
577
- unsub()
578
- if (!isServer && window.removeEventListener) {
579
- // Be sure to unsubscribe if a new handler is set
580
- window.removeEventListener('visibilitychange', router.onFocus)
581
- window.removeEventListener('focus', router.onFocus)
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
- onFocus: () => {
587
- router.load()
716
+ return () => {}
588
717
  },
589
718
 
590
719
  update: (opts) => {
591
720
  const newHistory = opts?.history !== router.history
592
- if (!router.__location || newHistory) {
721
+ if (!store.latestLocation || newHistory) {
593
722
  if (opts?.history) {
594
723
  router.history = opts.history
595
724
  }
596
- router.__location = router.__.parseLocation(router.history.location)
597
- router.state.location = router.__location
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 = router.__.buildRouteTree(routeConfig)
739
+ router.routeTree = buildRouteTree(routeConfig)
609
740
  }
610
741
 
611
742
  return router as any
612
743
  },
613
744
 
614
745
  cancelMatches: () => {
615
- ;[
616
- ...router.state.matches,
617
- ...(router.state.pending?.matches ?? []),
618
- ].forEach((match) => {
619
- match.cancel()
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
- const id = Math.random()
625
- router.startedLoadingAt = id
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
- // Match the routes
636
- const matches = router.matchRoutes(router.__location.pathname, {
637
- strictParseParams: true,
638
- })
761
+ let matches!: RouteMatch<any, any>[]
639
762
 
640
- if (typeof document !== 'undefined') {
641
- router.state = {
642
- ...router.state,
643
- pending: {
644
- matches: matches,
645
- location: router.__location,
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
- // Check if each match middleware to see if the route can be accessed
659
- try {
660
- await Promise.all(
661
- matches.map((match) =>
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
- router.notify()
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
- await router.loadMatches(matches)
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 (router.startedLoadingAt !== id) {
679
- // Ignore side-effects of match loading
680
- return router.navigationPromise
795
+ if (startedLoadingAt !== startedAt) {
796
+ // Ignore side-effects of outdated side-effects
797
+ return navigationPromise
681
798
  }
682
799
 
683
- const previousMatches = router.state.matches
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
- const now = Date.now()
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
- router.matchCache[d.matchId] = {
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 router.matchCache[d.matchId]
856
+ delete store.matchCache[d.matchId]
740
857
  })
741
858
 
742
- if (router.startedLoadingAt !== id) {
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
- router.state = {
756
- ...router.state,
757
- location: router.__location,
758
- matches,
759
- pending: undefined,
760
- status: 'idle',
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
- router.notify()
764
- router.resolveNavigation()
884
+ resolveNavigation()
765
885
  },
766
886
 
767
887
  cleanMatchCache: () => {
768
888
  const now = Date.now()
769
889
 
770
- Object.keys(router.matchCache).forEach((matchId) => {
771
- const entry = router.matchCache[matchId]!
890
+ setStore((s) => {
891
+ Object.keys(s.matchCache).forEach((matchId) => {
892
+ const entry = s.matchCache[matchId]!
772
893
 
773
- // Don't remove loading matches
774
- if (entry.match.status === 'loading') {
775
- return
776
- }
894
+ // Don't remove loading matches
895
+ if (entry.match.store.status === 'loading') {
896
+ return
897
+ }
777
898
 
778
- // Do not remove successful matches that are still valid
779
- if (entry.gc > 0 && entry.gc > now) {
780
- return
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
- // Everything else gets removed
784
- delete router.matchCache[matchId]
904
+ // Everything else gets removed
905
+ delete s.matchCache[matchId]
906
+ })
785
907
  })
786
908
  },
787
909
 
788
- loadRoute: async (navigateOpts = router.__location) => {
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 = router.__location, loaderOpts) => {
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
- ...router.state.matches,
829
- ...(router.state.pending?.matches ?? []),
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
- router.matchCache[matchId]?.match ||
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
- cascadeLoaderData(matches)
1043
+ linkMatches(matches)
929
1044
 
930
1045
  return matches
931
1046
  },
932
1047
 
933
1048
  loadMatches: async (resolvedMatches, loaderOpts) => {
934
- const matchPromises = resolvedMatches.map(async (match) => {
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
- const search = match.search as { __data?: any }
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
- ...router.state.matches,
1008
- ...(router.state.pending?.matches ?? []),
1009
- ].forEach((match) => {
1010
- if (unloadedMatchIds.includes(match.matchId)) {
1011
- match.invalidate()
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
- router.__.navigate({
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 (!router.state.pending?.location) {
1172
+ if (!store.pendingLocation) {
1041
1173
  return false
1042
1174
  }
1043
1175
 
1044
1176
  return !!matchPathname(
1045
1177
  router.basepath,
1046
- router.state.pending.location.pathname,
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 !!matchPathname(router.basepath, router.state.location.pathname, {
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 router.__.navigate({
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 = router.state.location.pathname === next.pathname
1137
- const currentPathSplit = router.state.location.pathname.split('/')
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 = router.state.location.hash === next.hash
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
- router.__.navigate(nextOpts)
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.preloadRoute(nextOpts, {
1173
- maxAge: userPreloadMaxAge,
1174
- gcMaxAge: userPreloadGcMaxAge,
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.preloadRoute(nextOpts, {
1190
- maxAge: userPreloadMaxAge,
1191
- gcMaxAge: userPreloadGcMaxAge,
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 = router.__.buildLocation(opts)
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 router.__.buildLocation({
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 cascadeLoaderData(matches: RouteMatch<any, any>[]) {
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.loaderData = replaceEqualDeep(match.loaderData, {
1460
- ...parent.loaderData,
1461
- ...match.routeLoaderData,
1462
- })
1399
+ match.__.setParentMatch(parent)
1400
+ } else {
1401
+ match.__.setParentMatch(undefined)
1463
1402
  }
1464
1403
  })
1465
1404
  }