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