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