@tanstack/router-core 0.0.1-beta.184 → 0.0.1-beta.186

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
@@ -29,6 +29,7 @@ import {
29
29
  RegisteredRouteComponent,
30
30
  RegisteredErrorRouteComponent,
31
31
  RegisteredPendingRouteComponent,
32
+ RouteMask,
32
33
  } from './route'
33
34
  import {
34
35
  RoutesById,
@@ -50,13 +51,15 @@ import {
50
51
  Updater,
51
52
  replaceEqualDeep,
52
53
  partialDeepEqual,
54
+ NonNullableUpdater,
53
55
  } from './utils'
54
56
  import {
55
57
  createBrowserHistory,
56
58
  createMemoryHistory,
59
+ HistoryLocation,
60
+ LocationState,
57
61
  RouterHistory,
58
62
  } from './history'
59
- import warning from 'tiny-warning'
60
63
 
61
64
  //
62
65
 
@@ -78,25 +81,20 @@ export type RegisteredRouter = Register extends {
78
81
  ? TRouter
79
82
  : AnyRouter
80
83
 
81
- export interface LocationState {}
82
-
83
- export interface ParsedLocation<
84
- TSearchObj extends AnySearchSchema = {},
85
- // TState extends LocationState = LocationState,
86
- > {
84
+ export interface ParsedLocation<TSearchObj extends AnySearchSchema = {}> {
87
85
  href: string
88
86
  pathname: string
89
87
  search: TSearchObj
90
88
  searchStr: string
91
89
  state: LocationState
92
90
  hash: string
93
- key?: string
91
+ maskedLocation?: ParsedLocation<TSearchObj>
92
+ unmaskOnReload?: boolean
94
93
  }
95
94
 
96
95
  export interface FromLocation {
97
96
  pathname: string
98
97
  search?: unknown
99
- key?: string
100
98
  hash?: string
101
99
  }
102
100
 
@@ -158,7 +156,7 @@ export interface RouterOptions<
158
156
  parseSearch?: SearchParser
159
157
  defaultPreload?: false | 'intent'
160
158
  defaultPreloadDelay?: number
161
- refetchOnWindowFocus?: boolean
159
+ reloadOnWindowFocus?: boolean
162
160
  defaultComponent?: RegisteredRouteComponent<
163
161
  unknown,
164
162
  AnySearchSchema,
@@ -192,12 +190,11 @@ export interface RouterOptions<
192
190
  }>
193
191
  dehydrate?: () => TDehydrated
194
192
  hydrate?: (dehydrated: TDehydrated) => void
193
+ routeMasks?: RouteMask<TRouteTree>[]
194
+ unmaskOnReload?: boolean
195
195
  }
196
196
 
197
- export interface RouterState<
198
- TRouteTree extends AnyRoute = AnyRoute,
199
- // TState extends LocationState = LocationState,
200
- > {
197
+ export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
201
198
  status: 'idle' | 'pending'
202
199
  isFetching: boolean
203
200
  matchesById: Record<string, RouteMatch<TRouteTree>>
@@ -219,11 +216,21 @@ export interface BuildNextOptions {
219
216
  params?: true | Updater<unknown>
220
217
  search?: true | Updater<unknown>
221
218
  hash?: true | Updater<string>
222
- state?: LocationState
223
- key?: string
219
+ state?: true | NonNullableUpdater<LocationState>
220
+ mask?: {
221
+ to?: string | number | null
222
+ params?: true | Updater<unknown>
223
+ search?: true | Updater<unknown>
224
+ hash?: true | Updater<string>
225
+ state?: true | NonNullableUpdater<LocationState>
226
+ unmaskOnReload?: boolean
227
+ }
224
228
  from?: string
225
- fromCurrent?: boolean
226
- __matches?: AnyRouteMatch[]
229
+ }
230
+
231
+ export interface CommitLocationOptions {
232
+ replace?: boolean
233
+ resetScroll?: boolean
227
234
  }
228
235
 
229
236
  export interface MatchLocation {
@@ -231,7 +238,6 @@ export interface MatchLocation {
231
238
  fuzzy?: boolean
232
239
  caseSensitive?: boolean
233
240
  from?: string
234
- fromCurrent?: boolean
235
241
  }
236
242
 
237
243
  export interface MatchRouteOptions {
@@ -331,6 +337,8 @@ export class Router<
331
337
  state: RouterState<TRouteTree>
332
338
  dehydratedData?: TDehydrated
333
339
  resetNextScroll = false
340
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`
341
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
334
342
 
335
343
  constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
336
344
  this.options = {
@@ -402,15 +410,15 @@ export class Router<
402
410
 
403
411
  this.update(options)
404
412
 
405
- const next = this.buildNext({
406
- hash: true,
407
- fromCurrent: true,
413
+ const nextLocation = this.buildLocation({
408
414
  search: true,
415
+ params: true,
416
+ hash: true,
409
417
  state: true,
410
418
  })
411
419
 
412
- if (this.state.location.href !== next.href) {
413
- this.#commitLocation({ ...next, replace: true })
420
+ if (this.state.location.href !== nextLocation.href) {
421
+ this.#commitLocation({ ...nextLocation, replace: true })
414
422
  }
415
423
  }
416
424
 
@@ -464,8 +472,10 @@ export class Router<
464
472
  }
465
473
 
466
474
  #onFocus = () => {
467
- if (this.options.refetchOnWindowFocus ?? true) {
468
- this.invalidate()
475
+ if (this.options.reloadOnWindowFocus ?? true) {
476
+ this.invalidate({
477
+ __fromFocus: true,
478
+ })
469
479
  }
470
480
  }
471
481
 
@@ -517,17 +527,6 @@ export class Router<
517
527
  return this
518
528
  }
519
529
 
520
- buildNext = (opts: BuildNextOptions): ParsedLocation => {
521
- const next = this.#buildLocation(opts)
522
-
523
- const __matches = this.matchRoutes(next.pathname, next.search)
524
-
525
- return this.#buildLocation({
526
- ...opts,
527
- __matches,
528
- })
529
- }
530
-
531
530
  cancelMatches = () => {
532
531
  this.state.matches.forEach((match) => {
533
532
  this.cancelMatch(match.id)
@@ -608,7 +607,7 @@ export class Router<
608
607
  try {
609
608
  // Load the matches
610
609
  try {
611
- await this.loadMatches(pendingMatches)
610
+ await this.loadMatches(pendingMatches.map((d) => d.id))
612
611
  } catch (err) {
613
612
  // swallow this error, since we'll display the
614
613
  // errors on the route components
@@ -683,10 +682,20 @@ export class Router<
683
682
  prevMatchesById: Record<string, RouteMatch<TRouteTree>>,
684
683
  nextMatches: AnyRouteMatch[],
685
684
  ): Record<string, RouteMatch<TRouteTree>> => {
686
- return {
687
- ...prevMatchesById,
688
- ...Object.fromEntries(nextMatches.map((match) => [match.id, match])),
689
- }
685
+ let matchesById = { ...prevMatchesById }
686
+
687
+ nextMatches.forEach((match) => {
688
+ if (!matchesById[match.id]) {
689
+ matchesById[match.id] = match
690
+ }
691
+
692
+ matchesById[match.id] = {
693
+ ...matchesById[match.id],
694
+ ...match,
695
+ }
696
+ })
697
+
698
+ return matchesById
690
699
  }
691
700
 
692
701
  getRoute = (id: string): Route => {
@@ -702,7 +711,8 @@ export class Router<
702
711
  maxAge?: number
703
712
  } = this.state.location,
704
713
  ) => {
705
- const next = this.buildNext(navigateOpts)
714
+ let next = this.buildLocation(navigateOpts)
715
+
706
716
  const matches = this.matchRoutes(next.pathname, next.search, {
707
717
  throwOnError: true,
708
718
  })
@@ -714,12 +724,15 @@ export class Router<
714
724
  }
715
725
  })
716
726
 
717
- await this.loadMatches(matches, {
718
- preload: true,
719
- maxAge: navigateOpts.maxAge,
720
- })
727
+ await this.loadMatches(
728
+ matches.map((d) => d.id),
729
+ {
730
+ preload: true,
731
+ maxAge: navigateOpts.maxAge,
732
+ },
733
+ )
721
734
 
722
- return matches
735
+ return [last(matches)!, matches] as const
723
736
  }
724
737
 
725
738
  cleanMatches = () => {
@@ -931,14 +944,13 @@ export class Router<
931
944
  }
932
945
 
933
946
  loadMatches = async (
934
- _resolvedMatches: AnyRouteMatch[],
947
+ matchIds: string[],
935
948
  opts?: {
936
949
  preload?: boolean
937
950
  maxAge?: number
938
951
  },
939
952
  ) => {
940
- const getFreshMatches = () =>
941
- _resolvedMatches.map((d) => this.getRouteMatch(d.id)!)
953
+ const getFreshMatches = () => matchIds.map((d) => this.getRouteMatch(d)!)
942
954
 
943
955
  if (!opts?.preload) {
944
956
  getFreshMatches().forEach((match) => {
@@ -1032,8 +1044,9 @@ export class Router<
1032
1044
  }
1033
1045
  }
1034
1046
  } catch (err) {
1035
- if (!opts?.preload) {
1036
- this.navigate(err as any)
1047
+ if (isRedirect(err)) {
1048
+ if (!opts?.preload) this.navigate(err as any)
1049
+ return
1037
1050
  }
1038
1051
 
1039
1052
  throw err
@@ -1158,6 +1171,8 @@ export class Router<
1158
1171
  navigate = async <
1159
1172
  TFrom extends RoutePaths<TRouteTree> = '/',
1160
1173
  TTo extends string = '',
1174
+ TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
1175
+ TMaskTo extends string = '',
1161
1176
  >({
1162
1177
  from,
1163
1178
  to = '' as any,
@@ -1166,7 +1181,7 @@ export class Router<
1166
1181
  replace,
1167
1182
  params,
1168
1183
  resetScroll,
1169
- }: NavigateOptions<TRouteTree, TFrom, TTo>) => {
1184
+ }: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>) => {
1170
1185
  // If this link simply reloads the current route,
1171
1186
  // make sure it has a new key so it will trigger a data refresh
1172
1187
 
@@ -1186,21 +1201,22 @@ export class Router<
1186
1201
  'Attempting to navigate to external url with this.navigate!',
1187
1202
  )
1188
1203
 
1189
- return this.#commitLocation({
1204
+ return this.#buildAndCommitLocation({
1190
1205
  from: fromString,
1191
1206
  to: toString,
1192
1207
  search,
1193
1208
  hash,
1194
- replace,
1195
1209
  params,
1210
+ replace,
1196
1211
  resetScroll,
1197
1212
  })
1198
1213
  }
1199
1214
 
1200
1215
  matchRoute = <
1216
+ TRouteTree extends AnyRoute = AnyRoute,
1201
1217
  TFrom extends RoutePaths<TRouteTree> = '/',
1202
1218
  TTo extends string = '',
1203
- TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
1219
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
1204
1220
  >(
1205
1221
  location: ToOptions<TRouteTree, TFrom, TTo>,
1206
1222
  opts?: MatchRouteOptions,
@@ -1212,7 +1228,7 @@ export class Router<
1212
1228
  : undefined,
1213
1229
  } as any
1214
1230
 
1215
- const next = this.buildNext(location)
1231
+ const next = this.buildLocation(location)
1216
1232
  if (opts?.pending && this.state.status !== 'pending') {
1217
1233
  return false
1218
1234
  }
@@ -1257,6 +1273,7 @@ export class Router<
1257
1273
  preloadDelay: userPreloadDelay,
1258
1274
  disabled,
1259
1275
  state,
1276
+ mask,
1260
1277
  resetScroll,
1261
1278
  }: LinkOptions<TRouteTree, TFrom, TTo>): LinkInfo => {
1262
1279
  // If this link simply reloads the current route,
@@ -1281,10 +1298,11 @@ export class Router<
1281
1298
  hash,
1282
1299
  replace,
1283
1300
  state,
1301
+ mask,
1284
1302
  resetScroll,
1285
1303
  }
1286
1304
 
1287
- const next = this.buildNext(nextOpts)
1305
+ const next = this.buildLocation(nextOpts)
1288
1306
 
1289
1307
  preload = preload ?? this.options.defaultPreload
1290
1308
  const preloadDelay =
@@ -1323,7 +1341,7 @@ export class Router<
1323
1341
  e.preventDefault()
1324
1342
 
1325
1343
  // All is well? Navigate!
1326
- this.#commitLocation(nextOpts as any)
1344
+ this.#commitLocation({ ...next, replace, resetScroll })
1327
1345
  }
1328
1346
  }
1329
1347
 
@@ -1603,154 +1621,269 @@ export class Router<
1603
1621
  #parseLocation = (
1604
1622
  previousLocation?: ParsedLocation,
1605
1623
  ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
1606
- let { pathname, search, hash, state } = this.history.location
1624
+ const parse = ({
1625
+ pathname,
1626
+ search,
1627
+ hash,
1628
+ state,
1629
+ }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
1630
+ const parsedSearch = this.options.parseSearch(search)
1631
+
1632
+ return {
1633
+ pathname: pathname,
1634
+ searchStr: search,
1635
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
1636
+ hash: hash.split('#').reverse()[0] ?? '',
1637
+ href: `${pathname}${search}${hash}`,
1638
+ state: replaceEqualDeep(
1639
+ previousLocation?.state,
1640
+ state,
1641
+ ) as LocationState,
1642
+ }
1643
+ }
1607
1644
 
1608
- const parsedSearch = this.options.parseSearch(search)
1645
+ const location = parse(this.history.location)
1609
1646
 
1610
- return {
1611
- pathname: pathname,
1612
- searchStr: search,
1613
- search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
1614
- hash: hash.split('#').reverse()[0] ?? '',
1615
- href: `${pathname}${search}${hash}`,
1616
- state: state as LocationState,
1617
- key: state?.key || '__init__',
1647
+ let { __tempLocation, __tempKey } = location.state
1648
+
1649
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1650
+ // Sync up the location keys
1651
+ const parsedTempLocation = parse(__tempLocation) as any
1652
+ parsedTempLocation.state.key = location.state.key
1653
+
1654
+ delete parsedTempLocation.state.__tempLocation
1655
+
1656
+ return {
1657
+ ...parsedTempLocation,
1658
+ maskedLocation: location,
1659
+ }
1618
1660
  }
1661
+
1662
+ return location
1619
1663
  }
1620
1664
 
1621
- #buildLocation = (dest: BuildNextOptions = {}): ParsedLocation => {
1622
- dest.fromCurrent = dest.fromCurrent ?? dest.to === ''
1665
+ buildLocation = (opts: BuildNextOptions = {}): ParsedLocation => {
1666
+ const build = (
1667
+ dest: BuildNextOptions & {
1668
+ unmaskOnReload?: boolean
1669
+ } = {},
1670
+ matches?: AnyRouteMatch[],
1671
+ ): ParsedLocation => {
1672
+ const from = this.state.location
1673
+
1674
+ const fromPathname = dest.from ?? from.pathname
1675
+
1676
+ let pathname = resolvePath(
1677
+ this.basepath ?? '/',
1678
+ fromPathname,
1679
+ `${dest.to ?? ''}`,
1680
+ )
1623
1681
 
1624
- const fromPathname = dest.fromCurrent
1625
- ? this.state.location.pathname
1626
- : dest.from ?? this.state.location.pathname
1682
+ const fromMatches = this.matchRoutes(from.pathname, from.search)
1627
1683
 
1628
- let pathname = resolvePath(
1629
- this.basepath ?? '/',
1630
- fromPathname,
1631
- `${dest.to ?? ''}`,
1632
- )
1684
+ const prevParams = { ...last(fromMatches)?.params }
1633
1685
 
1634
- const fromMatches = this.matchRoutes(
1635
- this.state.location.pathname,
1636
- this.state.location.search,
1637
- )
1686
+ let nextParams =
1687
+ (dest.params ?? true) === true
1688
+ ? prevParams
1689
+ : functionalUpdate(dest.params!, prevParams)
1638
1690
 
1639
- const prevParams = { ...last(fromMatches)?.params }
1691
+ if (nextParams) {
1692
+ matches
1693
+ ?.map((d) => this.getRoute(d.routeId).options.stringifyParams)
1694
+ .filter(Boolean)
1695
+ .forEach((fn) => {
1696
+ nextParams = { ...nextParams!, ...fn!(nextParams!) }
1697
+ })
1698
+ }
1640
1699
 
1641
- let nextParams =
1642
- (dest.params ?? true) === true
1643
- ? prevParams
1644
- : functionalUpdate(dest.params!, prevParams)
1700
+ pathname = interpolatePath(pathname, nextParams ?? {})
1701
+
1702
+ const preSearchFilters =
1703
+ matches
1704
+ ?.map(
1705
+ (match) =>
1706
+ this.getRoute(match.routeId).options.preSearchFilters ?? [],
1707
+ )
1708
+ .flat()
1709
+ .filter(Boolean) ?? []
1710
+
1711
+ const postSearchFilters =
1712
+ matches
1713
+ ?.map(
1714
+ (match) =>
1715
+ this.getRoute(match.routeId).options.postSearchFilters ?? [],
1716
+ )
1717
+ .flat()
1718
+ .filter(Boolean) ?? []
1719
+
1720
+ // Pre filters first
1721
+ const preFilteredSearch = preSearchFilters?.length
1722
+ ? preSearchFilters?.reduce((prev, next) => next(prev), from.search)
1723
+ : from.search
1724
+
1725
+ // Then the link/navigate function
1726
+ const destSearch =
1727
+ dest.search === true
1728
+ ? preFilteredSearch // Preserve resolvedFrom true
1729
+ : dest.search
1730
+ ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1731
+ : preSearchFilters?.length
1732
+ ? preFilteredSearch // Preserve resolvedFrom filters
1733
+ : {}
1734
+
1735
+ // Then post filters
1736
+ const postFilteredSearch = postSearchFilters?.length
1737
+ ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
1738
+ : destSearch
1739
+
1740
+ const search = replaceEqualDeep(from.search, postFilteredSearch)
1741
+
1742
+ const searchStr = this.options.stringifySearch(search)
1743
+
1744
+ const hash =
1745
+ dest.hash === true
1746
+ ? from.hash
1747
+ : dest.hash
1748
+ ? functionalUpdate(dest.hash!, from.hash)
1749
+ : from.hash
1750
+
1751
+ const hashStr = hash ? `#${hash}` : ''
1752
+
1753
+ let nextState =
1754
+ dest.state === true
1755
+ ? from.state
1756
+ : dest.state
1757
+ ? functionalUpdate(dest.state, from.state)
1758
+ : from.state
1759
+
1760
+ nextState = replaceEqualDeep(from.state, nextState)
1645
1761
 
1646
- if (nextParams) {
1647
- dest.__matches
1648
- ?.map((d) => this.getRoute(d.routeId).options.stringifyParams)
1649
- .filter(Boolean)
1650
- .forEach((fn) => {
1651
- nextParams = { ...nextParams!, ...fn!(nextParams!) }
1652
- })
1762
+ return {
1763
+ pathname,
1764
+ search,
1765
+ searchStr,
1766
+ state: nextState,
1767
+ hash,
1768
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1769
+ unmaskOnReload: dest.unmaskOnReload,
1770
+ }
1653
1771
  }
1654
1772
 
1655
- pathname = interpolatePath(pathname, nextParams ?? {})
1773
+ const buildWithMatches = (
1774
+ dest: BuildNextOptions = {},
1775
+ maskedDest?: BuildNextOptions,
1776
+ ) => {
1777
+ let next = build(dest)
1778
+ let maskedNext = maskedDest ? build(maskedDest) : undefined
1779
+
1780
+ if (!maskedNext) {
1781
+ const foundMask = this.options.routeMasks?.find((d) => {
1782
+ const match = matchPathname(this.basepath, next.pathname, {
1783
+ to: d.from,
1784
+ fuzzy: false,
1785
+ })
1656
1786
 
1657
- const preSearchFilters =
1658
- dest.__matches
1659
- ?.map(
1660
- (match) =>
1661
- this.getRoute(match.routeId).options.preSearchFilters ?? [],
1662
- )
1663
- .flat()
1664
- .filter(Boolean) ?? []
1665
-
1666
- const postSearchFilters =
1667
- dest.__matches
1668
- ?.map(
1669
- (match) =>
1670
- this.getRoute(match.routeId).options.postSearchFilters ?? [],
1671
- )
1672
- .flat()
1673
- .filter(Boolean) ?? []
1787
+ if (match) {
1788
+ return match
1789
+ }
1674
1790
 
1675
- // Pre filters first
1676
- const preFilteredSearch = preSearchFilters?.length
1677
- ? preSearchFilters?.reduce(
1678
- (prev, next) => next(prev),
1679
- this.state.location.search,
1680
- )
1681
- : this.state.location.search
1682
-
1683
- // Then the link/navigate function
1684
- const destSearch =
1685
- dest.search === true
1686
- ? preFilteredSearch // Preserve resolvedFrom true
1687
- : dest.search
1688
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1689
- : preSearchFilters?.length
1690
- ? preFilteredSearch // Preserve resolvedFrom filters
1691
- : {}
1692
-
1693
- // Then post filters
1694
- const postFilteredSearch = postSearchFilters?.length
1695
- ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
1696
- : destSearch
1697
-
1698
- const search = replaceEqualDeep(
1699
- this.state.location.search,
1700
- postFilteredSearch,
1701
- )
1791
+ return false
1792
+ })
1702
1793
 
1703
- const searchStr = this.options.stringifySearch(search)
1794
+ if (foundMask) {
1795
+ maskedDest = foundMask
1796
+ maskedNext = build(maskedDest)
1797
+ }
1798
+ }
1704
1799
 
1705
- const hash =
1706
- dest.hash === true
1707
- ? this.state.location.hash
1708
- : functionalUpdate(dest.hash!, this.state.location.hash)
1800
+ const nextMatches = this.matchRoutes(next.pathname, next.search)
1801
+ const maskedMatches = maskedNext
1802
+ ? this.matchRoutes(maskedNext.pathname, maskedNext.search)
1803
+ : undefined
1804
+ const maskedFinal = maskedNext
1805
+ ? build(maskedDest, maskedMatches)
1806
+ : undefined
1709
1807
 
1710
- const hashStr = hash ? `#${hash}` : ''
1808
+ const final = build(dest, nextMatches)
1711
1809
 
1712
- const nextState =
1713
- dest.state === true
1714
- ? this.state.location.state
1715
- : functionalUpdate(dest.state, this.state.location.state)!
1810
+ if (maskedFinal) {
1811
+ final.maskedLocation = maskedFinal
1812
+ }
1716
1813
 
1717
- return {
1718
- pathname,
1719
- search,
1720
- searchStr,
1721
- state: nextState,
1722
- hash,
1723
- href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1724
- key: dest.key,
1814
+ return final
1815
+ }
1816
+
1817
+ if (opts.mask) {
1818
+ return buildWithMatches(opts, {
1819
+ ...pick(opts, ['from']),
1820
+ ...opts.mask,
1821
+ })
1725
1822
  }
1823
+
1824
+ return buildWithMatches(opts)
1726
1825
  }
1727
1826
 
1728
- #commitLocation = async (
1729
- location: BuildNextOptions & { replace?: boolean; resetScroll?: boolean },
1730
- ) => {
1731
- const next = this.buildNext(location)
1827
+ #buildAndCommitLocation = ({
1828
+ replace,
1829
+ resetScroll,
1830
+ ...rest
1831
+ }: BuildNextOptions & CommitLocationOptions = {}) => {
1832
+ const location = this.buildLocation(rest)
1833
+ return this.#commitLocation({
1834
+ ...location,
1835
+ replace,
1836
+ resetScroll,
1837
+ })
1838
+ }
1732
1839
 
1840
+ #commitLocation = async (next: ParsedLocation & CommitLocationOptions) => {
1733
1841
  if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
1734
1842
 
1735
1843
  let nextAction: 'push' | 'replace' = 'replace'
1736
1844
 
1737
- if (!location.replace) {
1845
+ if (!next.replace) {
1738
1846
  nextAction = 'push'
1739
1847
  }
1740
1848
 
1741
1849
  const isSameUrl = this.state.location.href === next.href
1742
1850
 
1743
- if (isSameUrl && !next.key) {
1851
+ if (isSameUrl) {
1744
1852
  nextAction = 'replace'
1745
1853
  }
1746
1854
 
1747
- const href = `${next.pathname}${next.searchStr}${
1748
- next.hash ? `#${next.hash}` : ''
1749
- }`
1855
+ let { maskedLocation, ...nextHistory } = next
1856
+
1857
+ if (maskedLocation) {
1858
+ nextHistory = {
1859
+ ...maskedLocation,
1860
+ state: {
1861
+ ...maskedLocation.state,
1862
+ __tempKey: undefined,
1863
+ __tempLocation: {
1864
+ ...nextHistory,
1865
+ search: nextHistory.searchStr,
1866
+ state: {
1867
+ ...nextHistory.state,
1868
+ __tempKey: undefined!,
1869
+ __tempLocation: undefined!,
1870
+ key: undefined!,
1871
+ },
1872
+ },
1873
+ },
1874
+ }
1875
+
1876
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
1877
+ nextHistory.state.__tempKey = this.tempLocationKey
1878
+ }
1879
+ }
1750
1880
 
1751
- this.history[nextAction === 'push' ? 'push' : 'replace'](href, next.state)
1881
+ this.history[nextAction === 'push' ? 'push' : 'replace'](
1882
+ nextHistory.href,
1883
+ nextHistory.state,
1884
+ )
1752
1885
 
1753
- this.resetNextScroll = location.resetScroll ?? true
1886
+ this.resetNextScroll = next.resetScroll ?? true
1754
1887
 
1755
1888
  return this.latestLoadPromise
1756
1889
  }
@@ -1817,37 +1950,46 @@ export class Router<
1817
1950
  invalidate = async (opts?: {
1818
1951
  matchId?: string
1819
1952
  reload?: boolean
1953
+ __fromFocus?: boolean
1820
1954
  }): Promise<void> => {
1821
1955
  if (opts?.matchId) {
1822
1956
  this.setRouteMatch(opts.matchId, (s) => ({
1823
1957
  ...s,
1824
1958
  invalid: true,
1825
1959
  }))
1960
+
1826
1961
  const matchIndex = this.state.matches.findIndex(
1827
1962
  (d) => d.id === opts.matchId,
1828
1963
  )
1829
1964
  const childMatch = this.state.matches[matchIndex + 1]
1830
1965
 
1831
1966
  if (childMatch) {
1832
- return this.invalidate({ matchId: childMatch.id, reload: false })
1967
+ return this.invalidate({
1968
+ matchId: childMatch.id,
1969
+ reload: false,
1970
+ __fromFocus: opts.__fromFocus,
1971
+ })
1833
1972
  }
1834
1973
  } else {
1835
1974
  this.__store.batch(() => {
1836
1975
  Object.values(this.state.matchesById).forEach((match) => {
1837
- this.setRouteMatch(match.id, (s) => ({
1838
- ...s,
1839
- invalid: true,
1840
- }))
1976
+ const route = this.getRoute(match.routeId)
1977
+ const shouldInvalidate = opts?.__fromFocus
1978
+ ? route.options.reloadOnWindowFocus ?? true
1979
+ : true
1980
+
1981
+ if (shouldInvalidate) {
1982
+ this.setRouteMatch(match.id, (s) => ({
1983
+ ...s,
1984
+ invalid: true,
1985
+ }))
1986
+ }
1841
1987
  })
1842
1988
  })
1843
1989
  }
1844
1990
 
1845
1991
  if (opts?.reload ?? true) {
1846
- return this.navigate({
1847
- fromCurrent: true,
1848
- replace: true,
1849
- search: true,
1850
- } as any)
1992
+ return this.load()
1851
1993
  }
1852
1994
  }
1853
1995
  }
@@ -1882,7 +2024,9 @@ export type Redirect<
1882
2024
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1883
2025
  TFrom extends RoutePaths<TRouteTree> = '/',
1884
2026
  TTo extends string = '',
1885
- > = NavigateOptions<TRouteTree, TFrom, TTo> & {
2027
+ TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
2028
+ TMaskTo extends string = '',
2029
+ > = NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
1886
2030
  code?: number
1887
2031
  }
1888
2032