@tanstack/router-core 0.0.1-beta.14 → 0.0.1-beta.146
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/fileRoute.js +29 -0
- package/build/cjs/fileRoute.js.map +1 -0
- package/build/cjs/history.js +226 -0
- package/build/cjs/history.js.map +1 -0
- package/build/cjs/index.js +78 -0
- package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
- package/build/cjs/{packages/router-core/src/path.js → path.js} +45 -56
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/{packages/router-core/src/qss.js → qss.js} +10 -16
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/route.js +103 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/router.js +1102 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +11 -13
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/{packages/router-core/src/utils.js → utils.js} +54 -64
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +1403 -2096
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +203 -234
- package/build/types/index.d.ts +603 -422
- package/build/umd/index.development.js +1629 -2218
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +13 -2
- package/build/umd/index.production.js.map +1 -1
- package/package.json +11 -7
- package/src/fileRoute.ts +122 -0
- package/src/history.ts +292 -0
- package/src/index.ts +3 -10
- package/src/link.ts +118 -113
- package/src/path.ts +37 -17
- package/src/qss.ts +1 -2
- package/src/route.ts +891 -218
- package/src/routeInfo.ts +121 -197
- package/src/router.ts +1481 -1018
- package/src/searchParams.ts +1 -1
- package/src/utils.ts +80 -49
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -33
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
- package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
- package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
- package/build/cjs/node_modules/history/index.js +0 -815
- package/build/cjs/node_modules/history/index.js.map +0 -1
- package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
- package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
- package/build/cjs/packages/router-core/src/index.js +0 -58
- package/build/cjs/packages/router-core/src/path.js.map +0 -1
- package/build/cjs/packages/router-core/src/qss.js.map +0 -1
- package/build/cjs/packages/router-core/src/route.js +0 -147
- package/build/cjs/packages/router-core/src/route.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeConfig.js +0 -69
- package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeMatch.js +0 -226
- package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
- package/build/cjs/packages/router-core/src/router.js +0 -832
- package/build/cjs/packages/router-core/src/router.js.map +0 -1
- package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
- package/build/cjs/packages/router-core/src/utils.js.map +0 -1
- package/src/frameworks.ts +0 -11
- package/src/routeConfig.ts +0 -489
- package/src/routeMatch.ts +0 -312
package/src/router.ts
CHANGED
|
@@ -1,62 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BrowserHistory,
|
|
3
|
-
createBrowserHistory,
|
|
4
|
-
createMemoryHistory,
|
|
5
|
-
HashHistory,
|
|
6
|
-
History,
|
|
7
|
-
MemoryHistory,
|
|
8
|
-
} from 'history'
|
|
9
|
-
import React from 'react'
|
|
1
|
+
import { Store } from '@tanstack/react-store'
|
|
10
2
|
import invariant from 'tiny-invariant'
|
|
11
|
-
|
|
3
|
+
|
|
4
|
+
//
|
|
12
5
|
|
|
13
6
|
import {
|
|
14
7
|
LinkInfo,
|
|
15
8
|
LinkOptions,
|
|
16
|
-
|
|
9
|
+
NavigateOptions,
|
|
17
10
|
ToOptions,
|
|
18
|
-
|
|
11
|
+
ResolveRelativePath,
|
|
19
12
|
} from './link'
|
|
20
13
|
import {
|
|
21
14
|
cleanPath,
|
|
22
15
|
interpolatePath,
|
|
23
16
|
joinPaths,
|
|
24
17
|
matchPathname,
|
|
18
|
+
parsePathname,
|
|
25
19
|
resolvePath,
|
|
20
|
+
trimPath,
|
|
21
|
+
trimPathRight,
|
|
26
22
|
} from './path'
|
|
27
|
-
import { AnyRoute, createRoute, Route } from './route'
|
|
28
23
|
import {
|
|
29
|
-
|
|
30
|
-
AnyPathParams,
|
|
31
|
-
AnyRouteConfig,
|
|
24
|
+
Route,
|
|
32
25
|
AnySearchSchema,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
AnyRoute,
|
|
27
|
+
RootRoute,
|
|
28
|
+
AnyContext,
|
|
29
|
+
AnyPathParams,
|
|
30
|
+
RouteProps,
|
|
31
|
+
RegisteredRouteComponent,
|
|
32
|
+
RegisteredRouteErrorComponent,
|
|
33
|
+
} from './route'
|
|
37
34
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
AnyRouteInfo,
|
|
41
|
-
RouteInfo,
|
|
35
|
+
RoutesInfo,
|
|
36
|
+
AnyRoutesInfo,
|
|
42
37
|
RoutesById,
|
|
38
|
+
RoutesByPath,
|
|
39
|
+
DefaultRoutesInfo,
|
|
43
40
|
} from './routeInfo'
|
|
44
|
-
import { createRouteMatch, RouteMatch } from './routeMatch'
|
|
45
41
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
46
42
|
import {
|
|
47
43
|
functionalUpdate,
|
|
48
44
|
last,
|
|
45
|
+
NoInfer,
|
|
49
46
|
pick,
|
|
50
47
|
PickAsRequired,
|
|
51
|
-
PickRequired,
|
|
52
|
-
replaceEqualDeep,
|
|
53
48
|
Timeout,
|
|
54
49
|
Updater,
|
|
50
|
+
replaceEqualDeep,
|
|
51
|
+
partialDeepEqual,
|
|
55
52
|
} from './utils'
|
|
53
|
+
import {
|
|
54
|
+
createBrowserHistory,
|
|
55
|
+
createMemoryHistory,
|
|
56
|
+
RouterHistory,
|
|
57
|
+
} from './history'
|
|
58
|
+
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
declare global {
|
|
62
|
+
interface Window {
|
|
63
|
+
__TSR_DEHYDRATED__?: HydrationCtx
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Register {
|
|
68
|
+
// router: Router
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type AnyRouter = Router<any, any, any>
|
|
72
|
+
|
|
73
|
+
export type RegisteredRouterPair = Register extends {
|
|
74
|
+
router: infer TRouter extends AnyRouter
|
|
75
|
+
}
|
|
76
|
+
? [TRouter, TRouter['types']['RoutesInfo']]
|
|
77
|
+
: [Router, AnyRoutesInfo]
|
|
78
|
+
|
|
79
|
+
export type RegisteredRouter = RegisteredRouterPair[0]
|
|
80
|
+
export type RegisteredRoutesInfo = RegisteredRouterPair[1]
|
|
56
81
|
|
|
57
82
|
export interface LocationState {}
|
|
58
83
|
|
|
59
|
-
export interface
|
|
84
|
+
export interface ParsedLocation<
|
|
60
85
|
TSearchObj extends AnySearchSchema = {},
|
|
61
86
|
TState extends LocationState = LocationState,
|
|
62
87
|
> {
|
|
@@ -78,137 +103,119 @@ export interface FromLocation {
|
|
|
78
103
|
|
|
79
104
|
export type SearchSerializer = (searchObj: Record<string, any>) => string
|
|
80
105
|
export type SearchParser = (searchStr: string) => Record<string, any>
|
|
81
|
-
export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
|
|
82
|
-
routeConfigs: TRoute[],
|
|
83
|
-
) => TRoute[]
|
|
84
106
|
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
parseSearch?: SearchParser
|
|
89
|
-
filterRoutes?: FilterRoutesFn
|
|
90
|
-
defaultPreload?: false | 'intent'
|
|
91
|
-
defaultPreloadMaxAge?: number
|
|
92
|
-
defaultPreloadGcMaxAge?: number
|
|
93
|
-
defaultPreloadDelay?: number
|
|
94
|
-
useErrorBoundary?: boolean
|
|
95
|
-
defaultComponent?: GetFrameworkGeneric<'Component'>
|
|
96
|
-
defaultErrorComponent?: GetFrameworkGeneric<'Component'>
|
|
97
|
-
defaultPendingComponent?: GetFrameworkGeneric<'Component'>
|
|
98
|
-
defaultLoaderMaxAge?: number
|
|
99
|
-
defaultLoaderGcMaxAge?: number
|
|
100
|
-
caseSensitive?: boolean
|
|
101
|
-
routeConfig?: TRouteConfig
|
|
102
|
-
basepath?: string
|
|
103
|
-
createRouter?: (router: Router<any, any>) => void
|
|
104
|
-
createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
|
|
105
|
-
loadComponent?: (
|
|
106
|
-
component: GetFrameworkGeneric<'Component'>,
|
|
107
|
-
) => Promise<GetFrameworkGeneric<'Component'>>
|
|
108
|
-
// renderComponent?: (
|
|
109
|
-
// component: GetFrameworkGeneric<'Component'>,
|
|
110
|
-
// ) => GetFrameworkGeneric<'Element'>
|
|
107
|
+
export type HydrationCtx = {
|
|
108
|
+
router: DehydratedRouter
|
|
109
|
+
payload: Record<string, any>
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
export interface
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// TError = unknown,
|
|
112
|
+
export interface RouteMatch<
|
|
113
|
+
TRoutesInfo extends AnyRoutesInfo = DefaultRoutesInfo,
|
|
114
|
+
TRoute extends AnyRoute = Route,
|
|
117
115
|
> {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
latest?: ActionState<TPayload, TResponse>
|
|
124
|
-
submissions: ActionState<TPayload, TResponse>[]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface ActionState<
|
|
128
|
-
TPayload = unknown,
|
|
129
|
-
TResponse = unknown,
|
|
130
|
-
// TError = unknown,
|
|
131
|
-
> {
|
|
132
|
-
submittedAt: number
|
|
116
|
+
id: string
|
|
117
|
+
key?: string
|
|
118
|
+
routeId: string
|
|
119
|
+
pathname: string
|
|
120
|
+
params: TRoute['__types']['allParams']
|
|
133
121
|
status: 'idle' | 'pending' | 'success' | 'error'
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
isFetching: boolean
|
|
123
|
+
invalid: boolean
|
|
124
|
+
error: unknown
|
|
125
|
+
paramsError: unknown
|
|
126
|
+
searchError: unknown
|
|
127
|
+
updatedAt: number
|
|
128
|
+
invalidAt: number
|
|
129
|
+
preloadInvalidAt: number
|
|
130
|
+
loaderData: TRoute['__types']['loader']
|
|
131
|
+
loadPromise?: Promise<void>
|
|
132
|
+
__resolveLoadPromise?: () => void
|
|
133
|
+
routeContext: TRoute['__types']['routeContext']
|
|
134
|
+
context: TRoute['__types']['context']
|
|
135
|
+
routeSearch: TRoute['__types']['searchSchema']
|
|
136
|
+
search: TRoutesInfo['fullSearchSchema'] &
|
|
137
|
+
TRoute['__types']['fullSearchSchema']
|
|
138
|
+
fetchedAt: number
|
|
139
|
+
abortController: AbortController
|
|
138
140
|
}
|
|
139
141
|
|
|
140
|
-
export
|
|
141
|
-
TFullSearchSchema extends AnySearchSchema = {},
|
|
142
|
-
TAllParams extends AnyPathParams = {},
|
|
143
|
-
TRouteLoaderData = AnyLoaderData,
|
|
144
|
-
> {
|
|
145
|
-
fetch: keyof PickRequired<TFullSearchSchema> extends never
|
|
146
|
-
? keyof TAllParams extends never
|
|
147
|
-
? (loaderContext: { signal?: AbortSignal }) => Promise<TRouteLoaderData>
|
|
148
|
-
: (loaderContext: {
|
|
149
|
-
params: TAllParams
|
|
150
|
-
search?: TFullSearchSchema
|
|
151
|
-
signal?: AbortSignal
|
|
152
|
-
}) => Promise<TRouteLoaderData>
|
|
153
|
-
: keyof TAllParams extends never
|
|
154
|
-
? (loaderContext: {
|
|
155
|
-
search: TFullSearchSchema
|
|
156
|
-
params: TAllParams
|
|
157
|
-
signal?: AbortSignal
|
|
158
|
-
}) => Promise<TRouteLoaderData>
|
|
159
|
-
: (loaderContext: {
|
|
160
|
-
search: TFullSearchSchema
|
|
161
|
-
signal?: AbortSignal
|
|
162
|
-
}) => Promise<TRouteLoaderData>
|
|
163
|
-
current?: LoaderState<TFullSearchSchema, TAllParams>
|
|
164
|
-
latest?: LoaderState<TFullSearchSchema, TAllParams>
|
|
165
|
-
pending: LoaderState<TFullSearchSchema, TAllParams>[]
|
|
166
|
-
}
|
|
142
|
+
export type AnyRouteMatch = RouteMatch<AnyRoutesInfo, AnyRoute>
|
|
167
143
|
|
|
168
|
-
export
|
|
169
|
-
|
|
170
|
-
|
|
144
|
+
export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
145
|
+
AnyContext extends TRouteTree['__types']['routerContext']
|
|
146
|
+
? {
|
|
147
|
+
context?: TRouteTree['__types']['routerContext']
|
|
148
|
+
}
|
|
149
|
+
: {
|
|
150
|
+
context: TRouteTree['__types']['routerContext']
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface RouterOptions<
|
|
154
|
+
TRouteTree extends AnyRoute,
|
|
155
|
+
TDehydrated extends Record<string, any>,
|
|
171
156
|
> {
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
history?: RouterHistory
|
|
158
|
+
stringifySearch?: SearchSerializer
|
|
159
|
+
parseSearch?: SearchParser
|
|
160
|
+
defaultPreload?: false | 'intent'
|
|
161
|
+
defaultPreloadDelay?: number
|
|
162
|
+
defaultComponent?: RegisteredRouteComponent<
|
|
163
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
164
|
+
>
|
|
165
|
+
defaultErrorComponent?: RegisteredRouteErrorComponent<
|
|
166
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
167
|
+
>
|
|
168
|
+
defaultPendingComponent?: RegisteredRouteComponent<
|
|
169
|
+
RouteProps<unknown, AnySearchSchema, AnyPathParams, AnyContext, AnyContext>
|
|
170
|
+
>
|
|
171
|
+
defaultMaxAge?: number
|
|
172
|
+
defaultGcMaxAge?: number
|
|
173
|
+
defaultPreloadMaxAge?: number
|
|
174
|
+
caseSensitive?: boolean
|
|
175
|
+
routeTree?: TRouteTree
|
|
176
|
+
basepath?: string
|
|
177
|
+
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
|
|
178
|
+
onRouteChange?: () => void
|
|
179
|
+
context?: TRouteTree['__types']['routerContext']
|
|
180
|
+
Wrap?: React.ComponentType<{
|
|
181
|
+
children: React.ReactNode
|
|
182
|
+
dehydratedState?: TDehydrated
|
|
183
|
+
}>
|
|
184
|
+
dehydrate?: () => TDehydrated
|
|
185
|
+
hydrate?: (dehydrated: TDehydrated) => void
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
export interface RouterState
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
actions: Record<string, Action>
|
|
182
|
-
loaders: Record<string, Loader>
|
|
183
|
-
pending?: PendingState
|
|
188
|
+
export interface RouterState<
|
|
189
|
+
TRoutesInfo extends AnyRoutesInfo = AnyRoutesInfo,
|
|
190
|
+
TState extends LocationState = LocationState,
|
|
191
|
+
> {
|
|
192
|
+
status: 'idle' | 'pending'
|
|
184
193
|
isFetching: boolean
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
matchesById: Record<
|
|
195
|
+
string,
|
|
196
|
+
RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>
|
|
197
|
+
>
|
|
198
|
+
matchIds: string[]
|
|
199
|
+
pendingMatchIds: string[]
|
|
200
|
+
matches: RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>[]
|
|
201
|
+
pendingMatches: RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>[]
|
|
202
|
+
location: ParsedLocation<TRoutesInfo['fullSearchSchema'], TState>
|
|
203
|
+
resolvedLocation: ParsedLocation<TRoutesInfo['fullSearchSchema'], TState>
|
|
204
|
+
lastUpdated: number
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
type Listener = (router: Router<any, any>) => void
|
|
194
|
-
|
|
195
207
|
export type ListenerFn = () => void
|
|
196
208
|
|
|
197
209
|
export interface BuildNextOptions {
|
|
198
210
|
to?: string | number | null
|
|
199
|
-
params?: true | Updater<
|
|
211
|
+
params?: true | Updater<unknown>
|
|
200
212
|
search?: true | Updater<unknown>
|
|
201
213
|
hash?: true | Updater<string>
|
|
214
|
+
state?: LocationState
|
|
202
215
|
key?: string
|
|
203
216
|
from?: string
|
|
204
217
|
fromCurrent?: boolean
|
|
205
|
-
|
|
206
|
-
__postSearchFilters?: SearchFilter<any>[]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export type MatchCacheEntry = {
|
|
210
|
-
gc: number
|
|
211
|
-
match: RouteMatch
|
|
218
|
+
__matches?: AnyRouteMatch[]
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
export interface MatchLocation {
|
|
@@ -220,1093 +227,1549 @@ export interface MatchLocation {
|
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
export interface MatchRouteOptions {
|
|
223
|
-
pending
|
|
230
|
+
pending?: boolean
|
|
224
231
|
caseSensitive?: boolean
|
|
232
|
+
includeSearch?: boolean
|
|
233
|
+
fuzzy?: boolean
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
type LinkCurrentTargetElement = {
|
|
228
237
|
preloadTimeout?: null | ReturnType<typeof setTimeout>
|
|
229
238
|
}
|
|
230
239
|
|
|
231
|
-
interface DehydratedRouterState
|
|
232
|
-
extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {
|
|
233
|
-
|
|
240
|
+
export interface DehydratedRouterState
|
|
241
|
+
extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {}
|
|
242
|
+
|
|
243
|
+
export interface DehydratedRouter {
|
|
244
|
+
state: DehydratedRouterState
|
|
234
245
|
}
|
|
235
246
|
|
|
236
|
-
|
|
237
|
-
extends
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
export type RouterConstructorOptions<
|
|
248
|
+
TRouteTree extends AnyRoute,
|
|
249
|
+
TDehydrated extends Record<string, any>,
|
|
250
|
+
> = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> &
|
|
251
|
+
RouterContextOptions<TRouteTree>
|
|
252
|
+
|
|
253
|
+
export const componentTypes = [
|
|
254
|
+
'component',
|
|
255
|
+
'errorComponent',
|
|
256
|
+
'pendingComponent',
|
|
257
|
+
] as const
|
|
258
|
+
|
|
259
|
+
export class Router<
|
|
260
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
261
|
+
TRoutesInfo extends AnyRoutesInfo = RoutesInfo<TRouteTree>,
|
|
262
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
250
263
|
> {
|
|
251
|
-
|
|
264
|
+
types!: {
|
|
265
|
+
RootRoute: TRouteTree
|
|
266
|
+
RoutesInfo: TRoutesInfo
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
options: PickAsRequired<
|
|
253
|
-
RouterOptions<
|
|
254
|
-
'stringifySearch' | 'parseSearch'
|
|
270
|
+
RouterOptions<TRouteTree, TDehydrated>,
|
|
271
|
+
'stringifySearch' | 'parseSearch' | 'context'
|
|
255
272
|
>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
cancelMatches: () => void
|
|
281
|
-
load: (next?: Location) => Promise<void>
|
|
282
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
283
|
-
cleanMatchCache: () => void
|
|
284
|
-
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
285
|
-
id: TId,
|
|
286
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
287
|
-
loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
|
|
288
|
-
preloadRoute: (
|
|
289
|
-
navigateOpts: BuildNextOptions,
|
|
290
|
-
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
291
|
-
) => Promise<RouteMatch[]>
|
|
292
|
-
matchRoutes: (
|
|
293
|
-
pathname: string,
|
|
294
|
-
opts?: { strictParseParams?: boolean },
|
|
295
|
-
) => RouteMatch[]
|
|
296
|
-
loadMatches: (
|
|
297
|
-
resolvedMatches: RouteMatch[],
|
|
298
|
-
loaderOpts?:
|
|
299
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
300
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
301
|
-
) => Promise<void>
|
|
302
|
-
invalidateRoute: (opts: MatchLocation) => void
|
|
303
|
-
reload: () => Promise<void>
|
|
304
|
-
resolvePath: (from: string, path: string) => string
|
|
305
|
-
navigate: <
|
|
306
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
307
|
-
TTo extends string = '.',
|
|
308
|
-
>(
|
|
309
|
-
opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
|
|
310
|
-
) => Promise<void>
|
|
311
|
-
matchRoute: <
|
|
312
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
313
|
-
TTo extends string = '.',
|
|
314
|
-
>(
|
|
315
|
-
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
316
|
-
opts?: MatchRouteOptions,
|
|
317
|
-
) => boolean
|
|
318
|
-
buildLink: <
|
|
319
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
320
|
-
TTo extends string = '.',
|
|
321
|
-
>(
|
|
322
|
-
opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
|
|
323
|
-
) => LinkInfo
|
|
324
|
-
dehydrateState: () => DehydratedRouterState
|
|
325
|
-
hydrateState: (state: DehydratedRouterState) => void
|
|
326
|
-
__: {
|
|
327
|
-
buildRouteTree: (
|
|
328
|
-
routeConfig: RouteConfig,
|
|
329
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
330
|
-
parseLocation: (
|
|
331
|
-
location: History['location'],
|
|
332
|
-
previousLocation?: Location,
|
|
333
|
-
) => Location
|
|
334
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
335
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
336
|
-
navigate: (
|
|
337
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
338
|
-
) => Promise<void>
|
|
339
|
-
}
|
|
340
|
-
}
|
|
273
|
+
history!: RouterHistory
|
|
274
|
+
#unsubHistory?: () => void
|
|
275
|
+
basepath!: string
|
|
276
|
+
routeTree!: RootRoute
|
|
277
|
+
routesById!: RoutesById<TRoutesInfo>
|
|
278
|
+
routesByPath!: RoutesByPath<TRoutesInfo>
|
|
279
|
+
flatRoutes!: TRoutesInfo['routesByFullPath'][keyof TRoutesInfo['routesByFullPath']][]
|
|
280
|
+
navigateTimeout: undefined | Timeout
|
|
281
|
+
nextAction: undefined | 'push' | 'replace'
|
|
282
|
+
navigationPromise: undefined | Promise<void>
|
|
283
|
+
|
|
284
|
+
__store: Store<RouterState<TRoutesInfo>>
|
|
285
|
+
state: RouterState<TRoutesInfo>
|
|
286
|
+
dehydratedData?: TDehydrated
|
|
287
|
+
|
|
288
|
+
constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
|
|
289
|
+
this.options = {
|
|
290
|
+
defaultPreloadDelay: 50,
|
|
291
|
+
context: undefined!,
|
|
292
|
+
...options,
|
|
293
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
294
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
295
|
+
// fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
|
|
296
|
+
}
|
|
341
297
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
298
|
+
this.__store = new Store<RouterState<TRoutesInfo>>(
|
|
299
|
+
getInitialRouterState(),
|
|
300
|
+
{
|
|
301
|
+
onUpdate: () => {
|
|
302
|
+
const prev = this.state
|
|
345
303
|
|
|
346
|
-
|
|
347
|
-
const createDefaultHistory = () =>
|
|
348
|
-
isServer ? createMemoryHistory() : createBrowserHistory()
|
|
304
|
+
this.state = this.__store.state
|
|
349
305
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
location: null!,
|
|
354
|
-
matches: [],
|
|
355
|
-
actions: {},
|
|
356
|
-
loaders: {},
|
|
357
|
-
lastUpdated: Date.now(),
|
|
358
|
-
isFetching: false,
|
|
359
|
-
isPreloading: false,
|
|
360
|
-
}
|
|
361
|
-
}
|
|
306
|
+
const matchesByIdChanged = prev.matchesById !== this.state.matchesById
|
|
307
|
+
let matchesChanged
|
|
308
|
+
let pendingMatchesChanged
|
|
362
309
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
userOptions?: RouterOptions<TRouteConfig>,
|
|
368
|
-
): Router<TRouteConfig, TAllRouteInfo> {
|
|
369
|
-
const history = userOptions?.history || createDefaultHistory()
|
|
370
|
-
|
|
371
|
-
const originalOptions = {
|
|
372
|
-
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
373
|
-
defaultLoaderMaxAge: 0,
|
|
374
|
-
defaultPreloadMaxAge: 2000,
|
|
375
|
-
defaultPreloadDelay: 50,
|
|
376
|
-
...userOptions,
|
|
377
|
-
stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
|
|
378
|
-
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
379
|
-
}
|
|
310
|
+
if (!matchesByIdChanged) {
|
|
311
|
+
matchesChanged =
|
|
312
|
+
prev.matchIds.length !== this.state.matchIds.length ||
|
|
313
|
+
prev.matchIds.some((d, i) => d !== this.state.matchIds[i])
|
|
380
314
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
routesById: {} as any,
|
|
389
|
-
location: undefined!,
|
|
390
|
-
allRouteInfo: undefined!,
|
|
391
|
-
//
|
|
392
|
-
navigationPromise: Promise.resolve(),
|
|
393
|
-
resolveNavigation: () => {},
|
|
394
|
-
matchCache: {},
|
|
395
|
-
state: getInitialRouterState(),
|
|
396
|
-
reset: () => {
|
|
397
|
-
router.state = getInitialRouterState()
|
|
398
|
-
router.notify()
|
|
399
|
-
},
|
|
400
|
-
startedLoadingAt: Date.now(),
|
|
401
|
-
subscribe: (listener: Listener): (() => void) => {
|
|
402
|
-
router.listeners.push(listener as Listener)
|
|
403
|
-
return () => {
|
|
404
|
-
router.listeners = router.listeners.filter((x) => x !== listener)
|
|
405
|
-
}
|
|
406
|
-
},
|
|
407
|
-
getRoute: (id) => {
|
|
408
|
-
return router.routesById[id]
|
|
409
|
-
},
|
|
410
|
-
notify: (): void => {
|
|
411
|
-
const isFetching =
|
|
412
|
-
router.state.status === 'loading' ||
|
|
413
|
-
router.state.matches.some((d) => d.isFetching)
|
|
414
|
-
|
|
415
|
-
const isPreloading = Object.values(router.matchCache).some(
|
|
416
|
-
(d) =>
|
|
417
|
-
d.match.isFetching &&
|
|
418
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
419
|
-
)
|
|
315
|
+
pendingMatchesChanged =
|
|
316
|
+
prev.pendingMatchIds.length !==
|
|
317
|
+
this.state.pendingMatchIds.length ||
|
|
318
|
+
prev.pendingMatchIds.some(
|
|
319
|
+
(d, i) => d !== this.state.pendingMatchIds[i],
|
|
320
|
+
)
|
|
321
|
+
}
|
|
420
322
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
...router.state,
|
|
427
|
-
isFetching,
|
|
428
|
-
isPreloading,
|
|
429
|
-
}
|
|
430
|
-
}
|
|
323
|
+
if (matchesByIdChanged || matchesChanged) {
|
|
324
|
+
this.state.matches = this.state.matchIds.map((id) => {
|
|
325
|
+
return this.state.matchesById[id] as any
|
|
326
|
+
})
|
|
327
|
+
}
|
|
431
328
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
329
|
+
if (matchesByIdChanged || pendingMatchesChanged) {
|
|
330
|
+
this.state.pendingMatches = this.state.pendingMatchIds.map((id) => {
|
|
331
|
+
return this.state.matchesById[id] as any
|
|
332
|
+
})
|
|
333
|
+
}
|
|
435
334
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
'loaderData',
|
|
445
|
-
'isInvalid',
|
|
446
|
-
'invalidAt',
|
|
447
|
-
]),
|
|
448
|
-
),
|
|
449
|
-
}
|
|
450
|
-
},
|
|
335
|
+
this.state.isFetching = [
|
|
336
|
+
...this.state.matches,
|
|
337
|
+
...this.state.pendingMatches,
|
|
338
|
+
].some((d) => d.isFetching)
|
|
339
|
+
},
|
|
340
|
+
defaultPriority: 'low',
|
|
341
|
+
},
|
|
342
|
+
)
|
|
451
343
|
|
|
452
|
-
|
|
453
|
-
// Match the routes
|
|
454
|
-
const matches = router.matchRoutes(router.location.pathname, {
|
|
455
|
-
strictParseParams: true,
|
|
456
|
-
})
|
|
344
|
+
this.state = this.__store.state
|
|
457
345
|
|
|
458
|
-
|
|
459
|
-
const dehydratedMatch = dehydratedState.matches[index]
|
|
460
|
-
invariant(
|
|
461
|
-
dehydratedMatch,
|
|
462
|
-
'Oh no! Dehydrated route matches did not match the active state of the router 😬',
|
|
463
|
-
)
|
|
464
|
-
Object.assign(match, dehydratedMatch)
|
|
465
|
-
})
|
|
346
|
+
this.update(options)
|
|
466
347
|
|
|
467
|
-
|
|
348
|
+
const next = this.buildNext({
|
|
349
|
+
hash: true,
|
|
350
|
+
fromCurrent: true,
|
|
351
|
+
search: true,
|
|
352
|
+
state: true,
|
|
353
|
+
})
|
|
468
354
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
},
|
|
355
|
+
if (this.state.location.href !== next.href) {
|
|
356
|
+
this.#commitLocation({ ...next, replace: true })
|
|
357
|
+
}
|
|
358
|
+
}
|
|
475
359
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
search: true,
|
|
480
|
-
hash: true,
|
|
481
|
-
})
|
|
360
|
+
reset = () => {
|
|
361
|
+
this.__store.setState((s) => Object.assign(s, getInitialRouterState()))
|
|
362
|
+
}
|
|
482
363
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
364
|
+
mount = () => {
|
|
365
|
+
// If the router matches are empty, start loading the matches
|
|
366
|
+
// if (!this.state.matches.length) {
|
|
367
|
+
this.safeLoad()
|
|
368
|
+
// }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
update = (opts?: RouterOptions<any, any>): this => {
|
|
372
|
+
this.options = {
|
|
373
|
+
...this.options,
|
|
374
|
+
...opts,
|
|
375
|
+
context: {
|
|
376
|
+
...this.options.context,
|
|
377
|
+
...opts?.context,
|
|
378
|
+
},
|
|
379
|
+
}
|
|
488
380
|
|
|
489
|
-
|
|
490
|
-
|
|
381
|
+
if (
|
|
382
|
+
!this.history ||
|
|
383
|
+
(this.options.history && this.options.history !== this.history)
|
|
384
|
+
) {
|
|
385
|
+
if (this.#unsubHistory) {
|
|
386
|
+
this.#unsubHistory()
|
|
491
387
|
}
|
|
492
388
|
|
|
493
|
-
|
|
494
|
-
|
|
389
|
+
this.history =
|
|
390
|
+
this.options.history ??
|
|
391
|
+
(isServer ? createMemoryHistory() : createBrowserHistory()!)
|
|
392
|
+
|
|
393
|
+
const parsedLocation = this.#parseLocation()
|
|
394
|
+
|
|
395
|
+
this.__store.setState((s) => ({
|
|
396
|
+
...s,
|
|
397
|
+
resolvedLocation: parsedLocation,
|
|
398
|
+
location: parsedLocation,
|
|
399
|
+
}))
|
|
400
|
+
|
|
401
|
+
this.#unsubHistory = this.history.listen(() => {
|
|
402
|
+
this.safeLoad({
|
|
403
|
+
next: this.#parseLocation(this.state.location),
|
|
404
|
+
})
|
|
495
405
|
})
|
|
406
|
+
}
|
|
496
407
|
|
|
497
|
-
|
|
498
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
499
|
-
if (!isServer && window.addEventListener) {
|
|
500
|
-
// Listen to visibillitychange and focus
|
|
501
|
-
window.addEventListener('visibilitychange', router.onFocus, false)
|
|
502
|
-
window.addEventListener('focus', router.onFocus, false)
|
|
503
|
-
}
|
|
408
|
+
const { basepath, routeTree } = this.options
|
|
504
409
|
|
|
505
|
-
|
|
506
|
-
unsub()
|
|
507
|
-
if (!isServer && window.removeEventListener) {
|
|
508
|
-
// Be sure to unsubscribe if a new handler is set
|
|
509
|
-
window.removeEventListener('visibilitychange', router.onFocus)
|
|
510
|
-
window.removeEventListener('focus', router.onFocus)
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
},
|
|
410
|
+
this.basepath = `/${trimPath(basepath ?? '') ?? ''}`
|
|
514
411
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
412
|
+
if (routeTree && routeTree !== this.routeTree) {
|
|
413
|
+
this.#buildRouteTree(routeTree)
|
|
414
|
+
}
|
|
518
415
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (!router.location || newHistory) {
|
|
522
|
-
if (opts?.history) {
|
|
523
|
-
router.history = opts.history
|
|
524
|
-
}
|
|
525
|
-
router.location = router.__.parseLocation(router.history.location)
|
|
526
|
-
router.state.location = router.location
|
|
527
|
-
}
|
|
416
|
+
return this
|
|
417
|
+
}
|
|
528
418
|
|
|
529
|
-
|
|
419
|
+
buildNext = (opts: BuildNextOptions): ParsedLocation => {
|
|
420
|
+
const next = this.#buildLocation(opts)
|
|
530
421
|
|
|
531
|
-
|
|
422
|
+
const __matches = this.matchRoutes(next.pathname, next.search)
|
|
532
423
|
|
|
533
|
-
|
|
424
|
+
return this.#buildLocation({
|
|
425
|
+
...opts,
|
|
426
|
+
__matches,
|
|
427
|
+
})
|
|
428
|
+
}
|
|
534
429
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
430
|
+
cancelMatches = () => {
|
|
431
|
+
this.state.matches.forEach((match) => {
|
|
432
|
+
this.cancelMatch(match.id)
|
|
433
|
+
})
|
|
434
|
+
}
|
|
539
435
|
|
|
540
|
-
|
|
541
|
-
|
|
436
|
+
cancelMatch = (id: string) => {
|
|
437
|
+
this.getRouteMatch(id)?.abortController?.abort()
|
|
438
|
+
}
|
|
542
439
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
})
|
|
550
|
-
},
|
|
440
|
+
safeLoad = (opts?: { next?: ParsedLocation }) => {
|
|
441
|
+
return this.load(opts).catch((err) => {
|
|
442
|
+
// console.warn(err)
|
|
443
|
+
// invariant(false, 'Encountered an error during router.load()! ☝️.')
|
|
444
|
+
})
|
|
445
|
+
}
|
|
551
446
|
|
|
552
|
-
|
|
553
|
-
const id = Math.random()
|
|
554
|
-
router.startedLoadingAt = id
|
|
447
|
+
latestLoadPromise: Promise<void> = Promise.resolve()
|
|
555
448
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
449
|
+
load = async (opts?: { next?: ParsedLocation; throwOnError?: boolean }) => {
|
|
450
|
+
const promise = new Promise<void>(async (resolve, reject) => {
|
|
451
|
+
let latestPromise: Promise<void> | undefined | null
|
|
452
|
+
|
|
453
|
+
const checkLatest = (): undefined | Promise<void> | null => {
|
|
454
|
+
return this.latestLoadPromise !== promise
|
|
455
|
+
? this.latestLoadPromise
|
|
456
|
+
: undefined
|
|
559
457
|
}
|
|
560
458
|
|
|
561
459
|
// Cancel any pending matches
|
|
562
|
-
|
|
460
|
+
// this.cancelMatches()
|
|
563
461
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
462
|
+
let pendingMatches!: RouteMatch<any, any>[]
|
|
463
|
+
|
|
464
|
+
this.__store.batch(() => {
|
|
465
|
+
if (opts?.next) {
|
|
466
|
+
// Ingest the new location
|
|
467
|
+
this.__store.setState((s) => ({
|
|
468
|
+
...s,
|
|
469
|
+
location: opts.next!,
|
|
470
|
+
}))
|
|
471
|
+
}
|
|
568
472
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
473
|
+
// Match the routes
|
|
474
|
+
pendingMatches = this.matchRoutes(
|
|
475
|
+
this.state.location.pathname,
|
|
476
|
+
this.state.location.search,
|
|
477
|
+
{
|
|
478
|
+
throwOnError: opts?.throwOnError,
|
|
479
|
+
debug: true,
|
|
575
480
|
},
|
|
576
|
-
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
this.__store.setState((s) => ({
|
|
484
|
+
...s,
|
|
485
|
+
status: 'pending',
|
|
486
|
+
pendingMatchIds: pendingMatches.map((d) => d.id),
|
|
487
|
+
matchesById: this.#mergeMatches(s.matchesById, pendingMatches),
|
|
488
|
+
}))
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
// Load the matches
|
|
493
|
+
await this.loadMatches(pendingMatches)
|
|
494
|
+
|
|
495
|
+
// Only apply the latest transition
|
|
496
|
+
if ((latestPromise = checkLatest())) {
|
|
497
|
+
return await latestPromise
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const prevLocation = this.state.resolvedLocation
|
|
501
|
+
|
|
502
|
+
this.__store.setState((s) => ({
|
|
503
|
+
...s,
|
|
504
|
+
status: 'idle',
|
|
505
|
+
resolvedLocation: s.location,
|
|
506
|
+
matchIds: s.pendingMatchIds,
|
|
507
|
+
pendingMatchIds: [],
|
|
508
|
+
}))
|
|
509
|
+
|
|
510
|
+
if (prevLocation!.href !== this.state.location.href) {
|
|
511
|
+
this.options.onRouteChange?.()
|
|
577
512
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
513
|
+
|
|
514
|
+
resolve()
|
|
515
|
+
} catch (err) {
|
|
516
|
+
// Only apply the latest transition
|
|
517
|
+
if ((latestPromise = checkLatest())) {
|
|
518
|
+
return await latestPromise
|
|
584
519
|
}
|
|
520
|
+
|
|
521
|
+
reject(err)
|
|
585
522
|
}
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
this.latestLoadPromise = promise
|
|
586
526
|
|
|
587
|
-
|
|
527
|
+
return this.latestLoadPromise
|
|
528
|
+
}
|
|
588
529
|
|
|
589
|
-
|
|
590
|
-
|
|
530
|
+
#mergeMatches = (
|
|
531
|
+
prevMatchesById: Record<
|
|
532
|
+
string,
|
|
533
|
+
RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>
|
|
534
|
+
>,
|
|
535
|
+
nextMatches: RouteMatch[],
|
|
536
|
+
): Record<
|
|
537
|
+
string,
|
|
538
|
+
RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>
|
|
539
|
+
> => {
|
|
540
|
+
const nextMatchesById: any = {
|
|
541
|
+
...prevMatchesById,
|
|
542
|
+
}
|
|
591
543
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
544
|
+
let hadNew = false
|
|
545
|
+
|
|
546
|
+
nextMatches.forEach((match) => {
|
|
547
|
+
if (!nextMatchesById[match.id]) {
|
|
548
|
+
hadNew = true
|
|
549
|
+
nextMatchesById[match.id] = match
|
|
595
550
|
}
|
|
551
|
+
})
|
|
596
552
|
|
|
597
|
-
|
|
553
|
+
if (!hadNew) {
|
|
554
|
+
return prevMatchesById
|
|
555
|
+
}
|
|
598
556
|
|
|
599
|
-
|
|
600
|
-
|
|
557
|
+
return nextMatchesById
|
|
558
|
+
}
|
|
601
559
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
exiting.push(d)
|
|
607
|
-
}
|
|
608
|
-
})
|
|
560
|
+
getRoute = <TId extends keyof TRoutesInfo['routesById']>(
|
|
561
|
+
id: TId,
|
|
562
|
+
): TRoutesInfo['routesById'][TId] => {
|
|
563
|
+
const route = this.routesById[id]
|
|
609
564
|
|
|
610
|
-
|
|
611
|
-
return !previousMatches.find((dd) => dd.matchId === d.matchId)
|
|
612
|
-
})
|
|
565
|
+
invariant(route, `Route with id "${id as string}" not found`)
|
|
613
566
|
|
|
614
|
-
|
|
567
|
+
return route
|
|
568
|
+
}
|
|
615
569
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
570
|
+
preloadRoute = async (
|
|
571
|
+
navigateOpts: BuildNextOptions & {
|
|
572
|
+
maxAge?: number
|
|
573
|
+
} = this.state.location,
|
|
574
|
+
) => {
|
|
575
|
+
const next = this.buildNext(navigateOpts)
|
|
576
|
+
const matches = this.matchRoutes(next.pathname, next.search, {
|
|
577
|
+
throwOnError: true,
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
this.__store.setState((s) => {
|
|
581
|
+
return {
|
|
582
|
+
...s,
|
|
583
|
+
matchesById: this.#mergeMatches(s.matchesById, matches),
|
|
584
|
+
}
|
|
585
|
+
})
|
|
621
586
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
587
|
+
await this.loadMatches(matches, {
|
|
588
|
+
preload: true,
|
|
589
|
+
maxAge: navigateOpts.maxAge,
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
return matches
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
cleanMatches = () => {
|
|
596
|
+
const now = Date.now()
|
|
597
|
+
|
|
598
|
+
const outdatedMatchIds = Object.values(this.state.matchesById)
|
|
599
|
+
.filter((match) => {
|
|
600
|
+
const route = this.getRoute(match.routeId)
|
|
601
|
+
return (
|
|
602
|
+
!this.state.matchIds.includes(match.id) &&
|
|
603
|
+
!this.state.pendingMatchIds.includes(match.id) &&
|
|
604
|
+
match.preloadInvalidAt < now &&
|
|
605
|
+
(route.options.gcMaxAge
|
|
606
|
+
? match.updatedAt + route.options.gcMaxAge < now
|
|
607
|
+
: true)
|
|
630
608
|
)
|
|
631
|
-
if (gc > 0) {
|
|
632
|
-
router.matchCache[d.matchId] = {
|
|
633
|
-
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
634
|
-
match: d,
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
609
|
})
|
|
610
|
+
.map((d) => d.id)
|
|
638
611
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
612
|
+
if (outdatedMatchIds.length) {
|
|
613
|
+
this.__store.setState((s) => {
|
|
614
|
+
const matchesById = { ...s.matchesById }
|
|
615
|
+
outdatedMatchIds.forEach((id) => {
|
|
616
|
+
delete matchesById[id]
|
|
643
617
|
})
|
|
618
|
+
return {
|
|
619
|
+
...s,
|
|
620
|
+
matchesById,
|
|
621
|
+
}
|
|
644
622
|
})
|
|
623
|
+
}
|
|
624
|
+
}
|
|
645
625
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
626
|
+
matchRoutes = (
|
|
627
|
+
pathname: string,
|
|
628
|
+
locationSearch: AnySearchSchema,
|
|
629
|
+
opts?: { throwOnError?: boolean; debug?: boolean },
|
|
630
|
+
): RouteMatch<TRoutesInfo, TRoutesInfo['routeIntersection']>[] => {
|
|
631
|
+
let routeParams: AnyPathParams = {}
|
|
632
|
+
|
|
633
|
+
let foundRoute = this.flatRoutes.find((route) => {
|
|
634
|
+
const matchedParams = matchPathname(this.basepath, pathname, {
|
|
635
|
+
to: route.fullPath,
|
|
636
|
+
caseSensitive:
|
|
637
|
+
route.options.caseSensitive ?? this.options.caseSensitive,
|
|
652
638
|
})
|
|
653
639
|
|
|
654
|
-
if (
|
|
655
|
-
|
|
656
|
-
return
|
|
640
|
+
if (matchedParams) {
|
|
641
|
+
routeParams = matchedParams
|
|
642
|
+
return true
|
|
657
643
|
}
|
|
658
644
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (match.action) {
|
|
662
|
-
match.action.current = undefined
|
|
663
|
-
match.action.submissions = []
|
|
664
|
-
}
|
|
665
|
-
})
|
|
645
|
+
return false
|
|
646
|
+
})
|
|
666
647
|
|
|
667
|
-
|
|
668
|
-
...router.state,
|
|
669
|
-
location: router.location,
|
|
670
|
-
matches,
|
|
671
|
-
pending: undefined,
|
|
672
|
-
status: 'idle',
|
|
673
|
-
}
|
|
648
|
+
let routeCursor = foundRoute || (this.routesById['__root__'] as any)
|
|
674
649
|
|
|
675
|
-
|
|
676
|
-
router.resolveNavigation()
|
|
677
|
-
},
|
|
650
|
+
let matchedRoutes: AnyRoute[] = [routeCursor]
|
|
678
651
|
|
|
679
|
-
|
|
680
|
-
|
|
652
|
+
while (routeCursor?.parentRoute) {
|
|
653
|
+
routeCursor = routeCursor.parentRoute
|
|
654
|
+
if (routeCursor) matchedRoutes.unshift(routeCursor)
|
|
655
|
+
}
|
|
681
656
|
|
|
682
|
-
|
|
683
|
-
|
|
657
|
+
// Alright, by now we should have all of our
|
|
658
|
+
// matching routes and their param pairs, let's
|
|
659
|
+
// Turn them into actual `Match` objects and
|
|
660
|
+
// accumulate the params into a single params bag
|
|
661
|
+
let allParams = {}
|
|
684
662
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return
|
|
688
|
-
}
|
|
663
|
+
// Existing matches are matches that are already loaded along with
|
|
664
|
+
// pending matches that are still loading
|
|
689
665
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
666
|
+
const matches = matchedRoutes.map((route) => {
|
|
667
|
+
let parsedParams
|
|
668
|
+
let parsedParamsError
|
|
669
|
+
|
|
670
|
+
try {
|
|
671
|
+
parsedParams =
|
|
672
|
+
(route.options.parseParams as any)?.(routeParams!) ?? routeParams
|
|
673
|
+
// (typeof route.options.parseParams === 'object' &&
|
|
674
|
+
// route.options.parseParams.parse
|
|
675
|
+
// ? route.options.parseParams.parse(routeParams)
|
|
676
|
+
// : (route.options.parseParams as any)?.(routeParams!)) ?? routeParams
|
|
677
|
+
} catch (err: any) {
|
|
678
|
+
parsedParamsError = new PathParamError(err.message, {
|
|
679
|
+
cause: err,
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
if (opts?.throwOnError) {
|
|
683
|
+
throw parsedParamsError
|
|
693
684
|
}
|
|
685
|
+
}
|
|
694
686
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
})
|
|
698
|
-
},
|
|
687
|
+
// Add the parsed params to the accumulated params bag
|
|
688
|
+
Object.assign(allParams, parsedParams)
|
|
699
689
|
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
},
|
|
690
|
+
const interpolatedPath = interpolatePath(route.path, allParams)
|
|
691
|
+
const key = route.options.key
|
|
692
|
+
? route.options.key({
|
|
693
|
+
params: allParams,
|
|
694
|
+
search: locationSearch,
|
|
695
|
+
}) ?? ''
|
|
696
|
+
: ''
|
|
708
697
|
|
|
709
|
-
|
|
710
|
-
const next = router.buildNext(navigateOpts)
|
|
711
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
712
|
-
strictParseParams: true,
|
|
713
|
-
})
|
|
714
|
-
await router.loadMatches(matches, {
|
|
715
|
-
preload: true,
|
|
716
|
-
maxAge:
|
|
717
|
-
loaderOpts.maxAge ??
|
|
718
|
-
router.options.defaultPreloadMaxAge ??
|
|
719
|
-
router.options.defaultLoaderMaxAge ??
|
|
720
|
-
0,
|
|
721
|
-
gcMaxAge:
|
|
722
|
-
loaderOpts.gcMaxAge ??
|
|
723
|
-
router.options.defaultPreloadGcMaxAge ??
|
|
724
|
-
router.options.defaultLoaderGcMaxAge ??
|
|
725
|
-
0,
|
|
726
|
-
})
|
|
727
|
-
return matches
|
|
728
|
-
},
|
|
698
|
+
const stringifiedKey = key ? JSON.stringify(key) : ''
|
|
729
699
|
|
|
730
|
-
|
|
731
|
-
|
|
700
|
+
const matchId =
|
|
701
|
+
interpolatePath(route.id, allParams, true) + stringifiedKey
|
|
702
|
+
|
|
703
|
+
// Waste not, want not. If we already have a match for this route,
|
|
704
|
+
// reuse it. This is important for layout routes, which might stick
|
|
705
|
+
// around between navigation actions that only change leaf routes.
|
|
706
|
+
const existingMatch = this.getRouteMatch(matchId)
|
|
707
|
+
|
|
708
|
+
if (existingMatch) {
|
|
709
|
+
return { ...existingMatch }
|
|
710
|
+
}
|
|
732
711
|
|
|
733
|
-
|
|
712
|
+
// Create a fresh route match
|
|
713
|
+
const hasLoaders = !!(
|
|
714
|
+
route.options.loader ||
|
|
715
|
+
componentTypes.some((d) => (route.options[d] as any)?.preload)
|
|
716
|
+
)
|
|
734
717
|
|
|
735
|
-
|
|
736
|
-
|
|
718
|
+
const routeMatch: RouteMatch = {
|
|
719
|
+
id: matchId,
|
|
720
|
+
key: stringifiedKey,
|
|
721
|
+
routeId: route.id,
|
|
722
|
+
params: allParams,
|
|
723
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
724
|
+
updatedAt: Date.now(),
|
|
725
|
+
invalidAt: Infinity,
|
|
726
|
+
preloadInvalidAt: Infinity,
|
|
727
|
+
routeSearch: {},
|
|
728
|
+
search: {} as any,
|
|
729
|
+
status: hasLoaders ? 'idle' : 'success',
|
|
730
|
+
isFetching: false,
|
|
731
|
+
invalid: false,
|
|
732
|
+
error: undefined,
|
|
733
|
+
paramsError: parsedParamsError,
|
|
734
|
+
searchError: undefined,
|
|
735
|
+
loaderData: undefined,
|
|
736
|
+
loadPromise: Promise.resolve(),
|
|
737
|
+
routeContext: undefined!,
|
|
738
|
+
context: undefined!,
|
|
739
|
+
abortController: new AbortController(),
|
|
740
|
+
fetchedAt: 0,
|
|
737
741
|
}
|
|
738
742
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
+
return routeMatch
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
// Take each match and resolve its search params and context
|
|
747
|
+
// This has to happen after the matches are created or found
|
|
748
|
+
// so that we can use the parent match's search params and context
|
|
749
|
+
matches.forEach((match, i): any => {
|
|
750
|
+
const parentMatch = matches[i - 1]
|
|
751
|
+
const route = this.getRoute(match.routeId)
|
|
752
|
+
|
|
753
|
+
const searchInfo = (() => {
|
|
754
|
+
// Validate the search params and stabilize them
|
|
755
|
+
const parentSearchInfo = {
|
|
756
|
+
search: parentMatch?.search ?? locationSearch,
|
|
757
|
+
routeSearch: parentMatch?.routeSearch ?? locationSearch,
|
|
758
|
+
}
|
|
743
759
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
760
|
+
try {
|
|
761
|
+
const validator =
|
|
762
|
+
typeof route.options.validateSearch === 'object'
|
|
763
|
+
? route.options.validateSearch.parse
|
|
764
|
+
: route.options.validateSearch
|
|
747
765
|
|
|
748
|
-
|
|
766
|
+
const routeSearch = validator?.(parentSearchInfo.search) ?? {}
|
|
749
767
|
|
|
750
|
-
|
|
768
|
+
const search = {
|
|
769
|
+
...parentSearchInfo.search,
|
|
770
|
+
...routeSearch,
|
|
771
|
+
}
|
|
751
772
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
773
|
+
return {
|
|
774
|
+
routeSearch: replaceEqualDeep(match.routeSearch, routeSearch),
|
|
775
|
+
search: replaceEqualDeep(match.search, search),
|
|
776
|
+
}
|
|
777
|
+
} catch (err: any) {
|
|
778
|
+
match.searchError = new SearchParamError(err.message, {
|
|
779
|
+
cause: err,
|
|
780
|
+
})
|
|
760
781
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
782
|
+
if (opts?.throwOnError) {
|
|
783
|
+
throw match.searchError
|
|
784
|
+
}
|
|
764
785
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
786
|
+
return parentSearchInfo
|
|
787
|
+
}
|
|
788
|
+
})()
|
|
789
|
+
|
|
790
|
+
const contextInfo = (() => {
|
|
791
|
+
try {
|
|
792
|
+
const routeContext =
|
|
793
|
+
route.options.getContext?.({
|
|
794
|
+
parentContext: parentMatch?.routeContext ?? {},
|
|
795
|
+
context: parentMatch?.context ?? this?.options.context ?? {},
|
|
796
|
+
params: match.params,
|
|
797
|
+
search: match.search,
|
|
798
|
+
}) || ({} as any)
|
|
799
|
+
|
|
800
|
+
const context = {
|
|
801
|
+
...(parentMatch?.context ?? this?.options.context),
|
|
802
|
+
...routeContext,
|
|
803
|
+
} as any
|
|
804
|
+
|
|
805
|
+
return {
|
|
806
|
+
context,
|
|
807
|
+
routeContext,
|
|
808
|
+
}
|
|
809
|
+
} catch (err) {
|
|
810
|
+
route.options.onError?.(err)
|
|
811
|
+
throw err
|
|
812
|
+
}
|
|
813
|
+
})()
|
|
771
814
|
|
|
772
|
-
|
|
773
|
-
|
|
815
|
+
Object.assign(match, {
|
|
816
|
+
...searchInfo,
|
|
817
|
+
...contextInfo,
|
|
818
|
+
})
|
|
819
|
+
})
|
|
774
820
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
route.options.parseParams?.(matchParams!) ?? matchParams
|
|
778
|
-
} catch (err) {
|
|
779
|
-
if (opts?.strictParseParams) {
|
|
780
|
-
throw err
|
|
781
|
-
}
|
|
782
|
-
}
|
|
821
|
+
return matches as any
|
|
822
|
+
}
|
|
783
823
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
824
|
+
loadMatches = async (
|
|
825
|
+
resolvedMatches: AnyRouteMatch[],
|
|
826
|
+
opts?: {
|
|
827
|
+
preload?: boolean
|
|
828
|
+
maxAge?: number
|
|
829
|
+
},
|
|
830
|
+
) => {
|
|
831
|
+
this.cleanMatches()
|
|
832
|
+
|
|
833
|
+
let firstBadMatchIndex: number | undefined
|
|
834
|
+
|
|
835
|
+
// Check each match middleware to see if the route can be accessed
|
|
836
|
+
try {
|
|
837
|
+
await Promise.all(
|
|
838
|
+
resolvedMatches.map(async (match, index) => {
|
|
839
|
+
const route = this.getRoute(match.routeId)
|
|
840
|
+
|
|
841
|
+
if (!opts?.preload) {
|
|
842
|
+
// Update each match with its latest url data
|
|
843
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
844
|
+
...s,
|
|
845
|
+
routeSearch: match.routeSearch,
|
|
846
|
+
search: match.search,
|
|
847
|
+
routeContext: match.routeContext,
|
|
848
|
+
context: match.context,
|
|
849
|
+
error: match.error,
|
|
850
|
+
paramsError: match.paramsError,
|
|
851
|
+
searchError: match.searchError,
|
|
852
|
+
params: match.params,
|
|
853
|
+
}))
|
|
854
|
+
}
|
|
789
855
|
|
|
790
|
-
|
|
791
|
-
|
|
856
|
+
const handleError = (
|
|
857
|
+
err: any,
|
|
858
|
+
handler: undefined | ((err: any) => void),
|
|
859
|
+
) => {
|
|
860
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
861
|
+
handler = handler || route.options.onError
|
|
862
|
+
|
|
863
|
+
if (isRedirect(err)) {
|
|
864
|
+
throw err
|
|
792
865
|
}
|
|
793
866
|
|
|
794
|
-
|
|
795
|
-
|
|
867
|
+
try {
|
|
868
|
+
handler?.(err)
|
|
869
|
+
} catch (errorHandlerErr) {
|
|
870
|
+
err = errorHandlerErr
|
|
796
871
|
|
|
797
|
-
|
|
798
|
-
|
|
872
|
+
if (isRedirect(errorHandlerErr)) {
|
|
873
|
+
throw errorHandlerErr
|
|
874
|
+
}
|
|
875
|
+
}
|
|
799
876
|
|
|
800
|
-
|
|
877
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
878
|
+
...s,
|
|
879
|
+
error: err,
|
|
880
|
+
status: 'error',
|
|
881
|
+
updatedAt: Date.now(),
|
|
882
|
+
}))
|
|
883
|
+
}
|
|
801
884
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
885
|
+
if (match.paramsError) {
|
|
886
|
+
handleError(match.paramsError, route.options.onParseParamsError)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (match.searchError) {
|
|
890
|
+
handleError(match.searchError, route.options.onValidateSearchError)
|
|
891
|
+
}
|
|
805
892
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const match =
|
|
811
|
-
existingMatches.find((d) => d.matchId === matchId) ||
|
|
812
|
-
router.matchCache[matchId]?.match ||
|
|
813
|
-
createRouteMatch(router, foundRoute, {
|
|
814
|
-
matchId,
|
|
815
|
-
params,
|
|
816
|
-
pathname: joinPaths([pathname, interpolatedPath]),
|
|
893
|
+
try {
|
|
894
|
+
await route.options.beforeLoad?.({
|
|
895
|
+
...match,
|
|
896
|
+
preload: !!opts?.preload,
|
|
817
897
|
})
|
|
898
|
+
} catch (err) {
|
|
899
|
+
handleError(err, route.options.onBeforeLoadError)
|
|
900
|
+
}
|
|
901
|
+
}),
|
|
902
|
+
)
|
|
903
|
+
} catch (err) {
|
|
904
|
+
if (!opts?.preload) {
|
|
905
|
+
this.navigate(err as any)
|
|
906
|
+
}
|
|
818
907
|
|
|
819
|
-
|
|
820
|
-
|
|
908
|
+
throw err
|
|
909
|
+
}
|
|
821
910
|
|
|
822
|
-
|
|
911
|
+
const validResolvedMatches = resolvedMatches.slice(0, firstBadMatchIndex)
|
|
912
|
+
const matchPromises: Promise<any>[] = []
|
|
913
|
+
|
|
914
|
+
validResolvedMatches.forEach((match, index) => {
|
|
915
|
+
matchPromises.push(
|
|
916
|
+
(async () => {
|
|
917
|
+
const parentMatchPromise = matchPromises[index - 1]
|
|
918
|
+
const route = this.getRoute(match.routeId)
|
|
919
|
+
|
|
920
|
+
if (
|
|
921
|
+
match.isFetching ||
|
|
922
|
+
(match.status === 'success' &&
|
|
923
|
+
!this.getIsInvalid({ matchId: match.id, preload: opts?.preload }))
|
|
924
|
+
) {
|
|
925
|
+
return this.getRouteMatch(match.id)?.loadPromise
|
|
926
|
+
}
|
|
823
927
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
928
|
+
const fetchedAt = Date.now()
|
|
929
|
+
const checkLatest = () => {
|
|
930
|
+
const latest = this.getRouteMatch(match.id)
|
|
931
|
+
return latest && latest.fetchedAt !== fetchedAt
|
|
932
|
+
? latest.loadPromise
|
|
933
|
+
: undefined
|
|
934
|
+
}
|
|
828
935
|
|
|
829
|
-
|
|
936
|
+
const loadPromise = (async () => {
|
|
937
|
+
let latestPromise
|
|
830
938
|
|
|
831
|
-
|
|
939
|
+
const componentsPromise = Promise.all(
|
|
940
|
+
componentTypes.map(async (type) => {
|
|
941
|
+
const component = route.options[type]
|
|
832
942
|
|
|
833
|
-
|
|
834
|
-
|
|
943
|
+
if ((component as any)?.preload) {
|
|
944
|
+
await (component as any).preload()
|
|
945
|
+
}
|
|
946
|
+
}),
|
|
947
|
+
)
|
|
835
948
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
949
|
+
const loaderPromise = route.options.loader?.({
|
|
950
|
+
...match,
|
|
951
|
+
preload: !!opts?.preload,
|
|
952
|
+
parentMatchPromise,
|
|
953
|
+
})
|
|
841
954
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
955
|
+
const handleError = (err: any) => {
|
|
956
|
+
if (isRedirect(err)) {
|
|
957
|
+
if (!opts?.preload) {
|
|
958
|
+
this.navigate(err as any)
|
|
959
|
+
}
|
|
960
|
+
return true
|
|
961
|
+
}
|
|
847
962
|
|
|
848
|
-
|
|
963
|
+
return false
|
|
964
|
+
}
|
|
849
965
|
|
|
850
|
-
|
|
851
|
-
|
|
966
|
+
try {
|
|
967
|
+
const [_, loader] = await Promise.all([
|
|
968
|
+
componentsPromise,
|
|
969
|
+
loaderPromise,
|
|
970
|
+
])
|
|
971
|
+
if ((latestPromise = checkLatest())) return await latestPromise
|
|
852
972
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
.matchRoutes(next.pathname)
|
|
857
|
-
.map((d) => d.matchId)
|
|
858
|
-
;[
|
|
859
|
-
...router.state.matches,
|
|
860
|
-
...(router.state.pending?.matches ?? []),
|
|
861
|
-
].forEach((match) => {
|
|
862
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
863
|
-
match.invalidate()
|
|
864
|
-
}
|
|
865
|
-
})
|
|
866
|
-
},
|
|
973
|
+
this.setRouteMatchData(match.id, () => loader, opts)
|
|
974
|
+
} catch (err) {
|
|
975
|
+
if ((latestPromise = checkLatest())) return await latestPromise
|
|
867
976
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
replace: true,
|
|
872
|
-
search: true,
|
|
873
|
-
}),
|
|
977
|
+
if (handleError(err)) {
|
|
978
|
+
return
|
|
979
|
+
}
|
|
874
980
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
},
|
|
981
|
+
const errorHandler =
|
|
982
|
+
route.options.onLoadError ?? route.options.onError
|
|
878
983
|
|
|
879
|
-
|
|
880
|
-
// const location = router.buildNext(opts)
|
|
984
|
+
let caughtError = err
|
|
881
985
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
: undefined,
|
|
887
|
-
}
|
|
986
|
+
try {
|
|
987
|
+
errorHandler?.(err)
|
|
988
|
+
} catch (errorHandlerErr) {
|
|
989
|
+
caughtError = errorHandlerErr
|
|
888
990
|
|
|
889
|
-
|
|
991
|
+
if (handleError(errorHandlerErr)) {
|
|
992
|
+
return
|
|
993
|
+
}
|
|
994
|
+
}
|
|
890
995
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
996
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
997
|
+
...s,
|
|
998
|
+
error: caughtError,
|
|
999
|
+
status: 'error',
|
|
1000
|
+
isFetching: false,
|
|
1001
|
+
updatedAt: Date.now(),
|
|
1002
|
+
}))
|
|
1003
|
+
}
|
|
1004
|
+
})()
|
|
1005
|
+
|
|
1006
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
1007
|
+
...s,
|
|
1008
|
+
status: s.status !== 'success' ? 'pending' : s.status,
|
|
1009
|
+
isFetching: true,
|
|
1010
|
+
loadPromise,
|
|
1011
|
+
fetchedAt,
|
|
1012
|
+
invalid: false,
|
|
1013
|
+
}))
|
|
1014
|
+
|
|
1015
|
+
await loadPromise
|
|
1016
|
+
})(),
|
|
1017
|
+
)
|
|
1018
|
+
})
|
|
900
1019
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
to: next.pathname,
|
|
904
|
-
})
|
|
905
|
-
},
|
|
1020
|
+
await Promise.all(matchPromises)
|
|
1021
|
+
}
|
|
906
1022
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1023
|
+
reload = () => {
|
|
1024
|
+
return this.navigate({
|
|
1025
|
+
fromCurrent: true,
|
|
1026
|
+
replace: true,
|
|
1027
|
+
search: true,
|
|
1028
|
+
} as any)
|
|
1029
|
+
}
|
|
910
1030
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const fromString = String(from)
|
|
1031
|
+
resolvePath = (from: string, path: string) => {
|
|
1032
|
+
return resolvePath(this.basepath!, from, cleanPath(path))
|
|
1033
|
+
}
|
|
915
1034
|
|
|
916
|
-
|
|
1035
|
+
navigate = async <TFrom extends string = '/', TTo extends string = ''>({
|
|
1036
|
+
from,
|
|
1037
|
+
to = '' as any,
|
|
1038
|
+
search,
|
|
1039
|
+
hash,
|
|
1040
|
+
replace,
|
|
1041
|
+
params,
|
|
1042
|
+
}: NavigateOptions<TRoutesInfo, TFrom, TTo>) => {
|
|
1043
|
+
// If this link simply reloads the current route,
|
|
1044
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1045
|
+
|
|
1046
|
+
// If this `to` is a valid external URL, return
|
|
1047
|
+
// null for LinkUtils
|
|
1048
|
+
const toString = String(to)
|
|
1049
|
+
const fromString = typeof from === 'undefined' ? from : String(from)
|
|
1050
|
+
let isExternal
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
new URL(`${toString}`)
|
|
1054
|
+
isExternal = true
|
|
1055
|
+
} catch (e) {}
|
|
1056
|
+
|
|
1057
|
+
invariant(
|
|
1058
|
+
!isExternal,
|
|
1059
|
+
'Attempting to navigate to external url with this.navigate!',
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
return this.#commitLocation({
|
|
1063
|
+
from: fromString,
|
|
1064
|
+
to: toString,
|
|
1065
|
+
search,
|
|
1066
|
+
hash,
|
|
1067
|
+
replace,
|
|
1068
|
+
params,
|
|
1069
|
+
})
|
|
1070
|
+
}
|
|
917
1071
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1072
|
+
matchRoute = <
|
|
1073
|
+
TFrom extends string = '/',
|
|
1074
|
+
TTo extends string = '',
|
|
1075
|
+
TResolved extends string = ResolveRelativePath<TFrom, NoInfer<TTo>>,
|
|
1076
|
+
>(
|
|
1077
|
+
location: ToOptions<TRoutesInfo, TFrom, TTo>,
|
|
1078
|
+
opts?: MatchRouteOptions,
|
|
1079
|
+
): false | TRoutesInfo['routesById'][TResolved]['__types']['allParams'] => {
|
|
1080
|
+
location = {
|
|
1081
|
+
...location,
|
|
1082
|
+
to: location.to
|
|
1083
|
+
? this.resolvePath(location.from ?? '', location.to)
|
|
1084
|
+
: undefined,
|
|
1085
|
+
} as any
|
|
1086
|
+
|
|
1087
|
+
const next = this.buildNext(location)
|
|
1088
|
+
if (opts?.pending && this.state.status !== 'pending') {
|
|
1089
|
+
return false
|
|
1090
|
+
}
|
|
922
1091
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
)
|
|
1092
|
+
const baseLocation = opts?.pending
|
|
1093
|
+
? this.state.location
|
|
1094
|
+
: this.state.resolvedLocation
|
|
927
1095
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1096
|
+
if (!baseLocation) {
|
|
1097
|
+
return false
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const match = matchPathname(this.basepath, baseLocation.pathname, {
|
|
1101
|
+
...opts,
|
|
1102
|
+
to: next.pathname,
|
|
1103
|
+
}) as any
|
|
1104
|
+
|
|
1105
|
+
if (!match) {
|
|
1106
|
+
return false
|
|
1107
|
+
}
|
|
937
1108
|
|
|
938
|
-
|
|
1109
|
+
if (opts?.includeSearch ?? true) {
|
|
1110
|
+
return partialDeepEqual(baseLocation.search, next.search) ? match : false
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return match
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
buildLink = <TFrom extends string = '/', TTo extends string = ''>({
|
|
1117
|
+
from,
|
|
1118
|
+
to = '.' as any,
|
|
1119
|
+
search,
|
|
1120
|
+
params,
|
|
1121
|
+
hash,
|
|
1122
|
+
target,
|
|
1123
|
+
replace,
|
|
1124
|
+
activeOptions,
|
|
1125
|
+
preload,
|
|
1126
|
+
preloadDelay: userPreloadDelay,
|
|
1127
|
+
disabled,
|
|
1128
|
+
}: LinkOptions<TRoutesInfo, TFrom, TTo>): LinkInfo => {
|
|
1129
|
+
// If this link simply reloads the current route,
|
|
1130
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1131
|
+
|
|
1132
|
+
// If this `to` is a valid external URL, return
|
|
1133
|
+
// null for LinkUtils
|
|
1134
|
+
|
|
1135
|
+
try {
|
|
1136
|
+
new URL(`${to}`)
|
|
1137
|
+
return {
|
|
1138
|
+
type: 'external',
|
|
1139
|
+
href: to,
|
|
1140
|
+
}
|
|
1141
|
+
} catch (e) {}
|
|
1142
|
+
|
|
1143
|
+
const nextOpts = {
|
|
939
1144
|
from,
|
|
940
|
-
to
|
|
1145
|
+
to,
|
|
941
1146
|
search,
|
|
942
1147
|
params,
|
|
943
1148
|
hash,
|
|
944
|
-
target,
|
|
945
1149
|
replace,
|
|
946
|
-
|
|
947
|
-
preload,
|
|
948
|
-
preloadMaxAge: userPreloadMaxAge,
|
|
949
|
-
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
950
|
-
preloadDelay: userPreloadDelay,
|
|
951
|
-
disabled,
|
|
952
|
-
}) => {
|
|
953
|
-
// If this link simply reloads the current route,
|
|
954
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
1150
|
+
}
|
|
955
1151
|
|
|
956
|
-
|
|
957
|
-
|
|
1152
|
+
const next = this.buildNext(nextOpts)
|
|
1153
|
+
|
|
1154
|
+
preload = preload ?? this.options.defaultPreload
|
|
1155
|
+
const preloadDelay =
|
|
1156
|
+
userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0
|
|
1157
|
+
|
|
1158
|
+
// Compare path/hash for matches
|
|
1159
|
+
const currentPathSplit = this.state.location.pathname.split('/')
|
|
1160
|
+
const nextPathSplit = next.pathname.split('/')
|
|
1161
|
+
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
1162
|
+
(d, i) => d === currentPathSplit[i],
|
|
1163
|
+
)
|
|
1164
|
+
// Combine the matches based on user options
|
|
1165
|
+
const pathTest = activeOptions?.exact
|
|
1166
|
+
? this.state.location.pathname === next.pathname
|
|
1167
|
+
: pathIsFuzzyEqual
|
|
1168
|
+
const hashTest = activeOptions?.includeHash
|
|
1169
|
+
? this.state.location.hash === next.hash
|
|
1170
|
+
: true
|
|
1171
|
+
const searchTest =
|
|
1172
|
+
activeOptions?.includeSearch ?? true
|
|
1173
|
+
? partialDeepEqual(this.state.location.search, next.search)
|
|
1174
|
+
: true
|
|
1175
|
+
|
|
1176
|
+
// The final "active" test
|
|
1177
|
+
const isActive = pathTest && hashTest && searchTest
|
|
1178
|
+
|
|
1179
|
+
// The click handler
|
|
1180
|
+
const handleClick = (e: MouseEvent) => {
|
|
1181
|
+
if (
|
|
1182
|
+
!disabled &&
|
|
1183
|
+
!isCtrlEvent(e) &&
|
|
1184
|
+
!e.defaultPrevented &&
|
|
1185
|
+
(!target || target === '_self') &&
|
|
1186
|
+
e.button === 0
|
|
1187
|
+
) {
|
|
1188
|
+
e.preventDefault()
|
|
958
1189
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
return {
|
|
962
|
-
type: 'external',
|
|
963
|
-
href: to,
|
|
964
|
-
}
|
|
965
|
-
} catch (e) {}
|
|
966
|
-
|
|
967
|
-
const nextOpts = {
|
|
968
|
-
from,
|
|
969
|
-
to,
|
|
970
|
-
search,
|
|
971
|
-
params,
|
|
972
|
-
hash,
|
|
973
|
-
replace,
|
|
1190
|
+
// All is well? Navigate!
|
|
1191
|
+
this.#commitLocation(nextOpts as any)
|
|
974
1192
|
}
|
|
1193
|
+
}
|
|
975
1194
|
|
|
976
|
-
|
|
1195
|
+
// The click handler
|
|
1196
|
+
const handleFocus = (e: MouseEvent) => {
|
|
1197
|
+
if (preload) {
|
|
1198
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1199
|
+
console.warn(err)
|
|
1200
|
+
console.warn('Error preloading route! ☝️')
|
|
1201
|
+
})
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
977
1204
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1205
|
+
const handleTouchStart = (e: TouchEvent) => {
|
|
1206
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1207
|
+
console.warn(err)
|
|
1208
|
+
console.warn('Error preloading route! ☝️')
|
|
1209
|
+
})
|
|
1210
|
+
}
|
|
981
1211
|
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
const currentPathSplit = router.state.location.pathname.split('/')
|
|
985
|
-
const nextPathSplit = next.pathname.split('/')
|
|
986
|
-
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
987
|
-
(d, i) => d === currentPathSplit[i],
|
|
988
|
-
)
|
|
989
|
-
const hashIsEqual = router.state.location.hash === next.hash
|
|
990
|
-
// Combine the matches based on user options
|
|
991
|
-
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
992
|
-
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
993
|
-
|
|
994
|
-
// The final "active" test
|
|
995
|
-
const isActive = pathTest && hashTest
|
|
996
|
-
|
|
997
|
-
// The click handler
|
|
998
|
-
const handleClick = (e: MouseEvent) => {
|
|
999
|
-
if (
|
|
1000
|
-
!disabled &&
|
|
1001
|
-
!isCtrlEvent(e) &&
|
|
1002
|
-
!e.defaultPrevented &&
|
|
1003
|
-
(!target || target === '_self') &&
|
|
1004
|
-
e.button === 0
|
|
1005
|
-
) {
|
|
1006
|
-
e.preventDefault()
|
|
1007
|
-
if (pathIsEqual && !search && !hash) {
|
|
1008
|
-
router.invalidateRoute(nextOpts)
|
|
1009
|
-
}
|
|
1212
|
+
const handleEnter = (e: MouseEvent) => {
|
|
1213
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
1010
1214
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1215
|
+
if (preload) {
|
|
1216
|
+
if (target.preloadTimeout) {
|
|
1217
|
+
return
|
|
1013
1218
|
}
|
|
1014
|
-
}
|
|
1015
1219
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
1220
|
+
target.preloadTimeout = setTimeout(() => {
|
|
1221
|
+
target.preloadTimeout = null
|
|
1222
|
+
this.preloadRoute(nextOpts).catch((err) => {
|
|
1223
|
+
console.warn(err)
|
|
1224
|
+
console.warn('Error preloading route! ☝️')
|
|
1022
1225
|
})
|
|
1023
|
-
}
|
|
1226
|
+
}, preloadDelay)
|
|
1024
1227
|
}
|
|
1228
|
+
}
|
|
1025
1229
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (preload) {
|
|
1030
|
-
if (target.preloadTimeout) {
|
|
1031
|
-
return
|
|
1032
|
-
}
|
|
1230
|
+
const handleLeave = (e: MouseEvent) => {
|
|
1231
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
1033
1232
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
maxAge: userPreloadMaxAge,
|
|
1038
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
1039
|
-
})
|
|
1040
|
-
}, preloadDelay)
|
|
1041
|
-
}
|
|
1233
|
+
if (target.preloadTimeout) {
|
|
1234
|
+
clearTimeout(target.preloadTimeout)
|
|
1235
|
+
target.preloadTimeout = null
|
|
1042
1236
|
}
|
|
1237
|
+
}
|
|
1043
1238
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1239
|
+
return {
|
|
1240
|
+
type: 'internal',
|
|
1241
|
+
next,
|
|
1242
|
+
handleFocus,
|
|
1243
|
+
handleClick,
|
|
1244
|
+
handleEnter,
|
|
1245
|
+
handleLeave,
|
|
1246
|
+
handleTouchStart,
|
|
1247
|
+
isActive,
|
|
1248
|
+
disabled,
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1046
1251
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1252
|
+
dehydrate = (): DehydratedRouter => {
|
|
1253
|
+
return {
|
|
1254
|
+
state: pick(this.state, ['location', 'status', 'lastUpdated']),
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
|
|
1259
|
+
let _ctx = __do_not_use_server_ctx
|
|
1260
|
+
// Client hydrates from window
|
|
1261
|
+
if (typeof document !== 'undefined') {
|
|
1262
|
+
_ctx = window.__TSR_DEHYDRATED__
|
|
1263
|
+
}
|
|
1052
1264
|
|
|
1265
|
+
invariant(
|
|
1266
|
+
_ctx,
|
|
1267
|
+
'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
const ctx = _ctx
|
|
1271
|
+
this.dehydratedData = ctx.payload as any
|
|
1272
|
+
this.options.hydrate?.(ctx.payload as any)
|
|
1273
|
+
|
|
1274
|
+
this.__store.setState((s) => {
|
|
1053
1275
|
return {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
handleClick,
|
|
1058
|
-
handleEnter,
|
|
1059
|
-
handleLeave,
|
|
1060
|
-
isActive,
|
|
1061
|
-
disabled,
|
|
1276
|
+
...s,
|
|
1277
|
+
...ctx.router.state,
|
|
1278
|
+
resolvedLocation: ctx.router.state.location,
|
|
1062
1279
|
}
|
|
1063
|
-
}
|
|
1064
|
-
buildNext: (opts: BuildNextOptions) => {
|
|
1065
|
-
const next = router.__.buildLocation(opts)
|
|
1280
|
+
})
|
|
1066
1281
|
|
|
1067
|
-
|
|
1282
|
+
await this.load()
|
|
1068
1283
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
.flat()
|
|
1072
|
-
.filter(Boolean)
|
|
1284
|
+
return
|
|
1285
|
+
}
|
|
1073
1286
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1287
|
+
injectedHtml: (string | (() => Promise<string> | string))[] = []
|
|
1288
|
+
|
|
1289
|
+
injectHtml = async (html: string | (() => Promise<string> | string)) => {
|
|
1290
|
+
this.injectedHtml.push(html)
|
|
1291
|
+
}
|
|
1078
1292
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1293
|
+
dehydrateData = <T>(key: any, getData: T | (() => Promise<T> | T)) => {
|
|
1294
|
+
if (typeof document === 'undefined') {
|
|
1295
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
1296
|
+
|
|
1297
|
+
this.injectHtml(async () => {
|
|
1298
|
+
const id = `__TSR_DEHYDRATED__${strKey}`
|
|
1299
|
+
const data =
|
|
1300
|
+
typeof getData === 'function' ? await (getData as any)() : getData
|
|
1301
|
+
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
|
|
1302
|
+
strKey,
|
|
1303
|
+
)}"] = ${JSON.stringify(data)}
|
|
1304
|
+
;(() => {
|
|
1305
|
+
var el = document.getElementById('${id}')
|
|
1306
|
+
el.parentElement.removeChild(el)
|
|
1307
|
+
})()
|
|
1308
|
+
</script>`
|
|
1083
1309
|
})
|
|
1084
|
-
},
|
|
1085
1310
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const recurseRoutes = (
|
|
1089
|
-
routeConfigs: RouteConfig[],
|
|
1090
|
-
parent?: Route<TAllRouteInfo, any>,
|
|
1091
|
-
): Route<TAllRouteInfo, any>[] => {
|
|
1092
|
-
return routeConfigs.map((routeConfig) => {
|
|
1093
|
-
const routeOptions = routeConfig.options
|
|
1094
|
-
const route = createRoute(routeConfig, routeOptions, parent, router)
|
|
1095
|
-
const existingRoute = (router.routesById as any)[route.routeId]
|
|
1096
|
-
|
|
1097
|
-
if (existingRoute) {
|
|
1098
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1099
|
-
console.warn(
|
|
1100
|
-
`Duplicate routes found with id: ${String(route.routeId)}`,
|
|
1101
|
-
router.routesById,
|
|
1102
|
-
route,
|
|
1103
|
-
)
|
|
1104
|
-
}
|
|
1105
|
-
throw new Error()
|
|
1106
|
-
}
|
|
1311
|
+
return () => this.hydrateData<T>(key)
|
|
1312
|
+
}
|
|
1107
1313
|
|
|
1108
|
-
|
|
1314
|
+
return () => undefined
|
|
1315
|
+
}
|
|
1109
1316
|
|
|
1110
|
-
|
|
1317
|
+
hydrateData = <T = unknown>(key: any) => {
|
|
1318
|
+
if (typeof document !== 'undefined') {
|
|
1319
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
1111
1320
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
: undefined
|
|
1321
|
+
return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
|
|
1322
|
+
}
|
|
1115
1323
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1324
|
+
return undefined
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
1328
|
+
// this.state.matches
|
|
1329
|
+
// .find((d) => d.id === matchId)
|
|
1330
|
+
// ?.__promisesByKey[key]?.resolve(value)
|
|
1331
|
+
// }
|
|
1332
|
+
|
|
1333
|
+
#buildRouteTree = (routeTree: TRouteTree) => {
|
|
1334
|
+
this.routeTree = routeTree as any
|
|
1335
|
+
this.routesById = {} as any
|
|
1336
|
+
this.routesByPath = {} as any
|
|
1337
|
+
this.flatRoutes = [] as any
|
|
1338
|
+
|
|
1339
|
+
const recurseRoutes = (routes: AnyRoute[]) => {
|
|
1340
|
+
routes.forEach((route, i) => {
|
|
1341
|
+
route.init({ originalIndex: i, router: this })
|
|
1342
|
+
|
|
1343
|
+
const existingRoute = (this.routesById as any)[route.id]
|
|
1344
|
+
|
|
1345
|
+
invariant(
|
|
1346
|
+
!existingRoute,
|
|
1347
|
+
`Duplicate routes found with id: ${String(route.id)}`,
|
|
1348
|
+
)
|
|
1349
|
+
;(this.routesById as any)[route.id] = route
|
|
1350
|
+
|
|
1351
|
+
if (!route.isRoot && route.path) {
|
|
1352
|
+
const trimmedFullPath = trimPathRight(route.fullPath)
|
|
1353
|
+
if (
|
|
1354
|
+
!this.routesByPath[trimmedFullPath] ||
|
|
1355
|
+
route.fullPath.endsWith('/')
|
|
1356
|
+
) {
|
|
1357
|
+
;(this.routesByPath as any)[trimmedFullPath] = route
|
|
1358
|
+
}
|
|
1118
1359
|
}
|
|
1119
1360
|
|
|
1120
|
-
const
|
|
1361
|
+
const children = route.children as Route[]
|
|
1121
1362
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1363
|
+
if (children?.length) {
|
|
1364
|
+
recurseRoutes(children)
|
|
1365
|
+
}
|
|
1366
|
+
})
|
|
1367
|
+
}
|
|
1124
1368
|
|
|
1125
|
-
|
|
1126
|
-
location: History['location'],
|
|
1127
|
-
previousLocation?: Location,
|
|
1128
|
-
): Location => {
|
|
1129
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1369
|
+
recurseRoutes([routeTree])
|
|
1130
1370
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
key: location.key,
|
|
1371
|
+
this.flatRoutes = (Object.values(this.routesByPath) as AnyRoute[])
|
|
1372
|
+
.map((d, i) => {
|
|
1373
|
+
const trimmed = trimPath(d.fullPath)
|
|
1374
|
+
const parsed = parsePathname(trimmed)
|
|
1375
|
+
|
|
1376
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1377
|
+
parsed.shift()
|
|
1139
1378
|
}
|
|
1140
|
-
},
|
|
1141
1379
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1380
|
+
const score = parsed.map((d) => {
|
|
1381
|
+
if (d.type === 'param') {
|
|
1382
|
+
return 0.5
|
|
1383
|
+
}
|
|
1146
1384
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const fromPathname = dest.fromCurrent
|
|
1151
|
-
? router.location.pathname
|
|
1152
|
-
: dest.from ?? router.location.pathname
|
|
1153
|
-
|
|
1154
|
-
let pathname = resolvePath(
|
|
1155
|
-
router.basepath ?? '/',
|
|
1156
|
-
fromPathname,
|
|
1157
|
-
`${dest.to ?? '.'}`,
|
|
1158
|
-
)
|
|
1385
|
+
if (d.type === 'wildcard') {
|
|
1386
|
+
return 0.25
|
|
1387
|
+
}
|
|
1159
1388
|
|
|
1160
|
-
|
|
1161
|
-
strictParseParams: true,
|
|
1389
|
+
return 1
|
|
1162
1390
|
})
|
|
1163
1391
|
|
|
1164
|
-
|
|
1392
|
+
return { child: d, trimmed, parsed, index: i, score }
|
|
1393
|
+
})
|
|
1394
|
+
.sort((a, b) => {
|
|
1395
|
+
let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0
|
|
1165
1396
|
|
|
1166
|
-
|
|
1397
|
+
if (isIndex !== 0) return isIndex
|
|
1167
1398
|
|
|
1168
|
-
|
|
1169
|
-
(dest.params ?? true) === true
|
|
1170
|
-
? prevParams
|
|
1171
|
-
: functionalUpdate(dest.params!, prevParams)
|
|
1399
|
+
const length = Math.min(a.score.length, b.score.length)
|
|
1172
1400
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
.filter(Boolean)
|
|
1177
|
-
.forEach((fn) => {
|
|
1178
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1179
|
-
})
|
|
1401
|
+
// Sort by length of score
|
|
1402
|
+
if (a.score.length !== b.score.length) {
|
|
1403
|
+
return b.score.length - a.score.length
|
|
1180
1404
|
}
|
|
1181
1405
|
|
|
1182
|
-
|
|
1406
|
+
// Sort by min available score
|
|
1407
|
+
for (let i = 0; i < length; i++) {
|
|
1408
|
+
if (a.score[i] !== b.score[i]) {
|
|
1409
|
+
return b.score[i]! - a.score[i]!
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1183
1412
|
|
|
1184
|
-
//
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
: router.location.search
|
|
1191
|
-
|
|
1192
|
-
// Then the link/navigate function
|
|
1193
|
-
const destSearch =
|
|
1194
|
-
dest.search === true
|
|
1195
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1196
|
-
: dest.search
|
|
1197
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1198
|
-
: dest.__preSearchFilters?.length
|
|
1199
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1200
|
-
: {}
|
|
1201
|
-
|
|
1202
|
-
// Then post filters
|
|
1203
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1204
|
-
? dest.__postSearchFilters.reduce(
|
|
1205
|
-
(prev, next) => next(prev),
|
|
1206
|
-
destSearch,
|
|
1207
|
-
)
|
|
1208
|
-
: destSearch
|
|
1413
|
+
// Sort by min available parsed value
|
|
1414
|
+
for (let i = 0; i < length; i++) {
|
|
1415
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
1416
|
+
return a.parsed[i]!.value! > b.parsed[i]!.value! ? 1 : -1
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1209
1419
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1420
|
+
// Sort by length of trimmed full path
|
|
1421
|
+
if (a.trimmed !== b.trimmed) {
|
|
1422
|
+
return a.trimmed > b.trimmed ? 1 : -1
|
|
1423
|
+
}
|
|
1214
1424
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1425
|
+
// Sort by original index
|
|
1426
|
+
return a.index - b.index
|
|
1427
|
+
})
|
|
1428
|
+
.map((d, i) => {
|
|
1429
|
+
d.child.rank = i
|
|
1430
|
+
return d.child
|
|
1431
|
+
}) as any
|
|
1432
|
+
}
|
|
1221
1433
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
search,
|
|
1225
|
-
searchStr,
|
|
1226
|
-
state: router.location.state,
|
|
1227
|
-
hash,
|
|
1228
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1229
|
-
key: dest.key,
|
|
1230
|
-
}
|
|
1231
|
-
},
|
|
1434
|
+
#parseLocation = (previousLocation?: ParsedLocation): ParsedLocation => {
|
|
1435
|
+
let { pathname, search, hash, state } = this.history.location
|
|
1232
1436
|
|
|
1233
|
-
|
|
1234
|
-
const id = '' + Date.now() + Math.random()
|
|
1437
|
+
const parsedSearch = this.options.parseSearch(search)
|
|
1235
1438
|
|
|
1236
|
-
|
|
1439
|
+
return {
|
|
1440
|
+
pathname: pathname,
|
|
1441
|
+
searchStr: search,
|
|
1442
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1443
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1444
|
+
href: `${pathname}${search}${hash}`,
|
|
1445
|
+
state: state as LocationState,
|
|
1446
|
+
key: state?.key || '__init__',
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1237
1449
|
|
|
1238
|
-
|
|
1450
|
+
#buildLocation = (dest: BuildNextOptions = {}): ParsedLocation => {
|
|
1451
|
+
dest.fromCurrent = dest.fromCurrent ?? dest.to === ''
|
|
1239
1452
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1453
|
+
const fromPathname = dest.fromCurrent
|
|
1454
|
+
? this.state.location.pathname
|
|
1455
|
+
: dest.from ?? this.state.location.pathname
|
|
1243
1456
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1457
|
+
let pathname = resolvePath(
|
|
1458
|
+
this.basepath ?? '/',
|
|
1459
|
+
fromPathname,
|
|
1460
|
+
`${dest.to ?? ''}`,
|
|
1461
|
+
)
|
|
1246
1462
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1463
|
+
const fromMatches = this.matchRoutes(
|
|
1464
|
+
this.state.location.pathname,
|
|
1465
|
+
this.state.location.search,
|
|
1466
|
+
)
|
|
1250
1467
|
|
|
1251
|
-
|
|
1252
|
-
history.replace(
|
|
1253
|
-
{
|
|
1254
|
-
pathname: next.pathname,
|
|
1255
|
-
hash: next.hash,
|
|
1256
|
-
search: next.searchStr,
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
id,
|
|
1260
|
-
},
|
|
1261
|
-
)
|
|
1262
|
-
} else {
|
|
1263
|
-
history.push(
|
|
1264
|
-
{
|
|
1265
|
-
pathname: next.pathname,
|
|
1266
|
-
hash: next.hash,
|
|
1267
|
-
search: next.searchStr,
|
|
1268
|
-
},
|
|
1269
|
-
{
|
|
1270
|
-
id,
|
|
1271
|
-
},
|
|
1272
|
-
)
|
|
1273
|
-
}
|
|
1468
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
1274
1469
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1470
|
+
let nextParams =
|
|
1471
|
+
(dest.params ?? true) === true
|
|
1472
|
+
? prevParams
|
|
1473
|
+
: functionalUpdate(dest.params!, prevParams)
|
|
1277
1474
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1475
|
+
if (nextParams) {
|
|
1476
|
+
dest.__matches
|
|
1477
|
+
?.map((d) => this.getRoute(d.routeId).options.stringifyParams)
|
|
1478
|
+
.filter(Boolean)
|
|
1479
|
+
.forEach((fn) => {
|
|
1480
|
+
nextParams = { ...nextParams!, ...fn!(nextParams!) }
|
|
1282
1481
|
})
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1485
|
+
|
|
1486
|
+
const preSearchFilters =
|
|
1487
|
+
dest.__matches
|
|
1488
|
+
?.map(
|
|
1489
|
+
(match) =>
|
|
1490
|
+
this.getRoute(match.routeId).options.preSearchFilters ?? [],
|
|
1491
|
+
)
|
|
1492
|
+
.flat()
|
|
1493
|
+
.filter(Boolean) ?? []
|
|
1494
|
+
|
|
1495
|
+
const postSearchFilters =
|
|
1496
|
+
dest.__matches
|
|
1497
|
+
?.map(
|
|
1498
|
+
(match) =>
|
|
1499
|
+
this.getRoute(match.routeId).options.postSearchFilters ?? [],
|
|
1500
|
+
)
|
|
1501
|
+
.flat()
|
|
1502
|
+
.filter(Boolean) ?? []
|
|
1503
|
+
|
|
1504
|
+
// Pre filters first
|
|
1505
|
+
const preFilteredSearch = preSearchFilters?.length
|
|
1506
|
+
? preSearchFilters?.reduce(
|
|
1507
|
+
(prev, next) => next(prev),
|
|
1508
|
+
this.state.location.search,
|
|
1509
|
+
)
|
|
1510
|
+
: this.state.location.search
|
|
1511
|
+
|
|
1512
|
+
// Then the link/navigate function
|
|
1513
|
+
const destSearch =
|
|
1514
|
+
dest.search === true
|
|
1515
|
+
? preFilteredSearch // Preserve resolvedFrom true
|
|
1516
|
+
: dest.search
|
|
1517
|
+
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1518
|
+
: preSearchFilters?.length
|
|
1519
|
+
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1520
|
+
: {}
|
|
1521
|
+
|
|
1522
|
+
// Then post filters
|
|
1523
|
+
const postFilteredSearch = postSearchFilters?.length
|
|
1524
|
+
? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
|
|
1525
|
+
: destSearch
|
|
1526
|
+
|
|
1527
|
+
const search = replaceEqualDeep(
|
|
1528
|
+
this.state.location.search,
|
|
1529
|
+
postFilteredSearch,
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
const searchStr = this.options.stringifySearch(search)
|
|
1533
|
+
|
|
1534
|
+
const hash =
|
|
1535
|
+
dest.hash === true
|
|
1536
|
+
? this.state.location.hash
|
|
1537
|
+
: functionalUpdate(dest.hash!, this.state.location.hash)
|
|
1538
|
+
|
|
1539
|
+
const hashStr = hash ? `#${hash}` : ''
|
|
1540
|
+
|
|
1541
|
+
const nextState =
|
|
1542
|
+
dest.state === true
|
|
1543
|
+
? this.state.location.state
|
|
1544
|
+
: functionalUpdate(dest.state, this.state.location.state)!
|
|
1545
|
+
|
|
1546
|
+
return {
|
|
1547
|
+
pathname,
|
|
1548
|
+
search,
|
|
1549
|
+
searchStr,
|
|
1550
|
+
state: nextState,
|
|
1551
|
+
hash,
|
|
1552
|
+
href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
|
|
1553
|
+
key: dest.key,
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
#commitLocation = async (
|
|
1558
|
+
location: BuildNextOptions & { replace?: boolean },
|
|
1559
|
+
) => {
|
|
1560
|
+
const next = this.buildNext(location)
|
|
1561
|
+
const id = '' + Date.now() + Math.random()
|
|
1562
|
+
|
|
1563
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
|
|
1564
|
+
|
|
1565
|
+
let nextAction: 'push' | 'replace' = 'replace'
|
|
1566
|
+
|
|
1567
|
+
if (!location.replace) {
|
|
1568
|
+
nextAction = 'push'
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const isSameUrl = this.state.location.href === next.href
|
|
1283
1572
|
|
|
1284
|
-
|
|
1573
|
+
if (isSameUrl && !next.key) {
|
|
1574
|
+
nextAction = 'replace'
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const href = `${next.pathname}${next.searchStr}${
|
|
1578
|
+
next.hash ? `#${next.hash}` : ''
|
|
1579
|
+
}`
|
|
1580
|
+
|
|
1581
|
+
this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
|
|
1582
|
+
id,
|
|
1583
|
+
...next.state,
|
|
1584
|
+
})
|
|
1585
|
+
|
|
1586
|
+
return this.latestLoadPromise
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
getRouteMatch = (
|
|
1590
|
+
id: string,
|
|
1591
|
+
): undefined | RouteMatch<TRoutesInfo, AnyRoute> => {
|
|
1592
|
+
return this.state.matchesById[id]
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
setRouteMatch = (
|
|
1596
|
+
id: string,
|
|
1597
|
+
updater: (
|
|
1598
|
+
prev: RouteMatch<TRoutesInfo, AnyRoute>,
|
|
1599
|
+
) => RouteMatch<TRoutesInfo, AnyRoute>,
|
|
1600
|
+
) => {
|
|
1601
|
+
this.__store.setState((prev) => ({
|
|
1602
|
+
...prev,
|
|
1603
|
+
matchesById: {
|
|
1604
|
+
...prev.matchesById,
|
|
1605
|
+
[id]: updater(prev.matchesById[id] as any),
|
|
1285
1606
|
},
|
|
1607
|
+
}))
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
setRouteMatchData = (
|
|
1611
|
+
id: string,
|
|
1612
|
+
updater: (prev: any) => any,
|
|
1613
|
+
opts?: {
|
|
1614
|
+
updatedAt?: number
|
|
1615
|
+
maxAge?: number
|
|
1286
1616
|
},
|
|
1617
|
+
) => {
|
|
1618
|
+
const match = this.getRouteMatch(id)
|
|
1619
|
+
|
|
1620
|
+
if (!match) return
|
|
1621
|
+
|
|
1622
|
+
const route = this.getRoute(match.routeId)
|
|
1623
|
+
const updatedAt = opts?.updatedAt ?? Date.now()
|
|
1624
|
+
|
|
1625
|
+
const preloadInvalidAt =
|
|
1626
|
+
updatedAt +
|
|
1627
|
+
(opts?.maxAge ??
|
|
1628
|
+
route.options.preloadMaxAge ??
|
|
1629
|
+
this.options.defaultPreloadMaxAge ??
|
|
1630
|
+
5000)
|
|
1631
|
+
|
|
1632
|
+
const invalidAt =
|
|
1633
|
+
updatedAt +
|
|
1634
|
+
(opts?.maxAge ??
|
|
1635
|
+
route.options.maxAge ??
|
|
1636
|
+
this.options.defaultMaxAge ??
|
|
1637
|
+
Infinity)
|
|
1638
|
+
|
|
1639
|
+
this.setRouteMatch(id, (s) => ({
|
|
1640
|
+
...s,
|
|
1641
|
+
error: undefined,
|
|
1642
|
+
status: 'success',
|
|
1643
|
+
isFetching: false,
|
|
1644
|
+
updatedAt: Date.now(),
|
|
1645
|
+
loaderData: functionalUpdate(updater, s.loaderData),
|
|
1646
|
+
preloadInvalidAt,
|
|
1647
|
+
invalidAt,
|
|
1648
|
+
}))
|
|
1649
|
+
|
|
1650
|
+
if (this.state.matches.find((d) => d.id === id)) {
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
invalidate = async (opts?: {
|
|
1655
|
+
matchId?: string
|
|
1656
|
+
reload?: boolean
|
|
1657
|
+
}): Promise<void> => {
|
|
1658
|
+
if (opts?.matchId) {
|
|
1659
|
+
this.setRouteMatch(opts.matchId, (s) => ({
|
|
1660
|
+
...s,
|
|
1661
|
+
invalid: true,
|
|
1662
|
+
}))
|
|
1663
|
+
const matchIndex = this.state.matches.findIndex(
|
|
1664
|
+
(d) => d.id === opts.matchId,
|
|
1665
|
+
)
|
|
1666
|
+
const childMatch = this.state.matches[matchIndex + 1]
|
|
1667
|
+
|
|
1668
|
+
if (childMatch) {
|
|
1669
|
+
return this.invalidate({ matchId: childMatch.id, reload: false })
|
|
1670
|
+
}
|
|
1671
|
+
} else {
|
|
1672
|
+
this.__store.batch(() => {
|
|
1673
|
+
Object.values(this.state.matchesById).forEach((match) => {
|
|
1674
|
+
this.setRouteMatch(match.id, (s) => ({
|
|
1675
|
+
...s,
|
|
1676
|
+
invalid: true,
|
|
1677
|
+
}))
|
|
1678
|
+
})
|
|
1679
|
+
})
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
if (opts?.reload ?? true) {
|
|
1683
|
+
return this.reload()
|
|
1684
|
+
}
|
|
1287
1685
|
}
|
|
1288
1686
|
|
|
1289
|
-
|
|
1687
|
+
getIsInvalid = (opts?: { matchId: string; preload?: boolean }): boolean => {
|
|
1688
|
+
if (!opts?.matchId) {
|
|
1689
|
+
return !!this.state.matches.find((d) =>
|
|
1690
|
+
this.getIsInvalid({ matchId: d.id, preload: opts?.preload }),
|
|
1691
|
+
)
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const match = this.getRouteMatch(opts?.matchId)
|
|
1695
|
+
|
|
1696
|
+
if (!match) {
|
|
1697
|
+
return false
|
|
1698
|
+
}
|
|
1290
1699
|
|
|
1291
|
-
|
|
1292
|
-
router.options.createRouter?.(router)
|
|
1700
|
+
const now = Date.now()
|
|
1293
1701
|
|
|
1294
|
-
|
|
1702
|
+
return (
|
|
1703
|
+
match.invalid ||
|
|
1704
|
+
(opts?.preload ? match.preloadInvalidAt : match.invalidAt) < now
|
|
1705
|
+
)
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Detect if we're in the DOM
|
|
1710
|
+
const isServer = typeof window === 'undefined' || !window.document.createElement
|
|
1711
|
+
|
|
1712
|
+
function getInitialRouterState(): RouterState<any, any> {
|
|
1713
|
+
return {
|
|
1714
|
+
status: 'idle',
|
|
1715
|
+
isFetching: false,
|
|
1716
|
+
resolvedLocation: null!,
|
|
1717
|
+
location: null!,
|
|
1718
|
+
matchesById: {},
|
|
1719
|
+
matchIds: [],
|
|
1720
|
+
pendingMatchIds: [],
|
|
1721
|
+
matches: [],
|
|
1722
|
+
pendingMatches: [],
|
|
1723
|
+
lastUpdated: Date.now(),
|
|
1724
|
+
}
|
|
1295
1725
|
}
|
|
1296
1726
|
|
|
1297
1727
|
function isCtrlEvent(e: MouseEvent) {
|
|
1298
1728
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1299
1729
|
}
|
|
1300
1730
|
|
|
1301
|
-
|
|
1302
|
-
matches.forEach((match, index) => {
|
|
1303
|
-
const parent = matches[index - 1]
|
|
1731
|
+
export type AnyRedirect = Redirect<any, any, any>
|
|
1304
1732
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1733
|
+
export type Redirect<
|
|
1734
|
+
TRoutesInfo extends AnyRoutesInfo = RegisteredRoutesInfo,
|
|
1735
|
+
TFrom extends TRoutesInfo['routePaths'] = '/',
|
|
1736
|
+
TTo extends string = '',
|
|
1737
|
+
> = NavigateOptions<TRoutesInfo, TFrom, TTo> & {
|
|
1738
|
+
code?: number
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
export function redirect<
|
|
1742
|
+
TRoutesInfo extends AnyRoutesInfo = RegisteredRoutesInfo,
|
|
1743
|
+
TFrom extends TRoutesInfo['routePaths'] = '/',
|
|
1744
|
+
TTo extends string = '',
|
|
1745
|
+
>(opts: Redirect<TRoutesInfo, TFrom, TTo>): Redirect<TRoutesInfo, TFrom, TTo> {
|
|
1746
|
+
;(opts as any).isRedirect = true
|
|
1747
|
+
return opts
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
export function isRedirect(obj: any): obj is AnyRedirect {
|
|
1751
|
+
return !!obj?.isRedirect
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
export class SearchParamError extends Error {}
|
|
1755
|
+
export class PathParamError extends Error {}
|
|
1756
|
+
|
|
1757
|
+
function escapeJSON(jsonString: string) {
|
|
1758
|
+
return jsonString
|
|
1759
|
+
.replace(/\\/g, '\\\\') // Escape backslashes
|
|
1760
|
+
.replace(/'/g, "\\'") // Escape single quotes
|
|
1761
|
+
.replace(/"/g, '\\"') // Escape double quotes
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
1765
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
1766
|
+
// information along the way
|
|
1767
|
+
export function lazyFn<
|
|
1768
|
+
T extends Record<string, (...args: any[]) => any>,
|
|
1769
|
+
TKey extends keyof T = 'default',
|
|
1770
|
+
>(fn: () => Promise<T>, key?: TKey) {
|
|
1771
|
+
return async (...args: Parameters<T[TKey]>): Promise<ReturnType<T[TKey]>> => {
|
|
1772
|
+
const imported = await fn()
|
|
1773
|
+
return imported[key || 'default'](...args)
|
|
1774
|
+
}
|
|
1312
1775
|
}
|