@tanstack/router-core 1.128.4 → 1.128.7
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/path.cjs +237 -228
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +11 -7
- package/dist/cjs/router.cjs +29 -14
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/esm/path.d.ts +11 -7
- package/dist/esm/path.js +237 -228
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/router.js +30 -15
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/path.ts +308 -298
- package/src/router.ts +45 -16
package/src/path.ts
CHANGED
|
@@ -2,13 +2,22 @@ import { last } from './utils'
|
|
|
2
2
|
import type { MatchLocation } from './RouterProvider'
|
|
3
3
|
import type { AnyPathParams } from './route'
|
|
4
4
|
|
|
5
|
+
export const SEGMENT_TYPE_PATHNAME = 0
|
|
6
|
+
export const SEGMENT_TYPE_PARAM = 1
|
|
7
|
+
export const SEGMENT_TYPE_WILDCARD = 2
|
|
8
|
+
export const SEGMENT_TYPE_OPTIONAL_PARAM = 3
|
|
9
|
+
|
|
5
10
|
export interface Segment {
|
|
6
|
-
type:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
readonly type:
|
|
12
|
+
| typeof SEGMENT_TYPE_PATHNAME
|
|
13
|
+
| typeof SEGMENT_TYPE_PARAM
|
|
14
|
+
| typeof SEGMENT_TYPE_WILDCARD
|
|
15
|
+
| typeof SEGMENT_TYPE_OPTIONAL_PARAM
|
|
16
|
+
readonly value: string
|
|
17
|
+
readonly prefixSegment?: string
|
|
18
|
+
readonly suffixSegment?: string
|
|
10
19
|
// Indicates if there is a static segment after this required/optional param
|
|
11
|
-
hasStaticAfter?: boolean
|
|
20
|
+
readonly hasStaticAfter?: boolean
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
export function joinPaths(paths: Array<string | undefined>) {
|
|
@@ -94,6 +103,51 @@ interface ResolvePathOptions {
|
|
|
94
103
|
caseSensitive?: boolean
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
function segmentToString(segment: Segment): string {
|
|
107
|
+
const { type, value } = segment
|
|
108
|
+
if (type === SEGMENT_TYPE_PATHNAME) {
|
|
109
|
+
return value
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { prefixSegment, suffixSegment } = segment
|
|
113
|
+
|
|
114
|
+
if (type === SEGMENT_TYPE_PARAM) {
|
|
115
|
+
const param = value.substring(1)
|
|
116
|
+
if (prefixSegment && suffixSegment) {
|
|
117
|
+
return `${prefixSegment}{$${param}}${suffixSegment}`
|
|
118
|
+
} else if (prefixSegment) {
|
|
119
|
+
return `${prefixSegment}{$${param}}`
|
|
120
|
+
} else if (suffixSegment) {
|
|
121
|
+
return `{$${param}}${suffixSegment}`
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
126
|
+
const param = value.substring(1)
|
|
127
|
+
if (prefixSegment && suffixSegment) {
|
|
128
|
+
return `${prefixSegment}{-$${param}}${suffixSegment}`
|
|
129
|
+
} else if (prefixSegment) {
|
|
130
|
+
return `${prefixSegment}{-$${param}}`
|
|
131
|
+
} else if (suffixSegment) {
|
|
132
|
+
return `{-$${param}}${suffixSegment}`
|
|
133
|
+
}
|
|
134
|
+
return `{-$${param}}`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (type === SEGMENT_TYPE_WILDCARD) {
|
|
138
|
+
if (prefixSegment && suffixSegment) {
|
|
139
|
+
return `${prefixSegment}{$}${suffixSegment}`
|
|
140
|
+
} else if (prefixSegment) {
|
|
141
|
+
return `${prefixSegment}{$}`
|
|
142
|
+
} else if (suffixSegment) {
|
|
143
|
+
return `{$}${suffixSegment}`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// This case should never happen, should we throw instead?
|
|
148
|
+
return value
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
export function resolvePath({
|
|
98
152
|
basepath,
|
|
99
153
|
base,
|
|
@@ -104,79 +158,48 @@ export function resolvePath({
|
|
|
104
158
|
base = removeBasepath(basepath, base, caseSensitive)
|
|
105
159
|
to = removeBasepath(basepath, to, caseSensitive)
|
|
106
160
|
|
|
107
|
-
let baseSegments = parsePathname(base)
|
|
161
|
+
let baseSegments = parsePathname(base).slice()
|
|
108
162
|
const toSegments = parsePathname(to)
|
|
109
163
|
|
|
110
164
|
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
111
165
|
baseSegments.pop()
|
|
112
166
|
}
|
|
113
167
|
|
|
114
|
-
|
|
115
|
-
|
|
168
|
+
for (let index = 0, length = toSegments.length; index < length; index++) {
|
|
169
|
+
const toSegment = toSegments[index]!
|
|
170
|
+
const value = toSegment.value
|
|
171
|
+
if (value === '/') {
|
|
116
172
|
if (!index) {
|
|
117
173
|
// Leading slash
|
|
118
174
|
baseSegments = [toSegment]
|
|
119
|
-
} else if (index ===
|
|
175
|
+
} else if (index === length - 1) {
|
|
120
176
|
// Trailing Slash
|
|
121
177
|
baseSegments.push(toSegment)
|
|
122
178
|
} else {
|
|
123
179
|
// ignore inter-slashes
|
|
124
180
|
}
|
|
125
|
-
} else if (
|
|
181
|
+
} else if (value === '..') {
|
|
126
182
|
baseSegments.pop()
|
|
127
|
-
} else if (
|
|
183
|
+
} else if (value === '.') {
|
|
128
184
|
// ignore
|
|
129
185
|
} else {
|
|
130
186
|
baseSegments.push(toSegment)
|
|
131
187
|
}
|
|
132
|
-
}
|
|
188
|
+
}
|
|
133
189
|
|
|
134
190
|
if (baseSegments.length > 1) {
|
|
135
|
-
if (last(baseSegments)
|
|
191
|
+
if (last(baseSegments)!.value === '/') {
|
|
136
192
|
if (trailingSlash === 'never') {
|
|
137
193
|
baseSegments.pop()
|
|
138
194
|
}
|
|
139
195
|
} else if (trailingSlash === 'always') {
|
|
140
|
-
baseSegments.push({ type:
|
|
196
|
+
baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })
|
|
141
197
|
}
|
|
142
198
|
}
|
|
143
199
|
|
|
144
|
-
const segmentValues = baseSegments.map(
|
|
145
|
-
if (segment.type === 'param') {
|
|
146
|
-
const param = segment.value.substring(1)
|
|
147
|
-
if (segment.prefixSegment && segment.suffixSegment) {
|
|
148
|
-
return `${segment.prefixSegment}{$${param}}${segment.suffixSegment}`
|
|
149
|
-
} else if (segment.prefixSegment) {
|
|
150
|
-
return `${segment.prefixSegment}{$${param}}`
|
|
151
|
-
} else if (segment.suffixSegment) {
|
|
152
|
-
return `{$${param}}${segment.suffixSegment}`
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (segment.type === 'optional-param') {
|
|
156
|
-
const param = segment.value.substring(1)
|
|
157
|
-
if (segment.prefixSegment && segment.suffixSegment) {
|
|
158
|
-
return `${segment.prefixSegment}{-$${param}}${segment.suffixSegment}`
|
|
159
|
-
} else if (segment.prefixSegment) {
|
|
160
|
-
return `${segment.prefixSegment}{-$${param}}`
|
|
161
|
-
} else if (segment.suffixSegment) {
|
|
162
|
-
return `{-$${param}}${segment.suffixSegment}`
|
|
163
|
-
}
|
|
164
|
-
return `{-$${param}}`
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (segment.type === 'wildcard') {
|
|
168
|
-
if (segment.prefixSegment && segment.suffixSegment) {
|
|
169
|
-
return `${segment.prefixSegment}{$}${segment.suffixSegment}`
|
|
170
|
-
} else if (segment.prefixSegment) {
|
|
171
|
-
return `${segment.prefixSegment}{$}`
|
|
172
|
-
} else if (segment.suffixSegment) {
|
|
173
|
-
return `{$}${segment.suffixSegment}`
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return segment.value
|
|
177
|
-
})
|
|
200
|
+
const segmentValues = baseSegments.map(segmentToString)
|
|
178
201
|
const joined = joinPaths([basepath, ...segmentValues])
|
|
179
|
-
return
|
|
202
|
+
return joined
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
const PARAM_RE = /^\$.{1,}$/ // $paramName
|
|
@@ -204,7 +227,7 @@ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
|
|
|
204
227
|
* - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
|
|
205
228
|
* - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
|
|
206
229
|
*/
|
|
207
|
-
export function parsePathname(pathname?: string):
|
|
230
|
+
export function parsePathname(pathname?: string): ReadonlyArray<Segment> {
|
|
208
231
|
if (!pathname) {
|
|
209
232
|
return []
|
|
210
233
|
}
|
|
@@ -216,7 +239,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
216
239
|
if (pathname.slice(0, 1) === '/') {
|
|
217
240
|
pathname = pathname.substring(1)
|
|
218
241
|
segments.push({
|
|
219
|
-
type:
|
|
242
|
+
type: SEGMENT_TYPE_PATHNAME,
|
|
220
243
|
value: '/',
|
|
221
244
|
})
|
|
222
245
|
}
|
|
@@ -236,7 +259,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
236
259
|
const prefix = wildcardBracesMatch[1]
|
|
237
260
|
const suffix = wildcardBracesMatch[2]
|
|
238
261
|
return {
|
|
239
|
-
type:
|
|
262
|
+
type: SEGMENT_TYPE_WILDCARD,
|
|
240
263
|
value: '$',
|
|
241
264
|
prefixSegment: prefix || undefined,
|
|
242
265
|
suffixSegment: suffix || undefined,
|
|
@@ -252,7 +275,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
252
275
|
const paramName = optionalParamBracesMatch[2]!
|
|
253
276
|
const suffix = optionalParamBracesMatch[3]
|
|
254
277
|
return {
|
|
255
|
-
type:
|
|
278
|
+
type: SEGMENT_TYPE_OPTIONAL_PARAM,
|
|
256
279
|
value: paramName, // Now just $paramName (no prefix)
|
|
257
280
|
prefixSegment: prefix || undefined,
|
|
258
281
|
suffixSegment: suffix || undefined,
|
|
@@ -266,7 +289,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
266
289
|
const paramName = paramBracesMatch[2]
|
|
267
290
|
const suffix = paramBracesMatch[3]
|
|
268
291
|
return {
|
|
269
|
-
type:
|
|
292
|
+
type: SEGMENT_TYPE_PARAM,
|
|
270
293
|
value: '' + paramName,
|
|
271
294
|
prefixSegment: prefix || undefined,
|
|
272
295
|
suffixSegment: suffix || undefined,
|
|
@@ -277,7 +300,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
277
300
|
if (PARAM_RE.test(part)) {
|
|
278
301
|
const paramName = part.substring(1)
|
|
279
302
|
return {
|
|
280
|
-
type:
|
|
303
|
+
type: SEGMENT_TYPE_PARAM,
|
|
281
304
|
value: '$' + paramName,
|
|
282
305
|
prefixSegment: undefined,
|
|
283
306
|
suffixSegment: undefined,
|
|
@@ -287,7 +310,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
287
310
|
// Check for bare wildcard: $ (without curly braces)
|
|
288
311
|
if (WILDCARD_RE.test(part)) {
|
|
289
312
|
return {
|
|
290
|
-
type:
|
|
313
|
+
type: SEGMENT_TYPE_WILDCARD,
|
|
291
314
|
value: '$',
|
|
292
315
|
prefixSegment: undefined,
|
|
293
316
|
suffixSegment: undefined,
|
|
@@ -296,7 +319,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
296
319
|
|
|
297
320
|
// Handle regular pathname segment
|
|
298
321
|
return {
|
|
299
|
-
type:
|
|
322
|
+
type: SEGMENT_TYPE_PATHNAME,
|
|
300
323
|
value: part.includes('%25')
|
|
301
324
|
? part
|
|
302
325
|
.split('%25')
|
|
@@ -310,7 +333,7 @@ export function parsePathname(pathname?: string): Array<Segment> {
|
|
|
310
333
|
if (pathname.slice(-1) === '/') {
|
|
311
334
|
pathname = pathname.substring(1)
|
|
312
335
|
segments.push({
|
|
313
|
-
type:
|
|
336
|
+
type: SEGMENT_TYPE_PATHNAME,
|
|
314
337
|
value: '/',
|
|
315
338
|
})
|
|
316
339
|
}
|
|
@@ -345,7 +368,7 @@ export function interpolatePath({
|
|
|
345
368
|
const value = params[key]
|
|
346
369
|
const isValueString = typeof value === 'string'
|
|
347
370
|
|
|
348
|
-
if (
|
|
371
|
+
if (key === '*' || key === '_splat') {
|
|
349
372
|
// the splat/catch-all routes shouldn't have the '/' encoded out
|
|
350
373
|
return isValueString ? encodeURI(value) : value
|
|
351
374
|
} else {
|
|
@@ -360,7 +383,11 @@ export function interpolatePath({
|
|
|
360
383
|
const usedParams: Record<string, unknown> = {}
|
|
361
384
|
const interpolatedPath = joinPaths(
|
|
362
385
|
interpolatedPathSegments.map((segment) => {
|
|
363
|
-
if (segment.type ===
|
|
386
|
+
if (segment.type === SEGMENT_TYPE_PATHNAME) {
|
|
387
|
+
return segment.value
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (segment.type === SEGMENT_TYPE_WILDCARD) {
|
|
364
391
|
usedParams._splat = params._splat
|
|
365
392
|
const segmentPrefix = segment.prefixSegment || ''
|
|
366
393
|
const segmentSuffix = segment.suffixSegment || ''
|
|
@@ -386,7 +413,7 @@ export function interpolatePath({
|
|
|
386
413
|
return `${segmentPrefix}${value}${segmentSuffix}`
|
|
387
414
|
}
|
|
388
415
|
|
|
389
|
-
if (segment.type ===
|
|
416
|
+
if (segment.type === SEGMENT_TYPE_PARAM) {
|
|
390
417
|
const key = segment.value.substring(1)
|
|
391
418
|
if (!isMissingParams && !(key in params)) {
|
|
392
419
|
isMissingParams = true
|
|
@@ -402,7 +429,7 @@ export function interpolatePath({
|
|
|
402
429
|
return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`
|
|
403
430
|
}
|
|
404
431
|
|
|
405
|
-
if (segment.type ===
|
|
432
|
+
if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
406
433
|
const key = segment.value.substring(1)
|
|
407
434
|
|
|
408
435
|
const segmentPrefix = segment.prefixSegment || ''
|
|
@@ -410,6 +437,9 @@ export function interpolatePath({
|
|
|
410
437
|
|
|
411
438
|
// Check if optional parameter is missing or undefined
|
|
412
439
|
if (!(key in params) || params[key] == null) {
|
|
440
|
+
if (leaveWildcards) {
|
|
441
|
+
return `${segmentPrefix}${key}${segmentSuffix}`
|
|
442
|
+
}
|
|
413
443
|
// For optional params with prefix/suffix, keep the prefix/suffix but omit the param
|
|
414
444
|
if (segmentPrefix || segmentSuffix) {
|
|
415
445
|
return `${segmentPrefix}${segmentSuffix}`
|
|
@@ -502,165 +532,214 @@ export function removeBasepath(
|
|
|
502
532
|
export function matchByPath(
|
|
503
533
|
basepath: string,
|
|
504
534
|
from: string,
|
|
505
|
-
|
|
535
|
+
{
|
|
536
|
+
to,
|
|
537
|
+
fuzzy,
|
|
538
|
+
caseSensitive,
|
|
539
|
+
}: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,
|
|
506
540
|
): Record<string, string> | undefined {
|
|
507
541
|
// check basepath first
|
|
508
542
|
if (basepath !== '/' && !from.startsWith(basepath)) {
|
|
509
543
|
return undefined
|
|
510
544
|
}
|
|
511
545
|
// Remove the base path from the pathname
|
|
512
|
-
from = removeBasepath(basepath, from,
|
|
546
|
+
from = removeBasepath(basepath, from, caseSensitive)
|
|
513
547
|
// Default to to $ (wildcard)
|
|
514
|
-
|
|
515
|
-
basepath,
|
|
516
|
-
`${matchLocation.to ?? '$'}`,
|
|
517
|
-
matchLocation.caseSensitive,
|
|
518
|
-
)
|
|
548
|
+
to = removeBasepath(basepath, `${to ?? '$'}`, caseSensitive)
|
|
519
549
|
|
|
520
550
|
// Parse the from and to
|
|
521
|
-
const baseSegments = parsePathname(from)
|
|
522
|
-
const routeSegments = parsePathname(to)
|
|
523
|
-
|
|
524
|
-
if (!from.startsWith('/')) {
|
|
525
|
-
baseSegments.unshift({
|
|
526
|
-
type: 'pathname',
|
|
527
|
-
value: '/',
|
|
528
|
-
})
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (!to.startsWith('/')) {
|
|
532
|
-
routeSegments.unshift({
|
|
533
|
-
type: 'pathname',
|
|
534
|
-
value: '/',
|
|
535
|
-
})
|
|
536
|
-
}
|
|
551
|
+
const baseSegments = parsePathname(from.startsWith('/') ? from : `/${from}`)
|
|
552
|
+
const routeSegments = parsePathname(to.startsWith('/') ? to : `/${to}`)
|
|
537
553
|
|
|
538
554
|
const params: Record<string, string> = {}
|
|
539
555
|
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
556
|
+
const result = isMatch(
|
|
557
|
+
baseSegments,
|
|
558
|
+
routeSegments,
|
|
559
|
+
params,
|
|
560
|
+
fuzzy,
|
|
561
|
+
caseSensitive,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return result ? params : undefined
|
|
565
|
+
}
|
|
543
566
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
567
|
+
function isMatch(
|
|
568
|
+
baseSegments: ReadonlyArray<Segment>,
|
|
569
|
+
routeSegments: ReadonlyArray<Segment>,
|
|
570
|
+
params: Record<string, string>,
|
|
571
|
+
fuzzy?: boolean,
|
|
572
|
+
caseSensitive?: boolean,
|
|
573
|
+
): boolean {
|
|
574
|
+
let baseIndex = 0
|
|
575
|
+
let routeIndex = 0
|
|
550
576
|
|
|
551
|
-
|
|
552
|
-
|
|
577
|
+
while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {
|
|
578
|
+
const baseSegment = baseSegments[baseIndex]
|
|
579
|
+
const routeSegment = routeSegments[routeIndex]
|
|
553
580
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
581
|
+
if (routeSegment) {
|
|
582
|
+
if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {
|
|
583
|
+
// Capture all remaining segments for a wildcard
|
|
584
|
+
const remainingBaseSegments = baseSegments.slice(baseIndex)
|
|
558
585
|
|
|
559
|
-
|
|
586
|
+
let _splat: string
|
|
560
587
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
588
|
+
// If this is a wildcard with prefix/suffix, we need to handle the first segment specially
|
|
589
|
+
if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
|
|
590
|
+
if (!baseSegment) return false
|
|
564
591
|
|
|
565
|
-
|
|
566
|
-
|
|
592
|
+
const prefix = routeSegment.prefixSegment || ''
|
|
593
|
+
const suffix = routeSegment.suffixSegment || ''
|
|
567
594
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
595
|
+
// Check if the base segment starts with prefix and ends with suffix
|
|
596
|
+
const baseValue = baseSegment.value
|
|
597
|
+
if ('prefixSegment' in routeSegment) {
|
|
598
|
+
if (!baseValue.startsWith(prefix)) {
|
|
599
|
+
return false
|
|
574
600
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
|
|
601
|
+
}
|
|
602
|
+
if ('suffixSegment' in routeSegment) {
|
|
603
|
+
if (
|
|
604
|
+
!baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)
|
|
605
|
+
) {
|
|
606
|
+
return false
|
|
581
607
|
}
|
|
608
|
+
}
|
|
582
609
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
// Remove the prefix and suffix from the rejoined splat
|
|
588
|
-
if (prefix && rejoinedSplat.startsWith(prefix)) {
|
|
589
|
-
rejoinedSplat = rejoinedSplat.slice(prefix.length)
|
|
590
|
-
}
|
|
610
|
+
let rejoinedSplat = decodeURI(
|
|
611
|
+
joinPaths(remainingBaseSegments.map((d) => d.value)),
|
|
612
|
+
)
|
|
591
613
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
)
|
|
597
|
-
}
|
|
614
|
+
// Remove the prefix and suffix from the rejoined splat
|
|
615
|
+
if (prefix && rejoinedSplat.startsWith(prefix)) {
|
|
616
|
+
rejoinedSplat = rejoinedSplat.slice(prefix.length)
|
|
617
|
+
}
|
|
598
618
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
joinPaths(remainingBaseSegments.map((d) => d.value)),
|
|
619
|
+
if (suffix && rejoinedSplat.endsWith(suffix)) {
|
|
620
|
+
rejoinedSplat = rejoinedSplat.slice(
|
|
621
|
+
0,
|
|
622
|
+
rejoinedSplat.length - suffix.length,
|
|
604
623
|
)
|
|
605
624
|
}
|
|
606
625
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
626
|
+
_splat = rejoinedSplat
|
|
627
|
+
} else {
|
|
628
|
+
// If no prefix/suffix, just rejoin the remaining segments
|
|
629
|
+
_splat = decodeURI(
|
|
630
|
+
joinPaths(remainingBaseSegments.map((d) => d.value)),
|
|
631
|
+
)
|
|
611
632
|
}
|
|
612
633
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
634
|
+
// TODO: Deprecate *
|
|
635
|
+
params['*'] = _splat
|
|
636
|
+
params['_splat'] = _splat
|
|
637
|
+
return true
|
|
638
|
+
}
|
|
618
639
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
) {
|
|
640
|
+
if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {
|
|
641
|
+
if (routeSegment.value === '/' && !baseSegment?.value) {
|
|
642
|
+
routeIndex++
|
|
643
|
+
continue
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (baseSegment) {
|
|
647
|
+
if (caseSensitive) {
|
|
648
|
+
if (routeSegment.value !== baseSegment.value) {
|
|
628
649
|
return false
|
|
629
650
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
} else {
|
|
651
|
+
} else if (
|
|
652
|
+
routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()
|
|
653
|
+
) {
|
|
634
654
|
return false
|
|
635
655
|
}
|
|
656
|
+
baseIndex++
|
|
657
|
+
routeIndex++
|
|
658
|
+
continue
|
|
659
|
+
} else {
|
|
660
|
+
return false
|
|
636
661
|
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (routeSegment.type === SEGMENT_TYPE_PARAM) {
|
|
665
|
+
if (!baseSegment) {
|
|
666
|
+
return false
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (baseSegment.value === '/') {
|
|
670
|
+
return false
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let _paramValue = ''
|
|
674
|
+
let matched = false
|
|
637
675
|
|
|
638
|
-
|
|
639
|
-
|
|
676
|
+
// If this param has prefix/suffix, we need to extract the actual parameter value
|
|
677
|
+
if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
|
|
678
|
+
const prefix = routeSegment.prefixSegment || ''
|
|
679
|
+
const suffix = routeSegment.suffixSegment || ''
|
|
680
|
+
|
|
681
|
+
// Check if the base segment starts with prefix and ends with suffix
|
|
682
|
+
const baseValue = baseSegment.value
|
|
683
|
+
if (prefix && !baseValue.startsWith(prefix)) {
|
|
640
684
|
return false
|
|
641
685
|
}
|
|
642
|
-
|
|
643
|
-
if (baseSegment.value === '/') {
|
|
686
|
+
if (suffix && !baseValue.endsWith(suffix)) {
|
|
644
687
|
return false
|
|
645
688
|
}
|
|
646
689
|
|
|
647
|
-
let
|
|
648
|
-
|
|
690
|
+
let paramValue = baseValue
|
|
691
|
+
if (prefix && paramValue.startsWith(prefix)) {
|
|
692
|
+
paramValue = paramValue.slice(prefix.length)
|
|
693
|
+
}
|
|
694
|
+
if (suffix && paramValue.endsWith(suffix)) {
|
|
695
|
+
paramValue = paramValue.slice(0, paramValue.length - suffix.length)
|
|
696
|
+
}
|
|
649
697
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
698
|
+
_paramValue = decodeURIComponent(paramValue)
|
|
699
|
+
matched = true
|
|
700
|
+
} else {
|
|
701
|
+
// If no prefix/suffix, just decode the base segment value
|
|
702
|
+
_paramValue = decodeURIComponent(baseSegment.value)
|
|
703
|
+
matched = true
|
|
704
|
+
}
|
|
654
705
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
if (suffix && !baseValue.endsWith(suffix)) {
|
|
661
|
-
return false
|
|
662
|
-
}
|
|
706
|
+
if (matched) {
|
|
707
|
+
params[routeSegment.value.substring(1)] = _paramValue
|
|
708
|
+
baseIndex++
|
|
709
|
+
}
|
|
663
710
|
|
|
711
|
+
routeIndex++
|
|
712
|
+
continue
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
716
|
+
// Optional parameters can be missing - don't fail the match
|
|
717
|
+
if (!baseSegment) {
|
|
718
|
+
// No base segment for optional param - skip this route segment
|
|
719
|
+
routeIndex++
|
|
720
|
+
continue
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (baseSegment.value === '/') {
|
|
724
|
+
// Skip slash segments for optional params
|
|
725
|
+
routeIndex++
|
|
726
|
+
continue
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
let _paramValue = ''
|
|
730
|
+
let matched = false
|
|
731
|
+
|
|
732
|
+
// If this optional param has prefix/suffix, we need to extract the actual parameter value
|
|
733
|
+
if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
|
|
734
|
+
const prefix = routeSegment.prefixSegment || ''
|
|
735
|
+
const suffix = routeSegment.suffixSegment || ''
|
|
736
|
+
|
|
737
|
+
// Check if the base segment starts with prefix and ends with suffix
|
|
738
|
+
const baseValue = baseSegment.value
|
|
739
|
+
if (
|
|
740
|
+
(!prefix || baseValue.startsWith(prefix)) &&
|
|
741
|
+
(!suffix || baseValue.endsWith(suffix))
|
|
742
|
+
) {
|
|
664
743
|
let paramValue = baseValue
|
|
665
744
|
if (prefix && paramValue.startsWith(prefix)) {
|
|
666
745
|
paramValue = paramValue.slice(prefix.length)
|
|
@@ -674,146 +753,77 @@ export function matchByPath(
|
|
|
674
753
|
|
|
675
754
|
_paramValue = decodeURIComponent(paramValue)
|
|
676
755
|
matched = true
|
|
677
|
-
} else {
|
|
678
|
-
// If no prefix/suffix, just decode the base segment value
|
|
679
|
-
_paramValue = decodeURIComponent(baseSegment.value)
|
|
680
|
-
matched = true
|
|
681
756
|
}
|
|
682
|
-
|
|
683
|
-
if
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (!baseSegment) {
|
|
695
|
-
// No base segment for optional param - skip this route segment
|
|
696
|
-
routeIndex++
|
|
697
|
-
continue
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if (baseSegment.value === '/') {
|
|
701
|
-
// Skip slash segments for optional params
|
|
702
|
-
routeIndex++
|
|
703
|
-
continue
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
let _paramValue = ''
|
|
707
|
-
let matched = false
|
|
708
|
-
|
|
709
|
-
// If this optional param has prefix/suffix, we need to extract the actual parameter value
|
|
710
|
-
if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
|
|
711
|
-
const prefix = routeSegment.prefixSegment || ''
|
|
712
|
-
const suffix = routeSegment.suffixSegment || ''
|
|
713
|
-
|
|
714
|
-
// Check if the base segment starts with prefix and ends with suffix
|
|
715
|
-
const baseValue = baseSegment.value
|
|
757
|
+
} else {
|
|
758
|
+
// For optional params without prefix/suffix, we need to check if the current
|
|
759
|
+
// base segment should match this optional param or a later route segment
|
|
760
|
+
|
|
761
|
+
// Look ahead to see if there's a later route segment that matches the current base segment
|
|
762
|
+
let shouldMatchOptional = true
|
|
763
|
+
for (
|
|
764
|
+
let lookAhead = routeIndex + 1;
|
|
765
|
+
lookAhead < routeSegments.length;
|
|
766
|
+
lookAhead++
|
|
767
|
+
) {
|
|
768
|
+
const futureRouteSegment = routeSegments[lookAhead]
|
|
716
769
|
if (
|
|
717
|
-
|
|
718
|
-
|
|
770
|
+
futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&
|
|
771
|
+
futureRouteSegment.value === baseSegment.value
|
|
719
772
|
) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (suffix && paramValue.endsWith(suffix)) {
|
|
725
|
-
paramValue = paramValue.slice(
|
|
726
|
-
0,
|
|
727
|
-
paramValue.length - suffix.length,
|
|
728
|
-
)
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
_paramValue = decodeURIComponent(paramValue)
|
|
732
|
-
matched = true
|
|
733
|
-
}
|
|
734
|
-
} else {
|
|
735
|
-
// For optional params without prefix/suffix, we need to check if the current
|
|
736
|
-
// base segment should match this optional param or a later route segment
|
|
737
|
-
|
|
738
|
-
// Look ahead to see if there's a later route segment that matches the current base segment
|
|
739
|
-
let shouldMatchOptional = true
|
|
740
|
-
for (
|
|
741
|
-
let lookAhead = routeIndex + 1;
|
|
742
|
-
lookAhead < routeSegments.length;
|
|
743
|
-
lookAhead++
|
|
744
|
-
) {
|
|
745
|
-
const futureRouteSegment = routeSegments[lookAhead]
|
|
746
|
-
if (
|
|
747
|
-
futureRouteSegment?.type === 'pathname' &&
|
|
748
|
-
futureRouteSegment.value === baseSegment.value
|
|
749
|
-
) {
|
|
750
|
-
// The current base segment matches a future pathname segment,
|
|
751
|
-
// so we should skip this optional parameter
|
|
752
|
-
shouldMatchOptional = false
|
|
753
|
-
break
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// If we encounter a required param or wildcard, stop looking ahead
|
|
757
|
-
if (
|
|
758
|
-
futureRouteSegment?.type === 'param' ||
|
|
759
|
-
futureRouteSegment?.type === 'wildcard'
|
|
760
|
-
) {
|
|
761
|
-
break
|
|
762
|
-
}
|
|
773
|
+
// The current base segment matches a future pathname segment,
|
|
774
|
+
// so we should skip this optional parameter
|
|
775
|
+
shouldMatchOptional = false
|
|
776
|
+
break
|
|
763
777
|
}
|
|
764
778
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
779
|
+
// If we encounter a required param or wildcard, stop looking ahead
|
|
780
|
+
if (
|
|
781
|
+
futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||
|
|
782
|
+
futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD
|
|
783
|
+
) {
|
|
784
|
+
break
|
|
769
785
|
}
|
|
770
786
|
}
|
|
771
787
|
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
788
|
+
if (shouldMatchOptional) {
|
|
789
|
+
// If no prefix/suffix, just decode the base segment value
|
|
790
|
+
_paramValue = decodeURIComponent(baseSegment.value)
|
|
791
|
+
matched = true
|
|
775
792
|
}
|
|
793
|
+
}
|
|
776
794
|
|
|
777
|
-
|
|
778
|
-
|
|
795
|
+
if (matched) {
|
|
796
|
+
params[routeSegment.value.substring(1)] = _paramValue
|
|
797
|
+
baseIndex++
|
|
779
798
|
}
|
|
780
|
-
}
|
|
781
799
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
baseSegments.slice(baseIndex + 1).map((d) => d.value),
|
|
785
|
-
)
|
|
786
|
-
return !!matchLocation.fuzzy && routeSegment?.value !== '/'
|
|
800
|
+
routeIndex++
|
|
801
|
+
continue
|
|
787
802
|
}
|
|
803
|
+
}
|
|
788
804
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
|
|
805
|
+
// If we have base segments left but no route segments, it's a fuzzy match
|
|
806
|
+
if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {
|
|
807
|
+
params['**'] = joinPaths(
|
|
808
|
+
baseSegments.slice(baseIndex).map((d) => d.value),
|
|
809
|
+
)
|
|
810
|
+
return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'
|
|
811
|
+
}
|
|
796
812
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
for (let i = routeIndex; i < routeSegments.length; i++) {
|
|
804
|
-
if (routeSegments[i]?.type !== 'optional-param') {
|
|
805
|
-
return false
|
|
806
|
-
}
|
|
813
|
+
// If we have route segments left but no base segments, check if remaining are optional
|
|
814
|
+
if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {
|
|
815
|
+
// Check if all remaining route segments are optional
|
|
816
|
+
for (let i = routeIndex; i < routeSegments.length; i++) {
|
|
817
|
+
if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
818
|
+
return false
|
|
807
819
|
}
|
|
808
|
-
// All remaining are optional, so we can finish
|
|
809
|
-
break
|
|
810
820
|
}
|
|
811
|
-
|
|
821
|
+
// All remaining are optional, so we can finish
|
|
812
822
|
break
|
|
813
823
|
}
|
|
814
824
|
|
|
815
|
-
|
|
816
|
-
}
|
|
825
|
+
break
|
|
826
|
+
}
|
|
817
827
|
|
|
818
|
-
return
|
|
828
|
+
return true
|
|
819
829
|
}
|