@@ -197,11 +197,33 @@ export function resolvePath({
197197 return result
198198}
199199
200+ /**
201+ * Create a pre-compiled decode config from allowed characters.
202+ * This should be called once at router initialization.
203+ */
204+ export function compileDecodeCharMap (
205+ pathParamsAllowedCharacters : ReadonlyArray < string > ,
206+ ) {
207+ const charMap = new Map (
208+ pathParamsAllowedCharacters . map ( ( char ) => [ encodeURIComponent ( char ) , char ] ) ,
209+ )
210+ // Escape special regex characters and join with |
211+ const pattern = Array . from ( charMap . keys ( ) )
212+ . map ( ( key ) => key . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) )
213+ . join ( '|' )
214+ const regex = new RegExp ( pattern , 'g' )
215+ return ( encoded : string ) =>
216+ encoded . replace ( regex , ( match ) => charMap . get ( match ) ?? match )
217+ }
218+
200219interface InterpolatePathOptions {
201220 path ?: string
202221 params : Record < string , unknown >
203- // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params
204- decodeCharMap ?: Map < string , string >
222+ /**
223+ * A function that decodes a path parameter value.
224+ * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.
225+ */
226+ decoder ?: ( encoded : string ) => string
205227}
206228
207229type InterPolatePathResult = {
@@ -213,7 +235,7 @@ type InterPolatePathResult = {
213235function encodeParam (
214236 key : string ,
215237 params : InterpolatePathOptions [ 'params' ] ,
216- decodeCharMap : InterpolatePathOptions [ 'decodeCharMap ' ] ,
238+ decoder : InterpolatePathOptions [ 'decoder ' ] ,
217239) : any {
218240 const value = params [ key ]
219241 if ( typeof value !== 'string' ) return value
@@ -222,7 +244,7 @@ function encodeParam(
222244 // the splat/catch-all routes shouldn't have the '/' encoded out
223245 return encodeURI ( value )
224246 } else {
225- return encodePathParam ( value , decodeCharMap )
247+ return encodePathParam ( value , decoder )
226248 }
227249}
228250
@@ -235,7 +257,7 @@ function encodeParam(
235257export function interpolatePath ( {
236258 path,
237259 params,
238- decodeCharMap ,
260+ decoder ,
239261} : InterpolatePathOptions ) : InterPolatePathResult {
240262 // Tracking if any params are missing in the `params` object
241263 // when interpolating the path
@@ -286,7 +308,7 @@ export function interpolatePath({
286308 continue
287309 }
288310
289- const value = encodeParam ( '_splat' , params , decodeCharMap )
311+ const value = encodeParam ( '_splat' , params , decoder )
290312 joined += '/' + prefix + value + suffix
291313 continue
292314 }
@@ -300,7 +322,7 @@ export function interpolatePath({
300322
301323 const prefix = path . substring ( start , segment [ 1 ] )
302324 const suffix = path . substring ( segment [ 4 ] , end )
303- const value = encodeParam ( key , params , decodeCharMap ) ?? 'undefined'
325+ const value = encodeParam ( key , params , decoder ) ?? 'undefined'
304326 joined += '/' + prefix + value + suffix
305327 continue
306328 }
@@ -316,7 +338,7 @@ export function interpolatePath({
316338
317339 const prefix = path . substring ( start , segment [ 1 ] )
318340 const suffix = path . substring ( segment [ 4 ] , end )
319- const value = encodeParam ( key , params , decodeCharMap ) ?? ''
341+ const value = encodeParam ( key , params , decoder ) ?? ''
320342 joined += '/' + prefix + value + suffix
321343 continue
322344 }
@@ -329,12 +351,10 @@ export function interpolatePath({
329351 return { usedParams, interpolatedPath, isMissingParams }
330352}
331353
332- function encodePathParam ( value : string , decodeCharMap ?: Map < string , string > ) {
333- let encoded = encodeURIComponent ( value )
334- if ( decodeCharMap ) {
335- for ( const [ encodedChar , char ] of decodeCharMap ) {
336- encoded = encoded . replaceAll ( encodedChar , char )
337- }
338- }
339- return encoded
354+ function encodePathParam (
355+ value : string ,
356+ decoder ?: InterpolatePathOptions [ 'decoder' ] ,
357+ ) {
358+ const encoded = encodeURIComponent ( value )
359+ return decoder ?.( encoded ) ?? encoded
340360}
0 commit comments