@tanstack/react-router 0.0.1-beta.16 → 0.0.1-beta.161
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 +648 -0
- package/build/cjs/index.js.map +1 -0
- package/build/esm/index.js +509 -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 +2102 -2380
- 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 +948 -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,853 @@ 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
|
-
match?.routeId as string
|
|
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
|
+
return state.pendingMatchIds
|
|
408
494
|
}
|
|
409
495
|
|
|
410
|
-
|
|
496
|
+
return state.matchIds
|
|
497
|
+
},
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<matchIdsContext.Provider value={[undefined!, ...matchIds]}>
|
|
502
|
+
<CatchBoundary
|
|
503
|
+
errorComponent={ErrorComponent}
|
|
504
|
+
onCatch={() => {
|
|
505
|
+
warning(
|
|
506
|
+
false,
|
|
507
|
+
`Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`,
|
|
508
|
+
)
|
|
509
|
+
}}
|
|
510
|
+
>
|
|
511
|
+
<Outlet />
|
|
512
|
+
</CatchBoundary>
|
|
513
|
+
</matchIdsContext.Provider>
|
|
514
|
+
)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function useRouter(): RegisteredRouter {
|
|
518
|
+
const value = React.useContext(routerContext)
|
|
519
|
+
warning(value, 'useRouter must be used inside a <Router> component!')
|
|
520
|
+
return value
|
|
521
|
+
}
|
|
411
522
|
|
|
412
|
-
|
|
523
|
+
export function useMatches<T = RouteMatch[]>(opts?: {
|
|
524
|
+
select?: (matches: RouteMatch[]) => T
|
|
525
|
+
}): T {
|
|
526
|
+
const matchIds = React.useContext(matchIdsContext)
|
|
527
|
+
|
|
528
|
+
return useRouterState({
|
|
529
|
+
select: (state) => {
|
|
530
|
+
const matches = state.matches.slice(
|
|
531
|
+
state.matches.findIndex((d) => d.id === matchIds[0]),
|
|
532
|
+
)
|
|
533
|
+
return (opts?.select?.(matches) ?? matches) as T
|
|
413
534
|
},
|
|
414
|
-
|
|
415
|
-
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function useMatch<
|
|
539
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']>,
|
|
540
|
+
TStrict extends boolean = true,
|
|
541
|
+
TRouteMatchState = RouteMatch<
|
|
542
|
+
RegisteredRouter['routeTree'],
|
|
543
|
+
RouteById<RegisteredRouter['routeTree'], TFrom>
|
|
544
|
+
>,
|
|
545
|
+
TSelected = TRouteMatchState,
|
|
546
|
+
>(opts?: {
|
|
547
|
+
from: TFrom
|
|
548
|
+
strict?: TStrict
|
|
549
|
+
select?: (match: TRouteMatchState) => TSelected
|
|
550
|
+
}): TStrict extends true ? TRouteMatchState : TRouteMatchState | undefined {
|
|
551
|
+
const router = useRouter()
|
|
552
|
+
const nearestMatchId = React.useContext(matchIdsContext)[0]!
|
|
553
|
+
const nearestMatchRouteId = router.getRouteMatch(nearestMatchId)?.routeId
|
|
554
|
+
|
|
555
|
+
const matchRouteId = useRouterState({
|
|
556
|
+
select: (state) => {
|
|
557
|
+
const matches = state.matches
|
|
558
|
+
const match = opts?.from
|
|
559
|
+
? matches.find((d) => d.routeId === opts?.from)
|
|
560
|
+
: matches.find((d) => d.id === nearestMatchId)
|
|
416
561
|
|
|
417
|
-
|
|
562
|
+
return match!.routeId
|
|
418
563
|
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
if (opts?.strict ?? true) {
|
|
567
|
+
invariant(
|
|
568
|
+
nearestMatchRouteId == matchRouteId,
|
|
569
|
+
`useMatch("${
|
|
570
|
+
matchRouteId as string
|
|
571
|
+
}") is being called in a component that is meant to render the '${nearestMatchRouteId}' route. Did you mean to 'useMatch("${
|
|
572
|
+
matchRouteId as string
|
|
573
|
+
}", { strict: false })' or 'useRoute("${
|
|
574
|
+
matchRouteId as string
|
|
575
|
+
}")' instead?`,
|
|
576
|
+
)
|
|
577
|
+
}
|
|
424
578
|
|
|
425
|
-
|
|
579
|
+
const match = useRouterState({
|
|
580
|
+
select: (state) => {
|
|
581
|
+
const matches = state.matches
|
|
582
|
+
const match = opts?.from
|
|
583
|
+
? matches.find((d) => d.routeId === opts?.from)
|
|
584
|
+
: matches.find((d) => d.id === nearestMatchId)
|
|
585
|
+
|
|
586
|
+
invariant(
|
|
587
|
+
match,
|
|
588
|
+
`Could not find ${
|
|
589
|
+
opts?.from
|
|
590
|
+
? `an active match from "${opts.from}"`
|
|
591
|
+
: 'a nearest match!'
|
|
592
|
+
}`,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
return (opts?.select?.(match as any) ?? match) as TSelected
|
|
426
596
|
},
|
|
427
597
|
})
|
|
428
598
|
|
|
429
|
-
return
|
|
599
|
+
return match as any
|
|
430
600
|
}
|
|
431
601
|
|
|
432
|
-
export type
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
602
|
+
export type RouteFromIdOrRoute<T> = T extends ParseRoute<
|
|
603
|
+
RegisteredRouter['routeTree']
|
|
604
|
+
>
|
|
605
|
+
? T
|
|
606
|
+
: T extends RouteIds<RegisteredRouter['routeTree']>
|
|
607
|
+
? RoutesById<RegisteredRouter['routeTree']>[T]
|
|
608
|
+
: T extends string
|
|
609
|
+
? RouteIds<RegisteredRouter['routeTree']>
|
|
610
|
+
: never
|
|
611
|
+
|
|
612
|
+
export function useLoader<
|
|
613
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']>,
|
|
614
|
+
TStrict extends boolean = true,
|
|
615
|
+
TLoader = RouteById<
|
|
616
|
+
RegisteredRouter['routeTree'],
|
|
617
|
+
TFrom
|
|
618
|
+
>['__types']['loader'],
|
|
619
|
+
TSelected = TLoader,
|
|
620
|
+
>(opts?: {
|
|
621
|
+
from: TFrom
|
|
622
|
+
strict?: TStrict
|
|
623
|
+
select?: (search: TLoader) => TSelected
|
|
624
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
625
|
+
return useMatch({
|
|
626
|
+
...(opts as any),
|
|
627
|
+
select: (match: RouteMatch) =>
|
|
628
|
+
(opts?.select?.(match.loaderData as TLoader) ??
|
|
629
|
+
match.loaderData) as TSelected,
|
|
630
|
+
})
|
|
439
631
|
}
|
|
440
632
|
|
|
441
|
-
export function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
633
|
+
export function useRouterContext<
|
|
634
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']>,
|
|
635
|
+
TStrict extends boolean = true,
|
|
636
|
+
TContext = RouteById<
|
|
637
|
+
RegisteredRouter['routeTree'],
|
|
638
|
+
TFrom
|
|
639
|
+
>['__types']['context'],
|
|
640
|
+
TSelected = TContext,
|
|
641
|
+
>(opts?: {
|
|
642
|
+
from: TFrom
|
|
643
|
+
strict?: TStrict
|
|
644
|
+
select?: (search: TContext) => TSelected
|
|
645
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
646
|
+
return useMatch({
|
|
647
|
+
...(opts as any),
|
|
648
|
+
select: (match: RouteMatch) =>
|
|
649
|
+
(opts?.select?.(match.context as TContext) ?? match.context) as TSelected,
|
|
650
|
+
})
|
|
651
|
+
}
|
|
446
652
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
653
|
+
export function useRouteContext<
|
|
654
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']>,
|
|
655
|
+
TStrict extends boolean = true,
|
|
656
|
+
TRouteContext = RouteById<
|
|
657
|
+
RegisteredRouter['routeTree'],
|
|
658
|
+
TFrom
|
|
659
|
+
>['__types']['routeContext'],
|
|
660
|
+
TSelected = TRouteContext,
|
|
661
|
+
>(opts?: {
|
|
662
|
+
from: TFrom
|
|
663
|
+
strict?: TStrict
|
|
664
|
+
select?: (search: TRouteContext) => TSelected
|
|
665
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
666
|
+
return useMatch({
|
|
667
|
+
...(opts as any),
|
|
668
|
+
select: (match: RouteMatch) =>
|
|
669
|
+
(opts?.select?.(match.routeContext as TRouteContext) ??
|
|
670
|
+
match.routeContext) as TSelected,
|
|
671
|
+
})
|
|
672
|
+
}
|
|
451
673
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
674
|
+
export function useSearch<
|
|
675
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']>,
|
|
676
|
+
TStrict extends boolean = true,
|
|
677
|
+
TSearch = RouteById<
|
|
678
|
+
RegisteredRouter['routeTree'],
|
|
679
|
+
TFrom
|
|
680
|
+
>['__types']['fullSearchSchema'],
|
|
681
|
+
TSelected = TSearch,
|
|
682
|
+
>(opts?: {
|
|
683
|
+
from: TFrom
|
|
684
|
+
strict?: TStrict
|
|
685
|
+
select?: (search: TSearch) => TSelected
|
|
686
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
687
|
+
return useMatch({
|
|
688
|
+
...(opts as any),
|
|
689
|
+
select: (match: RouteMatch) => {
|
|
690
|
+
return (opts?.select?.(match.search as TSearch) ??
|
|
691
|
+
match.search) as TSelected
|
|
692
|
+
},
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export function useParams<
|
|
697
|
+
TFrom extends RouteIds<RegisteredRouter['routeTree']> = '/',
|
|
698
|
+
TDefaultSelected = AllParams<RegisteredRouter['routeTree']> &
|
|
699
|
+
RouteById<RegisteredRouter['routeTree'], TFrom>['__types']['allParams'],
|
|
700
|
+
TSelected = TDefaultSelected,
|
|
701
|
+
>(opts?: {
|
|
702
|
+
from: TFrom
|
|
703
|
+
select?: (search: TDefaultSelected) => TSelected
|
|
704
|
+
}): TSelected {
|
|
705
|
+
return useRouterState({
|
|
706
|
+
select: (state: any) => {
|
|
707
|
+
const params = (last(state.matches) as any)?.params
|
|
708
|
+
return (opts?.select?.(params) ?? params) as TSelected
|
|
709
|
+
},
|
|
710
|
+
})
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function useNavigate<
|
|
714
|
+
TDefaultFrom extends RoutePaths<RegisteredRouter['routeTree']> = '/',
|
|
715
|
+
>(defaultOpts?: { from?: TDefaultFrom }) {
|
|
716
|
+
const router = useRouter()
|
|
717
|
+
return React.useCallback(
|
|
718
|
+
<
|
|
719
|
+
TFrom extends RoutePaths<RegisteredRouter['routeTree']> = TDefaultFrom,
|
|
720
|
+
TTo extends string = '',
|
|
721
|
+
>(
|
|
722
|
+
opts?: NavigateOptions<RegisteredRouter['routeTree'], TFrom, TTo>,
|
|
723
|
+
) => {
|
|
724
|
+
return router.navigate({ ...defaultOpts, ...(opts as any) })
|
|
725
|
+
},
|
|
726
|
+
[],
|
|
458
727
|
)
|
|
459
728
|
}
|
|
460
729
|
|
|
461
|
-
export function
|
|
462
|
-
const
|
|
463
|
-
warning(!value, 'useRouter must be used inside a <Router> component!')
|
|
730
|
+
export function useMatchRoute() {
|
|
731
|
+
const router = useRouter()
|
|
464
732
|
|
|
465
|
-
|
|
733
|
+
return React.useCallback(
|
|
734
|
+
<TFrom extends string = '/', TTo extends string = ''>(
|
|
735
|
+
opts: MakeUseMatchRouteOptions<TFrom, TTo>,
|
|
736
|
+
) => {
|
|
737
|
+
const { pending, caseSensitive, ...rest } = opts
|
|
466
738
|
|
|
467
|
-
|
|
739
|
+
return router.matchRoute(rest as any, {
|
|
740
|
+
pending,
|
|
741
|
+
caseSensitive,
|
|
742
|
+
})
|
|
743
|
+
},
|
|
744
|
+
[],
|
|
745
|
+
)
|
|
468
746
|
}
|
|
469
747
|
|
|
470
|
-
export function
|
|
471
|
-
|
|
748
|
+
export function MatchRoute<TFrom extends string = '/', TTo extends string = ''>(
|
|
749
|
+
props: MakeMatchRouteOptions<TFrom, TTo>,
|
|
750
|
+
): any {
|
|
751
|
+
const matchRoute = useMatchRoute()
|
|
752
|
+
const params = matchRoute(props)
|
|
753
|
+
|
|
754
|
+
if (typeof props.children === 'function') {
|
|
755
|
+
return (props.children as any)(params)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return !!params ? props.children : null
|
|
472
759
|
}
|
|
473
760
|
|
|
474
761
|
export function Outlet() {
|
|
475
|
-
const
|
|
476
|
-
const matches = useMatches().slice(1)
|
|
477
|
-
const match = matches[0]
|
|
478
|
-
|
|
479
|
-
const defaultPending = React.useCallback(() => null, [])
|
|
762
|
+
const matchIds = React.useContext(matchIdsContext).slice(1)
|
|
480
763
|
|
|
481
|
-
if (!
|
|
764
|
+
if (!matchIds[0]) {
|
|
482
765
|
return null
|
|
483
766
|
}
|
|
484
767
|
|
|
485
|
-
|
|
768
|
+
return <Match matchIds={matchIds} />
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const defaultPending = () => null
|
|
772
|
+
|
|
773
|
+
function Match({ matchIds }: { matchIds: string[] }) {
|
|
774
|
+
const router = useRouter()
|
|
775
|
+
const matchId = matchIds[0]!
|
|
776
|
+
const routeId = router.getRouteMatch(matchId)!.routeId
|
|
777
|
+
const route = router.getRoute(routeId)
|
|
778
|
+
|
|
779
|
+
const PendingComponent = (route.options.pendingComponent ??
|
|
486
780
|
router.options.defaultPendingComponent ??
|
|
487
781
|
defaultPending) as any
|
|
488
782
|
|
|
489
783
|
const errorComponent =
|
|
490
|
-
|
|
784
|
+
route.options.errorComponent ??
|
|
785
|
+
router.options.defaultErrorComponent ??
|
|
786
|
+
ErrorComponent
|
|
787
|
+
|
|
788
|
+
const ResolvedSuspenseBoundary =
|
|
789
|
+
route.options.wrapInSuspense ?? !route.isRoot
|
|
790
|
+
? React.Suspense
|
|
791
|
+
: SafeFragment
|
|
792
|
+
|
|
793
|
+
const ResolvedCatchBoundary = !!errorComponent ? CatchBoundary : SafeFragment
|
|
491
794
|
|
|
492
795
|
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>
|
|
796
|
+
<matchIdsContext.Provider value={matchIds}>
|
|
797
|
+
<ResolvedSuspenseBoundary
|
|
798
|
+
fallback={React.createElement(PendingComponent, {
|
|
799
|
+
useLoader: route.useLoader,
|
|
800
|
+
useMatch: route.useMatch,
|
|
801
|
+
useContext: route.useContext,
|
|
802
|
+
useRouteContext: route.useRouteContext,
|
|
803
|
+
useSearch: route.useSearch,
|
|
804
|
+
useParams: route.useParams,
|
|
805
|
+
})}
|
|
806
|
+
>
|
|
807
|
+
<ResolvedCatchBoundary
|
|
808
|
+
key={route.id}
|
|
809
|
+
errorComponent={errorComponent}
|
|
810
|
+
onCatch={() => {
|
|
811
|
+
warning(false, `Error in route match: ${matchId}`)
|
|
812
|
+
}}
|
|
813
|
+
>
|
|
814
|
+
<MatchInner matchId={matchId} PendingComponent={PendingComponent} />
|
|
815
|
+
</ResolvedCatchBoundary>
|
|
816
|
+
</ResolvedSuspenseBoundary>
|
|
817
|
+
</matchIdsContext.Provider>
|
|
521
818
|
)
|
|
522
819
|
}
|
|
523
820
|
|
|
821
|
+
function MatchInner({
|
|
822
|
+
matchId,
|
|
823
|
+
PendingComponent,
|
|
824
|
+
}: {
|
|
825
|
+
matchId: string
|
|
826
|
+
PendingComponent: any
|
|
827
|
+
}): any {
|
|
828
|
+
const router = useRouter()
|
|
829
|
+
|
|
830
|
+
const match = useRouterState({
|
|
831
|
+
select: (d) => {
|
|
832
|
+
const match = d.matchesById[matchId]
|
|
833
|
+
return pick(match!, ['status', 'loadPromise', 'routeId', 'error'])
|
|
834
|
+
},
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const route = router.getRoute(match.routeId)
|
|
838
|
+
|
|
839
|
+
if (match.status === 'error') {
|
|
840
|
+
throw match.error
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (match.status === 'pending') {
|
|
844
|
+
return React.createElement(PendingComponent, {
|
|
845
|
+
useLoader: route.useLoader,
|
|
846
|
+
useMatch: route.useMatch,
|
|
847
|
+
useContext: route.useContext,
|
|
848
|
+
useRouteContext: route.useRouteContext,
|
|
849
|
+
useSearch: route.useSearch,
|
|
850
|
+
useParams: route.useParams,
|
|
851
|
+
})
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (match.status === 'success') {
|
|
855
|
+
let comp = route.options.component ?? router.options.defaultComponent
|
|
856
|
+
|
|
857
|
+
if (comp) {
|
|
858
|
+
return React.createElement(comp, {
|
|
859
|
+
useLoader: route.useLoader,
|
|
860
|
+
useMatch: route.useMatch,
|
|
861
|
+
useContext: route.useContext as any,
|
|
862
|
+
useRouteContext: route.useRouteContext as any,
|
|
863
|
+
useSearch: route.useSearch,
|
|
864
|
+
useParams: route.useParams as any,
|
|
865
|
+
})
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return <Outlet />
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
invariant(
|
|
872
|
+
false,
|
|
873
|
+
'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function SafeFragment(props: any) {
|
|
878
|
+
return <>{props.children}</>
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export function useInjectHtml() {
|
|
882
|
+
const router = useRouter()
|
|
883
|
+
|
|
884
|
+
return React.useCallback(
|
|
885
|
+
(html: string | (() => Promise<string> | string)) => {
|
|
886
|
+
router.injectHtml(html)
|
|
887
|
+
},
|
|
888
|
+
[],
|
|
889
|
+
)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export function useDehydrate() {
|
|
893
|
+
const router = useRouter()
|
|
894
|
+
|
|
895
|
+
return React.useCallback(function dehydrate<T>(
|
|
896
|
+
key: any,
|
|
897
|
+
data: T | (() => Promise<T> | T),
|
|
898
|
+
) {
|
|
899
|
+
return router.dehydrateData(key, data)
|
|
900
|
+
},
|
|
901
|
+
[])
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
export function useHydrate() {
|
|
905
|
+
const router = useRouter()
|
|
906
|
+
|
|
907
|
+
return function hydrate<T = unknown>(key: any) {
|
|
908
|
+
return router.hydrateData(key) as T
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// This is the messiest thing ever... I'm either seriously tired (likely) or
|
|
913
|
+
// there has to be a better way to reset error boundaries when the
|
|
914
|
+
// router's location key changes.
|
|
915
|
+
|
|
524
916
|
class CatchBoundary extends React.Component<{
|
|
525
917
|
children: any
|
|
526
918
|
errorComponent: any
|
|
919
|
+
onCatch: (error: any, info: any) => void
|
|
527
920
|
}> {
|
|
528
921
|
state = {
|
|
529
922
|
error: false,
|
|
923
|
+
info: undefined,
|
|
530
924
|
}
|
|
531
925
|
componentDidCatch(error: any, info: any) {
|
|
532
|
-
|
|
533
|
-
|
|
926
|
+
this.props.onCatch(error, info)
|
|
534
927
|
this.setState({
|
|
535
928
|
error,
|
|
536
929
|
info,
|
|
537
930
|
})
|
|
538
931
|
}
|
|
539
932
|
render() {
|
|
540
|
-
|
|
933
|
+
return (
|
|
934
|
+
<CatchBoundaryInner
|
|
935
|
+
{...this.props}
|
|
936
|
+
errorState={this.state}
|
|
937
|
+
reset={() => this.setState({})}
|
|
938
|
+
/>
|
|
939
|
+
)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function CatchBoundaryInner(props: {
|
|
944
|
+
children: any
|
|
945
|
+
errorComponent: any
|
|
946
|
+
errorState: { error: unknown; info: any }
|
|
947
|
+
reset: () => void
|
|
948
|
+
}) {
|
|
949
|
+
const locationKey = useRouterState({
|
|
950
|
+
select: (d) => d.resolvedLocation.key,
|
|
951
|
+
})
|
|
952
|
+
|
|
953
|
+
const [activeErrorState, setActiveErrorState] = React.useState(
|
|
954
|
+
props.errorState,
|
|
955
|
+
)
|
|
956
|
+
const errorComponent = props.errorComponent ?? ErrorComponent
|
|
957
|
+
const prevKeyRef = React.useRef('' as any)
|
|
541
958
|
|
|
542
|
-
|
|
543
|
-
|
|
959
|
+
React.useEffect(() => {
|
|
960
|
+
if (activeErrorState) {
|
|
961
|
+
if (locationKey !== prevKeyRef.current) {
|
|
962
|
+
setActiveErrorState({} as any)
|
|
963
|
+
}
|
|
544
964
|
}
|
|
545
965
|
|
|
546
|
-
|
|
966
|
+
prevKeyRef.current = locationKey
|
|
967
|
+
}, [activeErrorState, locationKey])
|
|
968
|
+
|
|
969
|
+
React.useEffect(() => {
|
|
970
|
+
if (props.errorState.error) {
|
|
971
|
+
setActiveErrorState(props.errorState)
|
|
972
|
+
}
|
|
973
|
+
// props.reset()
|
|
974
|
+
}, [props.errorState.error])
|
|
975
|
+
|
|
976
|
+
if (props.errorState.error && activeErrorState.error) {
|
|
977
|
+
return React.createElement(errorComponent, activeErrorState)
|
|
547
978
|
}
|
|
979
|
+
|
|
980
|
+
return props.children
|
|
548
981
|
}
|
|
549
982
|
|
|
550
|
-
export function
|
|
983
|
+
export function ErrorComponent({ error }: { error: any }) {
|
|
984
|
+
const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
|
|
985
|
+
|
|
551
986
|
return (
|
|
552
987
|
<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>
|
|
988
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
|
|
989
|
+
<strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
|
|
990
|
+
<button
|
|
991
|
+
style={{
|
|
992
|
+
appearance: 'none',
|
|
993
|
+
fontSize: '.6em',
|
|
994
|
+
border: '1px solid currentColor',
|
|
995
|
+
padding: '.1rem .2rem',
|
|
996
|
+
fontWeight: 'bold',
|
|
997
|
+
borderRadius: '.25rem',
|
|
998
|
+
}}
|
|
999
|
+
onClick={() => setShow((d) => !d)}
|
|
1000
|
+
>
|
|
1001
|
+
{show ? 'Hide Error' : 'Show Error'}
|
|
1002
|
+
</button>
|
|
571
1003
|
</div>
|
|
1004
|
+
<div style={{ height: '.25rem' }} />
|
|
1005
|
+
{show ? (
|
|
1006
|
+
<div>
|
|
1007
|
+
<pre
|
|
1008
|
+
style={{
|
|
1009
|
+
fontSize: '.7em',
|
|
1010
|
+
border: '1px solid red',
|
|
1011
|
+
borderRadius: '.25rem',
|
|
1012
|
+
padding: '.3rem',
|
|
1013
|
+
color: 'red',
|
|
1014
|
+
overflow: 'auto',
|
|
1015
|
+
}}
|
|
1016
|
+
>
|
|
1017
|
+
{error.message ? <code>{error.message}</code> : null}
|
|
1018
|
+
</pre>
|
|
1019
|
+
</div>
|
|
1020
|
+
) : null}
|
|
572
1021
|
</div>
|
|
573
1022
|
)
|
|
574
1023
|
}
|
|
575
1024
|
|
|
576
|
-
export function
|
|
1025
|
+
export function useBlocker(
|
|
1026
|
+
message: string,
|
|
1027
|
+
condition: boolean | any = true,
|
|
1028
|
+
): void {
|
|
577
1029
|
const router = useRouter()
|
|
578
1030
|
|
|
579
1031
|
React.useEffect(() => {
|
|
580
|
-
if (!
|
|
1032
|
+
if (!condition) return
|
|
581
1033
|
|
|
582
|
-
let unblock = router.history.block((
|
|
1034
|
+
let unblock = router.history.block((retry, cancel) => {
|
|
583
1035
|
if (window.confirm(message)) {
|
|
584
1036
|
unblock()
|
|
585
|
-
|
|
586
|
-
} else {
|
|
587
|
-
router.location.pathname = window.location.pathname
|
|
1037
|
+
retry()
|
|
588
1038
|
}
|
|
589
1039
|
})
|
|
590
1040
|
|
|
591
1041
|
return unblock
|
|
592
|
-
}
|
|
1042
|
+
})
|
|
593
1043
|
}
|
|
594
1044
|
|
|
595
|
-
export function
|
|
596
|
-
|
|
597
|
-
return (children ?? null) as
|
|
1045
|
+
export function Block({ message, condition, children }: PromptProps) {
|
|
1046
|
+
useBlocker(message, condition)
|
|
1047
|
+
return (children ?? null) as ReactNode
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
export function shallow<T>(objA: T, objB: T) {
|
|
1051
|
+
if (Object.is(objA, objB)) {
|
|
1052
|
+
return true
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (
|
|
1056
|
+
typeof objA !== 'object' ||
|
|
1057
|
+
objA === null ||
|
|
1058
|
+
typeof objB !== 'object' ||
|
|
1059
|
+
objB === null
|
|
1060
|
+
) {
|
|
1061
|
+
return false
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const keysA = Object.keys(objA)
|
|
1065
|
+
if (keysA.length !== Object.keys(objB).length) {
|
|
1066
|
+
return false
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
1070
|
+
if (
|
|
1071
|
+
!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
|
|
1072
|
+
!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
|
|
1073
|
+
) {
|
|
1074
|
+
return false
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return true
|
|
598
1078
|
}
|