@tanstack/react-router 1.78.3 → 1.79.0

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/react-router",
3
- "version": "1.78.3",
3
+ "version": "1.79.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  "@tanstack/history": "1.61.1"
56
56
  },
57
57
  "devDependencies": {
58
- "@testing-library/jest-dom": "^6.6.2",
58
+ "@testing-library/jest-dom": "^6.6.3",
59
59
  "@testing-library/react": "^16.0.1",
60
60
  "@vitejs/plugin-react": "^4.3.3",
61
61
  "combinate": "^1.1.11",
@@ -66,7 +66,7 @@
66
66
  "peerDependencies": {
67
67
  "react": ">=18",
68
68
  "react-dom": ">=18",
69
- "@tanstack/router-generator": "1.78.3"
69
+ "@tanstack/router-generator": "1.79.0"
70
70
  },
71
71
  "peerDependenciesMeta": {
72
72
  "@tanstack/router-generator": {
package/src/path.ts CHANGED
@@ -204,6 +204,8 @@ interface InterpolatePathOptions {
204
204
  params: Record<string, unknown>
205
205
  leaveWildcards?: boolean
206
206
  leaveParams?: boolean
207
+ // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params
208
+ decodeCharMap?: Map<string, string>
207
209
  }
208
210
 
209
211
  export function interpolatePath({
@@ -211,6 +213,7 @@ export function interpolatePath({
211
213
  params,
212
214
  leaveWildcards,
213
215
  leaveParams,
216
+ decodeCharMap,
214
217
  }: InterpolatePathOptions) {
215
218
  const interpolatedPathSegments = parsePathname(path)
216
219
  const encodedParams: any = {}
@@ -222,7 +225,9 @@ export function interpolatePath({
222
225
  // the splat/catch-all routes shouldn't have the '/' encoded out
223
226
  encodedParams[key] = isValueString ? encodeURI(value) : value
224
227
  } else {
225
- encodedParams[key] = isValueString ? encodeURIComponent(value) : value
228
+ encodedParams[key] = isValueString
229
+ ? encodePathParam(value, decodeCharMap)
230
+ : value
226
231
  }
227
232
  }
228
233
 
@@ -247,6 +252,16 @@ export function interpolatePath({
247
252
  )
248
253
  }
249
254
 
255
+ function encodePathParam(value: string, decodeCharMap?: Map<string, string>) {
256
+ let encoded = encodeURIComponent(value)
257
+ if (decodeCharMap) {
258
+ for (const [encodedChar, char] of decodeCharMap) {
259
+ encoded = encoded.replaceAll(encodedChar, char)
260
+ }
261
+ }
262
+ return encoded
263
+ }
264
+
250
265
  export function matchPathname(
251
266
  basepath: string,
252
267
  currentPathname: string,
package/src/router.ts CHANGED
@@ -464,6 +464,15 @@ export interface RouterOptions<
464
464
  */
465
465
  strict?: boolean
466
466
  }
467
+ /**
468
+ * Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
469
+ *
470
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#pathparamsallowedcharacters-property)
471
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#allowed-characters)
472
+ */
473
+ pathParamsAllowedCharacters?: Array<
474
+ ';' | ':' | '@' | '&' | '=' | '+' | '$' | ','
475
+ >
467
476
  }
468
477
 
469
478
  export interface RouterErrorSerializer<TSerializedError> {
@@ -711,6 +720,7 @@ export class Router<
711
720
  routesByPath!: RoutesByPath<TRouteTree>
712
721
  flatRoutes!: Array<AnyRoute>
713
722
  isServer!: boolean
723
+ pathParamsDecodeCharMap?: Map<string, string>
714
724
 
715
725
  /**
716
726
  * @deprecated Use the `createRouter` function instead
@@ -768,6 +778,15 @@ export class Router<
768
778
 
769
779
  this.isServer = this.options.isServer ?? typeof document === 'undefined'
770
780
 
781
+ this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters
782
+ ? new Map(
783
+ this.options.pathParamsAllowedCharacters.map((char) => [
784
+ encodeURIComponent(char),
785
+ char,
786
+ ]),
787
+ )
788
+ : undefined
789
+
771
790
  if (
772
791
  !this.basepath ||
773
792
  (newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
@@ -1187,6 +1206,7 @@ export class Router<
1187
1206
  const interpolatedPath = interpolatePath({
1188
1207
  path: route.fullPath,
1189
1208
  params: routeParams,
1209
+ decodeCharMap: this.pathParamsDecodeCharMap,
1190
1210
  })
1191
1211
 
1192
1212
  const matchId =
@@ -1194,6 +1214,7 @@ export class Router<
1194
1214
  path: route.id,
1195
1215
  params: routeParams,
1196
1216
  leaveWildcards: true,
1217
+ decodeCharMap: this.pathParamsDecodeCharMap,
1197
1218
  }) + loaderDepsHash
1198
1219
 
1199
1220
  // Waste not, want not. If we already have a match for this route,
@@ -1431,6 +1452,7 @@ export class Router<
1431
1452
  const interpolatedPath = interpolatePath({
1432
1453
  path: route.fullPath,
1433
1454
  params: matchedRoutesResult?.routeParams ?? {},
1455
+ decodeCharMap: this.pathParamsDecodeCharMap,
1434
1456
  })
1435
1457
  const pathname = joinPaths([this.basepath, interpolatedPath])
1436
1458
  return pathname === fromPath
@@ -1467,6 +1489,7 @@ export class Router<
1467
1489
  params: nextParams ?? {},
1468
1490
  leaveWildcards: false,
1469
1491
  leaveParams: opts.leaveParams,
1492
+ decodeCharMap: this.pathParamsDecodeCharMap,
1470
1493
  })
1471
1494
 
1472
1495
  let search = fromSearch