@tanstack/react-router 0.0.1-beta.15 → 0.0.1-beta.150
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 +934 -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,843 @@ 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 LinkFn<
|
|
389
|
+
TDefaultFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
390
|
+
TDefaultTo extends string = '',
|
|
391
|
+
> {
|
|
392
|
+
<
|
|
393
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = TDefaultFrom,
|
|
394
|
+
TTo extends string = TDefaultTo,
|
|
395
|
+
>(
|
|
396
|
+
props: MakeLinkOptions<TFrom, TTo> & React.RefAttributes<HTMLAnchorElement>,
|
|
397
|
+
): ReactNode
|
|
193
398
|
}
|
|
194
399
|
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
400
|
+
export const Link: LinkFn = React.forwardRef((props: any, ref) => {
|
|
401
|
+
const linkProps = useLinkProps(props)
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<a
|
|
405
|
+
{...{
|
|
406
|
+
ref: ref as any,
|
|
407
|
+
...linkProps,
|
|
408
|
+
children:
|
|
409
|
+
typeof props.children === 'function'
|
|
410
|
+
? props.children({
|
|
411
|
+
isActive: (linkProps as any)['data-status'] === 'active',
|
|
412
|
+
})
|
|
413
|
+
: props.children,
|
|
414
|
+
}}
|
|
415
|
+
/>
|
|
200
416
|
)
|
|
417
|
+
}) as any
|
|
418
|
+
|
|
419
|
+
export function Navigate<
|
|
420
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
421
|
+
TTo extends string = '',
|
|
422
|
+
>(props: NavigateOptions<RegisteredRoutesInfo, TFrom, TTo>): null {
|
|
423
|
+
const router = useRouter()
|
|
424
|
+
|
|
425
|
+
React.useLayoutEffect(() => {
|
|
426
|
+
router.navigate(props as any)
|
|
427
|
+
}, [])
|
|
428
|
+
|
|
429
|
+
return null
|
|
201
430
|
}
|
|
202
431
|
|
|
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
|
-
}
|
|
432
|
+
export const matchIdsContext = React.createContext<string[]>(null!)
|
|
433
|
+
export const routerContext = React.createContext<RegisteredRouter>(null!)
|
|
263
434
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const reactHandleClick = (e: Event) => {
|
|
274
|
-
React.startTransition(() => {
|
|
275
|
-
handleClick(e)
|
|
276
|
-
})
|
|
277
|
-
}
|
|
435
|
+
export type RouterProps<
|
|
436
|
+
TRouteConfig extends AnyRoute = AnyRoute,
|
|
437
|
+
TRoutesInfo extends AnyRoutesInfo = DefaultRoutesInfo,
|
|
438
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
439
|
+
> = Omit<RouterOptions<TRouteConfig, TDehydrated>, 'context'> & {
|
|
440
|
+
router: Router<TRouteConfig, TRoutesInfo>
|
|
441
|
+
context?: Partial<RouterOptions<TRouteConfig, TDehydrated>['context']>
|
|
442
|
+
}
|
|
278
443
|
|
|
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
|
-
}
|
|
444
|
+
export function useRouterState<TSelected = RegisteredRouter['state']>(opts?: {
|
|
445
|
+
select: (state: RegisteredRouter['state']) => TSelected
|
|
446
|
+
}): TSelected {
|
|
447
|
+
const router = useRouter()
|
|
448
|
+
return useStore(router.__store, opts?.select)
|
|
449
|
+
}
|
|
359
450
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
451
|
+
export function RouterProvider<
|
|
452
|
+
TRouteConfig extends AnyRoute = AnyRoute,
|
|
453
|
+
TRoutesInfo extends AnyRoutesInfo = DefaultRoutesInfo,
|
|
454
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
455
|
+
>({ router, ...rest }: RouterProps<TRouteConfig, TRoutesInfo, TDehydrated>) {
|
|
456
|
+
router.update(rest)
|
|
366
457
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
458
|
+
React.useEffect(() => {
|
|
459
|
+
let unsub
|
|
460
|
+
|
|
461
|
+
React.startTransition(() => {
|
|
462
|
+
unsub = router.mount()
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
return unsub
|
|
466
|
+
}, [router])
|
|
467
|
+
|
|
468
|
+
const Wrap = router.options.Wrap || React.Fragment
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<React.Suspense fallback={null}>
|
|
472
|
+
<Wrap>
|
|
473
|
+
<routerContext.Provider value={router as any}>
|
|
474
|
+
<Matches />
|
|
475
|
+
</routerContext.Provider>
|
|
476
|
+
</Wrap>
|
|
477
|
+
</React.Suspense>
|
|
478
|
+
)
|
|
479
|
+
}
|
|
382
480
|
|
|
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
|
-
},
|
|
481
|
+
function Matches() {
|
|
482
|
+
const router = useRouter()
|
|
483
|
+
|
|
484
|
+
const matchIds = useRouterState({
|
|
485
|
+
select: (state) => {
|
|
486
|
+
const hasPendingComponent = state.pendingMatches.some((d) => {
|
|
487
|
+
const route = router.getRoute(d.routeId as any)
|
|
488
|
+
return !!route?.options.pendingComponent
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
if (hasPendingComponent) {
|
|
492
|
+
console.log('hasPending')
|
|
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 keyof RegisteredRoutesInfo['routesById'],
|
|
540
|
+
TStrict extends boolean = true,
|
|
541
|
+
TRouteMatchState = RouteMatch<
|
|
542
|
+
RegisteredRoutesInfo,
|
|
543
|
+
RegisteredRoutesInfo['routesById'][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 RegisteredRoutesInfo['routeUnion']
|
|
603
|
+
? T
|
|
604
|
+
: T extends keyof RegisteredRoutesInfo['routesById']
|
|
605
|
+
? RegisteredRoutesInfo['routesById'][T]
|
|
606
|
+
: T extends string
|
|
607
|
+
? keyof RegisteredRoutesInfo['routesById']
|
|
608
|
+
: never
|
|
609
|
+
|
|
610
|
+
export function useLoader<
|
|
611
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
612
|
+
TStrict extends boolean = true,
|
|
613
|
+
TLoader = RegisteredRoutesInfo['routesById'][TFrom]['__types']['loader'],
|
|
614
|
+
TSelected = TLoader,
|
|
615
|
+
>(opts?: {
|
|
616
|
+
from: TFrom
|
|
617
|
+
strict?: TStrict
|
|
618
|
+
select?: (search: TLoader) => TSelected
|
|
619
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
620
|
+
return useMatch({
|
|
621
|
+
...(opts as any),
|
|
622
|
+
select: (match: RouteMatch) =>
|
|
623
|
+
(opts?.select?.(match.loaderData as TLoader) ??
|
|
624
|
+
match.loaderData) as TSelected,
|
|
625
|
+
})
|
|
439
626
|
}
|
|
440
627
|
|
|
441
|
-
export function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
628
|
+
export function useRouterContext<
|
|
629
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
630
|
+
TStrict extends boolean = true,
|
|
631
|
+
TContext = RegisteredRoutesInfo['routesById'][TFrom]['__types']['context'],
|
|
632
|
+
TSelected = TContext,
|
|
633
|
+
>(opts?: {
|
|
634
|
+
from: TFrom
|
|
635
|
+
strict?: TStrict
|
|
636
|
+
select?: (search: TContext) => TSelected
|
|
637
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
638
|
+
return useMatch({
|
|
639
|
+
...(opts as any),
|
|
640
|
+
select: (match: RouteMatch) =>
|
|
641
|
+
(opts?.select?.(match.context as TContext) ?? match.context) as TSelected,
|
|
642
|
+
})
|
|
643
|
+
}
|
|
446
644
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
645
|
+
export function useRouteContext<
|
|
646
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
647
|
+
TStrict extends boolean = true,
|
|
648
|
+
TRouteContext = RegisteredRoutesInfo['routesById'][TFrom]['__types']['routeContext'],
|
|
649
|
+
TSelected = TRouteContext,
|
|
650
|
+
>(opts?: {
|
|
651
|
+
from: TFrom
|
|
652
|
+
strict?: TStrict
|
|
653
|
+
select?: (search: TRouteContext) => TSelected
|
|
654
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
655
|
+
return useMatch({
|
|
656
|
+
...(opts as any),
|
|
657
|
+
select: (match: RouteMatch) =>
|
|
658
|
+
(opts?.select?.(match.routeContext as TRouteContext) ??
|
|
659
|
+
match.routeContext) as TSelected,
|
|
660
|
+
})
|
|
661
|
+
}
|
|
451
662
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
663
|
+
export function useSearch<
|
|
664
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'],
|
|
665
|
+
TStrict extends boolean = true,
|
|
666
|
+
TSearch = RegisteredRoutesInfo['routesById'][TFrom]['__types']['fullSearchSchema'],
|
|
667
|
+
TSelected = TSearch,
|
|
668
|
+
>(opts?: {
|
|
669
|
+
from: TFrom
|
|
670
|
+
strict?: TStrict
|
|
671
|
+
select?: (search: TSearch) => TSelected
|
|
672
|
+
}): TStrict extends true ? TSelected : TSelected | undefined {
|
|
673
|
+
return useMatch({
|
|
674
|
+
...(opts as any),
|
|
675
|
+
select: (match: RouteMatch) => {
|
|
676
|
+
return (opts?.select?.(match.search as TSearch) ??
|
|
677
|
+
match.search) as TSelected
|
|
678
|
+
},
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
export function useParams<
|
|
683
|
+
TFrom extends keyof RegisteredRoutesInfo['routesById'] = '/',
|
|
684
|
+
TDefaultSelected = RegisteredRoutesInfo['allParams'] &
|
|
685
|
+
RegisteredRoutesInfo['routesById'][TFrom]['__types']['allParams'],
|
|
686
|
+
TSelected = TDefaultSelected,
|
|
687
|
+
>(opts?: {
|
|
688
|
+
from: TFrom
|
|
689
|
+
select?: (search: TDefaultSelected) => TSelected
|
|
690
|
+
}): TSelected {
|
|
691
|
+
return useRouterState({
|
|
692
|
+
select: (state: any) => {
|
|
693
|
+
const params = (last(state.matches) as any)?.params
|
|
694
|
+
return (opts?.select?.(params) ?? params) as TSelected
|
|
695
|
+
},
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function useNavigate<
|
|
700
|
+
TDefaultFrom extends RegisteredRoutesInfo['routePaths'] = '/',
|
|
701
|
+
>(defaultOpts?: { from?: TDefaultFrom }) {
|
|
702
|
+
const router = useRouter()
|
|
703
|
+
return React.useCallback(
|
|
704
|
+
<
|
|
705
|
+
TFrom extends RegisteredRoutesInfo['routePaths'] = TDefaultFrom,
|
|
706
|
+
TTo extends string = '',
|
|
707
|
+
>(
|
|
708
|
+
opts?: NavigateOptions<RegisteredRoutesInfo, TFrom, TTo>,
|
|
709
|
+
) => {
|
|
710
|
+
return router.navigate({ ...defaultOpts, ...(opts as any) })
|
|
711
|
+
},
|
|
712
|
+
[],
|
|
458
713
|
)
|
|
459
714
|
}
|
|
460
715
|
|
|
461
|
-
export function
|
|
462
|
-
const
|
|
463
|
-
warning(!value, 'useRouter must be used inside a <Router> component!')
|
|
716
|
+
export function useMatchRoute() {
|
|
717
|
+
const router = useRouter()
|
|
464
718
|
|
|
465
|
-
|
|
719
|
+
return React.useCallback(
|
|
720
|
+
<TFrom extends string = '/', TTo extends string = ''>(
|
|
721
|
+
opts: MakeUseMatchRouteOptions<TFrom, TTo>,
|
|
722
|
+
) => {
|
|
723
|
+
const { pending, caseSensitive, ...rest } = opts
|
|
466
724
|
|
|
467
|
-
|
|
725
|
+
return router.matchRoute(rest as any, {
|
|
726
|
+
pending,
|
|
727
|
+
caseSensitive,
|
|
728
|
+
})
|
|
729
|
+
},
|
|
730
|
+
[],
|
|
731
|
+
)
|
|
468
732
|
}
|
|
469
733
|
|
|
470
|
-
export function
|
|
471
|
-
|
|
734
|
+
export function MatchRoute<TFrom extends string = '/', TTo extends string = ''>(
|
|
735
|
+
props: MakeMatchRouteOptions<TFrom, TTo>,
|
|
736
|
+
): any {
|
|
737
|
+
const matchRoute = useMatchRoute()
|
|
738
|
+
const params = matchRoute(props)
|
|
739
|
+
|
|
740
|
+
if (typeof props.children === 'function') {
|
|
741
|
+
return (props.children as any)(params)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return !!params ? props.children : null
|
|
472
745
|
}
|
|
473
746
|
|
|
474
747
|
export function Outlet() {
|
|
475
|
-
const
|
|
476
|
-
const matches = useMatches().slice(1)
|
|
477
|
-
const match = matches[0]
|
|
478
|
-
|
|
479
|
-
const defaultPending = React.useCallback(() => null, [])
|
|
748
|
+
const matchIds = React.useContext(matchIdsContext).slice(1)
|
|
480
749
|
|
|
481
|
-
if (!
|
|
750
|
+
if (!matchIds[0]) {
|
|
482
751
|
return null
|
|
483
752
|
}
|
|
484
753
|
|
|
485
|
-
|
|
754
|
+
return <Match matchIds={matchIds} />
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const defaultPending = () => null
|
|
758
|
+
|
|
759
|
+
function Match({ matchIds }: { matchIds: string[] }) {
|
|
760
|
+
const router = useRouter()
|
|
761
|
+
const matchId = matchIds[0]!
|
|
762
|
+
const routeId = router.getRouteMatch(matchId)!.routeId
|
|
763
|
+
const route = router.getRoute(routeId)
|
|
764
|
+
|
|
765
|
+
const PendingComponent = (route.options.pendingComponent ??
|
|
486
766
|
router.options.defaultPendingComponent ??
|
|
487
767
|
defaultPending) as any
|
|
488
768
|
|
|
489
769
|
const errorComponent =
|
|
490
|
-
|
|
770
|
+
route.options.errorComponent ??
|
|
771
|
+
router.options.defaultErrorComponent ??
|
|
772
|
+
ErrorComponent
|
|
773
|
+
|
|
774
|
+
const ResolvedSuspenseBoundary =
|
|
775
|
+
route.options.wrapInSuspense ?? !route.isRoot
|
|
776
|
+
? React.Suspense
|
|
777
|
+
: SafeFragment
|
|
778
|
+
|
|
779
|
+
const ResolvedCatchBoundary = !!errorComponent ? CatchBoundary : SafeFragment
|
|
491
780
|
|
|
492
781
|
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>
|
|
782
|
+
<matchIdsContext.Provider value={matchIds}>
|
|
783
|
+
<ResolvedSuspenseBoundary
|
|
784
|
+
fallback={React.createElement(PendingComponent, {
|
|
785
|
+
useLoader: route.useLoader,
|
|
786
|
+
useMatch: route.useMatch,
|
|
787
|
+
useContext: route.useContext,
|
|
788
|
+
useRouteContext: route.useRouteContext,
|
|
789
|
+
useSearch: route.useSearch,
|
|
790
|
+
useParams: route.useParams,
|
|
791
|
+
})}
|
|
792
|
+
>
|
|
793
|
+
<ResolvedCatchBoundary
|
|
794
|
+
key={route.id}
|
|
795
|
+
errorComponent={errorComponent}
|
|
796
|
+
onCatch={() => {
|
|
797
|
+
warning(false, `Error in route match: ${matchId}`)
|
|
798
|
+
}}
|
|
799
|
+
>
|
|
800
|
+
<MatchInner matchId={matchId} PendingComponent={PendingComponent} />
|
|
801
|
+
</ResolvedCatchBoundary>
|
|
802
|
+
</ResolvedSuspenseBoundary>
|
|
803
|
+
</matchIdsContext.Provider>
|
|
521
804
|
)
|
|
522
805
|
}
|
|
523
806
|
|
|
807
|
+
function MatchInner({
|
|
808
|
+
matchId,
|
|
809
|
+
PendingComponent,
|
|
810
|
+
}: {
|
|
811
|
+
matchId: string
|
|
812
|
+
PendingComponent: any
|
|
813
|
+
}): any {
|
|
814
|
+
const router = useRouter()
|
|
815
|
+
|
|
816
|
+
const match = useRouterState({
|
|
817
|
+
select: (d) => {
|
|
818
|
+
const match = d.matchesById[matchId]
|
|
819
|
+
return pick(match!, ['status', 'loadPromise', 'routeId', 'error'])
|
|
820
|
+
},
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
const route = router.getRoute(match.routeId)
|
|
824
|
+
|
|
825
|
+
if (match.status === 'error') {
|
|
826
|
+
throw match.error
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (match.status === 'pending') {
|
|
830
|
+
return React.createElement(PendingComponent, {
|
|
831
|
+
useLoader: route.useLoader,
|
|
832
|
+
useMatch: route.useMatch,
|
|
833
|
+
useContext: route.useContext,
|
|
834
|
+
useRouteContext: route.useRouteContext,
|
|
835
|
+
useSearch: route.useSearch,
|
|
836
|
+
useParams: route.useParams,
|
|
837
|
+
})
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (match.status === 'success') {
|
|
841
|
+
let comp = route.options.component ?? router.options.defaultComponent
|
|
842
|
+
|
|
843
|
+
if (comp) {
|
|
844
|
+
return React.createElement(comp, {
|
|
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
|
+
return <Outlet />
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
invariant(
|
|
858
|
+
false,
|
|
859
|
+
'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
|
|
860
|
+
)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function SafeFragment(props: any) {
|
|
864
|
+
return <>{props.children}</>
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export function useInjectHtml() {
|
|
868
|
+
const router = useRouter()
|
|
869
|
+
|
|
870
|
+
return React.useCallback(
|
|
871
|
+
(html: string | (() => Promise<string> | string)) => {
|
|
872
|
+
router.injectHtml(html)
|
|
873
|
+
},
|
|
874
|
+
[],
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export function useDehydrate() {
|
|
879
|
+
const router = useRouter()
|
|
880
|
+
|
|
881
|
+
return React.useCallback(function dehydrate<T>(
|
|
882
|
+
key: any,
|
|
883
|
+
data: T | (() => Promise<T> | T),
|
|
884
|
+
) {
|
|
885
|
+
return router.dehydrateData(key, data)
|
|
886
|
+
},
|
|
887
|
+
[])
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
export function useHydrate() {
|
|
891
|
+
const router = useRouter()
|
|
892
|
+
|
|
893
|
+
return function hydrate<T = unknown>(key: any) {
|
|
894
|
+
return router.hydrateData(key) as T
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// This is the messiest thing ever... I'm either seriously tired (likely) or
|
|
899
|
+
// there has to be a better way to reset error boundaries when the
|
|
900
|
+
// router's location key changes.
|
|
901
|
+
|
|
524
902
|
class CatchBoundary extends React.Component<{
|
|
525
903
|
children: any
|
|
526
904
|
errorComponent: any
|
|
905
|
+
onCatch: (error: any, info: any) => void
|
|
527
906
|
}> {
|
|
528
907
|
state = {
|
|
529
908
|
error: false,
|
|
909
|
+
info: undefined,
|
|
530
910
|
}
|
|
531
911
|
componentDidCatch(error: any, info: any) {
|
|
532
|
-
|
|
533
|
-
|
|
912
|
+
this.props.onCatch(error, info)
|
|
534
913
|
this.setState({
|
|
535
914
|
error,
|
|
536
915
|
info,
|
|
537
916
|
})
|
|
538
917
|
}
|
|
539
918
|
render() {
|
|
540
|
-
|
|
919
|
+
return (
|
|
920
|
+
<CatchBoundaryInner
|
|
921
|
+
{...this.props}
|
|
922
|
+
errorState={this.state}
|
|
923
|
+
reset={() => this.setState({})}
|
|
924
|
+
/>
|
|
925
|
+
)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function CatchBoundaryInner(props: {
|
|
930
|
+
children: any
|
|
931
|
+
errorComponent: any
|
|
932
|
+
errorState: { error: unknown; info: any }
|
|
933
|
+
reset: () => void
|
|
934
|
+
}) {
|
|
935
|
+
const locationKey = useRouterState({
|
|
936
|
+
select: (d) => d.resolvedLocation.key,
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
const [activeErrorState, setActiveErrorState] = React.useState(
|
|
940
|
+
props.errorState,
|
|
941
|
+
)
|
|
942
|
+
const errorComponent = props.errorComponent ?? ErrorComponent
|
|
943
|
+
const prevKeyRef = React.useRef('' as any)
|
|
541
944
|
|
|
542
|
-
|
|
543
|
-
|
|
945
|
+
React.useEffect(() => {
|
|
946
|
+
if (activeErrorState) {
|
|
947
|
+
if (locationKey !== prevKeyRef.current) {
|
|
948
|
+
setActiveErrorState({} as any)
|
|
949
|
+
}
|
|
544
950
|
}
|
|
545
951
|
|
|
546
|
-
|
|
952
|
+
prevKeyRef.current = locationKey
|
|
953
|
+
}, [activeErrorState, locationKey])
|
|
954
|
+
|
|
955
|
+
React.useEffect(() => {
|
|
956
|
+
if (props.errorState.error) {
|
|
957
|
+
setActiveErrorState(props.errorState)
|
|
958
|
+
}
|
|
959
|
+
// props.reset()
|
|
960
|
+
}, [props.errorState.error])
|
|
961
|
+
|
|
962
|
+
if (props.errorState.error && activeErrorState.error) {
|
|
963
|
+
return React.createElement(errorComponent, activeErrorState)
|
|
547
964
|
}
|
|
965
|
+
|
|
966
|
+
return props.children
|
|
548
967
|
}
|
|
549
968
|
|
|
550
|
-
export function
|
|
969
|
+
export function ErrorComponent({ error }: { error: any }) {
|
|
970
|
+
const [show, setShow] = React.useState(process.env.NODE_ENV !== 'production')
|
|
971
|
+
|
|
551
972
|
return (
|
|
552
973
|
<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>
|
|
974
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
|
|
975
|
+
<strong style={{ fontSize: '1rem' }}>Something went wrong!</strong>
|
|
976
|
+
<button
|
|
977
|
+
style={{
|
|
978
|
+
appearance: 'none',
|
|
979
|
+
fontSize: '.6em',
|
|
980
|
+
border: '1px solid currentColor',
|
|
981
|
+
padding: '.1rem .2rem',
|
|
982
|
+
fontWeight: 'bold',
|
|
983
|
+
borderRadius: '.25rem',
|
|
984
|
+
}}
|
|
985
|
+
onClick={() => setShow((d) => !d)}
|
|
986
|
+
>
|
|
987
|
+
{show ? 'Hide Error' : 'Show Error'}
|
|
988
|
+
</button>
|
|
571
989
|
</div>
|
|
990
|
+
<div style={{ height: '.25rem' }} />
|
|
991
|
+
{show ? (
|
|
992
|
+
<div>
|
|
993
|
+
<pre
|
|
994
|
+
style={{
|
|
995
|
+
fontSize: '.7em',
|
|
996
|
+
border: '1px solid red',
|
|
997
|
+
borderRadius: '.25rem',
|
|
998
|
+
padding: '.3rem',
|
|
999
|
+
color: 'red',
|
|
1000
|
+
overflow: 'auto',
|
|
1001
|
+
}}
|
|
1002
|
+
>
|
|
1003
|
+
{error.message ? <code>{error.message}</code> : null}
|
|
1004
|
+
</pre>
|
|
1005
|
+
</div>
|
|
1006
|
+
) : null}
|
|
572
1007
|
</div>
|
|
573
1008
|
)
|
|
574
1009
|
}
|
|
575
1010
|
|
|
576
|
-
export function
|
|
1011
|
+
export function useBlocker(
|
|
1012
|
+
message: string,
|
|
1013
|
+
condition: boolean | any = true,
|
|
1014
|
+
): void {
|
|
577
1015
|
const router = useRouter()
|
|
578
1016
|
|
|
579
1017
|
React.useEffect(() => {
|
|
580
|
-
if (!
|
|
1018
|
+
if (!condition) return
|
|
581
1019
|
|
|
582
|
-
let unblock = router.history.block((
|
|
1020
|
+
let unblock = router.history.block((retry, cancel) => {
|
|
583
1021
|
if (window.confirm(message)) {
|
|
584
1022
|
unblock()
|
|
585
|
-
|
|
586
|
-
} else {
|
|
587
|
-
router.location.pathname = window.location.pathname
|
|
1023
|
+
retry()
|
|
588
1024
|
}
|
|
589
1025
|
})
|
|
590
1026
|
|
|
591
1027
|
return unblock
|
|
592
|
-
}
|
|
1028
|
+
})
|
|
593
1029
|
}
|
|
594
1030
|
|
|
595
|
-
export function
|
|
596
|
-
|
|
597
|
-
return (children ?? null) as
|
|
1031
|
+
export function Block({ message, condition, children }: PromptProps) {
|
|
1032
|
+
useBlocker(message, condition)
|
|
1033
|
+
return (children ?? null) as ReactNode
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export function shallow<T>(objA: T, objB: T) {
|
|
1037
|
+
if (Object.is(objA, objB)) {
|
|
1038
|
+
return true
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (
|
|
1042
|
+
typeof objA !== 'object' ||
|
|
1043
|
+
objA === null ||
|
|
1044
|
+
typeof objB !== 'object' ||
|
|
1045
|
+
objB === null
|
|
1046
|
+
) {
|
|
1047
|
+
return false
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const keysA = Object.keys(objA)
|
|
1051
|
+
if (keysA.length !== Object.keys(objB).length) {
|
|
1052
|
+
return false
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
1056
|
+
if (
|
|
1057
|
+
!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
|
|
1058
|
+
!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
|
|
1059
|
+
) {
|
|
1060
|
+
return false
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return true
|
|
598
1064
|
}
|