@tanstack/router-core 1.133.27 → 1.133.35

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/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"
@@ -465,3 +488,73 @@ export function findLast<T>(
465
488
  }
466
489
  return undefined
467
490
  }
491
+
492
+ const DECODE_IGNORE_LIST = Array.from(
493
+ new Map([
494
+ ['%', '%25'],
495
+ ['\\', '%5C'],
496
+ ['/', '%2F'],
497
+ [';', '%3B'],
498
+ [':', '%3A'],
499
+ ['@', '%40'],
500
+ ['&', '%26'],
501
+ ['=', '%3D'],
502
+ ['+', '%2B'],
503
+ ['$', '%24'],
504
+ [',', '%2C'],
505
+ ]).values(),
506
+ )
507
+
508
+ export function decodePathSegment(
509
+ part: string,
510
+ decodeIgnore: Array<string> = DECODE_IGNORE_LIST,
511
+ startIndex = 0,
512
+ ): string {
513
+ function decode(part: string): string {
514
+ try {
515
+ return decodeURIComponent(part)
516
+ } catch {
517
+ // if the decoding fails, try to decode the various parts leaving the malformed tags in place
518
+ return part.replaceAll(/%[0-9A-Fa-f]{2}/g, (match) => {
519
+ try {
520
+ return decodeURIComponent(match)
521
+ } catch {
522
+ return match
523
+ }
524
+ })
525
+ }
526
+ }
527
+
528
+ // if the path segment does not contain any encoded uri components return the path as is
529
+ if (part === '' || !part.match(/%[0-9A-Fa-f]{2}/g)) return part
530
+
531
+ // decode the path / path segment by splitting it into parts defined by the ignore list.
532
+ // once these pieces have been decoded, join them back together to form the final decoded path segment with the ignored character in place.
533
+ // we walk through the ignore list linearly, breaking the segment up into pieces and decoding each piece individually.
534
+ // use index traversal to avoid making unnecessary copies of the array.
535
+ for (let i = startIndex; i < decodeIgnore.length; i++) {
536
+ const char = decodeIgnore[i]
537
+
538
+ // check if the part includes the current ignore character
539
+ // if it doesn't continue to the next ignore character
540
+ if (char && part.includes(char)) {
541
+ // split the part into pieces that needs to be checked and decoded
542
+ const partsToDecode = part.split(char)
543
+ const partsToJoin: Array<string> = []
544
+
545
+ // now check and decode each piece individually taking into consideration the remaining ignored characters.
546
+ // since we are walking through the list linearly, we only need to consider ignore items not yet traversed.
547
+ for (const partToDecode of partsToDecode) {
548
+ // once we have traversed the entire ignore list, each decoded part is returned.
549
+ partsToJoin.push(decodePathSegment(partToDecode, decodeIgnore, i + 1))
550
+ }
551
+
552
+ // and join them back together to form the final decoded path segment with the ignored character in place.
553
+ return partsToJoin.join(char)
554
+ }
555
+ }
556
+
557
+ // once we have reached the end of the ignore list, we start walking back returning each decoded part.
558
+ // should there be no matching characters, the path segment as a whole will be decoded.
559
+ return decode(part)
560
+ }