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