@tanstack/react-router 0.0.1-beta.22 → 0.0.1-beta.221

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/build/cjs/CatchBoundary.js +126 -0
  3. package/build/cjs/CatchBoundary.js.map +1 -0
  4. package/build/cjs/Matches.js +235 -0
  5. package/build/cjs/Matches.js.map +1 -0
  6. package/build/cjs/RouterProvider.js +1085 -0
  7. package/build/cjs/RouterProvider.js.map +1 -0
  8. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +1 -19
  9. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
  10. package/build/cjs/awaited.js +45 -0
  11. package/build/cjs/awaited.js.map +1 -0
  12. package/build/cjs/defer.js +39 -0
  13. package/build/cjs/defer.js.map +1 -0
  14. package/build/cjs/fileRoute.js +29 -0
  15. package/build/cjs/fileRoute.js.map +1 -0
  16. package/build/cjs/index.js +135 -0
  17. package/build/cjs/index.js.map +1 -0
  18. package/build/cjs/lazyRouteComponent.js +57 -0
  19. package/build/cjs/lazyRouteComponent.js.map +1 -0
  20. package/build/cjs/link.js +151 -0
  21. package/build/cjs/link.js.map +1 -0
  22. package/build/cjs/path.js +211 -0
  23. package/build/cjs/path.js.map +1 -0
  24. package/build/cjs/qss.js +65 -0
  25. package/build/cjs/qss.js.map +1 -0
  26. package/build/cjs/redirects.js +27 -0
  27. package/build/cjs/redirects.js.map +1 -0
  28. package/build/cjs/route.js +139 -0
  29. package/build/cjs/route.js.map +1 -0
  30. package/build/cjs/router.js +160 -0
  31. package/build/cjs/router.js.map +1 -0
  32. package/build/cjs/scroll-restoration.js +186 -0
  33. package/build/cjs/scroll-restoration.js.map +1 -0
  34. package/build/cjs/searchParams.js +83 -0
  35. package/build/cjs/searchParams.js.map +1 -0
  36. package/build/cjs/useBlocker.js +64 -0
  37. package/build/cjs/useBlocker.js.map +1 -0
  38. package/build/cjs/useNavigate.js +78 -0
  39. package/build/cjs/useNavigate.js.map +1 -0
  40. package/build/cjs/useParams.js +28 -0
  41. package/build/cjs/useParams.js.map +1 -0
  42. package/build/cjs/useSearch.js +27 -0
  43. package/build/cjs/useSearch.js.map +1 -0
  44. package/build/cjs/utils.js +236 -0
  45. package/build/cjs/utils.js.map +1 -0
  46. package/build/esm/index.js +2136 -2564
  47. package/build/esm/index.js.map +1 -1
  48. package/build/stats-html.html +59 -49
  49. package/build/stats-react.json +922 -43
  50. package/build/types/CatchBoundary.d.ts +33 -0
  51. package/build/types/Matches.d.ts +34 -0
  52. package/build/types/RouterProvider.d.ts +87 -0
  53. package/build/types/awaited.d.ts +8 -0
  54. package/build/types/defer.d.ts +19 -0
  55. package/build/types/fileRoute.d.ts +32 -0
  56. package/build/types/history.d.ts +7 -0
  57. package/build/types/index.d.ts +27 -104
  58. package/build/types/injectHtml.d.ts +0 -0
  59. package/build/types/lazyRouteComponent.d.ts +2 -0
  60. package/build/types/link.d.ts +105 -0
  61. package/build/types/location.d.ts +14 -0
  62. package/build/types/path.d.ts +16 -0
  63. package/build/types/qss.d.ts +2 -0
  64. package/build/types/redirects.d.ts +10 -0
  65. package/build/types/route.d.ts +270 -0
  66. package/build/types/routeInfo.d.ts +22 -0
  67. package/build/types/router.d.ts +123 -0
  68. package/build/types/scroll-restoration.d.ts +6 -0
  69. package/build/types/searchParams.d.ts +7 -0
  70. package/build/types/useBlocker.d.ts +8 -0
  71. package/build/types/useNavigate.d.ts +20 -0
  72. package/build/types/useParams.d.ts +7 -0
  73. package/build/types/useSearch.d.ts +7 -0
  74. package/build/types/utils.d.ts +66 -0
  75. package/build/umd/index.development.js +2399 -2484
  76. package/build/umd/index.development.js.map +1 -1
  77. package/build/umd/index.production.js +4 -4
  78. package/build/umd/index.production.js.map +1 -1
  79. package/package.json +9 -10
  80. package/src/CatchBoundary.tsx +98 -0
  81. package/src/Matches.tsx +345 -0
  82. package/src/RouterProvider.tsx +1575 -0
  83. package/src/awaited.tsx +40 -0
  84. package/src/defer.ts +55 -0
  85. package/src/fileRoute.ts +153 -0
  86. package/src/history.ts +8 -0
  87. package/src/index.tsx +28 -693
  88. package/src/injectHtml.ts +28 -0
  89. package/src/lazyRouteComponent.tsx +33 -0
  90. package/src/link.tsx +508 -0
  91. package/src/location.ts +15 -0
  92. package/src/path.ts +256 -0
  93. package/src/qss.ts +53 -0
  94. package/src/redirects.ts +31 -0
  95. package/src/route.ts +837 -0
  96. package/src/routeInfo.ts +68 -0
  97. package/src/router.ts +330 -0
  98. package/src/scroll-restoration.tsx +192 -0
  99. package/src/searchParams.ts +79 -0
  100. package/src/useBlocker.tsx +34 -0
  101. package/src/useNavigate.tsx +109 -0
  102. package/src/useParams.tsx +25 -0
  103. package/src/useSearch.tsx +25 -0
  104. package/src/utils.ts +350 -0
  105. package/build/cjs/react-router/src/index.js +0 -466
  106. package/build/cjs/react-router/src/index.js.map +0 -1
  107. package/build/cjs/router-core/build/esm/index.js +0 -2523
  108. package/build/cjs/router-core/build/esm/index.js.map +0 -1
@@ -0,0 +1,1575 @@
1
+ import {
2
+ HistoryLocation,
3
+ HistoryState,
4
+ RouterHistory,
5
+ createBrowserHistory,
6
+ } from '@tanstack/history'
7
+ import * as React from 'react'
8
+ import invariant from 'tiny-invariant'
9
+ import warning from 'tiny-warning'
10
+ import { Matches } from './Matches'
11
+ import {
12
+ LinkInfo,
13
+ LinkOptions,
14
+ NavigateOptions,
15
+ ResolveRelativePath,
16
+ ToOptions,
17
+ } from './link'
18
+ import { ParsedLocation } from './location'
19
+ import {
20
+ cleanPath,
21
+ interpolatePath,
22
+ joinPaths,
23
+ matchPathname,
24
+ parsePathname,
25
+ resolvePath,
26
+ trimPath,
27
+ trimPathRight,
28
+ } from './path'
29
+ import { isRedirect } from './redirects'
30
+ import {
31
+ AnyPathParams,
32
+ AnyRoute,
33
+ AnySearchSchema,
34
+ LoaderFnContext,
35
+ Route,
36
+ } from './route'
37
+ import {
38
+ FullSearchSchema,
39
+ ParseRoute,
40
+ RouteById,
41
+ RouteIds,
42
+ RoutePaths,
43
+ RoutesById,
44
+ RoutesByPath,
45
+ } from './routeInfo'
46
+ import {
47
+ BuildNextOptions,
48
+ DehydratedRouteMatch,
49
+ RegisteredRouter,
50
+ Router,
51
+ RouterOptions,
52
+ RouterState,
53
+ componentTypes,
54
+ } from './router'
55
+ import {
56
+ NoInfer,
57
+ PickAsRequired,
58
+ functionalUpdate,
59
+ last,
60
+ deepEqual,
61
+ pick,
62
+ replaceEqualDeep,
63
+ useStableCallback,
64
+ escapeJSON,
65
+ } from './utils'
66
+ import { MatchRouteOptions } from './Matches'
67
+
68
+ export interface CommitLocationOptions {
69
+ replace?: boolean
70
+ resetScroll?: boolean
71
+ startTransition?: boolean
72
+ }
73
+
74
+ export interface MatchLocation {
75
+ to?: string | number | null
76
+ fuzzy?: boolean
77
+ caseSensitive?: boolean
78
+ from?: string
79
+ }
80
+
81
+ type LinkCurrentTargetElement = {
82
+ preloadTimeout?: null | ReturnType<typeof setTimeout>
83
+ }
84
+
85
+ export type BuildLinkFn<TRouteTree extends AnyRoute> = <
86
+ TFrom extends RoutePaths<TRouteTree> = '/',
87
+ TTo extends string = '',
88
+ >(
89
+ dest: LinkOptions<TRouteTree, TFrom, TTo>,
90
+ ) => LinkInfo
91
+
92
+ export type NavigateFn<TRouteTree extends AnyRoute> = <
93
+ TFrom extends RoutePaths<TRouteTree> = '/',
94
+ TTo extends string = '',
95
+ TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
96
+ TMaskTo extends string = '',
97
+ >(
98
+ opts: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
99
+ ) => Promise<void>
100
+
101
+ export type MatchRouteFn<TRouteTree extends AnyRoute> = <
102
+ TFrom extends RoutePaths<TRouteTree> = '/',
103
+ TTo extends string = '',
104
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
105
+ >(
106
+ location: ToOptions<TRouteTree, TFrom, TTo>,
107
+ opts?: MatchRouteOptions,
108
+ ) => false | RouteById<TRouteTree, TResolved>['types']['allParams']
109
+
110
+ export type LoadFn = (opts?: {
111
+ next?: ParsedLocation
112
+ throwOnError?: boolean
113
+ __dehydratedMatches?: DehydratedRouteMatch[]
114
+ }) => Promise<void>
115
+
116
+ export type BuildLocationFn<TRouteTree extends AnyRoute> = (
117
+ opts: BuildNextOptions,
118
+ ) => ParsedLocation
119
+
120
+ export type InjectedHtmlEntry = string | (() => Promise<string> | string)
121
+
122
+ export type RouterContext<
123
+ TRouteTree extends AnyRoute,
124
+ // TDehydrated extends Record<string, any>,
125
+ > = {
126
+ buildLink: BuildLinkFn<TRouteTree>
127
+ state: RouterState<TRouteTree>
128
+ navigate: NavigateFn<TRouteTree>
129
+ matchRoute: MatchRouteFn<TRouteTree>
130
+ routeTree: TRouteTree
131
+ routesById: RoutesById<TRouteTree>
132
+ options: RouterOptions<TRouteTree>
133
+ history: RouterHistory
134
+ load: LoadFn
135
+ buildLocation: BuildLocationFn<TRouteTree>
136
+ subscribe: Router<TRouteTree>['subscribe']
137
+ resetNextScrollRef: React.MutableRefObject<boolean>
138
+ injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
139
+ injectHtml: (entry: InjectedHtmlEntry) => void
140
+ dehydrateData: <T>(
141
+ key: any,
142
+ getData: T | (() => Promise<T> | T),
143
+ ) => () => void
144
+ hydrateData: <T>(key: any) => T | undefined
145
+ }
146
+
147
+ export const routerContext = React.createContext<RouterContext<any>>(null!)
148
+
149
+ if (typeof document !== 'undefined') {
150
+ window.__TSR_ROUTER_CONTEXT__ = routerContext as any
151
+ }
152
+
153
+ const preloadWarning = 'Error preloading route! ☝️'
154
+
155
+ function isCtrlEvent(e: MouseEvent) {
156
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
157
+ }
158
+
159
+ export class SearchParamError extends Error {}
160
+
161
+ export class PathParamError extends Error {}
162
+
163
+ export function getInitialRouterState(
164
+ location: ParsedLocation,
165
+ ): RouterState<any> {
166
+ return {
167
+ status: 'idle',
168
+ resolvedLocation: location,
169
+ location,
170
+ matches: [],
171
+ pendingMatches: [],
172
+ lastUpdated: Date.now(),
173
+ }
174
+ }
175
+
176
+ export function RouterProvider<
177
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
178
+ TDehydrated extends Record<string, any> = Record<string, any>,
179
+ >({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
180
+ const options = {
181
+ ...router.options,
182
+ ...rest,
183
+ context: {
184
+ ...router.options.context,
185
+ ...rest?.context,
186
+ },
187
+ } as PickAsRequired<
188
+ RouterOptions<TRouteTree, TDehydrated>,
189
+ 'stringifySearch' | 'parseSearch' | 'context'
190
+ >
191
+
192
+ const history = React.useState(
193
+ () => options.history ?? createBrowserHistory(),
194
+ )[0]
195
+
196
+ const tempLocationKeyRef = React.useRef<string | undefined>(
197
+ `${Math.round(Math.random() * 10000000)}`,
198
+ )
199
+ const resetNextScrollRef = React.useRef<boolean>(true)
200
+ const navigateTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
201
+ const latestLoadPromiseRef = React.useRef<Promise<void>>(Promise.resolve())
202
+
203
+ const checkLatest = (promise: Promise<void>): undefined | Promise<void> => {
204
+ return latestLoadPromiseRef.current !== promise
205
+ ? latestLoadPromiseRef.current
206
+ : undefined
207
+ }
208
+
209
+ const parseLocation = useStableCallback(
210
+ (
211
+ previousLocation?: ParsedLocation,
212
+ ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
213
+ const parse = ({
214
+ pathname,
215
+ search,
216
+ hash,
217
+ state,
218
+ }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
219
+ const parsedSearch = options.parseSearch(search)
220
+
221
+ return {
222
+ pathname: pathname,
223
+ searchStr: search,
224
+ search: replaceEqualDeep(
225
+ previousLocation?.search,
226
+ parsedSearch,
227
+ ) as any,
228
+ hash: hash.split('#').reverse()[0] ?? '',
229
+ href: `${pathname}${search}${hash}`,
230
+ state: replaceEqualDeep(
231
+ previousLocation?.state,
232
+ state,
233
+ ) as HistoryState,
234
+ }
235
+ }
236
+
237
+ const location = parse(history.location)
238
+
239
+ let { __tempLocation, __tempKey } = location.state
240
+
241
+ if (
242
+ __tempLocation &&
243
+ (!__tempKey || __tempKey === tempLocationKeyRef.current)
244
+ ) {
245
+ // Sync up the location keys
246
+ const parsedTempLocation = parse(__tempLocation) as any
247
+ parsedTempLocation.state.key = location.state.key
248
+
249
+ delete parsedTempLocation.state.__tempLocation
250
+
251
+ return {
252
+ ...parsedTempLocation,
253
+ maskedLocation: location,
254
+ }
255
+ }
256
+
257
+ return location
258
+ },
259
+ )
260
+
261
+ const latestLocationRef = React.useRef<ParsedLocation>(parseLocation())
262
+ const [preState, setState] = React.useState<RouterState<TRouteTree>>(() =>
263
+ getInitialRouterState(latestLocationRef.current),
264
+ )
265
+ const [isTransitioning, startReactTransition] = React.useTransition()
266
+ const pendingMatchesRef = React.useRef<AnyRouteMatch[]>([])
267
+
268
+ const state = React.useMemo<RouterState<TRouteTree>>(
269
+ () => ({
270
+ ...preState,
271
+ status: isTransitioning ? 'pending' : 'idle',
272
+ location: isTransitioning ? latestLocationRef.current : preState.location,
273
+ pendingMatches: pendingMatchesRef.current,
274
+ }),
275
+ [preState, isTransitioning],
276
+ )
277
+
278
+ React.useLayoutEffect(() => {
279
+ if (!isTransitioning && state.resolvedLocation !== state.location) {
280
+ router.emit({
281
+ type: 'onResolved',
282
+ fromLocation: state.resolvedLocation,
283
+ toLocation: state.location,
284
+ pathChanged: state.location!.href !== state.resolvedLocation?.href,
285
+ })
286
+ pendingMatchesRef.current = []
287
+
288
+ setState((s) => ({
289
+ ...s,
290
+ resolvedLocation: s.location,
291
+ }))
292
+ }
293
+ })
294
+
295
+ const basepath = `/${trimPath(options.basepath ?? '') ?? ''}`
296
+
297
+ const resolvePathWithBase = useStableCallback(
298
+ (from: string, path: string) => {
299
+ return resolvePath(basepath!, from, cleanPath(path))
300
+ },
301
+ )
302
+
303
+ const [routesById, routesByPath] = React.useMemo(() => {
304
+ const routesById = {} as RoutesById<TRouteTree>
305
+ const routesByPath = {} as RoutesByPath<TRouteTree>
306
+
307
+ const recurseRoutes = (routes: AnyRoute[]) => {
308
+ routes.forEach((route, i) => {
309
+ route.init({ originalIndex: i })
310
+
311
+ const existingRoute = (routesById as any)[route.id]
312
+
313
+ invariant(
314
+ !existingRoute,
315
+ `Duplicate routes found with id: ${String(route.id)}`,
316
+ )
317
+ ;(routesById as any)[route.id] = route
318
+
319
+ if (!route.isRoot && route.path) {
320
+ const trimmedFullPath = trimPathRight(route.fullPath)
321
+ if (
322
+ !(routesByPath as any)[trimmedFullPath] ||
323
+ route.fullPath.endsWith('/')
324
+ ) {
325
+ ;(routesByPath as any)[trimmedFullPath] = route
326
+ }
327
+ }
328
+
329
+ const children = route.children as Route[]
330
+
331
+ if (children?.length) {
332
+ recurseRoutes(children)
333
+ }
334
+ })
335
+ }
336
+
337
+ recurseRoutes([router.routeTree])
338
+
339
+ return [routesById, routesByPath] as const
340
+ }, [])
341
+
342
+ const looseRoutesById = routesById as Record<string, AnyRoute>
343
+
344
+ const flatRoutes = React.useMemo(
345
+ () =>
346
+ (Object.values(routesByPath) as AnyRoute[])
347
+ .map((d, i) => {
348
+ const trimmed = trimPath(d.fullPath)
349
+ const parsed = parsePathname(trimmed)
350
+
351
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
352
+ parsed.shift()
353
+ }
354
+
355
+ const score = parsed.map((d) => {
356
+ if (d.type === 'param') {
357
+ return 0.5
358
+ }
359
+
360
+ if (d.type === 'wildcard') {
361
+ return 0.25
362
+ }
363
+
364
+ return 1
365
+ })
366
+
367
+ return { child: d, trimmed, parsed, index: i, score }
368
+ })
369
+ .sort((a, b) => {
370
+ let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0
371
+
372
+ if (isIndex !== 0) return isIndex
373
+
374
+ const length = Math.min(a.score.length, b.score.length)
375
+
376
+ // Sort by length of score
377
+ if (a.score.length !== b.score.length) {
378
+ return b.score.length - a.score.length
379
+ }
380
+
381
+ // Sort by min available score
382
+ for (let i = 0; i < length; i++) {
383
+ if (a.score[i] !== b.score[i]) {
384
+ return b.score[i]! - a.score[i]!
385
+ }
386
+ }
387
+
388
+ // Sort by min available parsed value
389
+ for (let i = 0; i < length; i++) {
390
+ if (a.parsed[i]!.value !== b.parsed[i]!.value) {
391
+ return a.parsed[i]!.value! > b.parsed[i]!.value! ? 1 : -1
392
+ }
393
+ }
394
+
395
+ // Sort by length of trimmed full path
396
+ if (a.trimmed !== b.trimmed) {
397
+ return a.trimmed > b.trimmed ? 1 : -1
398
+ }
399
+
400
+ // Sort by original index
401
+ return a.index - b.index
402
+ })
403
+ .map((d, i) => {
404
+ d.child.rank = i
405
+ return d.child
406
+ }),
407
+ [routesByPath],
408
+ )
409
+
410
+ const matchRoutes = useStableCallback(
411
+ <TRouteTree extends AnyRoute>(
412
+ pathname: string,
413
+ locationSearch: AnySearchSchema,
414
+ opts?: { throwOnError?: boolean; debug?: boolean },
415
+ ): RouteMatch<TRouteTree>[] => {
416
+ let routeParams: AnyPathParams = {}
417
+
418
+ let foundRoute = flatRoutes.find((route) => {
419
+ const matchedParams = matchPathname(basepath, trimPathRight(pathname), {
420
+ to: route.fullPath,
421
+ caseSensitive: route.options.caseSensitive ?? options.caseSensitive,
422
+ fuzzy: false,
423
+ })
424
+
425
+ if (matchedParams) {
426
+ routeParams = matchedParams
427
+ return true
428
+ }
429
+
430
+ return false
431
+ })
432
+
433
+ let routeCursor: AnyRoute = foundRoute || (routesById as any)['__root__']
434
+
435
+ let matchedRoutes: AnyRoute[] = [routeCursor]
436
+ // let includingLayouts = true
437
+ while (routeCursor?.parentRoute) {
438
+ routeCursor = routeCursor.parentRoute
439
+ if (routeCursor) matchedRoutes.unshift(routeCursor)
440
+ }
441
+
442
+ // Existing matches are matches that are already loaded along with
443
+ // pending matches that are still loading
444
+
445
+ const parseErrors = matchedRoutes.map((route) => {
446
+ let parsedParamsError
447
+
448
+ if (route.options.parseParams) {
449
+ try {
450
+ const parsedParams = route.options.parseParams(routeParams)
451
+ // Add the parsed params to the accumulated params bag
452
+ Object.assign(routeParams, parsedParams)
453
+ } catch (err: any) {
454
+ parsedParamsError = new PathParamError(err.message, {
455
+ cause: err,
456
+ })
457
+
458
+ if (opts?.throwOnError) {
459
+ throw parsedParamsError
460
+ }
461
+
462
+ return parsedParamsError
463
+ }
464
+ }
465
+
466
+ return
467
+ })
468
+
469
+ const matches = matchedRoutes.map((route, index) => {
470
+ const interpolatedPath = interpolatePath(route.path, routeParams)
471
+ const matchId = interpolatePath(route.id, routeParams, true)
472
+
473
+ // Waste not, want not. If we already have a match for this route,
474
+ // reuse it. This is important for layout routes, which might stick
475
+ // around between navigation actions that only change leaf routes.
476
+ const existingMatch = getRouteMatch(state, matchId)
477
+
478
+ if (existingMatch) {
479
+ return { ...existingMatch }
480
+ }
481
+
482
+ // Create a fresh route match
483
+ const hasLoaders = !!(
484
+ route.options.loader ||
485
+ componentTypes.some((d) => (route.options[d] as any)?.preload)
486
+ )
487
+
488
+ const routeMatch: AnyRouteMatch = {
489
+ id: matchId,
490
+ routeId: route.id,
491
+ params: routeParams,
492
+ pathname: joinPaths([basepath, interpolatedPath]),
493
+ updatedAt: Date.now(),
494
+ routeSearch: {},
495
+ search: {} as any,
496
+ status: hasLoaders ? 'pending' : 'success',
497
+ isFetching: false,
498
+ invalid: false,
499
+ error: undefined,
500
+ paramsError: parseErrors[index],
501
+ searchError: undefined,
502
+ loadPromise: Promise.resolve(),
503
+ context: undefined!,
504
+ abortController: new AbortController(),
505
+ shouldReloadDeps: undefined,
506
+ fetchedAt: 0,
507
+ }
508
+
509
+ return routeMatch
510
+ })
511
+
512
+ // Take each match and resolve its search params and context
513
+ // This has to happen after the matches are created or found
514
+ // so that we can use the parent match's search params and context
515
+ matches.forEach((match, i): any => {
516
+ const parentMatch = matches[i - 1]
517
+ const route = looseRoutesById[match.routeId]!
518
+
519
+ const searchInfo = (() => {
520
+ // Validate the search params and stabilize them
521
+ const parentSearchInfo = {
522
+ search: parentMatch?.search ?? locationSearch,
523
+ routeSearch: parentMatch?.routeSearch ?? locationSearch,
524
+ }
525
+
526
+ try {
527
+ const validator =
528
+ typeof route.options.validateSearch === 'object'
529
+ ? route.options.validateSearch.parse
530
+ : route.options.validateSearch
531
+
532
+ let routeSearch = validator?.(parentSearchInfo.search) ?? {}
533
+
534
+ let search = {
535
+ ...parentSearchInfo.search,
536
+ ...routeSearch,
537
+ }
538
+
539
+ routeSearch = replaceEqualDeep(match.routeSearch, routeSearch)
540
+ search = replaceEqualDeep(match.search, search)
541
+
542
+ return {
543
+ routeSearch,
544
+ search,
545
+ searchDidChange: match.routeSearch !== routeSearch,
546
+ }
547
+ } catch (err: any) {
548
+ match.searchError = new SearchParamError(err.message, {
549
+ cause: err,
550
+ })
551
+
552
+ if (opts?.throwOnError) {
553
+ throw match.searchError
554
+ }
555
+
556
+ return parentSearchInfo
557
+ }
558
+ })()
559
+
560
+ Object.assign(match, searchInfo)
561
+ })
562
+
563
+ return matches as any
564
+ },
565
+ )
566
+
567
+ const cancelMatch = useStableCallback(
568
+ <TRouteTree extends AnyRoute>(id: string) => {
569
+ getRouteMatch(state, id)?.abortController?.abort()
570
+ },
571
+ )
572
+
573
+ const cancelMatches = useStableCallback(
574
+ <TRouteTree extends AnyRoute>(state: RouterState<TRouteTree>) => {
575
+ state.matches.forEach((match) => {
576
+ cancelMatch(match.id)
577
+ })
578
+ },
579
+ )
580
+
581
+ const buildLocation = useStableCallback<BuildLocationFn<TRouteTree>>(
582
+ (opts) => {
583
+ const build = (
584
+ dest: BuildNextOptions & {
585
+ unmaskOnReload?: boolean
586
+ } = {},
587
+ matches?: AnyRouteMatch[],
588
+ ): ParsedLocation => {
589
+ const from = latestLocationRef.current
590
+ const fromPathname = dest.from ?? from.pathname
591
+
592
+ let pathname = resolvePathWithBase(fromPathname, `${dest.to ?? ''}`)
593
+
594
+ const fromMatches = matchRoutes(fromPathname, from.search)
595
+ const stayingMatches = matches?.filter((d) =>
596
+ fromMatches?.find((e) => e.routeId === d.routeId),
597
+ )
598
+
599
+ const prevParams = { ...last(fromMatches)?.params }
600
+
601
+ let nextParams =
602
+ (dest.params ?? true) === true
603
+ ? prevParams
604
+ : functionalUpdate(dest.params!, prevParams)
605
+
606
+ if (nextParams) {
607
+ matches
608
+ ?.map((d) => looseRoutesById[d.routeId]!.options.stringifyParams)
609
+ .filter(Boolean)
610
+ .forEach((fn) => {
611
+ nextParams = { ...nextParams!, ...fn!(nextParams!) }
612
+ })
613
+ }
614
+
615
+ pathname = interpolatePath(pathname, nextParams ?? {})
616
+
617
+ const preSearchFilters =
618
+ stayingMatches
619
+ ?.map(
620
+ (match) =>
621
+ looseRoutesById[match.routeId]!.options.preSearchFilters ?? [],
622
+ )
623
+ .flat()
624
+ .filter(Boolean) ?? []
625
+
626
+ const postSearchFilters =
627
+ stayingMatches
628
+ ?.map(
629
+ (match) =>
630
+ looseRoutesById[match.routeId]!.options.postSearchFilters ?? [],
631
+ )
632
+ .flat()
633
+ .filter(Boolean) ?? []
634
+
635
+ // Pre filters first
636
+ const preFilteredSearch = preSearchFilters?.length
637
+ ? preSearchFilters?.reduce(
638
+ (prev, next) => next(prev) as any,
639
+ from.search,
640
+ )
641
+ : from.search
642
+
643
+ // Then the link/navigate function
644
+ const destSearch =
645
+ dest.search === true
646
+ ? preFilteredSearch // Preserve resolvedFrom true
647
+ : dest.search
648
+ ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
649
+ : preSearchFilters?.length
650
+ ? preFilteredSearch // Preserve resolvedFrom filters
651
+ : {}
652
+
653
+ // Then post filters
654
+ const postFilteredSearch = postSearchFilters?.length
655
+ ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
656
+ : destSearch
657
+
658
+ const search = replaceEqualDeep(from.search, postFilteredSearch)
659
+
660
+ const searchStr = options.stringifySearch(search)
661
+
662
+ const hash =
663
+ dest.hash === true
664
+ ? from.hash
665
+ : dest.hash
666
+ ? functionalUpdate(dest.hash!, from.hash)
667
+ : from.hash
668
+
669
+ const hashStr = hash ? `#${hash}` : ''
670
+
671
+ let nextState =
672
+ dest.state === true
673
+ ? from.state
674
+ : dest.state
675
+ ? functionalUpdate(dest.state, from.state)
676
+ : from.state
677
+
678
+ nextState = replaceEqualDeep(from.state, nextState)
679
+
680
+ return {
681
+ pathname,
682
+ search,
683
+ searchStr,
684
+ state: nextState as any,
685
+ hash,
686
+ href: history.createHref(`${pathname}${searchStr}${hashStr}`),
687
+ unmaskOnReload: dest.unmaskOnReload,
688
+ }
689
+ }
690
+
691
+ const buildWithMatches = (
692
+ dest: BuildNextOptions = {},
693
+ maskedDest?: BuildNextOptions,
694
+ ) => {
695
+ let next = build(dest)
696
+ let maskedNext = maskedDest ? build(maskedDest) : undefined
697
+
698
+ if (!maskedNext) {
699
+ let params = {}
700
+
701
+ let foundMask = options.routeMasks?.find((d) => {
702
+ const match = matchPathname(basepath, next.pathname, {
703
+ to: d.from,
704
+ caseSensitive: false,
705
+ fuzzy: false,
706
+ })
707
+
708
+ if (match) {
709
+ params = match
710
+ return true
711
+ }
712
+
713
+ return false
714
+ })
715
+
716
+ if (foundMask) {
717
+ foundMask = {
718
+ ...foundMask,
719
+ from: interpolatePath(foundMask.from, params) as any,
720
+ }
721
+ maskedDest = foundMask
722
+ maskedNext = build(maskedDest)
723
+ }
724
+ }
725
+
726
+ const nextMatches = matchRoutes(next.pathname, next.search)
727
+ const maskedMatches = maskedNext
728
+ ? matchRoutes(maskedNext.pathname, maskedNext.search)
729
+ : undefined
730
+ const maskedFinal = maskedNext
731
+ ? build(maskedDest, maskedMatches)
732
+ : undefined
733
+
734
+ const final = build(dest, nextMatches)
735
+
736
+ if (maskedFinal) {
737
+ final.maskedLocation = maskedFinal
738
+ }
739
+
740
+ return final
741
+ }
742
+
743
+ if (opts.mask) {
744
+ return buildWithMatches(opts, {
745
+ ...pick(opts, ['from']),
746
+ ...opts.mask,
747
+ })
748
+ }
749
+
750
+ return buildWithMatches(opts)
751
+ },
752
+ )
753
+
754
+ const commitLocation = useStableCallback(
755
+ async ({
756
+ startTransition,
757
+ ...next
758
+ }: ParsedLocation & CommitLocationOptions) => {
759
+ if (navigateTimeoutRef.current) clearTimeout(navigateTimeoutRef.current)
760
+
761
+ const isSameUrl = latestLocationRef.current.href === next.href
762
+
763
+ // If the next urls are the same and we're not replacing,
764
+ // do nothing
765
+ if (!isSameUrl || !next.replace) {
766
+ let { maskedLocation, ...nextHistory } = next
767
+
768
+ if (maskedLocation) {
769
+ nextHistory = {
770
+ ...maskedLocation,
771
+ state: {
772
+ ...maskedLocation.state,
773
+ __tempKey: undefined,
774
+ __tempLocation: {
775
+ ...nextHistory,
776
+ search: nextHistory.searchStr,
777
+ state: {
778
+ ...nextHistory.state,
779
+ __tempKey: undefined!,
780
+ __tempLocation: undefined!,
781
+ key: undefined!,
782
+ },
783
+ },
784
+ },
785
+ }
786
+
787
+ if (nextHistory.unmaskOnReload ?? options.unmaskOnReload ?? false) {
788
+ nextHistory.state.__tempKey = tempLocationKeyRef.current
789
+ }
790
+ }
791
+
792
+ const apply = () => {
793
+ history[next.replace ? 'replace' : 'push'](
794
+ nextHistory.href,
795
+ nextHistory.state,
796
+ )
797
+ }
798
+
799
+ if (startTransition ?? true) {
800
+ startReactTransition(apply)
801
+ } else {
802
+ apply()
803
+ }
804
+ }
805
+
806
+ resetNextScrollRef.current = next.resetScroll ?? true
807
+
808
+ return latestLoadPromiseRef.current
809
+ },
810
+ )
811
+
812
+ const buildAndCommitLocation = useStableCallback(
813
+ ({
814
+ replace,
815
+ resetScroll,
816
+ startTransition,
817
+ ...rest
818
+ }: BuildNextOptions & CommitLocationOptions = {}) => {
819
+ const location = buildLocation(rest)
820
+ return commitLocation({
821
+ ...location,
822
+ startTransition,
823
+ replace,
824
+ resetScroll,
825
+ })
826
+ },
827
+ )
828
+
829
+ const navigate = useStableCallback<NavigateFn<TRouteTree>>(
830
+ ({ from, to = '', ...rest }) => {
831
+ // If this link simply reloads the current route,
832
+ // make sure it has a new key so it will trigger a data refresh
833
+
834
+ // If this `to` is a valid external URL, return
835
+ // null for LinkUtils
836
+ const toString = String(to)
837
+ const fromString = typeof from === 'undefined' ? from : String(from)
838
+ let isExternal
839
+
840
+ try {
841
+ new URL(`${toString}`)
842
+ isExternal = true
843
+ } catch (e) {}
844
+
845
+ invariant(
846
+ !isExternal,
847
+ 'Attempting to navigate to external url with this.navigate!',
848
+ )
849
+
850
+ return buildAndCommitLocation({
851
+ ...rest,
852
+ from: fromString,
853
+ to: toString,
854
+ })
855
+ },
856
+ )
857
+
858
+ const loadMatches = useStableCallback(
859
+ async ({
860
+ checkLatest,
861
+ matches,
862
+ preload,
863
+ }: {
864
+ checkLatest: () => Promise<void> | undefined
865
+ matches: AnyRouteMatch[]
866
+ preload?: boolean
867
+ }): Promise<RouteMatch[]> => {
868
+ let latestPromise
869
+ let firstBadMatchIndex: number | undefined
870
+
871
+ // Check each match middleware to see if the route can be accessed
872
+ try {
873
+ for (let [index, match] of matches.entries()) {
874
+ const parentMatch = matches[index - 1]
875
+ const route = looseRoutesById[match.routeId]!
876
+
877
+ const handleError = (err: any, code: string) => {
878
+ err.routerCode = code
879
+ firstBadMatchIndex = firstBadMatchIndex ?? index
880
+
881
+ if (isRedirect(err)) {
882
+ throw err
883
+ }
884
+
885
+ try {
886
+ route.options.onError?.(err)
887
+ } catch (errorHandlerErr) {
888
+ err = errorHandlerErr
889
+
890
+ if (isRedirect(errorHandlerErr)) {
891
+ throw errorHandlerErr
892
+ }
893
+ }
894
+
895
+ matches[index] = match = {
896
+ ...match,
897
+ error: err,
898
+ status: 'error',
899
+ updatedAt: Date.now(),
900
+ }
901
+ }
902
+
903
+ try {
904
+ if (match.paramsError) {
905
+ handleError(match.paramsError, 'PARSE_PARAMS')
906
+ }
907
+
908
+ if (match.searchError) {
909
+ handleError(match.searchError, 'VALIDATE_SEARCH')
910
+ }
911
+
912
+ const parentContext = parentMatch?.context ?? options.context ?? {}
913
+
914
+ const beforeLoadContext =
915
+ (await route.options.beforeLoad?.({
916
+ search: match.search,
917
+ abortController: match.abortController,
918
+ params: match.params,
919
+ preload: !!preload,
920
+ context: parentContext,
921
+ location: state.location,
922
+ navigate: (opts) =>
923
+ navigate({ ...opts, from: match.pathname } as any),
924
+ buildLocation,
925
+ })) ?? ({} as any)
926
+
927
+ const context = {
928
+ ...parentContext,
929
+ ...beforeLoadContext,
930
+ }
931
+
932
+ matches[index] = match = {
933
+ ...match,
934
+ context: replaceEqualDeep(match.context, context),
935
+ }
936
+ } catch (err) {
937
+ handleError(err, 'BEFORE_LOAD')
938
+ break
939
+ }
940
+ }
941
+ } catch (err) {
942
+ if (isRedirect(err)) {
943
+ if (!preload) navigate(err as any)
944
+ return matches
945
+ }
946
+
947
+ throw err
948
+ }
949
+
950
+ const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
951
+ const matchPromises: Promise<any>[] = []
952
+
953
+ validResolvedMatches.forEach((match, index) => {
954
+ matchPromises.push(
955
+ (async () => {
956
+ const parentMatchPromise = matchPromises[index - 1]
957
+ const route = looseRoutesById[match.routeId]!
958
+
959
+ const handleIfRedirect = (err: any) => {
960
+ if (isRedirect(err)) {
961
+ if (!preload) {
962
+ navigate(err as any)
963
+ }
964
+ return true
965
+ }
966
+ return false
967
+ }
968
+
969
+ let loadPromise: Promise<void> | undefined
970
+
971
+ matches[index] = match = {
972
+ ...match,
973
+ fetchedAt: Date.now(),
974
+ invalid: false,
975
+ }
976
+
977
+ if (match.isFetching) {
978
+ loadPromise = getRouteMatch(state, match.id)?.loadPromise
979
+ } else {
980
+ const cause = state.matches.find((d) => d.id === match.id)
981
+ ? 'stay'
982
+ : 'enter'
983
+
984
+ const loaderContext: LoaderFnContext = {
985
+ params: match.params,
986
+ search: match.search,
987
+ preload: !!preload,
988
+ parentMatchPromise,
989
+ abortController: match.abortController,
990
+ context: match.context,
991
+ location: state.location,
992
+ navigate: (opts) =>
993
+ navigate({ ...opts, from: match.pathname } as any),
994
+ cause,
995
+ }
996
+
997
+ // Default to reloading the route all the time
998
+ let shouldReload = true
999
+
1000
+ if (cause !== 'enter') {
1001
+ let shouldReloadDeps =
1002
+ typeof route.options.shouldReload === 'function'
1003
+ ? route.options.shouldReload?.(loaderContext)
1004
+ : !!(route.options.shouldReload ?? true)
1005
+
1006
+ if (typeof shouldReloadDeps === 'object') {
1007
+ // compare the deps to see if they've changed
1008
+ shouldReload = !deepEqual(
1009
+ shouldReloadDeps,
1010
+ match.shouldReloadDeps,
1011
+ )
1012
+
1013
+ match.shouldReloadDeps = shouldReloadDeps
1014
+ } else {
1015
+ shouldReload = !!shouldReloadDeps
1016
+ }
1017
+ }
1018
+
1019
+ // If the user doesn't want the route to reload, just
1020
+ // resolve with the existing loader data
1021
+
1022
+ if (!shouldReload) {
1023
+ loadPromise = Promise.resolve(match.loaderData)
1024
+ } else {
1025
+ // Otherwise, load the route
1026
+ matches[index] = match = {
1027
+ ...match,
1028
+ isFetching: true,
1029
+ }
1030
+
1031
+ const componentsPromise = Promise.all(
1032
+ componentTypes.map(async (type) => {
1033
+ const component = route.options[type]
1034
+
1035
+ if ((component as any)?.preload) {
1036
+ await (component as any).preload()
1037
+ }
1038
+ }),
1039
+ )
1040
+
1041
+ const loaderPromise = route.options.loader?.(loaderContext)
1042
+
1043
+ loadPromise = Promise.all([
1044
+ componentsPromise,
1045
+ loaderPromise,
1046
+ ]).then((d) => d[1])
1047
+ }
1048
+ }
1049
+
1050
+ matches[index] = match = {
1051
+ ...match,
1052
+ loadPromise,
1053
+ }
1054
+
1055
+ if (!preload) {
1056
+ setState((s) => ({
1057
+ ...s,
1058
+ matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1059
+ }))
1060
+ }
1061
+
1062
+ try {
1063
+ const loaderData = await loadPromise
1064
+ if ((latestPromise = checkLatest())) return await latestPromise
1065
+
1066
+ matches[index] = match = {
1067
+ ...match,
1068
+ error: undefined,
1069
+ status: 'success',
1070
+ isFetching: false,
1071
+ updatedAt: Date.now(),
1072
+ loaderData,
1073
+ loadPromise: undefined,
1074
+ }
1075
+ } catch (error) {
1076
+ if ((latestPromise = checkLatest())) return await latestPromise
1077
+ if (handleIfRedirect(error)) return
1078
+
1079
+ try {
1080
+ route.options.onError?.(error)
1081
+ } catch (onErrorError) {
1082
+ error = onErrorError
1083
+ if (handleIfRedirect(onErrorError)) return
1084
+ }
1085
+
1086
+ matches[index] = match = {
1087
+ ...match,
1088
+ error,
1089
+ status: 'error',
1090
+ isFetching: false,
1091
+ updatedAt: Date.now(),
1092
+ }
1093
+ }
1094
+
1095
+ if (!preload) {
1096
+ setState((s) => ({
1097
+ ...s,
1098
+ matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1099
+ }))
1100
+ }
1101
+ })(),
1102
+ )
1103
+ })
1104
+
1105
+ await Promise.all(matchPromises)
1106
+ return matches
1107
+ },
1108
+ )
1109
+
1110
+ const load = useStableCallback<LoadFn>(async () => {
1111
+ const promise = new Promise<void>(async (resolve, reject) => {
1112
+ const next = latestLocationRef.current
1113
+ const prevLocation = state.resolvedLocation
1114
+ const pathDidChange = prevLocation!.href !== next.href
1115
+ let latestPromise: Promise<void> | undefined | null
1116
+
1117
+ // Cancel any pending matches
1118
+ cancelMatches(state)
1119
+
1120
+ router.emit({
1121
+ type: 'onBeforeLoad',
1122
+ fromLocation: prevLocation,
1123
+ toLocation: next,
1124
+ pathChanged: pathDidChange,
1125
+ })
1126
+
1127
+ // Match the routes
1128
+ let matches: RouteMatch<any, any>[] = matchRoutes(
1129
+ next.pathname,
1130
+ next.search,
1131
+ {
1132
+ debug: true,
1133
+ },
1134
+ )
1135
+
1136
+ pendingMatchesRef.current = matches
1137
+
1138
+ const previousMatches = state.matches
1139
+
1140
+ // Ingest the new matches
1141
+ setState((s) => ({
1142
+ ...s,
1143
+ status: 'pending',
1144
+ location: next,
1145
+ matches,
1146
+ }))
1147
+
1148
+ try {
1149
+ try {
1150
+ // Load the matches
1151
+ await loadMatches({
1152
+ matches,
1153
+ checkLatest: () => checkLatest(promise),
1154
+ })
1155
+ } catch (err) {
1156
+ // swallow this error, since we'll display the
1157
+ // errors on the route components
1158
+ }
1159
+
1160
+ // Only apply the latest transition
1161
+ if ((latestPromise = checkLatest(promise))) {
1162
+ return latestPromise
1163
+ }
1164
+
1165
+ const exitingMatchIds = previousMatches.filter(
1166
+ (id) => !pendingMatchesRef.current.includes(id),
1167
+ )
1168
+ const enteringMatchIds = pendingMatchesRef.current.filter(
1169
+ (id) => !previousMatches.includes(id),
1170
+ )
1171
+ const stayingMatchIds = previousMatches.filter((id) =>
1172
+ pendingMatchesRef.current.includes(id),
1173
+ )
1174
+
1175
+ // setState((s) => ({
1176
+ // ...s,
1177
+ // status: 'idle',
1178
+ // resolvedLocation: s.location,
1179
+ // }))
1180
+
1181
+ //
1182
+ ;(
1183
+ [
1184
+ [exitingMatchIds, 'onLeave'],
1185
+ [enteringMatchIds, 'onEnter'],
1186
+ [stayingMatchIds, 'onTransition'],
1187
+ ] as const
1188
+ ).forEach(([matches, hook]) => {
1189
+ matches.forEach((match) => {
1190
+ looseRoutesById[match.routeId]!.options[hook]?.(match)
1191
+ })
1192
+ })
1193
+
1194
+ router.emit({
1195
+ type: 'onLoad',
1196
+ fromLocation: prevLocation,
1197
+ toLocation: next,
1198
+ pathChanged: pathDidChange,
1199
+ })
1200
+
1201
+ resolve()
1202
+ } catch (err) {
1203
+ // Only apply the latest transition
1204
+ if ((latestPromise = checkLatest(promise))) {
1205
+ return latestPromise
1206
+ }
1207
+
1208
+ reject(err)
1209
+ }
1210
+ })
1211
+
1212
+ latestLoadPromiseRef.current = promise
1213
+
1214
+ return latestLoadPromiseRef.current
1215
+ })
1216
+
1217
+ const preloadRoute = useStableCallback(
1218
+ async (navigateOpts: BuildNextOptions = state.location) => {
1219
+ let next = buildLocation(navigateOpts)
1220
+
1221
+ let matches = matchRoutes(next.pathname, next.search, {
1222
+ throwOnError: true,
1223
+ })
1224
+
1225
+ await loadMatches({
1226
+ matches,
1227
+ preload: true,
1228
+ checkLatest: () => undefined,
1229
+ })
1230
+
1231
+ return [last(matches)!, matches] as const
1232
+ },
1233
+ )
1234
+
1235
+ const buildLink = useStableCallback<BuildLinkFn<TRouteTree>>((dest) => {
1236
+ // If this link simply reloads the current route,
1237
+ // make sure it has a new key so it will trigger a data refresh
1238
+
1239
+ // If this `to` is a valid external URL, return
1240
+ // null for LinkUtils
1241
+
1242
+ const {
1243
+ to,
1244
+ preload: userPreload,
1245
+ preloadDelay: userPreloadDelay,
1246
+ activeOptions,
1247
+ disabled,
1248
+ target,
1249
+ replace,
1250
+ resetScroll,
1251
+ startTransition,
1252
+ } = dest
1253
+
1254
+ try {
1255
+ new URL(`${to}`)
1256
+ return {
1257
+ type: 'external',
1258
+ href: to as any,
1259
+ }
1260
+ } catch (e) {}
1261
+
1262
+ const nextOpts = dest
1263
+ const next = buildLocation(nextOpts as any)
1264
+
1265
+ const preload = userPreload ?? options.defaultPreload
1266
+ const preloadDelay = userPreloadDelay ?? options.defaultPreloadDelay ?? 0
1267
+
1268
+ // Compare path/hash for matches
1269
+ const currentPathSplit = latestLocationRef.current.pathname.split('/')
1270
+ const nextPathSplit = next.pathname.split('/')
1271
+ const pathIsFuzzyEqual = nextPathSplit.every(
1272
+ (d, i) => d === currentPathSplit[i],
1273
+ )
1274
+ // Combine the matches based on user options
1275
+ const pathTest = activeOptions?.exact
1276
+ ? latestLocationRef.current.pathname === next.pathname
1277
+ : pathIsFuzzyEqual
1278
+ const hashTest = activeOptions?.includeHash
1279
+ ? latestLocationRef.current.hash === next.hash
1280
+ : true
1281
+ const searchTest =
1282
+ activeOptions?.includeSearch ?? true
1283
+ ? deepEqual(latestLocationRef.current.search, next.search, true)
1284
+ : true
1285
+
1286
+ // The final "active" test
1287
+ const isActive = pathTest && hashTest && searchTest
1288
+
1289
+ // The click handler
1290
+ const handleClick = (e: MouseEvent) => {
1291
+ if (
1292
+ !disabled &&
1293
+ !isCtrlEvent(e) &&
1294
+ !e.defaultPrevented &&
1295
+ (!target || target === '_self') &&
1296
+ e.button === 0
1297
+ ) {
1298
+ e.preventDefault()
1299
+
1300
+ // All is well? Navigate!
1301
+ commitLocation({ ...next, replace, resetScroll, startTransition })
1302
+ }
1303
+ }
1304
+
1305
+ // The click handler
1306
+ const handleFocus = (e: MouseEvent) => {
1307
+ if (preload) {
1308
+ preloadRoute(nextOpts as any).catch((err) => {
1309
+ console.warn(err)
1310
+ console.warn(preloadWarning)
1311
+ })
1312
+ }
1313
+ }
1314
+
1315
+ const handleTouchStart = (e: TouchEvent) => {
1316
+ preloadRoute(nextOpts as any).catch((err) => {
1317
+ console.warn(err)
1318
+ console.warn(preloadWarning)
1319
+ })
1320
+ }
1321
+
1322
+ const handleEnter = (e: MouseEvent) => {
1323
+ const target = (e.target || {}) as LinkCurrentTargetElement
1324
+
1325
+ if (preload) {
1326
+ if (target.preloadTimeout) {
1327
+ return
1328
+ }
1329
+
1330
+ target.preloadTimeout = setTimeout(() => {
1331
+ target.preloadTimeout = null
1332
+ preloadRoute(nextOpts as any).catch((err) => {
1333
+ console.warn(err)
1334
+ console.warn(preloadWarning)
1335
+ })
1336
+ }, preloadDelay)
1337
+ }
1338
+ }
1339
+
1340
+ const handleLeave = (e: MouseEvent) => {
1341
+ const target = (e.target || {}) as LinkCurrentTargetElement
1342
+
1343
+ if (target.preloadTimeout) {
1344
+ clearTimeout(target.preloadTimeout)
1345
+ target.preloadTimeout = null
1346
+ }
1347
+ }
1348
+
1349
+ return {
1350
+ type: 'internal',
1351
+ next,
1352
+ handleFocus,
1353
+ handleClick,
1354
+ handleEnter,
1355
+ handleLeave,
1356
+ handleTouchStart,
1357
+ isActive,
1358
+ disabled,
1359
+ }
1360
+ })
1361
+
1362
+ React.useLayoutEffect(() => {
1363
+ const unsub = history.subscribe(() => {
1364
+ latestLocationRef.current = parseLocation(latestLocationRef.current)
1365
+
1366
+ if (state.location !== latestLocationRef.current) {
1367
+ startReactTransition(() => {
1368
+ try {
1369
+ load()
1370
+ } catch (err) {
1371
+ console.error(err)
1372
+ }
1373
+ })
1374
+ }
1375
+ })
1376
+
1377
+ const nextLocation = buildLocation({
1378
+ search: true,
1379
+ params: true,
1380
+ hash: true,
1381
+ state: true,
1382
+ })
1383
+
1384
+ if (state.location.href !== nextLocation.href) {
1385
+ commitLocation({ ...nextLocation, replace: true })
1386
+ }
1387
+
1388
+ return () => {
1389
+ unsub()
1390
+ }
1391
+ }, [history])
1392
+
1393
+ const matchRoute = useStableCallback<MatchRouteFn<TRouteTree>>(
1394
+ (location, opts) => {
1395
+ location = {
1396
+ ...location,
1397
+ to: location.to
1398
+ ? resolvePathWithBase((location.from || '') as string, location.to)
1399
+ : undefined,
1400
+ } as any
1401
+
1402
+ const next = buildLocation(location as any)
1403
+
1404
+ if (opts?.pending && state.status !== 'pending') {
1405
+ return false
1406
+ }
1407
+
1408
+ const baseLocation = opts?.pending
1409
+ ? latestLocationRef.current
1410
+ : state.resolvedLocation
1411
+
1412
+ // const baseLocation = state.resolvedLocation
1413
+
1414
+ if (!baseLocation) {
1415
+ return false
1416
+ }
1417
+
1418
+ const match = matchPathname(basepath, baseLocation.pathname, {
1419
+ ...opts,
1420
+ to: next.pathname,
1421
+ }) as any
1422
+
1423
+ if (!match) {
1424
+ return false
1425
+ }
1426
+
1427
+ if (match && (opts?.includeSearch ?? true)) {
1428
+ return deepEqual(baseLocation.search, next.search, true) ? match : false
1429
+ }
1430
+
1431
+ return match
1432
+ },
1433
+ )
1434
+
1435
+ const injectedHtmlRef = React.useRef<InjectedHtmlEntry[]>([])
1436
+
1437
+ const injectHtml = useStableCallback(
1438
+ async (html: string | (() => Promise<string> | string)) => {
1439
+ injectedHtmlRef.current.push(html)
1440
+ },
1441
+ )
1442
+
1443
+ const dehydrateData = useStableCallback(
1444
+ <T,>(key: any, getData: T | (() => Promise<T> | T)) => {
1445
+ if (typeof document === 'undefined') {
1446
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key)
1447
+
1448
+ injectHtml(async () => {
1449
+ const id = `__TSR_DEHYDRATED__${strKey}`
1450
+ const data =
1451
+ typeof getData === 'function' ? await (getData as any)() : getData
1452
+ return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
1453
+ strKey,
1454
+ )}"] = ${JSON.stringify(data)}
1455
+ ;(() => {
1456
+ var el = document.getElementById('${id}')
1457
+ el.parentElement.removeChild(el)
1458
+ })()
1459
+ </script>`
1460
+ })
1461
+
1462
+ return () => hydrateData<T>(key)
1463
+ }
1464
+
1465
+ return () => undefined
1466
+ },
1467
+ )
1468
+
1469
+ const hydrateData = useStableCallback(<T extends any = unknown>(key: any) => {
1470
+ if (typeof document !== 'undefined') {
1471
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key)
1472
+
1473
+ return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
1474
+ }
1475
+
1476
+ return undefined
1477
+ })
1478
+
1479
+ React.useLayoutEffect(() => {
1480
+ startReactTransition(() => {
1481
+ try {
1482
+ load()
1483
+ } catch (err) {
1484
+ console.error(err)
1485
+ }
1486
+ })
1487
+ }, [])
1488
+
1489
+ const routerContextValue: RouterContext<TRouteTree> = {
1490
+ routeTree: router.routeTree,
1491
+ navigate,
1492
+ buildLink,
1493
+ state,
1494
+ matchRoute,
1495
+ routesById,
1496
+ options,
1497
+ history,
1498
+ load,
1499
+ buildLocation,
1500
+ subscribe: router.subscribe,
1501
+ resetNextScrollRef,
1502
+ injectedHtmlRef,
1503
+ injectHtml,
1504
+ dehydrateData,
1505
+ hydrateData,
1506
+ }
1507
+
1508
+ return (
1509
+ <routerContext.Provider value={routerContextValue}>
1510
+ <Matches />
1511
+ </routerContext.Provider>
1512
+ )
1513
+ }
1514
+
1515
+ export function getRouteMatch<TRouteTree extends AnyRoute>(
1516
+ state: RouterState<TRouteTree>,
1517
+ id: string,
1518
+ ): undefined | RouteMatch<TRouteTree> {
1519
+ return [...state.pendingMatches, ...state.matches].find((d) => d.id === id)
1520
+ }
1521
+
1522
+ export function useRouterState<
1523
+ TSelected = RouterState<RegisteredRouter['routeTree']>,
1524
+ >(opts?: {
1525
+ select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
1526
+ }): TSelected {
1527
+ const { state } = useRouter()
1528
+ // return useStore(router.__store, opts?.select as any)
1529
+ return opts?.select ? opts.select(state) : (state as any)
1530
+ }
1531
+
1532
+ export type RouterProps<
1533
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1534
+ TDehydrated extends Record<string, any> = Record<string, any>,
1535
+ > = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & {
1536
+ router: Router<TRouteTree>
1537
+ context?: Partial<RouterOptions<TRouteTree, TDehydrated>['context']>
1538
+ }
1539
+
1540
+ export function useRouter<
1541
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1542
+ >(): RouterContext<TRouteTree> {
1543
+ const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
1544
+ const value = React.useContext(resolvedContext)
1545
+ warning(value, 'useRouter must be used inside a <RouterProvider> component!')
1546
+ return value as any
1547
+ }
1548
+ export interface RouteMatch<
1549
+ TRouteTree extends AnyRoute = AnyRoute,
1550
+ TRouteId extends RouteIds<TRouteTree> = ParseRoute<TRouteTree>['id'],
1551
+ > {
1552
+ id: string
1553
+ routeId: TRouteId
1554
+ pathname: string
1555
+ params: RouteById<TRouteTree, TRouteId>['types']['allParams']
1556
+ status: 'pending' | 'success' | 'error'
1557
+ isFetching: boolean
1558
+ invalid: boolean
1559
+ error: unknown
1560
+ paramsError: unknown
1561
+ searchError: unknown
1562
+ updatedAt: number
1563
+ loadPromise?: Promise<void>
1564
+ loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
1565
+ __resolveLoadPromise?: () => void
1566
+ context: RouteById<TRouteTree, TRouteId>['types']['allContext']
1567
+ routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema']
1568
+ search: FullSearchSchema<TRouteTree> &
1569
+ RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema']
1570
+ fetchedAt: number
1571
+ shouldReloadDeps: any
1572
+ abortController: AbortController
1573
+ }
1574
+
1575
+ export type AnyRouteMatch = RouteMatch<any>