@tanstack/react-router 1.76.1 → 1.77.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.76.1",
3
+ "version": "1.77.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
package/src/link.tsx CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  functionalUpdate,
10
10
  useForwardedRef,
11
11
  useIntersectionObserver,
12
+ useLayoutEffect,
12
13
  } from './utils'
13
14
  import { exactPathTest, removeTrailingSlash } from './path'
14
15
  import type { ParsedLocation } from './location'
@@ -497,7 +498,7 @@ export interface LinkOptionsProps {
497
498
  * - `'intent'` - Preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.
498
499
  * - `'viewport'` - Preload the linked route when it enters the viewport
499
500
  */
500
- preload?: false | 'intent' | 'viewport'
501
+ preload?: false | 'intent' | 'viewport' | 'render'
501
502
  /**
502
503
  * When a preload strategy is set, this delays the preload by this many milliseconds.
503
504
  * If the user exits the link before this delay, the preload will be cancelled.
@@ -589,6 +590,7 @@ export function useLinkProps<
589
590
  ): React.ComponentPropsWithRef<'a'> {
590
591
  const router = useRouter()
591
592
  const [isTransitioning, setIsTransitioning] = React.useState(false)
593
+ const hasRenderFetched = React.useRef(false)
592
594
  const innerRef = useForwardedRef(forwardedRef)
593
595
 
594
596
  const {
@@ -722,9 +724,19 @@ export function useLinkProps<
722
724
  innerRef,
723
725
  preloadViewportIoCallback,
724
726
  { rootMargin: '100px' },
725
- { disabled: !!disabled || preload !== 'viewport' },
727
+ { disabled: !!disabled || !(preload === 'viewport') },
726
728
  )
727
729
 
730
+ useLayoutEffect(() => {
731
+ if (hasRenderFetched.current) {
732
+ return
733
+ }
734
+ if (!disabled && preload === 'render') {
735
+ doPreload()
736
+ hasRenderFetched.current = true
737
+ }
738
+ }, [disabled, doPreload, preload])
739
+
728
740
  if (type === 'external') {
729
741
  return {
730
742
  ...propsSafeToSpread,
package/src/path.ts CHANGED
@@ -87,6 +87,7 @@ interface ResolvePathOptions {
87
87
  base: string
88
88
  to: string
89
89
  trailingSlash?: 'always' | 'never' | 'preserve'
90
+ caseSensitive?: boolean
90
91
  }
91
92
 
92
93
  export function resolvePath({
@@ -94,9 +95,10 @@ export function resolvePath({
94
95
  base,
95
96
  to,
96
97
  trailingSlash = 'never',
98
+ caseSensitive,
97
99
  }: ResolvePathOptions) {
98
- base = removeBasepath(basepath, base)
99
- to = removeBasepath(basepath, to)
100
+ base = removeBasepath(basepath, base, caseSensitive)
101
+ to = removeBasepath(basepath, to, caseSensitive)
100
102
 
101
103
  let baseSegments = parsePathname(base)
102
104
  const toSegments = parsePathname(to)
@@ -260,15 +262,23 @@ export function matchPathname(
260
262
  return pathParams ?? {}
261
263
  }
262
264
 
263
- export function removeBasepath(basepath: string, pathname: string) {
265
+ export function removeBasepath(
266
+ basepath: string,
267
+ pathname: string,
268
+ caseSensitive: boolean = false,
269
+ ) {
270
+ // normalize basepath and pathname for case-insensitive comparison if needed
271
+ const normalizedBasepath = caseSensitive ? basepath : basepath.toLowerCase()
272
+ const normalizedPathname = caseSensitive ? pathname : pathname.toLowerCase()
273
+
264
274
  switch (true) {
265
275
  // default behaviour is to serve app from the root - pathname
266
276
  // left untouched
267
- case basepath === '/':
277
+ case normalizedBasepath === '/':
268
278
  return pathname
269
279
 
270
- // shortcut for removing the basepath from the equal pathname
271
- case pathname === basepath:
280
+ // shortcut for removing the basepath if it matches the pathname
281
+ case normalizedPathname === normalizedBasepath:
272
282
  return ''
273
283
 
274
284
  // in case pathname is shorter than basepath - there is
@@ -280,11 +290,11 @@ export function removeBasepath(basepath: string, pathname: string) {
280
290
  // earlier, otherwise, basepath separated from pathname with
281
291
  // separator, therefore lack of separator means partial
282
292
  // segment match (`/app` should not match `/application`)
283
- case pathname[basepath.length] !== '/':
293
+ case normalizedPathname[normalizedBasepath.length] !== '/':
284
294
  return pathname
285
295
 
286
- // remove the basepath from the pathname in case there is any
287
- case pathname.startsWith(basepath):
296
+ // remove the basepath from the pathname if it starts with it
297
+ case normalizedPathname.startsWith(normalizedBasepath):
288
298
  return pathname.slice(basepath.length)
289
299
 
290
300
  // otherwise, return the pathname as is
@@ -303,9 +313,13 @@ export function matchByPath(
303
313
  return undefined
304
314
  }
305
315
  // Remove the base path from the pathname
306
- from = removeBasepath(basepath, from)
316
+ from = removeBasepath(basepath, from, matchLocation.caseSensitive)
307
317
  // Default to to $ (wildcard)
308
- const to = removeBasepath(basepath, `${matchLocation.to ?? '$'}`)
318
+ const to = removeBasepath(
319
+ basepath,
320
+ `${matchLocation.to ?? '$'}`,
321
+ matchLocation.caseSensitive,
322
+ )
309
323
 
310
324
  // Parse the from and to
311
325
  const baseSegments = parsePathname(from)
package/src/router.ts CHANGED
@@ -211,7 +211,7 @@ export interface RouterOptions<
211
211
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreload-property)
212
212
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
213
213
  */
214
- defaultPreload?: false | 'intent' | 'viewport'
214
+ defaultPreload?: false | 'intent' | 'viewport' | 'render'
215
215
  /**
216
216
  * The delay in milliseconds that a route must be hovered over or touched before it is preloaded.
217
217
  *
@@ -722,6 +722,7 @@ export class Router<
722
722
  defaultPendingMinMs: 500,
723
723
  context: undefined!,
724
724
  ...options,
725
+ caseSensitive: options.caseSensitive ?? false,
725
726
  notFoundMode: options.notFoundMode ?? 'fuzzy',
726
727
  stringifySearch: options.stringifySearch ?? defaultStringifySearch,
727
728
  parseSearch: options.parseSearch ?? defaultParseSearch,
@@ -1007,6 +1008,7 @@ export class Router<
1007
1008
  base: from,
1008
1009
  to: cleanPath(path),
1009
1010
  trailingSlash: this.options.trailingSlash,
1011
+ caseSensitive: this.options.caseSensitive,
1010
1012
  })
1011
1013
  return resolvedPath
1012
1014
  }
@@ -1620,7 +1622,7 @@ export class Router<
1620
1622
  })
1621
1623
 
1622
1624
  if (foundMask) {
1623
- const { from, ...maskProps } = foundMask
1625
+ const { from: _from, ...maskProps } = foundMask
1624
1626
  maskedDest = {
1625
1627
  ...pick(opts, ['from']),
1626
1628
  ...maskProps,