@tanstack/router-core 1.154.14 → 1.155.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/dist/cjs/new-process-route-tree.cjs +47 -42
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/path.cjs +18 -14
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +11 -2
- package/dist/cjs/router.cjs +6 -8
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +1 -1
- package/dist/esm/new-process-route-tree.js +47 -42
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/path.d.ts +11 -2
- package/dist/esm/path.js +18 -14
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/router.d.ts +1 -1
- package/dist/esm/router.js +7 -9
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/new-process-route-tree.ts +66 -46
- package/src/path.ts +36 -16
- package/src/router.ts +8 -11
package/package.json
CHANGED
|
@@ -27,11 +27,17 @@ type ExtendedSegmentKind =
|
|
|
27
27
|
| typeof SEGMENT_TYPE_INDEX
|
|
28
28
|
| typeof SEGMENT_TYPE_PATHLESS
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
function getOpenAndCloseBraces(
|
|
31
|
+
part: string,
|
|
32
|
+
): [openBrace: number, closeBrace: number] | null {
|
|
33
|
+
const openBrace = part.indexOf('{')
|
|
34
|
+
if (openBrace === -1) return null
|
|
35
|
+
const closeBrace = part.indexOf('}', openBrace)
|
|
36
|
+
if (closeBrace === -1) return null
|
|
37
|
+
const afterOpen = openBrace + 1
|
|
38
|
+
if (afterOpen >= part.length) return null
|
|
39
|
+
return [openBrace, closeBrace]
|
|
40
|
+
}
|
|
35
41
|
|
|
36
42
|
type ParsedSegment = Uint16Array & {
|
|
37
43
|
/** segment type (0 = pathname, 1 = param, 2 = wildcard, 3 = optional param) */
|
|
@@ -110,47 +116,61 @@ export function parseSegment(
|
|
|
110
116
|
return output as ParsedSegment
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
119
|
+
const braces = getOpenAndCloseBraces(part)
|
|
120
|
+
if (braces) {
|
|
121
|
+
const [openBrace, closeBrace] = braces
|
|
122
|
+
const firstChar = part.charCodeAt(openBrace + 1)
|
|
123
|
+
|
|
124
|
+
// Check for {-$...} (optional param)
|
|
125
|
+
// prefix{-$paramName}suffix
|
|
126
|
+
// /^([^{]*)\{-\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/
|
|
127
|
+
if (firstChar === 45) {
|
|
128
|
+
// '-'
|
|
129
|
+
if (
|
|
130
|
+
openBrace + 2 < part.length &&
|
|
131
|
+
part.charCodeAt(openBrace + 2) === 36 // '$'
|
|
132
|
+
) {
|
|
133
|
+
const paramStart = openBrace + 3
|
|
134
|
+
const paramEnd = closeBrace
|
|
135
|
+
// Validate param name exists
|
|
136
|
+
if (paramStart < paramEnd) {
|
|
137
|
+
output[0] = SEGMENT_TYPE_OPTIONAL_PARAM
|
|
138
|
+
output[1] = start + openBrace
|
|
139
|
+
output[2] = start + paramStart
|
|
140
|
+
output[3] = start + paramEnd
|
|
141
|
+
output[4] = start + closeBrace + 1
|
|
142
|
+
output[5] = end
|
|
143
|
+
return output as ParsedSegment
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (firstChar === 36) {
|
|
147
|
+
// '$'
|
|
148
|
+
const dollarPos = openBrace + 1
|
|
149
|
+
const afterDollar = openBrace + 2
|
|
150
|
+
// Check for {$} (wildcard)
|
|
151
|
+
if (afterDollar === closeBrace) {
|
|
152
|
+
// For wildcard, value should be '$' (from dollarPos to afterDollar)
|
|
153
|
+
// prefix{$}suffix
|
|
154
|
+
// /^([^{]*)\{\$\}([^}]*)$/
|
|
155
|
+
output[0] = SEGMENT_TYPE_WILDCARD
|
|
156
|
+
output[1] = start + openBrace
|
|
157
|
+
output[2] = start + dollarPos
|
|
158
|
+
output[3] = start + afterDollar
|
|
159
|
+
output[4] = start + closeBrace + 1
|
|
160
|
+
output[5] = path.length
|
|
161
|
+
return output as ParsedSegment
|
|
162
|
+
}
|
|
163
|
+
// Regular param {$paramName} - value is the param name (after $)
|
|
164
|
+
// prefix{$paramName}suffix
|
|
165
|
+
// /^([^{]*)\{\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/
|
|
166
|
+
output[0] = SEGMENT_TYPE_PARAM
|
|
167
|
+
output[1] = start + openBrace
|
|
168
|
+
output[2] = start + afterDollar
|
|
169
|
+
output[3] = start + closeBrace
|
|
170
|
+
output[4] = start + closeBrace + 1
|
|
171
|
+
output[5] = end
|
|
172
|
+
return output as ParsedSegment
|
|
173
|
+
}
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
// fallback to static pathname (should never happen)
|
package/src/path.ts
CHANGED
|
@@ -197,11 +197,33 @@ export function resolvePath({
|
|
|
197
197
|
return result
|
|
198
198
|
}
|
|
199
199
|
|
|
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
|
+
|
|
200
219
|
interface InterpolatePathOptions {
|
|
201
220
|
path?: string
|
|
202
221
|
params: Record<string, unknown>
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
/**
|
|
223
|
+
* A function that decodes a path parameter value.
|
|
224
|
+
* Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.
|
|
225
|
+
*/
|
|
226
|
+
decoder?: (encoded: string) => string
|
|
205
227
|
}
|
|
206
228
|
|
|
207
229
|
type InterPolatePathResult = {
|
|
@@ -213,7 +235,7 @@ type InterPolatePathResult = {
|
|
|
213
235
|
function encodeParam(
|
|
214
236
|
key: string,
|
|
215
237
|
params: InterpolatePathOptions['params'],
|
|
216
|
-
|
|
238
|
+
decoder: InterpolatePathOptions['decoder'],
|
|
217
239
|
): any {
|
|
218
240
|
const value = params[key]
|
|
219
241
|
if (typeof value !== 'string') return value
|
|
@@ -222,7 +244,7 @@ function encodeParam(
|
|
|
222
244
|
// the splat/catch-all routes shouldn't have the '/' encoded out
|
|
223
245
|
return encodeURI(value)
|
|
224
246
|
} else {
|
|
225
|
-
return encodePathParam(value,
|
|
247
|
+
return encodePathParam(value, decoder)
|
|
226
248
|
}
|
|
227
249
|
}
|
|
228
250
|
|
|
@@ -235,7 +257,7 @@ function encodeParam(
|
|
|
235
257
|
export function interpolatePath({
|
|
236
258
|
path,
|
|
237
259
|
params,
|
|
238
|
-
|
|
260
|
+
decoder,
|
|
239
261
|
}: InterpolatePathOptions): InterPolatePathResult {
|
|
240
262
|
// Tracking if any params are missing in the `params` object
|
|
241
263
|
// when interpolating the path
|
|
@@ -286,7 +308,7 @@ export function interpolatePath({
|
|
|
286
308
|
continue
|
|
287
309
|
}
|
|
288
310
|
|
|
289
|
-
const value = encodeParam('_splat', params,
|
|
311
|
+
const value = encodeParam('_splat', params, decoder)
|
|
290
312
|
joined += '/' + prefix + value + suffix
|
|
291
313
|
continue
|
|
292
314
|
}
|
|
@@ -300,7 +322,7 @@ export function interpolatePath({
|
|
|
300
322
|
|
|
301
323
|
const prefix = path.substring(start, segment[1])
|
|
302
324
|
const suffix = path.substring(segment[4], end)
|
|
303
|
-
const value = encodeParam(key, params,
|
|
325
|
+
const value = encodeParam(key, params, decoder) ?? 'undefined'
|
|
304
326
|
joined += '/' + prefix + value + suffix
|
|
305
327
|
continue
|
|
306
328
|
}
|
|
@@ -316,7 +338,7 @@ export function interpolatePath({
|
|
|
316
338
|
|
|
317
339
|
const prefix = path.substring(start, segment[1])
|
|
318
340
|
const suffix = path.substring(segment[4], end)
|
|
319
|
-
const value = encodeParam(key, params,
|
|
341
|
+
const value = encodeParam(key, params, decoder) ?? ''
|
|
320
342
|
joined += '/' + prefix + value + suffix
|
|
321
343
|
continue
|
|
322
344
|
}
|
|
@@ -329,12 +351,10 @@ export function interpolatePath({
|
|
|
329
351
|
return { usedParams, interpolatedPath, isMissingParams }
|
|
330
352
|
}
|
|
331
353
|
|
|
332
|
-
function encodePathParam(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
340
360
|
}
|
package/src/router.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from './new-process-route-tree'
|
|
20
20
|
import {
|
|
21
21
|
cleanPath,
|
|
22
|
+
compileDecodeCharMap,
|
|
22
23
|
interpolatePath,
|
|
23
24
|
resolvePath,
|
|
24
25
|
trimPath,
|
|
@@ -923,7 +924,7 @@ export class RouterCore<
|
|
|
923
924
|
routesByPath!: RoutesByPath<TRouteTree>
|
|
924
925
|
processedTree!: ProcessedTree<TRouteTree, any, any>
|
|
925
926
|
isServer!: boolean
|
|
926
|
-
|
|
927
|
+
pathParamsDecoder?: (encoded: string) => string
|
|
927
928
|
|
|
928
929
|
/**
|
|
929
930
|
* @deprecated Use the `createRouter` function instead
|
|
@@ -992,14 +993,10 @@ export class RouterCore<
|
|
|
992
993
|
|
|
993
994
|
this.isServer = this.options.isServer ?? typeof document === 'undefined'
|
|
994
995
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
char,
|
|
1000
|
-
]),
|
|
1001
|
-
)
|
|
1002
|
-
: undefined
|
|
996
|
+
if (this.options.pathParamsAllowedCharacters)
|
|
997
|
+
this.pathParamsDecoder = compileDecodeCharMap(
|
|
998
|
+
this.options.pathParamsAllowedCharacters,
|
|
999
|
+
)
|
|
1003
1000
|
|
|
1004
1001
|
if (
|
|
1005
1002
|
!this.history ||
|
|
@@ -1365,7 +1362,7 @@ export class RouterCore<
|
|
|
1365
1362
|
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1366
1363
|
path: route.fullPath,
|
|
1367
1364
|
params: routeParams,
|
|
1368
|
-
|
|
1365
|
+
decoder: this.pathParamsDecoder,
|
|
1369
1366
|
})
|
|
1370
1367
|
|
|
1371
1368
|
// Waste not, want not. If we already have a match for this route,
|
|
@@ -1721,7 +1718,7 @@ export class RouterCore<
|
|
|
1721
1718
|
interpolatePath({
|
|
1722
1719
|
path: nextTo,
|
|
1723
1720
|
params: nextParams,
|
|
1724
|
-
|
|
1721
|
+
decoder: this.pathParamsDecoder,
|
|
1725
1722
|
}).interpolatedPath,
|
|
1726
1723
|
)
|
|
1727
1724
|
|