@tanstack/router-core 1.141.0 → 1.141.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.
@@ -50,6 +50,29 @@ const resolvePreload = (inner: InnerLoadContext, matchId: string): boolean => {
50
50
  )
51
51
  }
52
52
 
53
+ /**
54
+ * Builds the accumulated context from router options and all matches up to (and optionally including) the given index.
55
+ * Merges __routeContext and __beforeLoadContext from each match.
56
+ */
57
+ const buildMatchContext = (
58
+ inner: InnerLoadContext,
59
+ index: number,
60
+ includeCurrentMatch: boolean = true,
61
+ ): Record<string, unknown> => {
62
+ const context: Record<string, unknown> = {
63
+ ...(inner.router.options.context ?? {}),
64
+ }
65
+ const end = includeCurrentMatch ? index : index - 1
66
+ for (let i = 0; i <= end; i++) {
67
+ const innerMatch = inner.matches[i]
68
+ if (!innerMatch) continue
69
+ const m = inner.router.getMatch(innerMatch.id)
70
+ if (!m) continue
71
+ Object.assign(context, m.__routeContext, m.__beforeLoadContext)
72
+ }
73
+ return context
74
+ }
75
+
53
76
  const _handleNotFound = (inner: InnerLoadContext, err: NotFoundError) => {
54
77
  // Find the route that should handle the not found error
55
78
  // First check if a specific route is requested to show the error
@@ -407,6 +430,12 @@ const executeBeforeLoad = (
407
430
 
408
431
  match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
409
432
 
433
+ // Build context from all parent matches, excluding current match's __beforeLoadContext
434
+ // (since we're about to execute beforeLoad for this match)
435
+ const context = {
436
+ ...buildMatchContext(inner, index, false),
437
+ ...match.__routeContext,
438
+ }
410
439
  const { search, params, cause } = match
411
440
  const preload = resolvePreload(inner, matchId)
412
441
  const beforeLoadFnContext: BeforeLoadContextOptions<
@@ -423,12 +452,7 @@ const executeBeforeLoad = (
423
452
  abortController,
424
453
  params,
425
454
  preload,
426
- // Include parent's __beforeLoadContext so child routes can access it during their beforeLoad
427
- context: {
428
- ...parentMatchContext,
429
- ...parentMatch?.__beforeLoadContext,
430
- ...match.__routeContext,
431
- },
455
+ context,
432
456
  location: inner.location,
433
457
  navigate: (opts: any) =>
434
458
  inner.router.navigate({
@@ -569,19 +593,7 @@ const getLoaderContext = (
569
593
  const { params, loaderDeps, abortController, cause } =
570
594
  inner.router.getMatch(matchId)!
571
595
 
572
- let context = inner.router.options.context ?? {}
573
-
574
- for (let i = 0; i <= index; i++) {
575
- const innerMatch = inner.matches[i]
576
- if (!innerMatch) continue
577
- const m = inner.router.getMatch(innerMatch.id)
578
- if (!m) continue
579
- context = {
580
- ...context,
581
- ...(m.__routeContext ?? {}),
582
- ...(m.__beforeLoadContext ?? {}),
583
- }
584
- }
596
+ const context = buildMatchContext(inner, index)
585
597
 
586
598
  const preload = resolvePreload(inner, matchId)
587
599
 
@@ -747,19 +759,9 @@ const loadRouteMatch = async (
747
759
  const route = inner.router.looseRoutesById[routeId]!
748
760
 
749
761
  const commitContext = () => {
750
- const context = { ...inner.router.options.context }
751
-
752
- for (let i = 0; i <= index; i++) {
753
- const innerMatch = inner.matches[i]
754
- if (!innerMatch) continue
755
- const m = inner.router.getMatch(innerMatch.id)
756
- if (!m) continue
757
- Object.assign(context, m.__routeContext, m.__beforeLoadContext)
758
- }
759
-
760
762
  inner.updateMatch(matchId, (prev) => ({
761
763
  ...prev,
762
- context,
764
+ context: buildMatchContext(inner, index),
763
765
  }))
764
766
  }
765
767
 
package/src/location.ts CHANGED
@@ -38,14 +38,14 @@ export interface ParsedLocation<TSearchObj extends AnySchema = {}> {
38
38
  unmaskOnReload?: boolean
39
39
  /**
40
40
  * @private
41
- * @description The public href of the location, including the origin before any rewrites.
41
+ * @description The public href of the location.
42
42
  * If a rewrite is applied, the `href` property will be the rewritten URL.
43
43
  */
44
44
  publicHref: string
45
45
  /**
46
46
  * @private
47
- * @description The full URL of the location, including the origin.
47
+ * @description The full URL of the location.
48
48
  * @private
49
49
  */
50
- url: string
50
+ url: URL
51
51
  }
package/src/router.ts CHANGED
@@ -1176,16 +1176,14 @@ export class RouterCore<
1176
1176
 
1177
1177
  const fullPath = url.href.replace(url.origin, '')
1178
1178
 
1179
- const { pathname, hash } = url
1180
-
1181
1179
  return {
1182
1180
  href: fullPath,
1183
1181
  publicHref: href,
1184
- url: url.href,
1185
- pathname: decodePath(pathname),
1182
+ url: url,
1183
+ pathname: decodePath(url.pathname),
1186
1184
  searchStr,
1187
1185
  search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
1188
- hash: hash.split('#').reverse()[0] ?? '',
1186
+ hash: url.hash.split('#').reverse()[0] ?? '',
1189
1187
  state: replaceEqualDeep(previousLocation?.state, state),
1190
1188
  }
1191
1189
  }
@@ -1765,7 +1763,7 @@ export class RouterCore<
1765
1763
  publicHref:
1766
1764
  rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
1767
1765
  href: fullPath,
1768
- url: rewrittenUrl.href,
1766
+ url: rewrittenUrl,
1769
1767
  pathname: nextPathname,
1770
1768
  search: nextSearch,
1771
1769
  searchStr,
@@ -1878,8 +1876,16 @@ export class RouterCore<
1878
1876
  if (isSameUrl && isSameState()) {
1879
1877
  this.load()
1880
1878
  } else {
1881
- // eslint-disable-next-line prefer-const
1882
- let { maskedLocation, hashScrollIntoView, ...nextHistory } = next
1879
+ let {
1880
+ // eslint-disable-next-line prefer-const
1881
+ maskedLocation,
1882
+ // eslint-disable-next-line prefer-const
1883
+ hashScrollIntoView,
1884
+ // don't pass url into history since it is a URL instance that cannot be serialized
1885
+ // eslint-disable-next-line prefer-const
1886
+ url: _url,
1887
+ ...nextHistory
1888
+ } = next
1883
1889
 
1884
1890
  if (maskedLocation) {
1885
1891
  nextHistory = {
@@ -2000,7 +2006,7 @@ export class RouterCore<
2000
2006
  if (reloadDocument) {
2001
2007
  if (!href) {
2002
2008
  const location = this.buildLocation({ to, ...rest } as any)
2003
- href = location.url
2009
+ href = location.url.href
2004
2010
  }
2005
2011
 
2006
2012
  // Check blockers for external URLs unless ignoreBlocker is true
@@ -2056,24 +2062,11 @@ export class RouterCore<
2056
2062
  _includeValidateSearch: true,
2057
2063
  })
2058
2064
 
2059
- // Normalize URLs for comparison to handle encoding differences
2060
- // Browser history always stores encoded URLs while buildLocation may produce decoded URLs
2061
- const normalizeUrl = (url: string) => {
2062
- try {
2063
- return encodeURI(decodeURI(url))
2064
- } catch {
2065
- return url
2066
- }
2067
- }
2068
-
2069
2065
  if (
2070
- trimPath(normalizeUrl(this.latestLocation.href)) !==
2071
- trimPath(normalizeUrl(nextLocation.href))
2066
+ this.latestLocation.publicHref !== nextLocation.publicHref ||
2067
+ nextLocation.url.origin !== this.origin
2072
2068
  ) {
2073
- let href = nextLocation.url
2074
- if (this.origin && href.startsWith(this.origin)) {
2075
- href = href.replace(this.origin, '') || '/'
2076
- }
2069
+ const href = this.getParsedLocationHref(nextLocation)
2077
2070
 
2078
2071
  throw redirect({ href })
2079
2072
  }
@@ -2395,13 +2388,18 @@ export class RouterCore<
2395
2388
  return this.load({ sync: opts?.sync })
2396
2389
  }
2397
2390
 
2391
+ getParsedLocationHref = (location: ParsedLocation) => {
2392
+ let href = location.url.href
2393
+ if (this.origin && location.url.origin === this.origin) {
2394
+ href = href.replace(this.origin, '') || '/'
2395
+ }
2396
+ return href
2397
+ }
2398
+
2398
2399
  resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
2399
2400
  if (!redirect.options.href) {
2400
2401
  const location = this.buildLocation(redirect.options)
2401
- let href = location.url
2402
- if (this.origin && href.startsWith(this.origin)) {
2403
- href = href.replace(this.origin, '') || '/'
2404
- }
2402
+ const href = this.getParsedLocationHref(location)
2405
2403
  redirect.options.href = location.href
2406
2404
  redirect.headers.set('Location', href)
2407
2405
  }