@tanstack/router-core 0.0.1-alpha.9 → 0.0.1-beta.2

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/src/router.ts CHANGED
@@ -25,8 +25,11 @@ import {
25
25
  } from './path'
26
26
  import { AnyRoute, cascadeLoaderData, createRoute, Route } from './route'
27
27
  import {
28
+ AnyLoaderData,
29
+ AnyPathParams,
28
30
  AnyRouteConfig,
29
31
  AnySearchSchema,
32
+ LoaderContext,
30
33
  RouteConfig,
31
34
  SearchFilter,
32
35
  } from './routeConfig'
@@ -43,6 +46,7 @@ import {
43
46
  functionalUpdate,
44
47
  last,
45
48
  PickAsRequired,
49
+ PickRequired,
46
50
  replaceEqualDeep,
47
51
  Timeout,
48
52
  Updater,
@@ -99,6 +103,9 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
99
103
  basepath?: string
100
104
  createRouter?: (router: Router<any, any>) => void
101
105
  createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
106
+ createElement?: (
107
+ element: GetFrameworkGeneric<'SyncOrAsyncElement'>,
108
+ ) => Promise<GetFrameworkGeneric<'Element'>>
102
109
  }
103
110
 
104
111
  export interface Action<
@@ -124,6 +131,42 @@ export interface ActionState<
124
131
  error?: unknown
125
132
  }
126
133
 
134
+ export interface Loader<
135
+ TFullSearchSchema extends AnySearchSchema = {},
136
+ TAllParams extends AnyPathParams = {},
137
+ TRouteLoaderData = AnyLoaderData,
138
+ > {
139
+ fetch: keyof PickRequired<TFullSearchSchema> extends never
140
+ ? keyof TAllParams extends never
141
+ ? (loaderContext: { signal?: AbortSignal }) => Promise<TRouteLoaderData>
142
+ : (loaderContext: {
143
+ params: TAllParams
144
+ search?: TFullSearchSchema
145
+ signal?: AbortSignal
146
+ }) => Promise<TRouteLoaderData>
147
+ : keyof TAllParams extends never
148
+ ? (loaderContext: {
149
+ search: TFullSearchSchema
150
+ params: TAllParams
151
+ signal?: AbortSignal
152
+ }) => Promise<TRouteLoaderData>
153
+ : (loaderContext: {
154
+ search: TFullSearchSchema
155
+ signal?: AbortSignal
156
+ }) => Promise<TRouteLoaderData>
157
+ current?: LoaderState<TFullSearchSchema, TAllParams>
158
+ latest?: LoaderState<TFullSearchSchema, TAllParams>
159
+ pending: LoaderState<TFullSearchSchema, TAllParams>[]
160
+ }
161
+
162
+ export interface LoaderState<
163
+ TFullSearchSchema = unknown,
164
+ TAllParams = unknown,
165
+ > {
166
+ loadedAt: number
167
+ loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
168
+ }
169
+
127
170
  export interface RouterState {
128
171
  status: 'idle' | 'loading'
129
172
  location: Location
@@ -133,6 +176,7 @@ export interface RouterState {
133
176
  currentAction?: ActionState
134
177
  latestAction?: ActionState
135
178
  actions: Record<string, Action>
179
+ loaders: Record<string, Loader>
136
180
  pending?: PendingState
137
181
  isFetching: boolean
138
182
  isPreloading: boolean
@@ -143,7 +187,7 @@ export interface PendingState {
143
187
  matches: RouteMatch[]
144
188
  }
145
189
 
146
- type Listener = () => void
190
+ type Listener = (router: Router<any, any>) => void
147
191
 
148
192
  export type ListenerFn = () => void
149
193
 
@@ -185,6 +229,7 @@ export interface Router<
185
229
  TRouteConfig extends AnyRouteConfig = RouteConfig,
186
230
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
187
231
  > {
232
+ history: BrowserHistory | MemoryHistory | HashHistory
188
233
  options: PickAsRequired<
189
234
  RouterOptions<TRouteConfig>,
190
235
  'stringifySearch' | 'parseSearch'
@@ -211,15 +256,7 @@ export interface Router<
211
256
  update: <TRouteConfig extends RouteConfig = RouteConfig>(
212
257
  opts?: RouterOptions<TRouteConfig>,
213
258
  ) => Router<TRouteConfig>
214
- buildRouteTree: (
215
- routeConfig: RouteConfig,
216
- ) => Route<TAllRouteInfo, AnyRouteInfo>
217
- parseLocation: (
218
- location: History['location'],
219
- previousLocation?: Location,
220
- ) => Location
221
- buildLocation: (dest: BuildNextOptions) => Location
222
- commitLocation: (next: Location, replace?: boolean) => Promise<void>
259
+
223
260
  buildNext: (opts: BuildNextOptions) => Location
224
261
  cancelMatches: () => void
225
262
  loadLocation: (next?: Location) => Promise<void>
@@ -247,9 +284,6 @@ export interface Router<
247
284
  invalidateRoute: (opts: MatchLocation) => void
248
285
  reload: () => Promise<void>
249
286
  resolvePath: (from: string, path: string) => string
250
- _navigate: (
251
- location: BuildNextOptions & { replace?: boolean },
252
- ) => Promise<void>
253
287
  navigate: <
254
288
  TFrom extends ValidFromPath<TAllRouteInfo> = '/',
255
289
  TTo extends string = '.',
@@ -269,6 +303,20 @@ export interface Router<
269
303
  >(
270
304
  opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
271
305
  ) => LinkInfo
306
+ __: {
307
+ buildRouteTree: (
308
+ routeConfig: RouteConfig,
309
+ ) => Route<TAllRouteInfo, AnyRouteInfo>
310
+ parseLocation: (
311
+ location: History['location'],
312
+ previousLocation?: Location,
313
+ ) => Location
314
+ buildLocation: (dest: BuildNextOptions) => Location
315
+ commitLocation: (next: Location, replace?: boolean) => Promise<void>
316
+ navigate: (
317
+ location: BuildNextOptions & { replace?: boolean },
318
+ ) => Promise<void>
319
+ }
272
320
  }
273
321
 
274
322
  // Detect if we're in the DOM
@@ -299,6 +347,7 @@ export function createRouter<
299
347
  }
300
348
 
301
349
  let router: Router<TRouteConfig, TAllRouteInfo> = {
350
+ history,
302
351
  options: originalOptions,
303
352
  listeners: [],
304
353
  removeActionQueue: [],
@@ -317,6 +366,7 @@ export function createRouter<
317
366
  location: null!,
318
367
  matches: [],
319
368
  actions: {},
369
+ loaders: {},
320
370
  loaderData: {} as any,
321
371
  lastUpdated: Date.now(),
322
372
  isFetching: false,
@@ -346,11 +396,11 @@ export function createRouter<
346
396
  }
347
397
 
348
398
  cascadeLoaderData(router.state.matches)
349
- router.listeners.forEach((listener) => listener())
399
+ router.listeners.forEach((listener) => listener(router))
350
400
  },
351
401
 
352
402
  mount: () => {
353
- const next = router.buildLocation({
403
+ const next = router.__.buildLocation({
354
404
  to: '.',
355
405
  search: true,
356
406
  hash: true,
@@ -359,14 +409,14 @@ export function createRouter<
359
409
  // If the current location isn't updated, trigger a navigation
360
410
  // to the current location. Otherwise, load the current location.
361
411
  if (next.href !== router.location.href) {
362
- router.commitLocation(next, true)
412
+ router.__.commitLocation(next, true)
363
413
  } else {
364
414
  router.loadLocation()
365
415
  }
366
416
 
367
417
  const unsub = history.listen((event) => {
368
418
  router.loadLocation(
369
- router.parseLocation(event.location, router.location),
419
+ router.__.parseLocation(event.location, router.location),
370
420
  )
371
421
  })
372
422
 
@@ -399,235 +449,12 @@ export function createRouter<
399
449
 
400
450
  if (routeConfig) {
401
451
  router.routesById = {} as any
402
- router.routeTree = router.buildRouteTree(routeConfig)
452
+ router.routeTree = router.__.buildRouteTree(routeConfig)
403
453
  }
404
454
 
405
455
  return router as any
406
456
  },
407
457
 
408
- buildRouteTree: (rootRouteConfig: RouteConfig) => {
409
- const recurseRoutes = (
410
- routeConfigs: RouteConfig[],
411
- parent?: Route<TAllRouteInfo, any>,
412
- ): Route<TAllRouteInfo, any>[] => {
413
- return routeConfigs.map((routeConfig) => {
414
- const routeOptions = routeConfig.options
415
- const route = createRoute(routeConfig, routeOptions, parent, router)
416
-
417
- // {
418
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
419
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
420
- // }
421
-
422
- const existingRoute = (router.routesById as any)[route.routeId]
423
-
424
- if (existingRoute) {
425
- if (process.env.NODE_ENV !== 'production') {
426
- console.warn(
427
- `Duplicate routes found with id: ${String(route.routeId)}`,
428
- router.routesById,
429
- route,
430
- )
431
- }
432
- throw new Error()
433
- }
434
-
435
- ;(router.routesById as any)[route.routeId] = route
436
-
437
- const children = routeConfig.children as RouteConfig[]
438
-
439
- route.childRoutes = children?.length
440
- ? recurseRoutes(children, route)
441
- : undefined
442
-
443
- return route
444
- })
445
- }
446
-
447
- const routes = recurseRoutes([rootRouteConfig])
448
-
449
- return routes[0]!
450
- },
451
-
452
- parseLocation: (
453
- location: History['location'],
454
- previousLocation?: Location,
455
- ): Location => {
456
- const parsedSearch = router.options.parseSearch(location.search)
457
-
458
- return {
459
- pathname: location.pathname,
460
- searchStr: location.search,
461
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
462
- hash: location.hash.split('#').reverse()[0] ?? '',
463
- href: `${location.pathname}${location.search}${location.hash}`,
464
- state: location.state as LocationState,
465
- key: location.key,
466
- }
467
- },
468
-
469
- buildLocation: (dest: BuildNextOptions = {}): Location => {
470
- // const resolvedFrom: Location = {
471
- // ...router.location,
472
- const fromPathname = dest.fromCurrent
473
- ? router.location.pathname
474
- : dest.from ?? router.location.pathname
475
-
476
- let pathname = resolvePath(
477
- router.basepath ?? '/',
478
- fromPathname,
479
- `${dest.to ?? '.'}`,
480
- )
481
-
482
- const fromMatches = router.matchRoutes(router.location.pathname, {
483
- strictParseParams: true,
484
- })
485
-
486
- const toMatches = router.matchRoutes(pathname)
487
-
488
- const prevParams = { ...last(fromMatches)?.params }
489
-
490
- let nextParams =
491
- (dest.params ?? true) === true
492
- ? prevParams
493
- : functionalUpdate(dest.params!, prevParams)
494
-
495
- if (nextParams) {
496
- toMatches
497
- .map((d) => d.options.stringifyParams)
498
- .filter(Boolean)
499
- .forEach((fn) => {
500
- Object.assign({}, nextParams!, fn!(nextParams!))
501
- })
502
- }
503
-
504
- pathname = interpolatePath(pathname, nextParams ?? {})
505
-
506
- // Pre filters first
507
- const preFilteredSearch = dest.__preSearchFilters?.length
508
- ? dest.__preSearchFilters.reduce(
509
- (prev, next) => next(prev),
510
- router.location.search,
511
- )
512
- : router.location.search
513
-
514
- // Then the link/navigate function
515
- const destSearch =
516
- dest.search === true
517
- ? preFilteredSearch // Preserve resolvedFrom true
518
- : dest.search
519
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
520
- : dest.__preSearchFilters?.length
521
- ? preFilteredSearch // Preserve resolvedFrom filters
522
- : {}
523
-
524
- // Then post filters
525
- const postFilteredSearch = dest.__postSearchFilters?.length
526
- ? dest.__postSearchFilters.reduce(
527
- (prev, next) => next(prev),
528
- destSearch,
529
- )
530
- : destSearch
531
-
532
- const search = replaceEqualDeep(
533
- router.location.search,
534
- postFilteredSearch,
535
- )
536
-
537
- const searchStr = router.options.stringifySearch(search)
538
- let hash =
539
- dest.hash === true
540
- ? router.location.hash
541
- : functionalUpdate(dest.hash!, router.location.hash)
542
- hash = hash ? `#${hash}` : ''
543
-
544
- return {
545
- pathname,
546
- search,
547
- searchStr,
548
- state: router.location.state,
549
- hash,
550
- href: `${pathname}${searchStr}${hash}`,
551
- key: dest.key,
552
- }
553
- },
554
-
555
- commitLocation: (next: Location, replace?: boolean): Promise<void> => {
556
- const id = '' + Date.now() + Math.random()
557
-
558
- if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
559
-
560
- let nextAction: 'push' | 'replace' = 'replace'
561
-
562
- if (!replace) {
563
- nextAction = 'push'
564
- }
565
-
566
- const isSameUrl =
567
- router.parseLocation(history.location).href === next.href
568
-
569
- if (isSameUrl && !next.key) {
570
- nextAction = 'replace'
571
- }
572
-
573
- if (nextAction === 'replace') {
574
- history.replace(
575
- {
576
- pathname: next.pathname,
577
- hash: next.hash,
578
- search: next.searchStr,
579
- },
580
- {
581
- id,
582
- },
583
- )
584
- } else {
585
- history.push(
586
- {
587
- pathname: next.pathname,
588
- hash: next.hash,
589
- search: next.searchStr,
590
- },
591
- {
592
- id,
593
- },
594
- )
595
- }
596
-
597
- router.navigationPromise = new Promise((resolve) => {
598
- const previousNavigationResolve = router.resolveNavigation
599
-
600
- router.resolveNavigation = () => {
601
- previousNavigationResolve()
602
- resolve()
603
- }
604
- })
605
-
606
- return router.navigationPromise
607
- },
608
-
609
- buildNext: (opts: BuildNextOptions) => {
610
- const next = router.buildLocation(opts)
611
-
612
- const matches = router.matchRoutes(next.pathname)
613
-
614
- const __preSearchFilters = matches
615
- .map((match) => match.options.preSearchFilters ?? [])
616
- .flat()
617
- .filter(Boolean)
618
-
619
- const __postSearchFilters = matches
620
- .map((match) => match.options.postSearchFilters ?? [])
621
- .flat()
622
- .filter(Boolean)
623
-
624
- return router.buildLocation({
625
- ...opts,
626
- __preSearchFilters,
627
- __postSearchFilters,
628
- })
629
- },
630
-
631
458
  cancelMatches: () => {
632
459
  ;[
633
460
  ...router.state.matches,
@@ -739,6 +566,7 @@ export function createRouter<
739
566
  params: d.params,
740
567
  search: d.search,
741
568
  })
569
+ delete router.matchCache[d.matchId]
742
570
  })
743
571
 
744
572
  if (matches.some((d) => d.status === 'loading')) {
@@ -922,38 +750,10 @@ export function createRouter<
922
750
  },
923
751
 
924
752
  loadMatches: async (resolvedMatches, loaderOpts) => {
925
- const now = Date.now()
926
- const minMaxAge = loaderOpts?.preload
927
- ? Math.max(loaderOpts?.maxAge, loaderOpts?.gcMaxAge)
928
- : 0
929
-
930
753
  const matchPromises = resolvedMatches.map(async (match) => {
931
754
  // Validate the match (loads search params etc)
932
755
  match.__.validate()
933
-
934
- // If this is a preload, add it to the preload cache
935
- if (loaderOpts?.preload && minMaxAge > 0) {
936
- // If the match is currently active, don't preload it
937
- if (router.state.matches.find((d) => d.matchId === match.matchId)) {
938
- return
939
- }
940
-
941
- router.matchCache[match.matchId] = {
942
- gc: now + loaderOpts.gcMaxAge,
943
- match,
944
- }
945
- }
946
-
947
- // If the match is invalid, errored or idle, trigger it to load
948
- if (
949
- (match.status === 'success' && match.getIsInvalid()) ||
950
- match.status === 'error' ||
951
- match.status === 'idle'
952
- ) {
953
- const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
954
-
955
- match.load({ maxAge })
956
- }
756
+ match.load(loaderOpts)
957
757
 
958
758
  if (match.status === 'loading') {
959
759
  // If requested, start the pending timers
@@ -986,7 +786,7 @@ export function createRouter<
986
786
  },
987
787
 
988
788
  reload: () =>
989
- router._navigate({
789
+ router.__.navigate({
990
790
  fromCurrent: true,
991
791
  replace: true,
992
792
  search: true,
@@ -1024,11 +824,6 @@ export function createRouter<
1024
824
  })
1025
825
  },
1026
826
 
1027
- _navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1028
- const next = router.buildNext(location)
1029
- return router.commitLocation(next, location.replace)
1030
- },
1031
-
1032
827
  navigate: async ({ from, to = '.', search, hash, replace, params }) => {
1033
828
  // If this link simply reloads the current route,
1034
829
  // make sure it has a new key so it will trigger a data refresh
@@ -1050,7 +845,7 @@ export function createRouter<
1050
845
  'Attempting to navigate to external url with router.navigate!',
1051
846
  )
1052
847
 
1053
- return router._navigate({
848
+ return router.__.navigate({
1054
849
  from: fromString,
1055
850
  to: toString,
1056
851
  search,
@@ -1134,7 +929,7 @@ export function createRouter<
1134
929
  }
1135
930
 
1136
931
  // All is well? Navigate!)
1137
- router._navigate(nextOpts)
932
+ router.__.navigate(nextOpts)
1138
933
  }
1139
934
  }
1140
935
 
@@ -1186,9 +981,238 @@ export function createRouter<
1186
981
  disabled,
1187
982
  }
1188
983
  },
984
+ buildNext: (opts: BuildNextOptions) => {
985
+ const next = router.__.buildLocation(opts)
986
+
987
+ const matches = router.matchRoutes(next.pathname)
988
+
989
+ const __preSearchFilters = matches
990
+ .map((match) => match.options.preSearchFilters ?? [])
991
+ .flat()
992
+ .filter(Boolean)
993
+
994
+ const __postSearchFilters = matches
995
+ .map((match) => match.options.postSearchFilters ?? [])
996
+ .flat()
997
+ .filter(Boolean)
998
+
999
+ return router.__.buildLocation({
1000
+ ...opts,
1001
+ __preSearchFilters,
1002
+ __postSearchFilters,
1003
+ })
1004
+ },
1005
+
1006
+ __: {
1007
+ buildRouteTree: (rootRouteConfig: RouteConfig) => {
1008
+ const recurseRoutes = (
1009
+ routeConfigs: RouteConfig[],
1010
+ parent?: Route<TAllRouteInfo, any>,
1011
+ ): Route<TAllRouteInfo, any>[] => {
1012
+ return routeConfigs.map((routeConfig) => {
1013
+ const routeOptions = routeConfig.options
1014
+ const route = createRoute(routeConfig, routeOptions, parent, router)
1015
+
1016
+ // {
1017
+ // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1018
+ // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1019
+ // }
1020
+
1021
+ const existingRoute = (router.routesById as any)[route.routeId]
1022
+
1023
+ if (existingRoute) {
1024
+ if (process.env.NODE_ENV !== 'production') {
1025
+ console.warn(
1026
+ `Duplicate routes found with id: ${String(route.routeId)}`,
1027
+ router.routesById,
1028
+ route,
1029
+ )
1030
+ }
1031
+ throw new Error()
1032
+ }
1033
+
1034
+ ;(router.routesById as any)[route.routeId] = route
1035
+
1036
+ const children = routeConfig.children as RouteConfig[]
1037
+
1038
+ route.childRoutes = children?.length
1039
+ ? recurseRoutes(children, route)
1040
+ : undefined
1041
+
1042
+ return route
1043
+ })
1044
+ }
1045
+
1046
+ const routes = recurseRoutes([rootRouteConfig])
1047
+
1048
+ return routes[0]!
1049
+ },
1050
+
1051
+ parseLocation: (
1052
+ location: History['location'],
1053
+ previousLocation?: Location,
1054
+ ): Location => {
1055
+ const parsedSearch = router.options.parseSearch(location.search)
1056
+
1057
+ return {
1058
+ pathname: location.pathname,
1059
+ searchStr: location.search,
1060
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1061
+ hash: location.hash.split('#').reverse()[0] ?? '',
1062
+ href: `${location.pathname}${location.search}${location.hash}`,
1063
+ state: location.state as LocationState,
1064
+ key: location.key,
1065
+ }
1066
+ },
1067
+
1068
+ navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1069
+ const next = router.buildNext(location)
1070
+ return router.__.commitLocation(next, location.replace)
1071
+ },
1072
+
1073
+ buildLocation: (dest: BuildNextOptions = {}): Location => {
1074
+ // const resolvedFrom: Location = {
1075
+ // ...router.location,
1076
+ const fromPathname = dest.fromCurrent
1077
+ ? router.location.pathname
1078
+ : dest.from ?? router.location.pathname
1079
+
1080
+ let pathname = resolvePath(
1081
+ router.basepath ?? '/',
1082
+ fromPathname,
1083
+ `${dest.to ?? '.'}`,
1084
+ )
1085
+
1086
+ const fromMatches = router.matchRoutes(router.location.pathname, {
1087
+ strictParseParams: true,
1088
+ })
1089
+
1090
+ const toMatches = router.matchRoutes(pathname)
1091
+
1092
+ const prevParams = { ...last(fromMatches)?.params }
1093
+
1094
+ let nextParams =
1095
+ (dest.params ?? true) === true
1096
+ ? prevParams
1097
+ : functionalUpdate(dest.params!, prevParams)
1098
+
1099
+ if (nextParams) {
1100
+ toMatches
1101
+ .map((d) => d.options.stringifyParams)
1102
+ .filter(Boolean)
1103
+ .forEach((fn) => {
1104
+ Object.assign({}, nextParams!, fn!(nextParams!))
1105
+ })
1106
+ }
1107
+
1108
+ pathname = interpolatePath(pathname, nextParams ?? {})
1109
+
1110
+ // Pre filters first
1111
+ const preFilteredSearch = dest.__preSearchFilters?.length
1112
+ ? dest.__preSearchFilters.reduce(
1113
+ (prev, next) => next(prev),
1114
+ router.location.search,
1115
+ )
1116
+ : router.location.search
1117
+
1118
+ // Then the link/navigate function
1119
+ const destSearch =
1120
+ dest.search === true
1121
+ ? preFilteredSearch // Preserve resolvedFrom true
1122
+ : dest.search
1123
+ ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1124
+ : dest.__preSearchFilters?.length
1125
+ ? preFilteredSearch // Preserve resolvedFrom filters
1126
+ : {}
1127
+
1128
+ // Then post filters
1129
+ const postFilteredSearch = dest.__postSearchFilters?.length
1130
+ ? dest.__postSearchFilters.reduce(
1131
+ (prev, next) => next(prev),
1132
+ destSearch,
1133
+ )
1134
+ : destSearch
1135
+
1136
+ const search = replaceEqualDeep(
1137
+ router.location.search,
1138
+ postFilteredSearch,
1139
+ )
1140
+
1141
+ const searchStr = router.options.stringifySearch(search)
1142
+ let hash =
1143
+ dest.hash === true
1144
+ ? router.location.hash
1145
+ : functionalUpdate(dest.hash!, router.location.hash)
1146
+ hash = hash ? `#${hash}` : ''
1147
+
1148
+ return {
1149
+ pathname,
1150
+ search,
1151
+ searchStr,
1152
+ state: router.location.state,
1153
+ hash,
1154
+ href: `${pathname}${searchStr}${hash}`,
1155
+ key: dest.key,
1156
+ }
1157
+ },
1158
+
1159
+ commitLocation: (next: Location, replace?: boolean): Promise<void> => {
1160
+ const id = '' + Date.now() + Math.random()
1161
+
1162
+ if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
1163
+
1164
+ let nextAction: 'push' | 'replace' = 'replace'
1165
+
1166
+ if (!replace) {
1167
+ nextAction = 'push'
1168
+ }
1169
+
1170
+ const isSameUrl =
1171
+ router.__.parseLocation(history.location).href === next.href
1172
+
1173
+ if (isSameUrl && !next.key) {
1174
+ nextAction = 'replace'
1175
+ }
1176
+
1177
+ if (nextAction === 'replace') {
1178
+ history.replace(
1179
+ {
1180
+ pathname: next.pathname,
1181
+ hash: next.hash,
1182
+ search: next.searchStr,
1183
+ },
1184
+ {
1185
+ id,
1186
+ },
1187
+ )
1188
+ } else {
1189
+ history.push(
1190
+ {
1191
+ pathname: next.pathname,
1192
+ hash: next.hash,
1193
+ search: next.searchStr,
1194
+ },
1195
+ {
1196
+ id,
1197
+ },
1198
+ )
1199
+ }
1200
+
1201
+ router.navigationPromise = new Promise((resolve) => {
1202
+ const previousNavigationResolve = router.resolveNavigation
1203
+
1204
+ router.resolveNavigation = () => {
1205
+ previousNavigationResolve()
1206
+ resolve()
1207
+ }
1208
+ })
1209
+
1210
+ return router.navigationPromise
1211
+ },
1212
+ },
1189
1213
  }
1190
1214
 
1191
- router.location = router.parseLocation(history.location)
1215
+ router.location = router.__.parseLocation(history.location)
1192
1216
  router.state.location = router.location
1193
1217
 
1194
1218
  router.update(userOptions)