@tanstack/react-router 0.0.1-beta.20 → 0.0.1-beta.200
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/LICENSE +21 -0
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +1 -19
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/build/cjs/awaited.js +45 -0
- package/build/cjs/awaited.js.map +1 -0
- package/build/cjs/index.js +66 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/react.js +642 -0
- package/build/cjs/react.js.map +1 -0
- package/build/cjs/scroll-restoration.js +56 -0
- package/build/cjs/scroll-restoration.js.map +1 -0
- package/build/esm/index.js +556 -2798
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +312 -37
- package/build/types/awaited.d.ts +8 -0
- package/build/types/index.d.ts +4 -105
- package/build/types/react.d.ts +200 -0
- package/build/types/scroll-restoration.d.ts +3 -0
- package/build/umd/index.development.js +2528 -2424
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +35 -4
- package/build/umd/index.production.js.map +1 -1
- package/package.json +11 -10
- package/src/awaited.tsx +40 -0
- package/src/index.tsx +5 -660
- package/src/react.tsx +1228 -0
- package/src/scroll-restoration.tsx +27 -0
- package/build/cjs/react-router/src/index.js +0 -443
- package/build/cjs/react-router/src/index.js.map +0 -1
- package/build/cjs/router-core/build/esm/index.js +0 -2523
- package/build/cjs/router-core/build/esm/index.js.map +0 -1
package/src/react.tsx
ADDED
|
@@ -0,0 +1,1228 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { NoInfer, useStore } from '@tanstack/react-store'
|
|
3
|
+
import invariant from 'tiny-invariant'
|
|
4
|
+
import warning from 'tiny-warning'
|
|
5
|
+
import {
|
|
6
|
+
functionalUpdate,
|
|
7
|
+
last,
|
|
8
|
+
pick,
|
|
9
|
+
MatchRouteOptions,
|
|
10
|
+
RegisteredRouter,
|
|
11
|
+
RouterOptions,
|
|
12
|
+
Router,
|
|
13
|
+
RouteMatch,
|
|
14
|
+
RouteByPath,
|
|
15
|
+
AnyRoute,
|
|
16
|
+
AnyRouteProps,
|
|
17
|
+
LinkOptions,
|
|
18
|
+
ToOptions,
|
|
19
|
+
ResolveRelativePath,
|
|
20
|
+
NavigateOptions,
|
|
21
|
+
ResolveFullPath,
|
|
22
|
+
ResolveId,
|
|
23
|
+
AnySearchSchema,
|
|
24
|
+
ParsePathParams,
|
|
25
|
+
RouteContext,
|
|
26
|
+
AnyContext,
|
|
27
|
+
UseLoaderResult,
|
|
28
|
+
ResolveFullSearchSchema,
|
|
29
|
+
Route,
|
|
30
|
+
RouteConstraints,
|
|
31
|
+
RoutePaths,
|
|
32
|
+
RoutesById,
|
|
33
|
+
RouteIds,
|
|
34
|
+
RouteById,
|
|
35
|
+
ParseRoute,
|
|
36
|
+
AllParams,
|
|
37
|
+
rootRouteId,
|
|
38
|
+
AnyPathParams,
|
|
39
|
+
Expand,
|
|
40
|
+
ResolveAllParams,
|
|
41
|
+
DeepMergeAll,
|
|
42
|
+
IsAny,
|
|
43
|
+
} from '@tanstack/router-core'
|
|
44
|
+
|
|
45
|
+
const useLayoutEffect =
|
|
46
|
+
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect
|
|
47
|
+
|
|
48
|
+
declare module '@tanstack/router-core' {
|
|
49
|
+
interface RouterOptions<
|
|
50
|
+
TRouteTree extends AnyRoute,
|
|
51
|
+
TDehydrated extends Record<string, any>,
|
|
52
|
+
> {
|
|
53
|
+
Wrap?: React.ComponentType<{
|
|
54
|
+
children: React.ReactNode
|
|
55
|
+
dehydratedState?: TDehydrated
|
|
56
|
+
}>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface RegisterRouteComponent<
|
|
60
|
+
TLoader = unknown,
|
|
61
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
62
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
63
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
64
|
+
> {
|
|
65
|
+
RouteComponent: RouteComponent<
|
|
66
|
+
RouteProps<TLoader, TFullSearchSchema, TAllParams, TAllContext>
|
|
67
|
+
>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface RegisterErrorRouteComponent<
|
|
71
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
72
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
73
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
74
|
+
> {
|
|
75
|
+
ErrorRouteComponent: RouteComponent<
|
|
76
|
+
ErrorRouteProps<TFullSearchSchema, TAllParams, TAllContext>
|
|
77
|
+
>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface RegisterPendingRouteComponent<
|
|
81
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
82
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
83
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
84
|
+
> {
|
|
85
|
+
PendingRouteComponent: RouteComponent<
|
|
86
|
+
PendingRouteProps<TFullSearchSchema, TAllParams, TAllContext>
|
|
87
|
+
>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface Route<
|
|
91
|
+
TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
|
|
92
|
+
TPath extends RouteConstraints['TPath'] = '/',
|
|
93
|
+
TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
|
|
94
|
+
TParentRoute,
|
|
95
|
+
TPath
|
|
96
|
+
>,
|
|
97
|
+
TCustomId extends RouteConstraints['TCustomId'] = string,
|
|
98
|
+
TId extends RouteConstraints['TId'] = ResolveId<
|
|
99
|
+
TParentRoute,
|
|
100
|
+
TCustomId,
|
|
101
|
+
TPath
|
|
102
|
+
>,
|
|
103
|
+
TLoaderContext extends RouteConstraints['TLoaderContext'] = AnyContext,
|
|
104
|
+
TLoader = unknown,
|
|
105
|
+
TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
|
|
106
|
+
TFullSearchSchema extends RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchema<
|
|
107
|
+
TParentRoute,
|
|
108
|
+
TSearchSchema
|
|
109
|
+
>,
|
|
110
|
+
TParams extends RouteConstraints['TParams'] = Expand<
|
|
111
|
+
Record<ParsePathParams<TPath>, string>
|
|
112
|
+
>,
|
|
113
|
+
TAllParams extends RouteConstraints['TAllParams'] = ResolveAllParams<
|
|
114
|
+
TParentRoute,
|
|
115
|
+
TParams
|
|
116
|
+
>,
|
|
117
|
+
TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
|
|
118
|
+
TAllContext extends RouteConstraints['TAllContext'] = Expand<
|
|
119
|
+
DeepMergeAll<
|
|
120
|
+
[
|
|
121
|
+
IsAny<TParentRoute['types']['context'], {}>,
|
|
122
|
+
TLoaderContext,
|
|
123
|
+
TRouteContext,
|
|
124
|
+
]
|
|
125
|
+
>
|
|
126
|
+
>,
|
|
127
|
+
TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
|
|
128
|
+
TChildren extends RouteConstraints['TChildren'] = unknown,
|
|
129
|
+
TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
|
|
130
|
+
> {
|
|
131
|
+
useMatch: <TSelected = TAllContext>(opts?: {
|
|
132
|
+
select?: (search: TAllContext) => TSelected
|
|
133
|
+
}) => TSelected
|
|
134
|
+
useLoader: <TSelected = TLoader>(opts?: {
|
|
135
|
+
select?: (search: TLoader) => TSelected
|
|
136
|
+
}) => UseLoaderResult<TSelected>
|
|
137
|
+
useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
138
|
+
select?: (search: TAllContext) => TSelected
|
|
139
|
+
}) => TSelected
|
|
140
|
+
useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
141
|
+
select?: (search: TFullSearchSchema) => TSelected
|
|
142
|
+
}) => TSelected
|
|
143
|
+
useParams: <TSelected = TAllParams>(opts?: {
|
|
144
|
+
select?: (search: TAllParams) => TSelected
|
|
145
|
+
}) => TSelected
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface RegisterRouteProps<
|
|
149
|
+
TLoader = unknown,
|
|
150
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
151
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
152
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
153
|
+
> {
|
|
154
|
+
RouteProps: RouteProps<TLoader, TFullSearchSchema, TAllParams, TAllContext>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface RegisterPendingRouteProps<
|
|
158
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
159
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
160
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
161
|
+
> {
|
|
162
|
+
PendingRouteProps: PendingRouteProps<
|
|
163
|
+
TFullSearchSchema,
|
|
164
|
+
TAllParams,
|
|
165
|
+
TAllContext
|
|
166
|
+
>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface RegisterErrorRouteProps<
|
|
170
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
171
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
172
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
173
|
+
> {
|
|
174
|
+
ErrorRouteProps: ErrorRouteProps
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type RouteProps<
|
|
179
|
+
TLoader = unknown,
|
|
180
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
181
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
182
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
183
|
+
> = {
|
|
184
|
+
useLoader: <TSelected = TLoader>(opts?: {
|
|
185
|
+
select?: (search: TLoader) => TSelected
|
|
186
|
+
}) => UseLoaderResult<TSelected>
|
|
187
|
+
useMatch: <TSelected = TAllContext>(opts?: {
|
|
188
|
+
select?: (search: TAllContext) => TSelected
|
|
189
|
+
}) => TSelected
|
|
190
|
+
useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
191
|
+
select?: (search: TAllContext) => TSelected
|
|
192
|
+
}) => TSelected
|
|
193
|
+
useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
194
|
+
select?: (search: TFullSearchSchema) => TSelected
|
|
195
|
+
}) => TSelected
|
|
196
|
+
useParams: <TSelected = TAllParams>(opts?: {
|
|
197
|
+
select?: (search: TAllParams) => TSelected
|
|
198
|
+
}) => TSelected
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type ErrorRouteProps<
|
|
202
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
203
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
204
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
205
|
+
> = {
|
|
206
|
+
error: unknown
|
|
207
|
+
info: { componentStack: string }
|
|
208
|
+
} & Omit<
|
|
209
|
+
RouteProps<unknown, TFullSearchSchema, TAllParams, TAllContext>,
|
|
210
|
+
'useLoader'
|
|
211
|
+
>
|
|
212
|
+
|
|
213
|
+
export type PendingRouteProps<
|
|
214
|
+
TFullSearchSchema extends Record<string, any> = AnySearchSchema,
|
|
215
|
+
TAllParams extends AnyPathParams = AnyPathParams,
|
|
216
|
+
TAllContext extends Record<string, any> = AnyContext,
|
|
217
|
+
> = Omit<
|
|
218
|
+
RouteProps<unknown, TFullSearchSchema, TAllParams, TAllContext>,
|
|
219
|
+
'useLoader'
|
|
220
|
+
>
|
|
221
|
+
|
|
222
|
+
Route.__onInit = (route) => {
|
|
223
|
+
Object.assign(route, {
|
|
224
|
+
useMatch: (opts = {}) => {
|
|
225
|
+
return useMatch({ ...opts, from: route.id }) as any
|
|
226
|
+
},
|
|
227
|
+
useLoader: (opts = {}) => {
|
|
228
|
+
return useLoader({ ...opts, from: route.id }) as any
|
|
229
|
+
},
|
|
230
|
+
useRouteContext: (opts: any = {}) => {
|
|
231
|
+
return useMatch({
|
|
232
|
+
...opts,
|
|
233
|
+
from: route.id,
|
|
234
|
+
select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
235
|
+
} as any)
|
|
236
|
+
},
|
|
237
|
+
useSearch: (opts = {}) => {
|
|
238
|
+
return useSearch({ ...opts, from: route.id } as any)
|
|
239
|
+
},
|
|
240
|
+
useParams: (opts = {}) => {
|
|
241
|
+
return useParams({ ...opts, from: route.id } as any)
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
//
|
|
247
|
+
|
|
248
|
+
type ReactNode = any
|
|
249
|
+
|
|
250
|
+
export type SyncRouteComponent<TProps> =
|
|
251
|
+
| ((props: TProps) => ReactNode)
|
|
252
|
+
| React.LazyExoticComponent<(props: TProps) => ReactNode>
|
|
253
|
+
|
|
254
|
+
export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
|
|
255
|
+
preload?: () => Promise<void>
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export type ErrorRouteComponent = AsyncRouteComponent<ErrorRouteComponentProps>
|
|
259
|
+
|
|
260
|
+
export type ErrorRouteComponentProps = {
|
|
261
|
+
error: Error
|
|
262
|
+
info: { componentStack: string }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export type AnyRouteComponent = RouteComponent<AnyRouteProps>
|
|
266
|
+
|
|
267
|
+
export type RouteComponent<TProps> = AsyncRouteComponent<TProps>
|
|
268
|
+
|
|
269
|
+
export function lazyRouteComponent<
|
|
270
|
+
T extends Record<string, any>,
|
|
271
|
+
TKey extends keyof T = 'default',
|
|
272
|
+
>(
|
|
273
|
+
importer: () => Promise<T>,
|
|
274
|
+
exportName?: TKey,
|
|
275
|
+
): T[TKey] extends (props: infer TProps) => any
|
|
276
|
+
? AsyncRouteComponent<TProps>
|
|
277
|
+
: never {
|
|
278
|
+
let loadPromise: Promise<any>
|
|
279
|
+
|
|
280
|
+
const load = () => {
|
|
281
|
+
if (!loadPromise) {
|
|
282
|
+
loadPromise = importer()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return loadPromise
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const lazyComp = React.lazy(async () => {
|
|
289
|
+
const moduleExports = await load()
|
|
290
|
+
const comp = moduleExports[exportName ?? 'default']
|
|
291
|
+
return {
|
|
292
|
+
default: comp,
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
;(lazyComp as any).preload = load
|
|
297
|
+
|
|
298
|
+
return lazyComp as any
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type LinkPropsOptions<
|
|
302
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
303
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
304
|
+
TTo extends string = '',
|
|
305
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
306
|
+
TMaskTo extends string = '',
|
|
307
|
+
> = LinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
|
|
308
|
+
// A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
309
|
+
activeProps?:
|
|
310
|
+
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
311
|
+
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
312
|
+
// A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
|
|
313
|
+
inactiveProps?:
|
|
314
|
+
| React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
315
|
+
| (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
|
|
316
|
+
// If set to `true`, the link's underlying navigate() call will be wrapped in a `React.startTransition` call. Defaults to `true`.
|
|
317
|
+
startTransition?: boolean
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export type MakeUseMatchRouteOptions<
|
|
321
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
322
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
323
|
+
TTo extends string = '',
|
|
324
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
325
|
+
TMaskTo extends string = '',
|
|
326
|
+
> = ToOptions<AnyRoute, TFrom, TTo, TMaskFrom, TMaskTo> & MatchRouteOptions
|
|
327
|
+
|
|
328
|
+
export type MakeMatchRouteOptions<
|
|
329
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
330
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
331
|
+
TTo extends string = '',
|
|
332
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
333
|
+
TMaskTo extends string = '',
|
|
334
|
+
> = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
335
|
+
MatchRouteOptions & {
|
|
336
|
+
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
|
|
337
|
+
children?:
|
|
338
|
+
| ((
|
|
339
|
+
params?: RouteByPath<
|
|
340
|
+
TRouteTree,
|
|
341
|
+
ResolveRelativePath<TFrom, NoInfer<TTo>>
|
|
342
|
+
>['types']['allParams'],
|
|
343
|
+
) => ReactNode)
|
|
344
|
+
| React.ReactNode
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export type MakeLinkPropsOptions<
|
|
348
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
349
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
350
|
+
TTo extends string = '',
|
|
351
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
352
|
+
TMaskTo extends string = '',
|
|
353
|
+
> = LinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
354
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
355
|
+
|
|
356
|
+
export type MakeLinkOptions<
|
|
357
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
358
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
359
|
+
TTo extends string = '',
|
|
360
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
361
|
+
TMaskTo extends string = '',
|
|
362
|
+
> = LinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
363
|
+
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
|
|
364
|
+
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
|
|
365
|
+
children?:
|
|
366
|
+
| React.ReactNode
|
|
367
|
+
| ((state: { isActive: boolean }) => React.ReactNode)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type PromptProps = {
|
|
371
|
+
message: string
|
|
372
|
+
condition?: boolean | any
|
|
373
|
+
children?: ReactNode
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
//
|
|
377
|
+
|
|
378
|
+
export function useLinkProps<
|
|
379
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
380
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
381
|
+
TTo extends string = '',
|
|
382
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
383
|
+
TMaskTo extends string = '',
|
|
384
|
+
>(
|
|
385
|
+
options: MakeLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
386
|
+
): React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
387
|
+
const router = useRouter()
|
|
388
|
+
|
|
389
|
+
const {
|
|
390
|
+
// custom props
|
|
391
|
+
type,
|
|
392
|
+
children,
|
|
393
|
+
target,
|
|
394
|
+
activeProps = () => ({ className: 'active' }),
|
|
395
|
+
inactiveProps = () => ({}),
|
|
396
|
+
activeOptions,
|
|
397
|
+
disabled,
|
|
398
|
+
// fromCurrent,
|
|
399
|
+
hash,
|
|
400
|
+
search,
|
|
401
|
+
params,
|
|
402
|
+
to = '.',
|
|
403
|
+
state,
|
|
404
|
+
mask,
|
|
405
|
+
preload,
|
|
406
|
+
preloadDelay,
|
|
407
|
+
replace,
|
|
408
|
+
// element props
|
|
409
|
+
style,
|
|
410
|
+
className,
|
|
411
|
+
onClick,
|
|
412
|
+
onFocus,
|
|
413
|
+
onMouseEnter,
|
|
414
|
+
onMouseLeave,
|
|
415
|
+
onTouchStart,
|
|
416
|
+
...rest
|
|
417
|
+
} = options
|
|
418
|
+
|
|
419
|
+
const linkInfo = router.buildLink(options as any)
|
|
420
|
+
|
|
421
|
+
if (linkInfo.type === 'external') {
|
|
422
|
+
const { href } = linkInfo
|
|
423
|
+
return { href }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const {
|
|
427
|
+
handleClick,
|
|
428
|
+
handleFocus,
|
|
429
|
+
handleEnter,
|
|
430
|
+
handleLeave,
|
|
431
|
+
handleTouchStart,
|
|
432
|
+
isActive,
|
|
433
|
+
next,
|
|
434
|
+
} = linkInfo
|
|
435
|
+
|
|
436
|
+
const handleReactClick = (e: Event) => {
|
|
437
|
+
if (options.startTransition ?? true) {
|
|
438
|
+
;(React.startTransition || ((d) => d))(() => {
|
|
439
|
+
handleClick(e)
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const composeHandlers =
|
|
445
|
+
(handlers: (undefined | ((e: any) => void))[]) =>
|
|
446
|
+
(e: React.SyntheticEvent) => {
|
|
447
|
+
if (e.persist) e.persist()
|
|
448
|
+
handlers.filter(Boolean).forEach((handler) => {
|
|
449
|
+
if (e.defaultPrevented) return
|
|
450
|
+
handler!(e)
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Get the active props
|
|
455
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
456
|
+
? functionalUpdate(activeProps as any, {}) ?? {}
|
|
457
|
+
: {}
|
|
458
|
+
|
|
459
|
+
// Get the inactive props
|
|
460
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
461
|
+
isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
...resolvedActiveProps,
|
|
465
|
+
...resolvedInactiveProps,
|
|
466
|
+
...rest,
|
|
467
|
+
href: disabled
|
|
468
|
+
? undefined
|
|
469
|
+
: next.maskedLocation
|
|
470
|
+
? next.maskedLocation.href
|
|
471
|
+
: next.href,
|
|
472
|
+
onClick: composeHandlers([onClick, handleReactClick]),
|
|
473
|
+
onFocus: composeHandlers([onFocus, handleFocus]),
|
|
474
|
+
onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
|
|
475
|
+
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
476
|
+
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
477
|
+
target,
|
|
478
|
+
style: {
|
|
479
|
+
...style,
|
|
480
|
+
...resolvedActiveProps.style,
|
|
481
|
+
...resolvedInactiveProps.style,
|
|
482
|
+
},
|
|
483
|
+
className:
|
|
484
|
+
[
|
|
485
|
+
className,
|
|
486
|
+
resolvedActiveProps.className,
|
|
487
|
+
resolvedInactiveProps.className,
|
|
488
|
+
]
|
|
489
|
+
.filter(Boolean)
|
|
490
|
+
.join(' ') || undefined,
|
|
491
|
+
...(disabled
|
|
492
|
+
? {
|
|
493
|
+
role: 'link',
|
|
494
|
+
'aria-disabled': true,
|
|
495
|
+
}
|
|
496
|
+
: undefined),
|
|
497
|
+
['data-status']: isActive ? 'active' : undefined,
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export interface LinkComponent<TProps extends Record<string, any> = {}> {
|
|
502
|
+
<
|
|
503
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
504
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
505
|
+
TTo extends string = '',
|
|
506
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
507
|
+
TMaskTo extends string = '',
|
|
508
|
+
>(
|
|
509
|
+
props: MakeLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
510
|
+
TProps &
|
|
511
|
+
React.RefAttributes<HTMLAnchorElement>,
|
|
512
|
+
): ReactNode
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export const Link: LinkComponent = React.forwardRef((props: any, ref) => {
|
|
516
|
+
const linkProps = useLinkProps(props)
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<a
|
|
520
|
+
{...{
|
|
521
|
+
ref: ref as any,
|
|
522
|
+
...linkProps,
|
|
523
|
+
children:
|
|
524
|
+
typeof props.children === 'function'
|
|
525
|
+
? props.children({
|
|
526
|
+
isActive: (linkProps as any)['data-status'] === 'active',
|
|
527
|
+
})
|
|
528
|
+
: props.children,
|
|
529
|
+
}}
|
|
530
|
+
/>
|
|
531
|
+
)
|
|
532
|
+
}) as any
|
|
533
|
+
|
|
534
|
+
export function Navigate<
|
|
535
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
536
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
537
|
+
TTo extends string = '',
|
|
538
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
539
|
+
TMaskTo extends string = '',
|
|
540
|
+
>(props: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>): null {
|
|
541
|
+
const router = useRouter()
|
|
542
|
+
|
|
543
|
+
useLayoutEffect(() => {
|
|
544
|
+
router.navigate(props as any)
|
|
545
|
+
}, [])
|
|
546
|
+
|
|
547
|
+
return null
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export const matchIdsContext = React.createContext<string[]>(null!)
|
|
551
|
+
export const routerContext = React.createContext<RegisteredRouter>(null!)
|
|
552
|
+
|
|
553
|
+
export type RouterProps<
|
|
554
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
555
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
556
|
+
> = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> & {
|
|
557
|
+
router: Router<TRouteTree>
|
|
558
|
+
context?: Partial<RouterOptions<TRouteTree, TDehydrated>['context']>
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function useRouterState<TSelected = RegisteredRouter['state']>(opts?: {
|
|
562
|
+
select: (state: RegisteredRouter['state']) => TSelected
|
|
563
|
+
}): TSelected {
|
|
564
|
+
const router = useRouter()
|
|
565
|
+
return useStore(router.__store, opts?.select as any)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function RouterProvider<
|
|
569
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
570
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
571
|
+
>({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
|
|
572
|
+
router.update(rest)
|
|
573
|
+
|
|
574
|
+
React.useEffect(() => {
|
|
575
|
+
let unsub
|
|
576
|
+
|
|
577
|
+
React.startTransition(() => {
|
|
578
|
+
unsub = router.mount()
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
return unsub
|
|
582
|
+
}, [router])
|
|
583
|
+
|
|
584
|
+
const Wrap = router.options.Wrap || React.Fragment
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
<Wrap>
|
|
588
|
+
<routerContext.Provider value={router as any}>
|
|
589
|
+
<Matches />
|
|
590
|
+
</routerContext.Provider>
|
|
591
|
+
</Wrap>
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function Matches() {
|
|
596
|
+
const router = useRouter()
|
|
597
|
+
|
|
598
|
+
const matchIds = useRouterState({
|
|
599
|
+
select: (state) => {
|
|
600
|
+
return state.renderedMatchIds
|
|
601
|
+
},
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
const locationKey = useRouterState({
|
|
605
|
+
select: (d) => d.resolvedLocation.state?.key,
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
const route = router.getRoute(rootRouteId)
|
|
609
|
+
|
|
610
|
+
const errorComponent = React.useCallback(
|
|
611
|
+
(props: any) => {
|
|
612
|
+
return React.createElement(ErrorComponent, {
|
|
613
|
+
...props,
|
|
614
|
+
useMatch: route.useMatch,
|
|
615
|
+
useRouteContext: route.useRouteContext,
|
|
616
|
+
useSearch: route.useSearch,
|
|
617
|
+
useParams: route.useParams,
|
|
618
|
+
})
|
|
619
|
+
},
|
|
620
|
+
[route],
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<matchIdsContext.Provider value={[undefined!, ...matchIds]}>
|
|
625
|
+
<CatchBoundary
|
|
626
|
+
resetKey={locationKey}
|
|
627
|
+
errorComponent={errorComponent}
|
|
628
|
+
onCatch={() => {
|
|
629
|
+
warning(
|
|
630
|
+
false,
|
|
631
|
+
`Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
|
|
632
|
+
)
|
|
633
|
+
}}
|
|
634
|
+
>
|
|
635
|
+
<Outlet />
|
|
636
|
+
</CatchBoundary>
|
|
637
|
+
</matchIdsContext.Provider>
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function useRouter(): RegisteredRouter {
|
|
642
|
+
const value = React.useContext(routerContext)
|
|
643
|
+
warning(value, 'useRouter must be used inside a <Router> component!')
|
|
644
|
+
return value
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export function useMatches<T = RouteMatch[]>(opts?: {
|
|
648
|
+
select?: (matches: RouteMatch[]) => T
|
|
649
|
+
}): T {
|
|
650
|
+
const matchIds = React.useContext(matchIdsContext)
|
|
651
|
+
|
|
652
|
+
return useRouterState({
|
|
653
|
+
select: (state) => {
|
|
654
|
+
const matches = state.renderedMatches.slice(
|
|
655
|
+
state.renderedMatches.findIndex((d) => d.id === matchIds[0]),
|
|
656
|
+
)
|
|
657
|
+
return opts?.select ? opts.select(matches) : (matches as T)
|
|
658
|
+
},
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
type StrictOrFrom<TFrom> =
|
|
663
|
+
| {
|
|
664
|
+
from: TFrom
|
|
665
|
+
strict?: true
|
|
666
|
+
}
|
|
667
|
+
| {
|
|
668
|
+
from?: never
|
|
669
|
+
strict: false
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function useMatch<
|
|
673
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
674
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
675
|
+
TStrict extends boolean = true,
|
|
676
|
+
TRouteMatchState = RouteMatch<TRouteTree, TFrom>,
|
|
677
|
+
TSelected = TRouteMatchState,
|
|
678
|
+
>(
|
|
679
|
+
opts: StrictOrFrom<TFrom> & {
|
|
680
|
+
select?: (match: TRouteMatchState) => TSelected
|
|
681
|
+
},
|
|
682
|
+
): TStrict extends true ? TRouteMatchState : TRouteMatchState | undefined {
|
|
683
|
+
const router = useRouter()
|
|
684
|
+
const nearestMatchId = React.useContext(matchIdsContext)[0]!
|
|
685
|
+
const nearestMatchRouteId = router.getRouteMatch(nearestMatchId)?.routeId
|
|
686
|
+
|
|
687
|
+
const matchRouteId = useRouterState({
|
|
688
|
+
select: (state) => {
|
|
689
|
+
const match = opts?.from
|
|
690
|
+
? state.renderedMatches.find((d) => d.routeId === opts?.from)
|
|
691
|
+
: state.renderedMatches.find((d) => d.id === nearestMatchId)
|
|
692
|
+
|
|
693
|
+
return match!.routeId
|
|
694
|
+
},
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
if (opts?.strict ?? true) {
|
|
698
|
+
invariant(
|
|
699
|
+
nearestMatchRouteId == matchRouteId,
|
|
700
|
+
`useMatch("${
|
|
701
|
+
matchRouteId as string
|
|
702
|
+
}") is being called in a component that is meant to render the '${nearestMatchRouteId}' route. Did you mean to 'useMatch("${
|
|
703
|
+
matchRouteId as string
|
|
704
|
+
}", { strict: false })' or 'useRoute("${
|
|
705
|
+
matchRouteId as string
|
|
706
|
+
}")' instead?`,
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const matchSelection = useRouterState({
|
|
711
|
+
select: (state) => {
|
|
712
|
+
const match = opts?.from
|
|
713
|
+
? state.renderedMatches.find((d) => d.routeId === opts?.from)
|
|
714
|
+
: state.renderedMatches.find((d) => d.id === nearestMatchId)
|
|
715
|
+
|
|
716
|
+
invariant(
|
|
717
|
+
match,
|
|
718
|
+
`Could not find ${
|
|
719
|
+
opts?.from
|
|
720
|
+
? `an active match from "${opts.from}"`
|
|
721
|
+
: 'a nearest match!'
|
|
722
|
+
}`,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
return opts?.select ? opts.select(match as any) : match
|
|
726
|
+
},
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
return matchSelection as any
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export type RouteFromIdOrRoute<
|
|
733
|
+
T,
|
|
734
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
735
|
+
> = T extends ParseRoute<TRouteTree>
|
|
736
|
+
? T
|
|
737
|
+
: T extends RouteIds<TRouteTree>
|
|
738
|
+
? RoutesById<TRouteTree>[T]
|
|
739
|
+
: T extends string
|
|
740
|
+
? RouteIds<TRouteTree>
|
|
741
|
+
: never
|
|
742
|
+
|
|
743
|
+
export function useLoader<
|
|
744
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
745
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
746
|
+
TStrict extends boolean = true,
|
|
747
|
+
TLoader = RouteById<TRouteTree, TFrom>['types']['loader'],
|
|
748
|
+
TSelected = TLoader,
|
|
749
|
+
>(
|
|
750
|
+
opts: StrictOrFrom<TFrom> & {
|
|
751
|
+
select?: (search: TLoader) => TSelected
|
|
752
|
+
},
|
|
753
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
754
|
+
return useMatch({
|
|
755
|
+
...(opts as any),
|
|
756
|
+
select: (match: RouteMatch) =>
|
|
757
|
+
opts?.select
|
|
758
|
+
? opts?.select(match.loaderData as TLoader)
|
|
759
|
+
: match.loaderData,
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function useRouterContext<
|
|
764
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
765
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
766
|
+
TStrict extends boolean = true,
|
|
767
|
+
TContext = RouteById<TRouteTree, TFrom>['types']['context'],
|
|
768
|
+
TSelected = TContext,
|
|
769
|
+
>(
|
|
770
|
+
opts: StrictOrFrom<TFrom> & {
|
|
771
|
+
select?: (search: TContext) => TSelected
|
|
772
|
+
},
|
|
773
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
774
|
+
return useMatch({
|
|
775
|
+
...(opts as any),
|
|
776
|
+
select: (match: RouteMatch) =>
|
|
777
|
+
opts?.select ? opts.select(match.context as TContext) : match.context,
|
|
778
|
+
})
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export function useRouteContext<
|
|
782
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
783
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
784
|
+
TStrict extends boolean = true,
|
|
785
|
+
TRouteContext = RouteById<TRouteTree, TFrom>['types']['context'],
|
|
786
|
+
TSelected = TRouteContext,
|
|
787
|
+
>(
|
|
788
|
+
opts: StrictOrFrom<TFrom> & {
|
|
789
|
+
select?: (search: TRouteContext) => TSelected
|
|
790
|
+
},
|
|
791
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
792
|
+
return useMatch({
|
|
793
|
+
...(opts as any),
|
|
794
|
+
select: (match: RouteMatch) =>
|
|
795
|
+
opts?.select
|
|
796
|
+
? opts.select(match.context as TRouteContext)
|
|
797
|
+
: match.context,
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
export function useSearch<
|
|
802
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
803
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
804
|
+
TStrict extends boolean = true,
|
|
805
|
+
TSearch = RouteById<TRouteTree, TFrom>['types']['fullSearchSchema'],
|
|
806
|
+
TSelected = TSearch,
|
|
807
|
+
>(
|
|
808
|
+
opts: StrictOrFrom<TFrom> & {
|
|
809
|
+
select?: (search: TSearch) => TSelected
|
|
810
|
+
},
|
|
811
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
812
|
+
return useMatch({
|
|
813
|
+
...(opts as any),
|
|
814
|
+
select: (match: RouteMatch) => {
|
|
815
|
+
return opts?.select ? opts.select(match.search as TSearch) : match.search
|
|
816
|
+
},
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export function useParams<
|
|
821
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
822
|
+
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
823
|
+
TDefaultSelected = AllParams<TRouteTree> &
|
|
824
|
+
RouteById<TRouteTree, TFrom>['types']['allParams'],
|
|
825
|
+
TSelected = TDefaultSelected,
|
|
826
|
+
>(
|
|
827
|
+
opts: StrictOrFrom<TFrom> & {
|
|
828
|
+
select?: (search: TDefaultSelected) => TSelected
|
|
829
|
+
},
|
|
830
|
+
): TSelected {
|
|
831
|
+
return useRouterState({
|
|
832
|
+
select: (state: any) => {
|
|
833
|
+
const params = (last(state.renderedMatches) as any)?.params
|
|
834
|
+
return opts?.select ? opts.select(params) : params
|
|
835
|
+
},
|
|
836
|
+
})
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export function useNavigate<
|
|
840
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
841
|
+
TDefaultFrom extends RoutePaths<TRouteTree> = '/',
|
|
842
|
+
>(defaultOpts?: { from?: TDefaultFrom }) {
|
|
843
|
+
const router = useRouter()
|
|
844
|
+
return React.useCallback(
|
|
845
|
+
<
|
|
846
|
+
TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,
|
|
847
|
+
TTo extends string = '',
|
|
848
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
849
|
+
TMaskTo extends string = '',
|
|
850
|
+
>(
|
|
851
|
+
opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
852
|
+
) => {
|
|
853
|
+
return router.navigate({ ...defaultOpts, ...(opts as any) })
|
|
854
|
+
},
|
|
855
|
+
[],
|
|
856
|
+
)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export function useMatchRoute<
|
|
860
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
861
|
+
>() {
|
|
862
|
+
const router = useRouter()
|
|
863
|
+
|
|
864
|
+
return React.useCallback(
|
|
865
|
+
<
|
|
866
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
867
|
+
TTo extends string = '',
|
|
868
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
869
|
+
TMaskTo extends string = '',
|
|
870
|
+
TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
|
|
871
|
+
>(
|
|
872
|
+
opts: MakeUseMatchRouteOptions<
|
|
873
|
+
TRouteTree,
|
|
874
|
+
TFrom,
|
|
875
|
+
TTo,
|
|
876
|
+
TMaskFrom,
|
|
877
|
+
TMaskTo
|
|
878
|
+
>,
|
|
879
|
+
): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
|
|
880
|
+
const { pending, caseSensitive, ...rest } = opts
|
|
881
|
+
|
|
882
|
+
return router.matchRoute(rest as any, {
|
|
883
|
+
pending,
|
|
884
|
+
caseSensitive,
|
|
885
|
+
})
|
|
886
|
+
},
|
|
887
|
+
[],
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export function MatchRoute<
|
|
892
|
+
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
893
|
+
TFrom extends RoutePaths<TRouteTree> = '/',
|
|
894
|
+
TTo extends string = '',
|
|
895
|
+
TMaskFrom extends RoutePaths<TRouteTree> = '/',
|
|
896
|
+
TMaskTo extends string = '',
|
|
897
|
+
>(
|
|
898
|
+
props: MakeMatchRouteOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
899
|
+
): any {
|
|
900
|
+
const matchRoute = useMatchRoute()
|
|
901
|
+
const params = matchRoute(props as any)
|
|
902
|
+
|
|
903
|
+
if (typeof props.children === 'function') {
|
|
904
|
+
return (props.children as any)(params)
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return !!params ? props.children : null
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
export function Outlet() {
|
|
911
|
+
const matchIds = React.useContext(matchIdsContext).slice(1)
|
|
912
|
+
|
|
913
|
+
if (!matchIds[0]) {
|
|
914
|
+
return null
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return <Match matchIds={matchIds} />
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const defaultPending = () => null
|
|
921
|
+
|
|
922
|
+
function Match({ matchIds }: { matchIds: string[] }) {
|
|
923
|
+
const router = useRouter()
|
|
924
|
+
const matchId = matchIds[0]!
|
|
925
|
+
const routeId = router.getRouteMatch(matchId)!.routeId
|
|
926
|
+
const route = router.getRoute(routeId)
|
|
927
|
+
const locationKey = useRouterState({
|
|
928
|
+
select: (s) => s.resolvedLocation.state?.key,
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
const PendingComponent = (route.options.pendingComponent ??
|
|
932
|
+
router.options.defaultPendingComponent ??
|
|
933
|
+
defaultPending) as any
|
|
934
|
+
|
|
935
|
+
const routeErrorComponent =
|
|
936
|
+
route.options.errorComponent ??
|
|
937
|
+
router.options.defaultErrorComponent ??
|
|
938
|
+
ErrorComponent
|
|
939
|
+
|
|
940
|
+
const ResolvedSuspenseBoundary =
|
|
941
|
+
route.options.wrapInSuspense ?? !route.isRoot
|
|
942
|
+
? React.Suspense
|
|
943
|
+
: SafeFragment
|
|
944
|
+
|
|
945
|
+
const ResolvedCatchBoundary = !!routeErrorComponent
|
|
946
|
+
? CatchBoundary
|
|
947
|
+
: SafeFragment
|
|
948
|
+
|
|
949
|
+
const errorComponent = React.useCallback(
|
|
950
|
+
(props: any) => {
|
|
951
|
+
return React.createElement(routeErrorComponent, {
|
|
952
|
+
...props,
|
|
953
|
+
useMatch: route.useMatch,
|
|
954
|
+
useRouteContext: route.useRouteContext,
|
|
955
|
+
useSearch: route.useSearch,
|
|
956
|
+
useParams: route.useParams,
|
|
957
|
+
})
|
|
958
|
+
},
|
|
959
|
+
[route],
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
return (
|
|
963
|
+
<matchIdsContext.Provider value={matchIds}>
|
|
964
|
+
<ResolvedSuspenseBoundary
|
|
965
|
+
fallback={React.createElement(PendingComponent, {
|
|
966
|
+
useMatch: route.useMatch,
|
|
967
|
+
useRouteContext: route.useRouteContext,
|
|
968
|
+
useSearch: route.useSearch,
|
|
969
|
+
useParams: route.useParams,
|
|
970
|
+
})}
|
|
971
|
+
>
|
|
972
|
+
<ResolvedCatchBoundary
|
|
973
|
+
resetKey={locationKey}
|
|
974
|
+
errorComponent={errorComponent}
|
|
975
|
+
onCatch={() => {
|
|
976
|
+
warning(false, `Error in route match: ${matchId}`)
|
|
977
|
+
}}
|
|
978
|
+
>
|
|
979
|
+
<MatchInner matchId={matchId} PendingComponent={PendingComponent} />
|
|
980
|
+
</ResolvedCatchBoundary>
|
|
981
|
+
</ResolvedSuspenseBoundary>
|
|
982
|
+
</matchIdsContext.Provider>
|
|
983
|
+
)
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function MatchInner({
|
|
987
|
+
matchId,
|
|
988
|
+
PendingComponent,
|
|
989
|
+
}: {
|
|
990
|
+
matchId: string
|
|
991
|
+
PendingComponent: any
|
|
992
|
+
}): any {
|
|
993
|
+
const router = useRouter()
|
|
994
|
+
|
|
995
|
+
const match = useRouterState({
|
|
996
|
+
select: (d) => {
|
|
997
|
+
const match = d.matchesById[matchId]
|
|
998
|
+
return pick(match!, ['status', 'loadPromise', 'routeId', 'error'])
|
|
999
|
+
},
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
const route = router.getRoute(match.routeId)
|
|
1003
|
+
|
|
1004
|
+
if (match.status === 'error') {
|
|
1005
|
+
throw match.error
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (match.status === 'pending') {
|
|
1009
|
+
return React.createElement(PendingComponent, {
|
|
1010
|
+
useLoader: route.useLoader,
|
|
1011
|
+
useMatch: route.useMatch,
|
|
1012
|
+
useRouteContext: route.useRouteContext,
|
|
1013
|
+
useSearch: route.useSearch,
|
|
1014
|
+
useParams: route.useParams,
|
|
1015
|
+
})
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (match.status === 'success') {
|
|
1019
|
+
let comp = route.options.component ?? router.options.defaultComponent
|
|
1020
|
+
|
|
1021
|
+
if (comp) {
|
|
1022
|
+
return React.createElement(comp, {
|
|
1023
|
+
useLoader: route.useLoader,
|
|
1024
|
+
useMatch: route.useMatch,
|
|
1025
|
+
useRouteContext: route.useRouteContext as any,
|
|
1026
|
+
useSearch: route.useSearch,
|
|
1027
|
+
useParams: route.useParams as any,
|
|
1028
|
+
} as any)
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
return <Outlet />
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
invariant(
|
|
1035
|
+
false,
|
|
1036
|
+
'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
|
|
1037
|
+
)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function SafeFragment(props: any) {
|
|
1041
|
+
return <>{props.children}</>
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
export function useInjectHtml() {
|
|
1045
|
+
const router = useRouter()
|
|
1046
|
+
|
|
1047
|
+
return React.useCallback(
|
|
1048
|
+
(html: string | (() => Promise<string> | string)) => {
|
|
1049
|
+
router.injectHtml(html)
|
|
1050
|
+
},
|
|
1051
|
+
[],
|
|
1052
|
+
)
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
export function useDehydrate() {
|
|
1056
|
+
const router = useRouter()
|
|
1057
|
+
|
|
1058
|
+
return React.useCallback(function dehydrate<T>(
|
|
1059
|
+
key: any,
|
|
1060
|
+
data: T | (() => Promise<T> | T),
|
|
1061
|
+
) {
|
|
1062
|
+
return router.dehydrateData(key, data)
|
|
1063
|
+
},
|
|
1064
|
+
[])
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
export function useHydrate() {
|
|
1068
|
+
const router = useRouter()
|
|
1069
|
+
|
|
1070
|
+
return function hydrate<T = unknown>(key: any) {
|
|
1071
|
+
return router.hydrateData(key) as T
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// This is the messiest thing ever... I'm either seriously tired (likely) or
|
|
1076
|
+
// there has to be a better way to reset error boundaries when the
|
|
1077
|
+
// router's location key changes.
|
|
1078
|
+
|
|
1079
|
+
export function CatchBoundary(props: {
|
|
1080
|
+
resetKey: string
|
|
1081
|
+
children: any
|
|
1082
|
+
errorComponent?: any
|
|
1083
|
+
onCatch: (error: any) => void
|
|
1084
|
+
}) {
|
|
1085
|
+
const errorComponent = props.errorComponent ?? ErrorComponent
|
|
1086
|
+
|
|
1087
|
+
return (
|
|
1088
|
+
<CatchBoundaryImpl
|
|
1089
|
+
resetKey={props.resetKey}
|
|
1090
|
+
onCatch={props.onCatch}
|
|
1091
|
+
children={({ error }) => {
|
|
1092
|
+
if (error) {
|
|
1093
|
+
return React.createElement(errorComponent, {
|
|
1094
|
+
error,
|
|
1095
|
+
})
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return props.children
|
|
1099
|
+
}}
|
|
1100
|
+
/>
|
|
1101
|
+
)
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export class CatchBoundaryImpl extends React.Component<{
|
|
1105
|
+
resetKey: string
|
|
1106
|
+
children: (props: { error: any; reset: () => void }) => any
|
|
1107
|
+
onCatch?: (error: any) => void
|
|
1108
|
+
}> {
|
|
1109
|
+
state = { error: null } as any
|
|
1110
|
+
static getDerivedStateFromError(error: any) {
|
|
1111
|
+
return { error }
|
|
1112
|
+
}
|
|
1113
|
+
componentDidUpdate(
|
|
1114
|
+
prevProps: Readonly<{
|
|
1115
|
+
resetKey: string
|
|
1116
|
+
children: (props: { error: any; reset: () => void }) => any
|
|
1117
|
+
onCatch?: ((error: any, info: any) => void) | undefined
|
|
1118
|
+
}>,
|
|
1119
|
+
prevState: any,
|
|
1120
|
+
): void {
|
|
1121
|
+
if (prevState.error && prevProps.resetKey !== this.props.resetKey) {
|
|
1122
|
+
this.setState({ error: null })
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
componentDidCatch(error: any) {
|
|
1126
|
+
this.props.onCatch?.(error)
|
|
1127
|
+
}
|
|
1128
|
+
render() {
|
|
1129
|
+
return this.props.children(this.state)
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export function ErrorComponent({ error }: { error: any }) {
|
|
1134
|
+
const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
|
|
1135
|
+
|
|
1136
|
+
return (
|
|
1137
|
+
<div style={{ padding: '.5rem', maxWidth: '100%' }}>
|
|
1138
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
|
|
1139
|
+
<strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
|
|
1140
|
+
<button
|
|
1141
|
+
style={{
|
|
1142
|
+
appearance: 'none',
|
|
1143
|
+
fontSize: '.6em',
|
|
1144
|
+
border: '1px solid currentColor',
|
|
1145
|
+
padding: '.1rem .2rem',
|
|
1146
|
+
fontWeight: 'bold',
|
|
1147
|
+
borderRadius: '.25rem',
|
|
1148
|
+
}}
|
|
1149
|
+
onClick={() => setShow((d) => !d)}
|
|
1150
|
+
>
|
|
1151
|
+
{show ? 'Hide Error' : 'Show Error'}
|
|
1152
|
+
</button>
|
|
1153
|
+
</div>
|
|
1154
|
+
<div style={{ height: '.25rem' }} />
|
|
1155
|
+
{show ? (
|
|
1156
|
+
<div>
|
|
1157
|
+
<pre
|
|
1158
|
+
style={{
|
|
1159
|
+
fontSize: '.7em',
|
|
1160
|
+
border: '1px solid red',
|
|
1161
|
+
borderRadius: '.25rem',
|
|
1162
|
+
padding: '.3rem',
|
|
1163
|
+
color: 'red',
|
|
1164
|
+
overflow: 'auto',
|
|
1165
|
+
}}
|
|
1166
|
+
>
|
|
1167
|
+
{error.message ? <code>{error.message}</code> : null}
|
|
1168
|
+
</pre>
|
|
1169
|
+
</div>
|
|
1170
|
+
) : null}
|
|
1171
|
+
</div>
|
|
1172
|
+
)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export function useBlocker(
|
|
1176
|
+
message: string,
|
|
1177
|
+
condition: boolean | any = true,
|
|
1178
|
+
): void {
|
|
1179
|
+
const router = useRouter()
|
|
1180
|
+
|
|
1181
|
+
React.useEffect(() => {
|
|
1182
|
+
if (!condition) return
|
|
1183
|
+
|
|
1184
|
+
let unblock = router.history.block((retry, cancel) => {
|
|
1185
|
+
if (window.confirm(message)) {
|
|
1186
|
+
unblock()
|
|
1187
|
+
retry()
|
|
1188
|
+
}
|
|
1189
|
+
})
|
|
1190
|
+
|
|
1191
|
+
return unblock
|
|
1192
|
+
})
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export function Block({ message, condition, children }: PromptProps) {
|
|
1196
|
+
useBlocker(message, condition)
|
|
1197
|
+
return (children ?? null) as ReactNode
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
export function shallow<T>(objA: T, objB: T) {
|
|
1201
|
+
if (Object.is(objA, objB)) {
|
|
1202
|
+
return true
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (
|
|
1206
|
+
typeof objA !== 'object' ||
|
|
1207
|
+
objA === null ||
|
|
1208
|
+
typeof objB !== 'object' ||
|
|
1209
|
+
objB === null
|
|
1210
|
+
) {
|
|
1211
|
+
return false
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const keysA = Object.keys(objA)
|
|
1215
|
+
if (keysA.length !== Object.keys(objB).length) {
|
|
1216
|
+
return false
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
1220
|
+
if (
|
|
1221
|
+
!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
|
|
1222
|
+
!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
|
|
1223
|
+
) {
|
|
1224
|
+
return false
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return true
|
|
1228
|
+
}
|