@tanstack/router-core 1.133.25 → 1.133.28

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/src/path.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { rootRouteId } from './root'
2
1
  import { last } from './utils'
3
2
  import type { LRUCache } from './lru-cache'
4
3
  import type { MatchLocation } from './RouterProvider'
@@ -22,6 +21,7 @@ export interface Segment {
22
21
  readonly hasStaticAfter?: boolean
23
22
  }
24
23
 
24
+ /** Join path segments, cleaning duplicate slashes between parts. */
25
25
  /** Join path segments, cleaning duplicate slashes between parts. */
26
26
  export function joinPaths(paths: Array<string | undefined>) {
27
27
  return cleanPath(
@@ -33,22 +33,26 @@ export function joinPaths(paths: Array<string | undefined>) {
33
33
  )
34
34
  }
35
35
 
36
+ /** Remove repeated slashes from a path string. */
36
37
  /** Remove repeated slashes from a path string. */
37
38
  export function cleanPath(path: string) {
38
39
  // remove double slashes
39
40
  return path.replace(/\/{2,}/g, '/')
40
41
  }
41
42
 
43
+ /** Trim leading slashes (except preserving root '/'). */
42
44
  /** Trim leading slashes (except preserving root '/'). */
43
45
  export function trimPathLeft(path: string) {
44
46
  return path === '/' ? path : path.replace(/^\/{1,}/, '')
45
47
  }
46
48
 
49
+ /** Trim trailing slashes (except preserving root '/'). */
47
50
  /** Trim trailing slashes (except preserving root '/'). */
48
51
  export function trimPathRight(path: string) {
49
52
  return path === '/' ? path : path.replace(/\/{1,}$/, '')
50
53
  }
51
54
 
55
+ /** Trim both leading and trailing slashes. */
52
56
  /** Trim both leading and trailing slashes. */
53
57
  export function trimPath(path: string) {
54
58
  return trimPathRight(trimPathLeft(path))
@@ -66,6 +70,10 @@ export function removeTrailingSlash(value: string, basepath: string): string {
66
70
  // see the usage in the isActive under useLinkProps
67
71
  // /sample/path1 = /sample/path1/
68
72
  // /sample/path1/some <> /sample/path1
73
+ /**
74
+ * Compare two pathnames for exact equality after normalizing trailing slashes
75
+ * relative to the provided `basepath`.
76
+ */
69
77
  /**
70
78
  * Compare two pathnames for exact equality after normalizing trailing slashes
71
79
  * relative to the provided `basepath`.
@@ -169,8 +177,8 @@ export function resolvePath({
169
177
  trailingSlash = 'never',
170
178
  parseCache,
171
179
  }: ResolvePathOptions) {
172
- let baseSegments = parseBasePathSegments(base, parseCache).slice()
173
- const toSegments = parseRoutePathSegments(to, parseCache)
180
+ let baseSegments = parsePathname(base, parseCache).slice()
181
+ const toSegments = parsePathname(to, parseCache)
174
182
 
175
183
  if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
176
184
  baseSegments.pop()
@@ -216,16 +224,10 @@ export function resolvePath({
216
224
 
217
225
  export type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>
218
226
 
219
- export const parseBasePathSegments = (
220
- pathname?: string,
221
- cache?: ParsePathnameCache,
222
- ): ReadonlyArray<Segment> => parsePathname(pathname, cache, true)
223
-
224
- export const parseRoutePathSegments = (
225
- pathname?: string,
226
- cache?: ParsePathnameCache,
227
- ): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)
228
-
227
+ /**
228
+ * Parse a pathname into an array of typed segments used by the router's
229
+ * matcher. Results are optionally cached via an LRU cache.
230
+ */
229
231
  /**
230
232
  * Parse a pathname into an array of typed segments used by the router's
231
233
  * matcher. Results are optionally cached via an LRU cache.
@@ -233,12 +235,11 @@ export const parseRoutePathSegments = (
233
235
  export const parsePathname = (
234
236
  pathname?: string,
235
237
  cache?: ParsePathnameCache,
236
- basePathValues?: boolean,
237
238
  ): ReadonlyArray<Segment> => {
238
239
  if (!pathname) return []
239
240
  const cached = cache?.get(pathname)
240
241
  if (cached) return cached
241
- const parsed = baseParsePathname(pathname, basePathValues)
242
+ const parsed = baseParsePathname(pathname)
242
243
  cache?.set(pathname, parsed)
243
244
  return parsed
244
245
  }
@@ -268,10 +269,7 @@ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
268
269
  * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
269
270
  * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
270
271
  */
271
- function baseParsePathname(
272
- pathname: string,
273
- basePathValues?: boolean,
274
- ): ReadonlyArray<Segment> {
272
+ function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
275
273
  pathname = cleanPath(pathname)
276
274
 
277
275
  const segments: Array<Segment> = []
@@ -293,14 +291,8 @@ function baseParsePathname(
293
291
 
294
292
  segments.push(
295
293
  ...split.map((part): Segment => {
296
- // strip tailing underscore for non-nested paths
297
- const partToMatch =
298
- !basePathValues && part !== rootRouteId && part.slice(-1) === '_'
299
- ? part.slice(0, -1)
300
- : part
301
-
302
294
  // Check for wildcard with curly braces: prefix{$}suffix
303
- const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE)
295
+ const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)
304
296
  if (wildcardBracesMatch) {
305
297
  const prefix = wildcardBracesMatch[1]
306
298
  const suffix = wildcardBracesMatch[2]
@@ -313,7 +305,7 @@ function baseParsePathname(
313
305
  }
314
306
 
315
307
  // Check for optional parameter format: prefix{-$paramName}suffix
316
- const optionalParamBracesMatch = partToMatch.match(
308
+ const optionalParamBracesMatch = part.match(
317
309
  OPTIONAL_PARAM_W_CURLY_BRACES_RE,
318
310
  )
319
311
  if (optionalParamBracesMatch) {
@@ -329,7 +321,7 @@ function baseParsePathname(
329
321
  }
330
322
 
331
323
  // Check for the new parameter format: prefix{$paramName}suffix
332
- const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE)
324
+ const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)
333
325
  if (paramBracesMatch) {
334
326
  const prefix = paramBracesMatch[1]
335
327
  const paramName = paramBracesMatch[2]
@@ -343,8 +335,8 @@ function baseParsePathname(
343
335
  }
344
336
 
345
337
  // Check for bare parameter format: $paramName (without curly braces)
346
- if (PARAM_RE.test(partToMatch)) {
347
- const paramName = partToMatch.substring(1)
338
+ if (PARAM_RE.test(part)) {
339
+ const paramName = part.substring(1)
348
340
  return {
349
341
  type: SEGMENT_TYPE_PARAM,
350
342
  value: '$' + paramName,
@@ -354,7 +346,7 @@ function baseParsePathname(
354
346
  }
355
347
 
356
348
  // Check for bare wildcard: $ (without curly braces)
357
- if (WILDCARD_RE.test(partToMatch)) {
349
+ if (WILDCARD_RE.test(part)) {
358
350
  return {
359
351
  type: SEGMENT_TYPE_WILDCARD,
360
352
  value: '$',
@@ -366,12 +358,12 @@ function baseParsePathname(
366
358
  // Handle regular pathname segment
367
359
  return {
368
360
  type: SEGMENT_TYPE_PATHNAME,
369
- value: partToMatch.includes('%25')
370
- ? partToMatch
361
+ value: part.includes('%25')
362
+ ? part
371
363
  .split('%25')
372
364
  .map((segment) => decodeURI(segment))
373
365
  .join('%25')
374
- : decodeURI(partToMatch),
366
+ : decodeURI(part),
375
367
  }
376
368
  }),
377
369
  )
@@ -409,6 +401,10 @@ type InterPolatePathResult = {
409
401
  * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards
410
402
  * - Optionally leaves placeholders or wildcards in place
411
403
  */
404
+ /**
405
+ * Interpolate params and wildcards into a route path template.
406
+ * Encodes safely and supports optional params and custom decode char maps.
407
+ */
412
408
  export function interpolatePath({
413
409
  path,
414
410
  params,
@@ -417,7 +413,7 @@ export function interpolatePath({
417
413
  decodeCharMap,
418
414
  parseCache,
419
415
  }: InterpolatePathOptions): InterPolatePathResult {
420
- const interpolatedPathSegments = parseRoutePathSegments(path, parseCache)
416
+ const interpolatedPathSegments = parsePathname(path, parseCache)
421
417
 
422
418
  function encodeParam(key: string): any {
423
419
  const value = params[key]
@@ -535,6 +531,10 @@ function encodePathParam(value: string, decodeCharMap?: Map<string, string>) {
535
531
  return encoded
536
532
  }
537
533
 
534
+ /**
535
+ * Match a pathname against a route destination and return extracted params
536
+ * or `undefined`. Uses the same parsing as the router for consistency.
537
+ */
538
538
  /**
539
539
  * Match a pathname against a route destination and return extracted params
540
540
  * or `undefined`. Uses the same parsing as the router for consistency.
@@ -554,6 +554,7 @@ export function matchPathname(
554
554
  return pathParams ?? {}
555
555
  }
556
556
 
557
+ /** Low-level matcher that compares two path strings and extracts params. */
557
558
  /** Low-level matcher that compares two path strings and extracts params. */
558
559
  export function matchByPath(
559
560
  from: string,
@@ -567,11 +568,11 @@ export function matchByPath(
567
568
  const stringTo = to as string
568
569
 
569
570
  // Parse the from and to
570
- const baseSegments = parseBasePathSegments(
571
+ const baseSegments = parsePathname(
571
572
  from.startsWith('/') ? from : `/${from}`,
572
573
  parseCache,
573
574
  )
574
- const routeSegments = parseRoutePathSegments(
575
+ const routeSegments = parsePathname(
575
576
  stringTo.startsWith('/') ? stringTo : `/${stringTo}`,
576
577
  parseCache,
577
578
  )
@@ -3,7 +3,7 @@ import {
3
3
  SEGMENT_TYPE_OPTIONAL_PARAM,
4
4
  SEGMENT_TYPE_PARAM,
5
5
  SEGMENT_TYPE_PATHNAME,
6
- parseRoutePathSegments,
6
+ parsePathname,
7
7
  trimPathLeft,
8
8
  trimPathRight,
9
9
  } from './path'
@@ -70,7 +70,7 @@ function sortRoutes<TRouteLike extends RouteLike>(
70
70
  }
71
71
 
72
72
  const trimmed = trimPathLeft(d.fullPath)
73
- let parsed = parseRoutePathSegments(trimmed)
73
+ let parsed = parsePathname(trimmed)
74
74
 
75
75
  // Removes the leading slash if it is not the only remaining segment
76
76
  let skip = 0
package/src/qss.ts CHANGED
@@ -47,6 +47,7 @@ export function encode(
47
47
  * // Example input: toValue("123")
48
48
  * // Expected output: 123
49
49
  */
50
+ /** Convert a string into a primitive boolean/number when possible. */
50
51
  function toValue(str: unknown) {
51
52
  if (!str) return ''
52
53
 
package/src/rewrite.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { joinPaths, trimPath } from './path'
2
2
  import type { LocationRewrite } from './router'
3
3
 
4
+ /** Compose multiple rewrite pairs into a single in/out rewrite. */
4
5
  /** Compose multiple rewrite pairs into a single in/out rewrite. */
5
6
  export function composeRewrites(rewrites: Array<LocationRewrite>) {
6
7
  return {
@@ -19,6 +20,7 @@ export function composeRewrites(rewrites: Array<LocationRewrite>) {
19
20
  } satisfies LocationRewrite
20
21
  }
21
22
 
23
+ /** Create a rewrite pair that strips/adds a basepath on input/output. */
22
24
  /** Create a rewrite pair that strips/adds a basepath on input/output. */
23
25
  export function rewriteBasepath(opts: {
24
26
  basepath: string
@@ -56,6 +58,7 @@ export function rewriteBasepath(opts: {
56
58
  } satisfies LocationRewrite
57
59
  }
58
60
 
61
+ /** Execute a location input rewrite if provided. */
59
62
  /** Execute a location input rewrite if provided. */
60
63
  export function executeRewriteInput(
61
64
  rewrite: LocationRewrite | undefined,
@@ -72,6 +75,7 @@ export function executeRewriteInput(
72
75
  return url
73
76
  }
74
77
 
78
+ /** Execute a location output rewrite if provided. */
75
79
  /** Execute a location output rewrite if provided. */
76
80
  export function executeRewriteOutput(
77
81
  rewrite: LocationRewrite | undefined,
@@ -35,6 +35,7 @@ function getSafeSessionStorage() {
35
35
 
36
36
  /** SessionStorage key used to persist scroll restoration state. */
37
37
  /** SessionStorage key used to store scroll positions across navigations. */
38
+ /** SessionStorage key used to store scroll positions across navigations. */
38
39
  export const storageKey = 'tsr-scroll-restoration-v1_3'
39
40
 
40
41
  const throttle = (fn: (...args: Array<any>) => void, wait: number) => {
@@ -72,6 +73,7 @@ function createScrollRestorationCache(): ScrollRestorationCache | null {
72
73
  }
73
74
  }
74
75
 
76
+ /** In-memory handle to the persisted scroll restoration cache. */
75
77
  /** In-memory handle to the persisted scroll restoration cache. */
76
78
  /** In-memory handle to the persisted scroll restoration cache. */
77
79
  export const scrollRestorationCache = createScrollRestorationCache()
@@ -89,6 +91,9 @@ export const scrollRestorationCache = createScrollRestorationCache()
89
91
  /**
90
92
  * Default scroll restoration cache key: location state key or full href.
91
93
  */
94
+ /**
95
+ * Default scroll restoration cache key: location state key or full href.
96
+ */
92
97
  export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
93
98
  return location.state.__TSR_key! || location.href
94
99
  }
@@ -112,6 +117,9 @@ let ignoreScroll = false
112
117
  // unless they are passed in as arguments. Why? Because we need to be able to
113
118
  // toString() it into a script tag to execute as early as possible in the browser
114
119
  // during SSR. Additionally, we also call it from within the router lifecycle
120
+ /**
121
+ * Restore scroll positions for window/elements based on cached entries.
122
+ */
115
123
  /**
116
124
  * Restore scroll positions for window/elements based on cached entries.
117
125
  */
@@ -214,6 +222,7 @@ export function restoreScroll({
214
222
  ignoreScroll = false
215
223
  }
216
224
 
225
+ /** Setup global listeners and hooks to support scroll restoration. */
217
226
  /** Setup global listeners and hooks to support scroll restoration. */
218
227
  export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
219
228
  if (!scrollRestorationCache && !router.isServer) {
@@ -388,6 +397,11 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
388
397
  * Provides hash scrolling for programmatic navigation when default browser handling is prevented.
389
398
  * @param router The router instance containing current location and state
390
399
  */
400
+ /**
401
+ * @private
402
+ * Handles hash-based scrolling after navigation completes.
403
+ * To be used in framework-specific Transitioners.
404
+ */
391
405
  export function handleHashScroll(router: AnyRouter) {
392
406
  if (typeof document !== 'undefined' && (document as any).querySelector) {
393
407
  const hashScrollIntoViewOptions =
@@ -1,9 +1,11 @@
1
1
  import { decode, encode } from './qss'
2
2
  import type { AnySchema } from './validators'
3
3
 
4
+ /** Default `parseSearch` that strips leading '?' and JSON-parses values. */
4
5
  /** Default `parseSearch` that strips leading '?' and JSON-parses values. */
5
6
  /** Default `parseSearch` that strips leading '?' and JSON-parses values. */
6
7
  export const defaultParseSearch = parseSearchWith(JSON.parse)
8
+ /** Default `stringifySearch` using JSON.stringify for complex values. */
7
9
  export const defaultStringifySearch = stringifySearchWith(
8
10
  JSON.stringify,
9
11
  JSON.parse,
package/src/utils.ts CHANGED
@@ -184,6 +184,10 @@ export type LooseAsyncReturnType<T> = T extends (
184
184
  : TReturn
185
185
  : never
186
186
 
187
+ /**
188
+ * Return the last element of an array.
189
+ * Intended for non-empty arrays used within router internals.
190
+ */
187
191
  export function last<T>(arr: Array<T>) {
188
192
  return arr[arr.length - 1]
189
193
  }
@@ -192,6 +196,10 @@ function isFunction(d: any): d is Function {
192
196
  return typeof d === 'function'
193
197
  }
194
198
 
199
+ /**
200
+ * Apply a value-or-updater to a previous value.
201
+ * Accepts either a literal value or a function of the previous value.
202
+ */
195
203
  export function functionalUpdate<TPrevious, TResult = TPrevious>(
196
204
  updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,
197
205
  previous: TPrevious,
@@ -311,10 +319,17 @@ function hasObjectPrototype(o: any) {
311
319
  return Object.prototype.toString.call(o) === '[object Object]'
312
320
  }
313
321
 
322
+ /**
323
+ * Check if a value is a "plain" array (no extra enumerable keys).
324
+ */
314
325
  export function isPlainArray(value: unknown): value is Array<unknown> {
315
326
  return Array.isArray(value) && value.length === Object.keys(value).length
316
327
  }
317
328
 
329
+ /**
330
+ * Perform a deep equality check with options for partial comparison and
331
+ * ignoring `undefined` values. Optimized for router state comparisons.
332
+ */
318
333
  export function deepEqual(
319
334
  a: any,
320
335
  b: any,
@@ -407,6 +422,10 @@ export type ControlledPromise<T> = Promise<T> & {
407
422
  value?: T
408
423
  }
409
424
 
425
+ /**
426
+ * Create a promise with exposed resolve/reject and status fields.
427
+ * Useful for coordinating async router lifecycle operations.
428
+ */
410
429
  export function createControlledPromise<T>(onResolve?: (value: T) => void) {
411
430
  let resolveLoadPromise!: (value: T) => void
412
431
  let rejectLoadPromise!: (value: any) => void
@@ -433,6 +452,10 @@ export function createControlledPromise<T>(onResolve?: (value: T) => void) {
433
452
  return controlledPromise
434
453
  }
435
454
 
455
+ /**
456
+ * Heuristically detect dynamic import "module not found" errors
457
+ * across major browsers for lazy route component handling.
458
+ */
436
459
  export function isModuleNotFoundError(error: any): boolean {
437
460
  // chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
438
461
  // firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"