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