@tanstack/react-router 0.0.1-beta.28 → 0.0.1-beta.280

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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/build/cjs/CatchBoundary.js +128 -0
  3. package/build/cjs/CatchBoundary.js.map +1 -0
  4. package/build/cjs/Matches.js +233 -0
  5. package/build/cjs/Matches.js.map +1 -0
  6. package/build/cjs/RouterProvider.js +170 -0
  7. package/build/cjs/RouterProvider.js.map +1 -0
  8. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +2 -22
  9. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
  10. package/build/cjs/awaited.js +43 -0
  11. package/build/cjs/awaited.js.map +1 -0
  12. package/build/cjs/defer.js +37 -0
  13. package/build/cjs/defer.js.map +1 -0
  14. package/build/cjs/fileRoute.js +27 -0
  15. package/build/cjs/fileRoute.js.map +1 -0
  16. package/build/cjs/index.js +130 -0
  17. package/build/cjs/index.js.map +1 -0
  18. package/build/cjs/lazyRouteComponent.js +54 -0
  19. package/build/cjs/lazyRouteComponent.js.map +1 -0
  20. package/build/cjs/link.js +223 -0
  21. package/build/cjs/link.js.map +1 -0
  22. package/build/cjs/path.js +214 -0
  23. package/build/cjs/path.js.map +1 -0
  24. package/build/cjs/qss.js +63 -0
  25. package/build/cjs/qss.js.map +1 -0
  26. package/build/cjs/redirects.js +28 -0
  27. package/build/cjs/redirects.js.map +1 -0
  28. package/build/cjs/route.js +191 -0
  29. package/build/cjs/route.js.map +1 -0
  30. package/build/cjs/router.js +1049 -0
  31. package/build/cjs/router.js.map +1 -0
  32. package/build/cjs/scroll-restoration.js +202 -0
  33. package/build/cjs/scroll-restoration.js.map +1 -0
  34. package/build/cjs/searchParams.js +81 -0
  35. package/build/cjs/searchParams.js.map +1 -0
  36. package/build/cjs/useBlocker.js +55 -0
  37. package/build/cjs/useBlocker.js.map +1 -0
  38. package/build/cjs/useNavigate.js +86 -0
  39. package/build/cjs/useNavigate.js.map +1 -0
  40. package/build/cjs/useParams.js +26 -0
  41. package/build/cjs/useParams.js.map +1 -0
  42. package/build/cjs/useSearch.js +25 -0
  43. package/build/cjs/useSearch.js.map +1 -0
  44. package/build/cjs/utils.js +239 -0
  45. package/build/cjs/utils.js.map +1 -0
  46. package/build/esm/index.js +2255 -2571
  47. package/build/esm/index.js.map +1 -1
  48. package/build/stats-html.html +3498 -2694
  49. package/build/stats-react.json +1204 -44
  50. package/build/types/CatchBoundary.d.ts +36 -0
  51. package/build/types/Matches.d.ts +62 -0
  52. package/build/types/RouterProvider.d.ts +35 -0
  53. package/build/types/awaited.d.ts +9 -0
  54. package/build/types/defer.d.ts +19 -0
  55. package/build/types/fileRoute.d.ts +38 -0
  56. package/build/types/history.d.ts +7 -0
  57. package/build/types/index.d.ts +27 -114
  58. package/build/types/lazyRouteComponent.d.ts +2 -0
  59. package/build/types/link.d.ts +91 -0
  60. package/build/types/location.d.ts +12 -0
  61. package/build/types/path.d.ts +17 -0
  62. package/build/types/qss.d.ts +2 -0
  63. package/build/types/redirects.d.ts +11 -0
  64. package/build/types/route.d.ts +282 -0
  65. package/build/types/routeInfo.d.ts +22 -0
  66. package/build/types/router.d.ts +188 -0
  67. package/build/types/scroll-restoration.d.ts +18 -0
  68. package/build/types/searchParams.d.ts +7 -0
  69. package/build/types/useBlocker.d.ts +9 -0
  70. package/build/types/useNavigate.d.ts +19 -0
  71. package/build/types/useParams.d.ts +7 -0
  72. package/build/types/useSearch.d.ts +7 -0
  73. package/build/types/utils.d.ts +66 -0
  74. package/build/umd/index.development.js +2858 -2548
  75. package/build/umd/index.development.js.map +1 -1
  76. package/build/umd/index.production.js +4 -4
  77. package/build/umd/index.production.js.map +1 -1
  78. package/package.json +11 -10
  79. package/src/CatchBoundary.tsx +101 -0
  80. package/src/Matches.tsx +421 -0
  81. package/src/RouterProvider.tsx +252 -0
  82. package/src/awaited.tsx +40 -0
  83. package/src/defer.ts +55 -0
  84. package/src/fileRoute.ts +152 -0
  85. package/src/history.ts +8 -0
  86. package/src/index.tsx +28 -761
  87. package/src/lazyRouteComponent.tsx +33 -0
  88. package/src/link.tsx +598 -0
  89. package/src/location.ts +13 -0
  90. package/src/path.ts +261 -0
  91. package/src/qss.ts +53 -0
  92. package/src/redirects.ts +39 -0
  93. package/src/route.ts +872 -0
  94. package/src/routeInfo.ts +70 -0
  95. package/src/router.ts +1630 -0
  96. package/src/scroll-restoration.tsx +230 -0
  97. package/src/searchParams.ts +79 -0
  98. package/src/useBlocker.tsx +27 -0
  99. package/src/useNavigate.tsx +111 -0
  100. package/src/useParams.tsx +25 -0
  101. package/src/useSearch.tsx +25 -0
  102. package/src/utils.ts +350 -0
  103. package/build/cjs/react-router/src/index.js +0 -508
  104. package/build/cjs/react-router/src/index.js.map +0 -1
  105. package/build/cjs/router-core/build/esm/index.js +0 -2530
  106. package/build/cjs/router-core/build/esm/index.js.map +0 -1
@@ -0,0 +1,33 @@
1
+ import * as React from 'react'
2
+ import { AsyncRouteComponent } from './route'
3
+
4
+ export function lazyRouteComponent<
5
+ T extends Record<string, any>,
6
+ TKey extends keyof T = 'default',
7
+ >(
8
+ importer: () => Promise<T>,
9
+ exportName?: TKey,
10
+ ): T[TKey] extends (props: infer TProps) => any
11
+ ? AsyncRouteComponent<TProps>
12
+ : never {
13
+ let loadPromise: Promise<any>
14
+
15
+ const load = () => {
16
+ if (!loadPromise) {
17
+ loadPromise = importer()
18
+ }
19
+
20
+ return loadPromise
21
+ }
22
+
23
+ const lazyComp = React.lazy(async () => {
24
+ const moduleExports = await load()
25
+ const comp = moduleExports[exportName ?? 'default']
26
+ return {
27
+ default: comp,
28
+ }
29
+ })
30
+ ;(lazyComp as any).preload = load
31
+
32
+ return lazyComp as any
33
+ }
package/src/link.tsx ADDED
@@ -0,0 +1,598 @@
1
+ import * as React from 'react'
2
+ import { useMatch } from './Matches'
3
+ import { useRouter, useRouterState } from './RouterProvider'
4
+ import { Trim } from './fileRoute'
5
+ import { AnyRoute, ReactNode } from './route'
6
+ import {
7
+ AllParams,
8
+ FullSearchSchema,
9
+ RouteByPath,
10
+ RouteIds,
11
+ RoutePaths,
12
+ } from './routeInfo'
13
+ import { RegisteredRouter } from './router'
14
+ import { LinkProps, UseLinkPropsOptions } from './useNavigate'
15
+ import {
16
+ Expand,
17
+ NoInfer,
18
+ NonNullableUpdater,
19
+ PickRequired,
20
+ UnionToIntersection,
21
+ Updater,
22
+ deepEqual,
23
+ functionalUpdate,
24
+ } from './utils'
25
+ import { HistoryState } from '@tanstack/history'
26
+
27
+ export type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
28
+ ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
29
+ : T extends `${infer L}//`
30
+ ? `${CleanPath<L>}/`
31
+ : T extends `//${infer L}`
32
+ ? `/${CleanPath<L>}`
33
+ : T
34
+
35
+ export type Split<S, TIncludeTrailingSlash = true> = S extends unknown
36
+ ? string extends S
37
+ ? string[]
38
+ : S extends string
39
+ ? CleanPath<S> extends ''
40
+ ? []
41
+ : TIncludeTrailingSlash extends true
42
+ ? CleanPath<S> extends `${infer T}/`
43
+ ? [...Split<T>, '/']
44
+ : CleanPath<S> extends `/${infer U}`
45
+ ? Split<U>
46
+ : CleanPath<S> extends `${infer T}/${infer U}`
47
+ ? [...Split<T>, ...Split<U>]
48
+ : [S]
49
+ : CleanPath<S> extends `${infer T}/${infer U}`
50
+ ? [...Split<T>, ...Split<U>]
51
+ : S extends string
52
+ ? [S]
53
+ : never
54
+ : never
55
+ : never
56
+
57
+ export type ParsePathParams<T extends string> = keyof {
58
+ [K in Trim<Split<T>[number], '_'> as K extends `$${infer L}` ? L : never]: K
59
+ }
60
+
61
+ export type Join<T, Delimiter extends string = '/'> = T extends []
62
+ ? ''
63
+ : T extends [infer L extends string]
64
+ ? L
65
+ : T extends [infer L extends string, ...infer Tail extends [...string[]]]
66
+ ? CleanPath<`${L}${Delimiter}${Join<Tail>}`>
67
+ : never
68
+
69
+ export type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never
70
+
71
+ export type RelativeToPathAutoComplete<
72
+ AllPaths extends string,
73
+ TFrom extends string,
74
+ TTo extends string,
75
+ SplitPaths extends string[] = Split<AllPaths, false>,
76
+ > = TTo extends `..${infer _}`
77
+ ? SplitPaths extends [
78
+ ...Split<ResolveRelativePath<TFrom, TTo>, false>,
79
+ ...infer TToRest,
80
+ ]
81
+ ? `${CleanPath<
82
+ Join<
83
+ [
84
+ ...Split<TTo, false>,
85
+ ...(
86
+ | TToRest
87
+ | (Split<
88
+ ResolveRelativePath<TFrom, TTo>,
89
+ false
90
+ >['length'] extends 1
91
+ ? never
92
+ : ['../'])
93
+ ),
94
+ ]
95
+ >
96
+ >}`
97
+ : never
98
+ : TTo extends `./${infer RestTTo}`
99
+ ? SplitPaths extends [
100
+ ...Split<TFrom, false>,
101
+ ...Split<RestTTo, false>,
102
+ ...infer RestPath,
103
+ ]
104
+ ? `${TTo}${Join<RestPath>}`
105
+ : never
106
+ :
107
+ | (TFrom extends `/`
108
+ ? never
109
+ : SplitPaths extends [...Split<TFrom, false>, ...infer RestPath]
110
+ ? Join<RestPath> extends { length: 0 }
111
+ ? never
112
+ : './'
113
+ : never)
114
+ | (TFrom extends `/` ? never : '../')
115
+ | AllPaths
116
+
117
+ export type NavigateOptions<
118
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
119
+ TFrom extends RoutePaths<TRouteTree> = '/',
120
+ TTo extends string = '',
121
+ TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
122
+ TMaskTo extends string = '',
123
+ > = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
124
+ // `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one.
125
+ replace?: boolean
126
+ resetScroll?: boolean
127
+ // If set to `true`, the link's underlying navigate() call will be wrapped in a `React.startTransition` call. Defaults to `true`.
128
+ startTransition?: boolean
129
+ }
130
+
131
+ export type ToOptions<
132
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
133
+ TFrom extends RoutePaths<TRouteTree> = '/',
134
+ TTo extends string = '',
135
+ TMaskFrom extends RoutePaths<TRouteTree> = '/',
136
+ TMaskTo extends string = '',
137
+ > = ToSubOptions<TRouteTree, TFrom, TTo> & {
138
+ mask?: ToMaskOptions<TRouteTree, TMaskFrom, TMaskTo>
139
+ }
140
+
141
+ export type ToMaskOptions<
142
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
143
+ TMaskFrom extends RoutePaths<TRouteTree> = '/',
144
+ TMaskTo extends string = '',
145
+ > = ToSubOptions<TRouteTree, TMaskFrom, TMaskTo> & {
146
+ unmaskOnReload?: boolean
147
+ }
148
+
149
+ export type ToSubOptions<
150
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
151
+ TFrom extends RoutePaths<TRouteTree> = '/',
152
+ TTo extends string = '',
153
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
154
+ > = {
155
+ to?: ToPathOption<TRouteTree, TFrom, TTo>
156
+ // The new has string or a function to update it
157
+ hash?: true | Updater<string>
158
+ // State to pass to the history stack
159
+ state?: true | NonNullableUpdater<HistoryState>
160
+ // The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
161
+ from?: TFrom
162
+ // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
163
+ } & CheckPath<TRouteTree, NoInfer<TResolved>, {}> &
164
+ SearchParamOptions<TRouteTree, TFrom, TTo, TResolved> &
165
+ PathParamOptions<TRouteTree, TFrom, TResolved>
166
+
167
+ export type SearchParamOptions<
168
+ TRouteTree extends AnyRoute,
169
+ TFrom,
170
+ TTo,
171
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
172
+ TFromSearchEnsured = '/' extends TFrom
173
+ ? FullSearchSchema<TRouteTree>
174
+ : Expand<
175
+ UnionToIntersection<
176
+ PickRequired<
177
+ RouteByPath<TRouteTree, TFrom>['types']['fullSearchSchema']
178
+ >
179
+ >
180
+ >,
181
+ TFromSearchOptional = Omit<AllParams<TRouteTree>, keyof TFromSearchEnsured>,
182
+ TFromSearch = Expand<TFromSearchEnsured & TFromSearchOptional>,
183
+ TToSearch = '' extends TTo
184
+ ? FullSearchSchema<TRouteTree>
185
+ : Expand<RouteByPath<TRouteTree, TResolved>['types']['fullSearchSchema']>,
186
+ > = keyof PickRequired<TToSearch> extends never
187
+ ? {
188
+ search?: true | SearchReducer<TFromSearch, TToSearch>
189
+ }
190
+ : {
191
+ search: TFromSearchEnsured extends PickRequired<TToSearch>
192
+ ? true | SearchReducer<TFromSearch, TToSearch>
193
+ : SearchReducer<TFromSearch, TToSearch>
194
+ }
195
+
196
+ type SearchReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
197
+
198
+ export type PathParamOptions<
199
+ TRouteTree extends AnyRoute,
200
+ TFrom,
201
+ TTo,
202
+ TFromParamsEnsured = Expand<
203
+ UnionToIntersection<
204
+ PickRequired<RouteByPath<TRouteTree, TFrom>['types']['allParams']>
205
+ >
206
+ >,
207
+ TFromParamsOptional = Omit<AllParams<TRouteTree>, keyof TFromParamsEnsured>,
208
+ TFromParams = Expand<TFromParamsOptional & TFromParamsEnsured>,
209
+ TToParams = Expand<RouteByPath<TRouteTree, TTo>['types']['allParams']>,
210
+ > = keyof PickRequired<TToParams> extends never
211
+ ? {
212
+ params?: true | ParamsReducer<TFromParams, TToParams>
213
+ }
214
+ : {
215
+ params: TFromParamsEnsured extends PickRequired<TToParams>
216
+ ? true | ParamsReducer<TFromParams, TToParams>
217
+ : ParamsReducer<TFromParams, TToParams>
218
+ }
219
+
220
+ type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
221
+
222
+ export type ToPathOption<
223
+ TRouteTree extends AnyRoute = AnyRoute,
224
+ TFrom extends RoutePaths<TRouteTree> = '/',
225
+ TTo extends string = '',
226
+ > =
227
+ | TTo
228
+ | RelativeToPathAutoComplete<
229
+ RoutePaths<TRouteTree>,
230
+ NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
231
+ NoInfer<TTo> & string
232
+ >
233
+
234
+ export type ToIdOption<
235
+ TRouteTree extends AnyRoute = AnyRoute,
236
+ TFrom extends RoutePaths<TRouteTree> = '/',
237
+ TTo extends string = '',
238
+ > =
239
+ | TTo
240
+ | RelativeToPathAutoComplete<
241
+ RouteIds<TRouteTree>,
242
+ NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
243
+ NoInfer<TTo> & string
244
+ >
245
+
246
+ export interface ActiveOptions {
247
+ exact?: boolean
248
+ includeHash?: boolean
249
+ includeSearch?: boolean
250
+ }
251
+
252
+ export type LinkOptions<
253
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
254
+ TFrom extends RoutePaths<TRouteTree> = '/',
255
+ TTo extends string = '',
256
+ TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
257
+ TMaskTo extends string = '',
258
+ > = NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
259
+ // The standard anchor tag target attribute
260
+ target?: HTMLAnchorElement['target']
261
+ // Defaults to `{ exact: false, includeHash: false }`
262
+ activeOptions?: ActiveOptions
263
+ // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.
264
+ preload?: false | 'intent'
265
+ // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled.
266
+ preloadDelay?: number
267
+ // If true, will render the link without the href attribute
268
+ disabled?: boolean
269
+ }
270
+
271
+ export type CheckRelativePath<
272
+ TRouteTree extends AnyRoute,
273
+ TFrom,
274
+ TTo,
275
+ > = TTo extends string
276
+ ? TFrom extends string
277
+ ? ResolveRelativePath<TFrom, TTo> extends RoutePaths<TRouteTree>
278
+ ? {}
279
+ : {
280
+ Error: `${TFrom} + ${TTo} resolves to ${ResolveRelativePath<
281
+ TFrom,
282
+ TTo
283
+ >}, which is not a valid route path.`
284
+ 'Valid Route Paths': RoutePaths<TRouteTree>
285
+ }
286
+ : {}
287
+ : {}
288
+
289
+ export type CheckPath<TRouteTree extends AnyRoute, TPath, TPass> = Exclude<
290
+ TPath,
291
+ RoutePaths<TRouteTree>
292
+ > extends never
293
+ ? TPass
294
+ : CheckPathError<TRouteTree, Exclude<TPath, RoutePaths<TRouteTree>>>
295
+
296
+ export type CheckPathError<TRouteTree extends AnyRoute, TInvalids> = {
297
+ to: RoutePaths<TRouteTree>
298
+ }
299
+
300
+ export type CheckId<TRouteTree extends AnyRoute, TPath, TPass> = Exclude<
301
+ TPath,
302
+ RouteIds<TRouteTree>
303
+ > extends never
304
+ ? TPass
305
+ : CheckIdError<TRouteTree, Exclude<TPath, RouteIds<TRouteTree>>>
306
+
307
+ export type CheckIdError<TRouteTree extends AnyRoute, TInvalids> = {
308
+ Error: `${TInvalids extends string
309
+ ? TInvalids
310
+ : never} is not a valid route ID.`
311
+ 'Valid Route IDs': RouteIds<TRouteTree>
312
+ }
313
+
314
+ export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
315
+ ? TTo extends string
316
+ ? TTo extends '.'
317
+ ? TFrom
318
+ : TTo extends `./`
319
+ ? Join<[TFrom, '/']>
320
+ : TTo extends `./${infer TRest}`
321
+ ? ResolveRelativePath<TFrom, TRest>
322
+ : TTo extends `/${infer TRest}`
323
+ ? TTo
324
+ : Split<TTo> extends ['..', ...infer ToRest]
325
+ ? Split<TFrom> extends [...infer FromRest, infer FromTail]
326
+ ? ToRest extends ['/']
327
+ ? Join<[...FromRest, '/']>
328
+ : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
329
+ : never
330
+ : Split<TTo> extends ['.', ...infer ToRest]
331
+ ? ToRest extends ['/']
332
+ ? Join<[TFrom, '/']>
333
+ : ResolveRelativePath<TFrom, Join<ToRest>>
334
+ : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
335
+ : never
336
+ : never
337
+
338
+ type LinkCurrentTargetElement = {
339
+ preloadTimeout?: null | ReturnType<typeof setTimeout>
340
+ }
341
+
342
+ const preloadWarning = 'Error preloading route! ☝️'
343
+
344
+ export function useLinkProps<
345
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
346
+ TFrom extends RoutePaths<TRouteTree> = '/',
347
+ TTo extends string = '',
348
+ TMaskFrom extends RoutePaths<TRouteTree> = '/',
349
+ TMaskTo extends string = '',
350
+ >(
351
+ options: UseLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
352
+ ): React.AnchorHTMLAttributes<HTMLAnchorElement> {
353
+ const router = useRouter()
354
+ const matchPathname = useMatch({
355
+ strict: false,
356
+ select: (s) => s.pathname,
357
+ })
358
+
359
+ const {
360
+ // custom props
361
+ children,
362
+ target,
363
+ activeProps = () => ({ className: 'active' }),
364
+ inactiveProps = () => ({}),
365
+ activeOptions,
366
+ disabled,
367
+ hash,
368
+ search,
369
+ params,
370
+ to,
371
+ state,
372
+ mask,
373
+ preload: userPreload,
374
+ preloadDelay: userPreloadDelay,
375
+ replace,
376
+ startTransition,
377
+ resetScroll,
378
+ // element props
379
+ style,
380
+ className,
381
+ onClick,
382
+ onFocus,
383
+ onMouseEnter,
384
+ onMouseLeave,
385
+ onTouchStart,
386
+ ...rest
387
+ } = options
388
+
389
+ // If this link simply reloads the current route,
390
+ // make sure it has a new key so it will trigger a data refresh
391
+
392
+ // If this `to` is a valid external URL, return
393
+ // null for LinkUtils
394
+
395
+ const dest = {
396
+ from: options.to ? matchPathname : undefined,
397
+ ...options,
398
+ }
399
+
400
+ let type: 'internal' | 'external' = 'internal'
401
+
402
+ try {
403
+ new URL(`${to}`)
404
+ type = 'external'
405
+ } catch {}
406
+
407
+ if (type === 'external') {
408
+ return {
409
+ href: to,
410
+ }
411
+ }
412
+
413
+ const next = router.buildLocation(dest as any)
414
+
415
+ const preload = userPreload ?? router.options.defaultPreload
416
+ const preloadDelay =
417
+ userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
418
+
419
+ const isActive = useRouterState({
420
+ select: (s) => {
421
+ // Compare path/hash for matches
422
+ const currentPathSplit = s.location.pathname.split('/')
423
+ const nextPathSplit = next.pathname.split('/')
424
+ const pathIsFuzzyEqual = nextPathSplit.every(
425
+ (d, i) => d === currentPathSplit[i],
426
+ )
427
+ // Combine the matches based on user router.options
428
+ const pathTest = activeOptions?.exact
429
+ ? s.location.pathname === next.pathname
430
+ : pathIsFuzzyEqual
431
+ const hashTest = activeOptions?.includeHash
432
+ ? s.location.hash === next.hash
433
+ : true
434
+ const searchTest =
435
+ activeOptions?.includeSearch ?? true
436
+ ? deepEqual(s.location.search, next.search, !activeOptions?.exact)
437
+ : true
438
+
439
+ // The final "active" test
440
+ return pathTest && hashTest && searchTest
441
+ },
442
+ })
443
+
444
+ // The click handler
445
+ const handleClick = (e: MouseEvent) => {
446
+ if (
447
+ !disabled &&
448
+ !isCtrlEvent(e) &&
449
+ !e.defaultPrevented &&
450
+ (!target || target === '_self') &&
451
+ e.button === 0
452
+ ) {
453
+ e.preventDefault()
454
+
455
+ // All is well? Navigate!
456
+ router.commitLocation({ ...next, replace, resetScroll, startTransition })
457
+ }
458
+ }
459
+
460
+ // The click handler
461
+ const handleFocus = (e: MouseEvent) => {
462
+ if (preload) {
463
+ router.preloadRoute(dest as any).catch((err) => {
464
+ console.warn(err)
465
+ console.warn(preloadWarning)
466
+ })
467
+ }
468
+ }
469
+
470
+ const handleTouchStart = (e: TouchEvent) => {
471
+ if (preload) {
472
+ router.preloadRoute(dest as any).catch((err) => {
473
+ console.warn(err)
474
+ console.warn(preloadWarning)
475
+ })
476
+ }
477
+ }
478
+
479
+ const handleEnter = (e: MouseEvent) => {
480
+ const target = (e.target || {}) as LinkCurrentTargetElement
481
+
482
+ if (preload) {
483
+ if (target.preloadTimeout) {
484
+ return
485
+ }
486
+
487
+ target.preloadTimeout = setTimeout(() => {
488
+ target.preloadTimeout = null
489
+ router.preloadRoute(dest as any).catch((err) => {
490
+ console.warn(err)
491
+ console.warn(preloadWarning)
492
+ })
493
+ }, preloadDelay)
494
+ }
495
+ }
496
+
497
+ const handleLeave = (e: MouseEvent) => {
498
+ const target = (e.target || {}) as LinkCurrentTargetElement
499
+
500
+ if (target.preloadTimeout) {
501
+ clearTimeout(target.preloadTimeout)
502
+ target.preloadTimeout = null
503
+ }
504
+ }
505
+
506
+ const composeHandlers =
507
+ (handlers: (undefined | ((e: any) => void))[]) =>
508
+ (e: React.SyntheticEvent) => {
509
+ if (e.persist) e.persist()
510
+ handlers.filter(Boolean).forEach((handler) => {
511
+ if (e.defaultPrevented) return
512
+ handler!(e)
513
+ })
514
+ }
515
+
516
+ // Get the active props
517
+ const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
518
+ ? functionalUpdate(activeProps as any, {}) ?? {}
519
+ : {}
520
+
521
+ // Get the inactive props
522
+ const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
523
+ isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
524
+
525
+ return {
526
+ ...resolvedActiveProps,
527
+ ...resolvedInactiveProps,
528
+ ...rest,
529
+ href: disabled
530
+ ? undefined
531
+ : next.maskedLocation
532
+ ? next.maskedLocation.href
533
+ : next.href,
534
+ onClick: composeHandlers([onClick, handleClick]),
535
+ onFocus: composeHandlers([onFocus, handleFocus]),
536
+ onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
537
+ onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
538
+ onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
539
+ target,
540
+ style: {
541
+ ...style,
542
+ ...resolvedActiveProps.style,
543
+ ...resolvedInactiveProps.style,
544
+ },
545
+ className:
546
+ [
547
+ className,
548
+ resolvedActiveProps.className,
549
+ resolvedInactiveProps.className,
550
+ ]
551
+ .filter(Boolean)
552
+ .join(' ') || undefined,
553
+ ...(disabled
554
+ ? {
555
+ role: 'link',
556
+ 'aria-disabled': true,
557
+ }
558
+ : undefined),
559
+ ['data-status']: isActive ? 'active' : undefined,
560
+ }
561
+ }
562
+
563
+ export interface LinkComponent<TProps extends Record<string, any> = {}> {
564
+ <
565
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
566
+ TFrom extends RoutePaths<TRouteTree> = '/',
567
+ TTo extends string = '',
568
+ TMaskFrom extends RoutePaths<TRouteTree> = '/',
569
+ TMaskTo extends string = '',
570
+ >(
571
+ props: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
572
+ TProps &
573
+ React.RefAttributes<HTMLAnchorElement>,
574
+ ): ReactNode
575
+ }
576
+
577
+ export const Link: LinkComponent = React.forwardRef((props: any, ref) => {
578
+ const linkProps = useLinkProps(props)
579
+
580
+ return (
581
+ <a
582
+ {...{
583
+ ref: ref as any,
584
+ ...linkProps,
585
+ children:
586
+ typeof props.children === 'function'
587
+ ? props.children({
588
+ isActive: (linkProps as any)['data-status'] === 'active',
589
+ })
590
+ : props.children,
591
+ }}
592
+ />
593
+ )
594
+ }) as any
595
+
596
+ function isCtrlEvent(e: MouseEvent) {
597
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
598
+ }
@@ -0,0 +1,13 @@
1
+ import { HistoryState } from '@tanstack/history'
2
+ import { AnySearchSchema } from './route'
3
+
4
+ export interface ParsedLocation<TSearchObj extends AnySearchSchema = {}> {
5
+ href: string
6
+ pathname: string
7
+ search: TSearchObj
8
+ searchStr: string
9
+ state: HistoryState
10
+ hash: string
11
+ maskedLocation?: ParsedLocation<TSearchObj>
12
+ unmaskOnReload?: boolean
13
+ }