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