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