@tanstack/router-core 1.154.2 → 1.154.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.154.2",
3
+ "version": "1.154.4",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -846,6 +846,13 @@ function findMatch<T extends RouteLike>(
846
846
  }
847
847
  }
848
848
 
849
+ type ParamExtractionState = {
850
+ part: number
851
+ node: number
852
+ path: number
853
+ segment: number
854
+ }
855
+
849
856
  /**
850
857
  * This function is "resumable":
851
858
  * - the `leaf` input can contain `extract` and `rawParams` properties from a previous `extractParams` call
@@ -859,27 +866,42 @@ function extractParams<T extends RouteLike>(
859
866
  leaf: {
860
867
  node: AnySegmentNode<T>
861
868
  skipped: number
862
- extract?: { part: number; node: number; path: number }
869
+ extract?: ParamExtractionState
863
870
  rawParams?: Record<string, string>
864
871
  },
865
- ): [
866
- rawParams: Record<string, string>,
867
- state: { part: number; node: number; path: number },
868
- ] {
872
+ ): [rawParams: Record<string, string>, state: ParamExtractionState] {
869
873
  const list = buildBranch(leaf.node)
870
874
  let nodeParts: Array<string> | null = null
871
875
  const rawParams: Record<string, string> = {}
876
+ /** which segment of the path we're currently processing */
872
877
  let partIndex = leaf.extract?.part ?? 0
878
+ /** which node of the route tree branch we're currently processing */
873
879
  let nodeIndex = leaf.extract?.node ?? 0
880
+ /** index of the 1st character of the segment we're processing in the path string */
874
881
  let pathIndex = leaf.extract?.path ?? 0
875
- for (; nodeIndex < list.length; partIndex++, nodeIndex++, pathIndex++) {
882
+ /** which fullPath segment we're currently processing */
883
+ let segmentCount = leaf.extract?.segment ?? 0
884
+ for (
885
+ ;
886
+ nodeIndex < list.length;
887
+ partIndex++, nodeIndex++, pathIndex++, segmentCount++
888
+ ) {
876
889
  const node = list[nodeIndex]!
890
+ // index nodes are terminating nodes, nothing to extract, just leave
891
+ if (node.kind === SEGMENT_TYPE_INDEX) break
892
+ // pathless nodes do not consume a path segment
893
+ if (node.kind === SEGMENT_TYPE_PATHLESS) {
894
+ segmentCount--
895
+ partIndex--
896
+ pathIndex--
897
+ continue
898
+ }
877
899
  const part = parts[partIndex]
878
900
  const currentPathIndex = pathIndex
879
901
  if (part) pathIndex += part.length
880
902
  if (node.kind === SEGMENT_TYPE_PARAM) {
881
903
  nodeParts ??= leaf.node.fullPath.split('/')
882
- const nodePart = nodeParts[nodeIndex]!
904
+ const nodePart = nodeParts[segmentCount]!
883
905
  const preLength = node.prefix?.length ?? 0
884
906
  // we can't rely on the presence of prefix/suffix to know whether it's curly-braced or not, because `/{$param}/` is valid, but has no prefix/suffix
885
907
  const isCurlyBraced = nodePart.charCodeAt(preLength) === 123 // '{'
@@ -903,7 +925,7 @@ function extractParams<T extends RouteLike>(
903
925
  continue
904
926
  }
905
927
  nodeParts ??= leaf.node.fullPath.split('/')
906
- const nodePart = nodeParts[nodeIndex]!
928
+ const nodePart = nodeParts[segmentCount]!
907
929
  const preLength = node.prefix?.length ?? 0
908
930
  const sufLength = node.suffix?.length ?? 0
909
931
  const name = nodePart.substring(
@@ -929,7 +951,15 @@ function extractParams<T extends RouteLike>(
929
951
  }
930
952
  }
931
953
  if (leaf.rawParams) Object.assign(rawParams, leaf.rawParams)
932
- return [rawParams, { part: partIndex, node: nodeIndex, path: pathIndex }]
954
+ return [
955
+ rawParams,
956
+ {
957
+ part: partIndex,
958
+ node: nodeIndex,
959
+ path: pathIndex,
960
+ segment: segmentCount,
961
+ },
962
+ ]
933
963
  }
934
964
 
935
965
  function buildRouteBranch<T extends RouteLike>(route: T) {
@@ -968,7 +998,7 @@ type MatchStackFrame<T extends RouteLike> = {
968
998
  dynamics: number
969
999
  optionals: number
970
1000
  /** intermediary state for param extraction */
971
- extract?: { part: number; node: number; path: number }
1001
+ extract?: ParamExtractionState
972
1002
  /** intermediary params from param extraction */
973
1003
  rawParams?: Record<string, string>
974
1004
  parsedParams?: Record<string, unknown>
package/src/router.ts CHANGED
@@ -594,6 +594,12 @@ export interface MatchRoutesOpts {
594
594
  snapshot?: MatchSnapshot
595
595
  }
596
596
 
597
+ export interface MatchRoutesResult {
598
+ matches: Array<AnyRouteMatch>
599
+ /** Raw string params extracted from path (before parsing) */
600
+ rawParams: Record<string, string>
601
+ }
602
+
597
603
  export type InferRouterContext<TRouteTree extends AnyRoute> =
598
604
  TRouteTree['types']['routerContext']
599
605
 
@@ -1265,16 +1271,17 @@ export class RouterCore<
1265
1271
  search: locationSearchOrOpts,
1266
1272
  } as ParsedLocation,
1267
1273
  opts,
1268
- )
1274
+ ).matches
1269
1275
  }
1270
1276
 
1271
1277
  return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1278
+ .matches
1272
1279
  }
1273
1280
 
1274
1281
  private matchRoutesInternal(
1275
1282
  next: ParsedLocation,
1276
1283
  opts?: MatchRoutesOpts,
1277
- ): Array<AnyRouteMatch> {
1284
+ ): MatchRoutesResult {
1278
1285
  // Fast-path: use snapshot hint if valid
1279
1286
  const snapshot = opts?.snapshot
1280
1287
  const snapshotValid =
@@ -1284,6 +1291,7 @@ export class RouterCore<
1284
1291
 
1285
1292
  let matchedRoutes: ReadonlyArray<AnyRoute>
1286
1293
  let routeParams: Record<string, string>
1294
+ let rawParams: Record<string, string>
1287
1295
  let globalNotFoundRouteId: string | undefined
1288
1296
  let parsedParams: Record<string, unknown>
1289
1297
 
@@ -1291,6 +1299,7 @@ export class RouterCore<
1291
1299
  // Rebuild matched routes from snapshot
1292
1300
  matchedRoutes = snapshot.routeIds.map((id) => this.routesById[id]!)
1293
1301
  routeParams = { ...snapshot.params }
1302
+ rawParams = { ...snapshot.params }
1294
1303
  globalNotFoundRouteId = snapshot.globalNotFoundRouteId
1295
1304
  parsedParams = snapshot.parsedParams
1296
1305
  } else {
@@ -1298,6 +1307,7 @@ export class RouterCore<
1298
1307
  const matchedRoutesResult = this.getMatchedRoutes(next.pathname)
1299
1308
  const { foundRoute, routeParams: rp } = matchedRoutesResult
1300
1309
  routeParams = rp
1310
+ rawParams = { ...rp } // Capture before routeParams gets modified
1301
1311
  matchedRoutes = matchedRoutesResult.matchedRoutes
1302
1312
  parsedParams = matchedRoutesResult.parsedParams
1303
1313
 
@@ -1642,7 +1652,7 @@ export class RouterCore<
1642
1652
  }
1643
1653
  })
1644
1654
 
1645
- return matches
1655
+ return { matches, rawParams }
1646
1656
  }
1647
1657
 
1648
1658
  getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
@@ -1765,9 +1775,10 @@ export class RouterCore<
1765
1775
  params: nextParams,
1766
1776
  }).interpolatedPath
1767
1777
 
1768
- const destMatches = this.matchRoutes(interpolatedNextTo, undefined, {
1769
- _buildLocation: true,
1770
- })
1778
+ const { matches: destMatches, rawParams } = this.matchRoutesInternal(
1779
+ { pathname: interpolatedNextTo } as ParsedLocation,
1780
+ { _buildLocation: true },
1781
+ )
1771
1782
  const destRoutes = destMatches.map(
1772
1783
  (d) => this.looseRoutesById[d.routeId]!,
1773
1784
  )
@@ -1856,10 +1867,15 @@ export class RouterCore<
1856
1867
  nextState = replaceEqualDeep(currentLocation.state, nextState)
1857
1868
 
1858
1869
  // Build match snapshot for fast-path on back/forward navigation
1859
- // Use destRoutes and nextParams directly (after stringify)
1870
+ // Use raw params captured during matchRoutesInternal (needed for literal path navigation
1871
+ // where nextParams may be empty but path contains param values)
1872
+ const snapshotParams = {
1873
+ ...rawParams,
1874
+ ...nextParams,
1875
+ }
1860
1876
  const matchSnapshot = buildMatchSnapshotFromRoutes({
1861
1877
  routes: destRoutes,
1862
- params: nextParams,
1878
+ params: snapshotParams,
1863
1879
  searchStr,
1864
1880
  globalNotFoundRouteId: globalNotFoundMatch?.routeId,
1865
1881
  })