@tanstack/router-core 0.0.1-beta.5 → 0.0.1-beta.51
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/actions.js +94 -0
- package/build/cjs/actions.js.map +1 -0
- package/build/cjs/history.js +163 -0
- package/build/cjs/history.js.map +1 -0
- package/build/cjs/{packages/router-core/src/index.js → index.js} +26 -11
- package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
- package/build/cjs/interop.js +175 -0
- package/build/cjs/interop.js.map +1 -0
- package/build/cjs/{packages/router-core/src/path.js → path.js} +23 -48
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/{packages/router-core/src/qss.js → qss.js} +8 -13
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/route.js +33 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/{packages/router-core/src/routeConfig.js → routeConfig.js} +13 -18
- package/build/cjs/routeConfig.js.map +1 -0
- package/build/cjs/routeMatch.js +237 -0
- package/build/cjs/routeMatch.js.map +1 -0
- package/build/cjs/router.js +824 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +10 -12
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/store.js +54 -0
- package/build/cjs/store.js.map +1 -0
- package/build/cjs/utils.js +47 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +1386 -2058
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +59 -49
- package/build/stats-react.json +248 -193
- package/build/types/index.d.ts +385 -317
- package/build/umd/index.development.js +1489 -2142
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +6 -4
- package/src/actions.ts +157 -0
- package/src/frameworks.ts +2 -2
- package/src/history.ts +199 -0
- package/src/index.ts +4 -7
- package/src/interop.ts +169 -0
- package/src/link.ts +87 -44
- package/src/path.ts +12 -8
- package/src/route.ts +36 -229
- package/src/routeConfig.ts +99 -102
- package/src/routeInfo.ts +28 -25
- package/src/routeMatch.ts +293 -322
- package/src/router.ts +1060 -884
- package/src/store.ts +52 -0
- package/src/utils.ts +14 -72
- 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 -161
- package/build/cjs/packages/router-core/src/route.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
- package/build/cjs/packages/router-core/src/routeMatch.js +0 -266
- package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
- package/build/cjs/packages/router-core/src/router.js +0 -797
- 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 +0 -118
- package/build/cjs/packages/router-core/src/utils.js.map +0 -1
package/src/router.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BrowserHistory,
|
|
3
|
-
createBrowserHistory,
|
|
4
|
-
createMemoryHistory,
|
|
5
|
-
HashHistory,
|
|
6
|
-
History,
|
|
7
|
-
MemoryHistory,
|
|
8
|
-
} from 'history'
|
|
9
1
|
import invariant from 'tiny-invariant'
|
|
10
2
|
import { GetFrameworkGeneric } from './frameworks'
|
|
11
3
|
|
|
12
4
|
import {
|
|
13
5
|
LinkInfo,
|
|
14
6
|
LinkOptions,
|
|
15
|
-
|
|
7
|
+
NavigateOptions,
|
|
16
8
|
ToOptions,
|
|
17
9
|
ValidFromPath,
|
|
10
|
+
ResolveRelativePath,
|
|
18
11
|
} from './link'
|
|
19
12
|
import {
|
|
20
13
|
cleanPath,
|
|
@@ -22,8 +15,9 @@ import {
|
|
|
22
15
|
joinPaths,
|
|
23
16
|
matchPathname,
|
|
24
17
|
resolvePath,
|
|
18
|
+
trimPath,
|
|
25
19
|
} from './path'
|
|
26
|
-
import { AnyRoute,
|
|
20
|
+
import { AnyRoute, Route } from './route'
|
|
27
21
|
import {
|
|
28
22
|
AnyLoaderData,
|
|
29
23
|
AnyPathParams,
|
|
@@ -36,25 +30,51 @@ import {
|
|
|
36
30
|
import {
|
|
37
31
|
AllRouteInfo,
|
|
38
32
|
AnyAllRouteInfo,
|
|
39
|
-
AnyRouteInfo,
|
|
40
33
|
RouteInfo,
|
|
41
34
|
RoutesById,
|
|
42
35
|
} from './routeInfo'
|
|
43
|
-
import {
|
|
36
|
+
import { RouteMatch, RouteMatchStore } from './routeMatch'
|
|
44
37
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
38
|
+
import { createStore, batch, Store } from './store'
|
|
45
39
|
import {
|
|
46
40
|
functionalUpdate,
|
|
47
41
|
last,
|
|
42
|
+
NoInfer,
|
|
43
|
+
pick,
|
|
48
44
|
PickAsRequired,
|
|
49
45
|
PickRequired,
|
|
50
|
-
replaceEqualDeep,
|
|
51
46
|
Timeout,
|
|
52
47
|
Updater,
|
|
53
48
|
} from './utils'
|
|
49
|
+
import { replaceEqualDeep } from './interop'
|
|
50
|
+
import {
|
|
51
|
+
createBrowserHistory,
|
|
52
|
+
createMemoryHistory,
|
|
53
|
+
RouterHistory,
|
|
54
|
+
} from './history'
|
|
55
|
+
import { createMemo } from '@solidjs/reactivity'
|
|
56
|
+
|
|
57
|
+
export interface RegisterRouter {
|
|
58
|
+
// router: Router
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type AnyRouter = Router<any, any, any>
|
|
62
|
+
|
|
63
|
+
export type RegisteredRouter = RegisterRouter extends {
|
|
64
|
+
router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
|
|
65
|
+
}
|
|
66
|
+
? Router<TRouteConfig, TAllRouteInfo, TRouterContext>
|
|
67
|
+
: Router
|
|
68
|
+
|
|
69
|
+
export type RegisteredAllRouteInfo = RegisterRouter extends {
|
|
70
|
+
router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
|
|
71
|
+
}
|
|
72
|
+
? TAllRouteInfo
|
|
73
|
+
: AnyAllRouteInfo
|
|
54
74
|
|
|
55
75
|
export interface LocationState {}
|
|
56
76
|
|
|
57
|
-
export interface
|
|
77
|
+
export interface ParsedLocation<
|
|
58
78
|
TSearchObj extends AnySearchSchema = {},
|
|
59
79
|
TState extends LocationState = LocationState,
|
|
60
80
|
> {
|
|
@@ -80,8 +100,11 @@ export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
|
|
|
80
100
|
routeConfigs: TRoute[],
|
|
81
101
|
) => TRoute[]
|
|
82
102
|
|
|
83
|
-
export interface RouterOptions<
|
|
84
|
-
|
|
103
|
+
export interface RouterOptions<
|
|
104
|
+
TRouteConfig extends AnyRouteConfig,
|
|
105
|
+
TRouterContext,
|
|
106
|
+
> {
|
|
107
|
+
history?: RouterHistory
|
|
85
108
|
stringifySearch?: SearchSerializer
|
|
86
109
|
parseSearch?: SearchParser
|
|
87
110
|
filterRoutes?: FilterRoutesFn
|
|
@@ -89,47 +112,29 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
|
|
|
89
112
|
defaultPreloadMaxAge?: number
|
|
90
113
|
defaultPreloadGcMaxAge?: number
|
|
91
114
|
defaultPreloadDelay?: number
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
defaultCatchElement?: GetFrameworkGeneric<'Element'>
|
|
96
|
-
defaultPendingElement?: GetFrameworkGeneric<'Element'>
|
|
97
|
-
defaultPendingMs?: number
|
|
98
|
-
defaultPendingMinMs?: number
|
|
115
|
+
defaultComponent?: GetFrameworkGeneric<'Component'>
|
|
116
|
+
defaultErrorComponent?: GetFrameworkGeneric<'ErrorComponent'>
|
|
117
|
+
defaultPendingComponent?: GetFrameworkGeneric<'Component'>
|
|
99
118
|
defaultLoaderMaxAge?: number
|
|
100
119
|
defaultLoaderGcMaxAge?: number
|
|
101
120
|
caseSensitive?: boolean
|
|
102
121
|
routeConfig?: TRouteConfig
|
|
103
122
|
basepath?: string
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
useServerData?: boolean
|
|
124
|
+
Router?: (router: AnyRouter) => void
|
|
125
|
+
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
|
|
126
|
+
context?: TRouterContext
|
|
127
|
+
loadComponent?: (
|
|
128
|
+
component: GetFrameworkGeneric<'Component'>,
|
|
129
|
+
) => Promise<GetFrameworkGeneric<'Component'>>
|
|
130
|
+
onRouteChange?: () => void
|
|
131
|
+
fetchServerDataFn?: FetchServerDataFn
|
|
109
132
|
}
|
|
110
133
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
> {
|
|
116
|
-
submit: (submission?: TPayload) => Promise<TResponse>
|
|
117
|
-
current?: ActionState<TPayload, TResponse>
|
|
118
|
-
latest?: ActionState<TPayload, TResponse>
|
|
119
|
-
pending: ActionState<TPayload, TResponse>[]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface ActionState<
|
|
123
|
-
TPayload = unknown,
|
|
124
|
-
TResponse = unknown,
|
|
125
|
-
// TError = unknown,
|
|
126
|
-
> {
|
|
127
|
-
submittedAt: number
|
|
128
|
-
status: 'idle' | 'pending' | 'success' | 'error'
|
|
129
|
-
submission: TPayload
|
|
130
|
-
data?: TResponse
|
|
131
|
-
error?: unknown
|
|
132
|
-
}
|
|
134
|
+
type FetchServerDataFn = (ctx: {
|
|
135
|
+
router: AnyRouter
|
|
136
|
+
routeMatch: RouteMatch
|
|
137
|
+
}) => Promise<any>
|
|
133
138
|
|
|
134
139
|
export interface Loader<
|
|
135
140
|
TFullSearchSchema extends AnySearchSchema = {},
|
|
@@ -160,41 +165,38 @@ export interface Loader<
|
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
export interface LoaderState<
|
|
163
|
-
TFullSearchSchema =
|
|
164
|
-
TAllParams =
|
|
168
|
+
TFullSearchSchema extends AnySearchSchema = {},
|
|
169
|
+
TAllParams extends AnyPathParams = {},
|
|
165
170
|
> {
|
|
166
171
|
loadedAt: number
|
|
167
172
|
loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
export interface
|
|
175
|
+
export interface RouterStore<
|
|
176
|
+
TSearchObj extends AnySearchSchema = {},
|
|
177
|
+
TState extends LocationState = LocationState,
|
|
178
|
+
> {
|
|
171
179
|
status: 'idle' | 'loading'
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
latestLocation: ParsedLocation<TSearchObj, TState>
|
|
181
|
+
currentMatches: RouteMatch[]
|
|
182
|
+
currentLocation: ParsedLocation<TSearchObj, TState>
|
|
183
|
+
pendingMatches?: RouteMatch[]
|
|
184
|
+
pendingLocation?: ParsedLocation<TSearchObj, TState>
|
|
174
185
|
lastUpdated: number
|
|
175
|
-
currentAction?: ActionState
|
|
176
|
-
latestAction?: ActionState
|
|
177
|
-
actions: Record<string, Action>
|
|
178
186
|
loaders: Record<string, Loader>
|
|
179
|
-
pending?: PendingState
|
|
180
187
|
isFetching: boolean
|
|
181
188
|
isPreloading: boolean
|
|
189
|
+
matchCache: Record<string, MatchCacheEntry>
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
export interface PendingState {
|
|
185
|
-
location: Location
|
|
186
|
-
matches: RouteMatch[]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
type Listener = (router: Router<any, any>) => void
|
|
190
|
-
|
|
191
192
|
export type ListenerFn = () => void
|
|
192
193
|
|
|
193
194
|
export interface BuildNextOptions {
|
|
194
195
|
to?: string | number | null
|
|
195
|
-
params?: true | Updater<
|
|
196
|
+
params?: true | Updater<unknown>
|
|
196
197
|
search?: true | Updater<unknown>
|
|
197
198
|
hash?: true | Updater<string>
|
|
199
|
+
state?: LocationState
|
|
198
200
|
key?: string
|
|
199
201
|
from?: string
|
|
200
202
|
fromCurrent?: boolean
|
|
@@ -216,397 +218,361 @@ export interface MatchLocation {
|
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
export interface MatchRouteOptions {
|
|
219
|
-
pending
|
|
221
|
+
pending?: boolean
|
|
220
222
|
caseSensitive?: boolean
|
|
223
|
+
fuzzy?: boolean
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
type LinkCurrentTargetElement = {
|
|
224
227
|
preloadTimeout?: null | ReturnType<typeof setTimeout>
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
export interface
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
export interface DehydratedRouterState
|
|
231
|
+
extends Pick<
|
|
232
|
+
RouterStore,
|
|
233
|
+
'status' | 'latestLocation' | 'currentLocation' | 'lastUpdated'
|
|
234
|
+
> {
|
|
235
|
+
currentMatches: DehydratedRouteMatch[]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface DehydratedRouter<TRouterContext = unknown> {
|
|
239
|
+
// location: Router['__location']
|
|
240
|
+
state: DehydratedRouterState
|
|
241
|
+
context: TRouterContext
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export type MatchCache = Record<string, MatchCacheEntry>
|
|
245
|
+
|
|
246
|
+
interface DehydratedRouteMatch {
|
|
247
|
+
id: string
|
|
248
|
+
state: Pick<
|
|
249
|
+
RouteMatchStore<any, any>,
|
|
250
|
+
'status' | 'routeLoaderData' | 'invalid' | 'invalidAt'
|
|
235
251
|
>
|
|
236
|
-
// Computed in this.update()
|
|
237
|
-
basepath: string
|
|
238
|
-
// Internal:
|
|
239
|
-
allRouteInfo: TAllRouteInfo
|
|
240
|
-
listeners: Listener[]
|
|
241
|
-
location: Location
|
|
242
|
-
navigateTimeout?: Timeout
|
|
243
|
-
nextAction?: 'push' | 'replace'
|
|
244
|
-
state: RouterState
|
|
245
|
-
routeTree: Route<TAllRouteInfo, RouteInfo>
|
|
246
|
-
routesById: RoutesById<TAllRouteInfo>
|
|
247
|
-
navigationPromise: Promise<void>
|
|
248
|
-
removeActionQueue: { action: Action; actionState: ActionState }[]
|
|
249
|
-
startedLoadingAt: number
|
|
250
|
-
resolveNavigation: () => void
|
|
251
|
-
subscribe: (listener: Listener) => () => void
|
|
252
|
-
notify: () => void
|
|
253
|
-
mount: () => () => void
|
|
254
|
-
onFocus: () => void
|
|
255
|
-
update: <TRouteConfig extends RouteConfig = RouteConfig>(
|
|
256
|
-
opts?: RouterOptions<TRouteConfig>,
|
|
257
|
-
) => Router<TRouteConfig>
|
|
258
|
-
|
|
259
|
-
buildNext: (opts: BuildNextOptions) => Location
|
|
260
|
-
cancelMatches: () => void
|
|
261
|
-
loadLocation: (next?: Location) => Promise<void>
|
|
262
|
-
matchCache: Record<string, MatchCacheEntry>
|
|
263
|
-
cleanMatchCache: () => void
|
|
264
|
-
getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
265
|
-
id: TId,
|
|
266
|
-
) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
|
|
267
|
-
loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
|
|
268
|
-
preloadRoute: (
|
|
269
|
-
navigateOpts: BuildNextOptions,
|
|
270
|
-
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
271
|
-
) => Promise<RouteMatch[]>
|
|
272
|
-
matchRoutes: (
|
|
273
|
-
pathname: string,
|
|
274
|
-
opts?: { strictParseParams?: boolean },
|
|
275
|
-
) => RouteMatch[]
|
|
276
|
-
loadMatches: (
|
|
277
|
-
resolvedMatches: RouteMatch[],
|
|
278
|
-
loaderOpts?: { withPending?: boolean } & (
|
|
279
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
280
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never }
|
|
281
|
-
),
|
|
282
|
-
) => Promise<void>
|
|
283
|
-
invalidateRoute: (opts: MatchLocation) => void
|
|
284
|
-
reload: () => Promise<void>
|
|
285
|
-
resolvePath: (from: string, path: string) => string
|
|
286
|
-
navigate: <
|
|
287
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
288
|
-
TTo extends string = '.',
|
|
289
|
-
>(
|
|
290
|
-
opts: NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo>,
|
|
291
|
-
) => Promise<void>
|
|
292
|
-
matchRoute: <
|
|
293
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
294
|
-
TTo extends string = '.',
|
|
295
|
-
>(
|
|
296
|
-
matchLocation: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
297
|
-
opts?: MatchRouteOptions,
|
|
298
|
-
) => boolean
|
|
299
|
-
buildLink: <
|
|
300
|
-
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
301
|
-
TTo extends string = '.',
|
|
302
|
-
>(
|
|
303
|
-
opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
|
|
304
|
-
) => LinkInfo
|
|
305
|
-
__: {
|
|
306
|
-
buildRouteTree: (
|
|
307
|
-
routeConfig: RouteConfig,
|
|
308
|
-
) => Route<TAllRouteInfo, AnyRouteInfo>
|
|
309
|
-
parseLocation: (
|
|
310
|
-
location: History['location'],
|
|
311
|
-
previousLocation?: Location,
|
|
312
|
-
) => Location
|
|
313
|
-
buildLocation: (dest: BuildNextOptions) => Location
|
|
314
|
-
commitLocation: (next: Location, replace?: boolean) => Promise<void>
|
|
315
|
-
navigate: (
|
|
316
|
-
location: BuildNextOptions & { replace?: boolean },
|
|
317
|
-
) => Promise<void>
|
|
318
|
-
}
|
|
319
252
|
}
|
|
320
253
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
254
|
+
export interface RouterContext {}
|
|
255
|
+
|
|
256
|
+
export const defaultFetchServerDataFn: FetchServerDataFn = async ({
|
|
257
|
+
router,
|
|
258
|
+
routeMatch,
|
|
259
|
+
}) => {
|
|
260
|
+
const next = router.buildNext({
|
|
261
|
+
to: '.',
|
|
262
|
+
search: (d: any) => ({
|
|
263
|
+
...(d ?? {}),
|
|
264
|
+
__data: {
|
|
265
|
+
matchId: routeMatch.id,
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const res = await fetch(next.href, {
|
|
271
|
+
method: 'GET',
|
|
272
|
+
signal: routeMatch.abortController.signal,
|
|
273
|
+
})
|
|
324
274
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
275
|
+
if (res.ok) {
|
|
276
|
+
return res.json()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw new Error('Failed to fetch match data')
|
|
280
|
+
}
|
|
328
281
|
|
|
329
|
-
export
|
|
282
|
+
export class Router<
|
|
330
283
|
TRouteConfig extends AnyRouteConfig = RouteConfig,
|
|
331
284
|
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
339
|
-
defaultLoaderMaxAge: 0,
|
|
340
|
-
defaultPreloadMaxAge: 2000,
|
|
341
|
-
defaultPreloadDelay: 50,
|
|
342
|
-
...userOptions,
|
|
343
|
-
stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
|
|
344
|
-
parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
|
|
285
|
+
TRouterContext = unknown,
|
|
286
|
+
> {
|
|
287
|
+
types!: {
|
|
288
|
+
// Super secret internal stuff
|
|
289
|
+
RouteConfig: TRouteConfig
|
|
290
|
+
AllRouteInfo: TAllRouteInfo
|
|
345
291
|
}
|
|
346
292
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
isFetching:
|
|
386
|
-
router.state.status === 'loading' ||
|
|
387
|
-
router.state.matches.some((d) => d.isFetching),
|
|
388
|
-
isPreloading: Object.values(router.matchCache).some(
|
|
389
|
-
(d) =>
|
|
390
|
-
d.match.isFetching &&
|
|
391
|
-
!router.state.matches.find((dd) => dd.matchId === d.match.matchId),
|
|
392
|
-
),
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
cascadeLoaderData(router.state.matches)
|
|
396
|
-
router.listeners.forEach((listener) => listener(router))
|
|
397
|
-
},
|
|
293
|
+
options: PickAsRequired<
|
|
294
|
+
RouterOptions<TRouteConfig, TRouterContext>,
|
|
295
|
+
'stringifySearch' | 'parseSearch' | 'context'
|
|
296
|
+
>
|
|
297
|
+
history!: RouterHistory
|
|
298
|
+
basepath: string
|
|
299
|
+
// __location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
300
|
+
routeTree!: Route<TAllRouteInfo, RouteInfo>
|
|
301
|
+
routesById!: RoutesById<TAllRouteInfo>
|
|
302
|
+
navigateTimeout: undefined | Timeout
|
|
303
|
+
nextAction: undefined | 'push' | 'replace'
|
|
304
|
+
navigationPromise: undefined | Promise<void>
|
|
305
|
+
|
|
306
|
+
store: Store<RouterStore<TAllRouteInfo['fullSearchSchema']>>
|
|
307
|
+
startedLoadingAt = Date.now()
|
|
308
|
+
resolveNavigation = () => {}
|
|
309
|
+
|
|
310
|
+
constructor(options?: RouterOptions<TRouteConfig, TRouterContext>) {
|
|
311
|
+
this.options = {
|
|
312
|
+
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
313
|
+
defaultLoaderMaxAge: 0,
|
|
314
|
+
defaultPreloadMaxAge: 2000,
|
|
315
|
+
defaultPreloadDelay: 50,
|
|
316
|
+
context: undefined!,
|
|
317
|
+
...options,
|
|
318
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
319
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
320
|
+
fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.store = createStore(getInitialRouterState())
|
|
324
|
+
this.basepath = ''
|
|
325
|
+
|
|
326
|
+
this.update(options)
|
|
327
|
+
|
|
328
|
+
// Allow frameworks to hook into the router creation
|
|
329
|
+
this.options.Router?.(this)
|
|
330
|
+
}
|
|
398
331
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
search: true,
|
|
403
|
-
hash: true,
|
|
404
|
-
})
|
|
332
|
+
reset = () => {
|
|
333
|
+
this.store.setState((s) => Object.assign(s, getInitialRouterState()))
|
|
334
|
+
}
|
|
405
335
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
336
|
+
mount = () => {
|
|
337
|
+
// Mount only does anything on the client
|
|
338
|
+
if (!isServer) {
|
|
339
|
+
// If the router matches are empty, load the matches
|
|
340
|
+
if (!this.store.state.currentMatches.length) {
|
|
341
|
+
this.load()
|
|
410
342
|
}
|
|
411
343
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const unsub = router.history.listen((event) => {
|
|
415
|
-
console.log(event.location)
|
|
416
|
-
router.loadLocation(
|
|
417
|
-
router.__.parseLocation(event.location, router.location),
|
|
418
|
-
)
|
|
344
|
+
const unsubHistory = this.history.listen(() => {
|
|
345
|
+
this.load(this.#parseLocation(this.store.state.latestLocation))
|
|
419
346
|
})
|
|
420
347
|
|
|
348
|
+
const visibilityChangeEvent = 'visibilitychange'
|
|
349
|
+
const focusEvent = 'focus'
|
|
350
|
+
|
|
421
351
|
// addEventListener does not exist in React Native, but window does
|
|
352
|
+
// In the future, we might need to invert control here for more adapters
|
|
422
353
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
423
|
-
if (
|
|
424
|
-
// Listen to
|
|
425
|
-
window.addEventListener(
|
|
426
|
-
window.addEventListener(
|
|
354
|
+
if (window.addEventListener) {
|
|
355
|
+
// Listen to visibilitychange and focus
|
|
356
|
+
window.addEventListener(visibilityChangeEvent, this.#onFocus, false)
|
|
357
|
+
window.addEventListener(focusEvent, this.#onFocus, false)
|
|
427
358
|
}
|
|
428
359
|
|
|
429
360
|
return () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
window.removeEventListener('focus', router.onFocus)
|
|
434
|
-
}
|
|
435
|
-
},
|
|
361
|
+
unsubHistory()
|
|
362
|
+
if (window.removeEventListener) {
|
|
363
|
+
// Be sure to unsubscribe if a new handler is set
|
|
436
364
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
},
|
|
440
|
-
|
|
441
|
-
update: (opts) => {
|
|
442
|
-
const newHistory = opts?.history !== router.history
|
|
443
|
-
if (!router.location || newHistory) {
|
|
444
|
-
if (opts?.history) {
|
|
445
|
-
router.history = opts.history
|
|
365
|
+
window.removeEventListener(visibilityChangeEvent, this.#onFocus)
|
|
366
|
+
window.removeEventListener(focusEvent, this.#onFocus)
|
|
446
367
|
}
|
|
447
|
-
router.location = router.__.parseLocation(router.history.location)
|
|
448
|
-
router.state.location = router.location
|
|
449
368
|
}
|
|
369
|
+
}
|
|
450
370
|
|
|
451
|
-
|
|
371
|
+
return () => {}
|
|
372
|
+
}
|
|
452
373
|
|
|
453
|
-
|
|
374
|
+
update = <
|
|
375
|
+
TRouteConfig extends RouteConfig = RouteConfig,
|
|
376
|
+
TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
|
|
377
|
+
TRouterContext = unknown,
|
|
378
|
+
>(
|
|
379
|
+
opts?: RouterOptions<TRouteConfig, TRouterContext>,
|
|
380
|
+
): Router<TRouteConfig, TAllRouteInfo, TRouterContext> => {
|
|
381
|
+
Object.assign(this.options, opts)
|
|
382
|
+
|
|
383
|
+
if (
|
|
384
|
+
!this.history ||
|
|
385
|
+
(this.options.history && this.options.history !== this.history)
|
|
386
|
+
) {
|
|
387
|
+
this.history =
|
|
388
|
+
this.options?.history ?? isServer
|
|
389
|
+
? createMemoryHistory()
|
|
390
|
+
: createBrowserHistory()!
|
|
391
|
+
|
|
392
|
+
this.store.setState((s) => {
|
|
393
|
+
s.latestLocation = this.#parseLocation()
|
|
394
|
+
s.currentLocation = s.latestLocation
|
|
395
|
+
})
|
|
396
|
+
}
|
|
454
397
|
|
|
455
|
-
|
|
398
|
+
const { basepath, routeConfig } = this.options
|
|
456
399
|
|
|
457
|
-
|
|
458
|
-
router.routesById = {} as any
|
|
459
|
-
router.routeTree = router.__.buildRouteTree(routeConfig)
|
|
460
|
-
}
|
|
400
|
+
this.basepath = `/${trimPath(basepath ?? '') ?? ''}`
|
|
461
401
|
|
|
462
|
-
|
|
463
|
-
|
|
402
|
+
if (routeConfig) {
|
|
403
|
+
this.routesById = {} as any
|
|
404
|
+
this.routeTree = this.#buildRouteTree(routeConfig)
|
|
405
|
+
}
|
|
464
406
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
407
|
+
return this as any
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
buildNext = (opts: BuildNextOptions) => {
|
|
411
|
+
const next = this.#buildLocation(opts)
|
|
412
|
+
|
|
413
|
+
const matches = this.matchRoutes(next.pathname)
|
|
414
|
+
|
|
415
|
+
const __preSearchFilters = matches
|
|
416
|
+
.map((match) => match.route.options.preSearchFilters ?? [])
|
|
417
|
+
.flat()
|
|
418
|
+
.filter(Boolean)
|
|
419
|
+
|
|
420
|
+
const __postSearchFilters = matches
|
|
421
|
+
.map((match) => match.route.options.postSearchFilters ?? [])
|
|
422
|
+
.flat()
|
|
423
|
+
.filter(Boolean)
|
|
424
|
+
|
|
425
|
+
return this.#buildLocation({
|
|
426
|
+
...opts,
|
|
427
|
+
__preSearchFilters,
|
|
428
|
+
__postSearchFilters,
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
cancelMatches = () => {
|
|
433
|
+
;[
|
|
434
|
+
...this.store.state.currentMatches,
|
|
435
|
+
...(this.store.state.pendingMatches || []),
|
|
436
|
+
].forEach((match) => {
|
|
437
|
+
match.cancel()
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
load = async (next?: ParsedLocation) => {
|
|
442
|
+
let now = Date.now()
|
|
443
|
+
const startedAt = now
|
|
444
|
+
this.startedLoadingAt = startedAt
|
|
445
|
+
|
|
446
|
+
// Cancel any pending matches
|
|
447
|
+
this.cancelMatches()
|
|
473
448
|
|
|
474
|
-
|
|
475
|
-
const id = Math.random()
|
|
476
|
-
router.startedLoadingAt = id
|
|
449
|
+
let matches!: RouteMatch<any, any>[]
|
|
477
450
|
|
|
451
|
+
batch(() => {
|
|
478
452
|
if (next) {
|
|
479
453
|
// Ingest the new location
|
|
480
|
-
|
|
454
|
+
this.store.setState((s) => {
|
|
455
|
+
s.latestLocation = next
|
|
456
|
+
})
|
|
481
457
|
}
|
|
482
458
|
|
|
483
|
-
// Clear out old actions
|
|
484
|
-
router.removeActionQueue.forEach(({ action, actionState }) => {
|
|
485
|
-
if (router.state.currentAction === actionState) {
|
|
486
|
-
router.state.currentAction = undefined
|
|
487
|
-
}
|
|
488
|
-
if (action.current === actionState) {
|
|
489
|
-
action.current = undefined
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
router.removeActionQueue = []
|
|
493
|
-
|
|
494
|
-
// Cancel any pending matches
|
|
495
|
-
router.cancelMatches()
|
|
496
|
-
|
|
497
459
|
// Match the routes
|
|
498
|
-
|
|
460
|
+
matches = this.matchRoutes(this.store.state.latestLocation.pathname, {
|
|
499
461
|
strictParseParams: true,
|
|
500
462
|
})
|
|
501
463
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
464
|
+
this.store.setState((s) => {
|
|
465
|
+
s.status = 'loading'
|
|
466
|
+
s.pendingMatches = matches
|
|
467
|
+
s.pendingLocation = this.store.state.latestLocation
|
|
468
|
+
})
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
// Load the matches
|
|
472
|
+
try {
|
|
473
|
+
await this.loadMatches(matches)
|
|
474
|
+
} catch (err: any) {
|
|
475
|
+
console.warn(err)
|
|
476
|
+
invariant(
|
|
477
|
+
false,
|
|
478
|
+
'Matches failed to load due to error above ☝️. Navigation cancelled!',
|
|
479
|
+
)
|
|
480
|
+
}
|
|
510
481
|
|
|
511
|
-
|
|
482
|
+
if (this.startedLoadingAt !== startedAt) {
|
|
483
|
+
// Ignore side-effects of outdated side-effects
|
|
484
|
+
return this.navigationPromise
|
|
485
|
+
}
|
|
512
486
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
487
|
+
const previousMatches = this.store.state.currentMatches
|
|
488
|
+
|
|
489
|
+
const exiting: RouteMatch[] = [],
|
|
490
|
+
staying: RouteMatch[] = []
|
|
517
491
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
492
|
+
previousMatches.forEach((d) => {
|
|
493
|
+
if (matches.find((dd) => dd.id === d.id)) {
|
|
494
|
+
staying.push(d)
|
|
495
|
+
} else {
|
|
496
|
+
exiting.push(d)
|
|
521
497
|
}
|
|
498
|
+
})
|
|
522
499
|
|
|
523
|
-
|
|
500
|
+
const entering = matches.filter((d) => {
|
|
501
|
+
return !previousMatches.find((dd) => dd.id === d.id)
|
|
502
|
+
})
|
|
524
503
|
|
|
525
|
-
|
|
526
|
-
staying: RouteMatch[] = []
|
|
504
|
+
now = Date.now()
|
|
527
505
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
exiting.push(d)
|
|
533
|
-
}
|
|
506
|
+
exiting.forEach((d) => {
|
|
507
|
+
d.__onExit?.({
|
|
508
|
+
params: d.params,
|
|
509
|
+
search: d.store.state.routeSearch,
|
|
534
510
|
})
|
|
535
511
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
search: d.routeSearch,
|
|
512
|
+
// Clear non-loading error states when match leaves
|
|
513
|
+
if (d.store.state.status === 'error' && !d.store.state.isFetching) {
|
|
514
|
+
d.store.setState((s) => {
|
|
515
|
+
s.status = 'idle'
|
|
516
|
+
s.error = undefined
|
|
542
517
|
})
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const gc = Math.max(
|
|
521
|
+
d.route.options.loaderGcMaxAge ??
|
|
522
|
+
this.options.defaultLoaderGcMaxAge ??
|
|
523
|
+
0,
|
|
524
|
+
d.route.options.loaderMaxAge ?? this.options.defaultLoaderMaxAge ?? 0,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
if (gc > 0) {
|
|
528
|
+
this.store.setState((s) => {
|
|
529
|
+
s.matchCache[d.id] = {
|
|
554
530
|
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
555
531
|
match: d,
|
|
556
532
|
}
|
|
557
|
-
}
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
staying.forEach((d) => {
|
|
561
|
-
d.options.onTransition?.({
|
|
562
|
-
params: d.params,
|
|
563
|
-
search: d.routeSearch,
|
|
564
533
|
})
|
|
565
|
-
}
|
|
534
|
+
}
|
|
535
|
+
})
|
|
566
536
|
|
|
567
|
-
|
|
568
|
-
|
|
537
|
+
staying.forEach((d) => {
|
|
538
|
+
d.route.options.onTransition?.({
|
|
539
|
+
params: d.params,
|
|
540
|
+
search: d.store.state.routeSearch,
|
|
569
541
|
})
|
|
542
|
+
})
|
|
570
543
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
})
|
|
576
|
-
delete router.matchCache[d.matchId]
|
|
544
|
+
entering.forEach((d) => {
|
|
545
|
+
d.__onExit = d.route.options.onLoaded?.({
|
|
546
|
+
params: d.params,
|
|
547
|
+
search: d.store.state.search,
|
|
577
548
|
})
|
|
549
|
+
delete this.store.state.matchCache[d.id]
|
|
550
|
+
})
|
|
578
551
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
await Promise.all(
|
|
582
|
-
matches.map((d) => d.__.loaderPromise || Promise.resolve()),
|
|
583
|
-
)
|
|
584
|
-
}
|
|
585
|
-
if (router.startedLoadingAt !== id) {
|
|
586
|
-
// Ignore side-effects of match loading
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
router.state = {
|
|
591
|
-
...router.state,
|
|
592
|
-
location: router.location,
|
|
593
|
-
matches,
|
|
594
|
-
pending: undefined,
|
|
552
|
+
this.store.setState((s) => {
|
|
553
|
+
Object.assign(s, {
|
|
595
554
|
status: 'idle',
|
|
596
|
-
|
|
555
|
+
currentLocation: this.store.state.latestLocation,
|
|
556
|
+
currentMatches: matches,
|
|
557
|
+
pendingLocation: undefined,
|
|
558
|
+
pendingMatches: undefined,
|
|
559
|
+
})
|
|
560
|
+
})
|
|
597
561
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
562
|
+
this.options.onRouteChange?.()
|
|
563
|
+
|
|
564
|
+
this.resolveNavigation()
|
|
565
|
+
}
|
|
601
566
|
|
|
602
|
-
|
|
603
|
-
|
|
567
|
+
cleanMatchCache = () => {
|
|
568
|
+
const now = Date.now()
|
|
604
569
|
|
|
605
|
-
|
|
606
|
-
|
|
570
|
+
this.store.setState((s) => {
|
|
571
|
+
Object.keys(s.matchCache).forEach((matchId) => {
|
|
572
|
+
const entry = s.matchCache[matchId]!
|
|
607
573
|
|
|
608
574
|
// Don't remove loading matches
|
|
609
|
-
if (entry.match.status === 'loading') {
|
|
575
|
+
if (entry.match.store.state.status === 'loading') {
|
|
610
576
|
return
|
|
611
577
|
}
|
|
612
578
|
|
|
@@ -616,617 +582,827 @@ export function createRouter<
|
|
|
616
582
|
}
|
|
617
583
|
|
|
618
584
|
// Everything else gets removed
|
|
619
|
-
delete
|
|
585
|
+
delete s.matchCache[matchId]
|
|
620
586
|
})
|
|
621
|
-
}
|
|
587
|
+
})
|
|
588
|
+
}
|
|
622
589
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
})
|
|
628
|
-
await router.loadMatches(matches)
|
|
629
|
-
return matches
|
|
630
|
-
},
|
|
590
|
+
getRoute = <TId extends keyof TAllRouteInfo['routeInfoById']>(
|
|
591
|
+
id: TId,
|
|
592
|
+
): Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]> => {
|
|
593
|
+
const route = this.routesById[id]
|
|
631
594
|
|
|
632
|
-
|
|
633
|
-
const next = router.buildNext(navigateOpts)
|
|
634
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
635
|
-
strictParseParams: true,
|
|
636
|
-
})
|
|
637
|
-
await router.loadMatches(matches, {
|
|
638
|
-
preload: true,
|
|
639
|
-
maxAge:
|
|
640
|
-
loaderOpts.maxAge ??
|
|
641
|
-
router.options.defaultPreloadMaxAge ??
|
|
642
|
-
router.options.defaultLoaderMaxAge ??
|
|
643
|
-
0,
|
|
644
|
-
gcMaxAge:
|
|
645
|
-
loaderOpts.gcMaxAge ??
|
|
646
|
-
router.options.defaultPreloadGcMaxAge ??
|
|
647
|
-
router.options.defaultLoaderGcMaxAge ??
|
|
648
|
-
0,
|
|
649
|
-
})
|
|
650
|
-
return matches
|
|
651
|
-
},
|
|
595
|
+
invariant(route, `Route with id "${id as string}" not found`)
|
|
652
596
|
|
|
653
|
-
|
|
654
|
-
|
|
597
|
+
return route
|
|
598
|
+
}
|
|
655
599
|
|
|
656
|
-
|
|
600
|
+
loadRoute = async (
|
|
601
|
+
navigateOpts: BuildNextOptions = this.store.state.latestLocation,
|
|
602
|
+
): Promise<RouteMatch[]> => {
|
|
603
|
+
const next = this.buildNext(navigateOpts)
|
|
604
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
605
|
+
strictParseParams: true,
|
|
606
|
+
})
|
|
607
|
+
await this.loadMatches(matches)
|
|
608
|
+
return matches
|
|
609
|
+
}
|
|
657
610
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
611
|
+
preloadRoute = async (
|
|
612
|
+
navigateOpts: BuildNextOptions = this.store.state.latestLocation,
|
|
613
|
+
loaderOpts: { maxAge?: number; gcMaxAge?: number },
|
|
614
|
+
) => {
|
|
615
|
+
const next = this.buildNext(navigateOpts)
|
|
616
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
617
|
+
strictParseParams: true,
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
await this.loadMatches(matches, {
|
|
621
|
+
preload: true,
|
|
622
|
+
maxAge:
|
|
623
|
+
loaderOpts.maxAge ??
|
|
624
|
+
this.options.defaultPreloadMaxAge ??
|
|
625
|
+
this.options.defaultLoaderMaxAge ??
|
|
626
|
+
0,
|
|
627
|
+
gcMaxAge:
|
|
628
|
+
loaderOpts.gcMaxAge ??
|
|
629
|
+
this.options.defaultPreloadGcMaxAge ??
|
|
630
|
+
this.options.defaultLoaderGcMaxAge ??
|
|
631
|
+
0,
|
|
632
|
+
})
|
|
633
|
+
return matches
|
|
634
|
+
}
|
|
661
635
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
...(router.state.pending?.matches ?? []),
|
|
665
|
-
]
|
|
636
|
+
matchRoutes = (pathname: string, opts?: { strictParseParams?: boolean }) => {
|
|
637
|
+
const matches: RouteMatch[] = []
|
|
666
638
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
639
|
+
if (!this.routeTree) {
|
|
640
|
+
return matches
|
|
641
|
+
}
|
|
670
642
|
|
|
671
|
-
|
|
643
|
+
const existingMatches = [
|
|
644
|
+
...this.store.state.currentMatches,
|
|
645
|
+
...(this.store.state.pendingMatches ?? []),
|
|
646
|
+
]
|
|
672
647
|
|
|
673
|
-
|
|
648
|
+
const recurse = async (routes: Route<any, any>[]): Promise<void> => {
|
|
649
|
+
const parentMatch = last(matches)
|
|
650
|
+
let params = parentMatch?.params ?? {}
|
|
674
651
|
|
|
675
|
-
|
|
676
|
-
routes.some((route) => {
|
|
677
|
-
if (!route.routePath && route.childRoutes?.length) {
|
|
678
|
-
return findMatchInRoutes(
|
|
679
|
-
[...foundRoutes, route],
|
|
680
|
-
route.childRoutes,
|
|
681
|
-
)
|
|
682
|
-
}
|
|
652
|
+
const filteredRoutes = this.options.filterRoutes?.(routes) ?? routes
|
|
683
653
|
|
|
684
|
-
|
|
685
|
-
route.routePath !== '/' || route.childRoutes?.length
|
|
686
|
-
)
|
|
654
|
+
let foundRoutes: Route[] = []
|
|
687
655
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
})
|
|
656
|
+
const findMatchInRoutes = (parentRoutes: Route[], routes: Route[]) => {
|
|
657
|
+
routes.some((route) => {
|
|
658
|
+
if (!route.path && route.childRoutes?.length) {
|
|
659
|
+
return findMatchInRoutes([...foundRoutes, route], route.childRoutes)
|
|
660
|
+
}
|
|
694
661
|
|
|
695
|
-
|
|
696
|
-
let parsedParams
|
|
662
|
+
const fuzzy = !!(route.path !== '/' || route.childRoutes?.length)
|
|
697
663
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
664
|
+
const matchParams = matchPathname(this.basepath, pathname, {
|
|
665
|
+
to: route.fullPath,
|
|
666
|
+
fuzzy,
|
|
667
|
+
caseSensitive:
|
|
668
|
+
route.options.caseSensitive ?? this.options.caseSensitive,
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
if (matchParams) {
|
|
672
|
+
let parsedParams
|
|
706
673
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
674
|
+
try {
|
|
675
|
+
parsedParams =
|
|
676
|
+
route.options.parseParams?.(matchParams!) ?? matchParams
|
|
677
|
+
} catch (err) {
|
|
678
|
+
if (opts?.strictParseParams) {
|
|
679
|
+
throw err
|
|
710
680
|
}
|
|
711
681
|
}
|
|
712
682
|
|
|
713
|
-
|
|
714
|
-
|
|
683
|
+
params = {
|
|
684
|
+
...params,
|
|
685
|
+
...parsedParams,
|
|
715
686
|
}
|
|
687
|
+
}
|
|
716
688
|
|
|
717
|
-
|
|
718
|
-
|
|
689
|
+
if (!!matchParams) {
|
|
690
|
+
foundRoutes = [...parentRoutes, route]
|
|
691
|
+
}
|
|
719
692
|
|
|
720
693
|
return !!foundRoutes.length
|
|
721
|
-
}
|
|
694
|
+
})
|
|
722
695
|
|
|
723
|
-
|
|
696
|
+
return !!foundRoutes.length
|
|
697
|
+
}
|
|
724
698
|
|
|
725
|
-
|
|
726
|
-
return
|
|
727
|
-
}
|
|
699
|
+
findMatchInRoutes([], filteredRoutes)
|
|
728
700
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
701
|
+
if (!foundRoutes.length) {
|
|
702
|
+
return
|
|
703
|
+
}
|
|
732
704
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
705
|
+
foundRoutes.forEach((foundRoute) => {
|
|
706
|
+
const interpolatedPath = interpolatePath(foundRoute.path, params)
|
|
707
|
+
const matchId = interpolatePath(foundRoute.id, params, true)
|
|
708
|
+
|
|
709
|
+
const match =
|
|
710
|
+
existingMatches.find((d) => d.id === matchId) ||
|
|
711
|
+
this.store.state.matchCache[matchId]?.match ||
|
|
712
|
+
new RouteMatch(this, foundRoute, {
|
|
713
|
+
id: matchId,
|
|
714
|
+
params,
|
|
715
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
716
|
+
})
|
|
741
717
|
|
|
742
|
-
|
|
743
|
-
|
|
718
|
+
matches.push(match)
|
|
719
|
+
})
|
|
744
720
|
|
|
745
|
-
|
|
721
|
+
const foundRoute = last(foundRoutes)!
|
|
746
722
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
723
|
+
if (foundRoute.childRoutes?.length) {
|
|
724
|
+
recurse(foundRoute.childRoutes)
|
|
750
725
|
}
|
|
726
|
+
}
|
|
751
727
|
|
|
752
|
-
|
|
728
|
+
recurse([this.routeTree])
|
|
753
729
|
|
|
754
|
-
|
|
730
|
+
linkMatches(matches)
|
|
755
731
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
loadMatches: async (resolvedMatches, loaderOpts) => {
|
|
760
|
-
const matchPromises = resolvedMatches.map(async (match) => {
|
|
761
|
-
// Validate the match (loads search params etc)
|
|
762
|
-
match.__.validate()
|
|
763
|
-
match.load(loaderOpts)
|
|
732
|
+
return matches
|
|
733
|
+
}
|
|
764
734
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
735
|
+
loadMatches = async (
|
|
736
|
+
resolvedMatches: RouteMatch[],
|
|
737
|
+
loaderOpts?:
|
|
738
|
+
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
739
|
+
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
740
|
+
) => {
|
|
741
|
+
this.cleanMatchCache()
|
|
742
|
+
resolvedMatches.forEach(async (match) => {
|
|
743
|
+
// Validate the match (loads search params etc)
|
|
744
|
+
match.__validate()
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
// Check each match middleware to see if the route can be accessed
|
|
748
|
+
await Promise.all(
|
|
749
|
+
resolvedMatches.map(async (match) => {
|
|
750
|
+
try {
|
|
751
|
+
await match.route.options.beforeLoad?.({
|
|
752
|
+
router: this as any,
|
|
753
|
+
match,
|
|
754
|
+
})
|
|
755
|
+
} catch (err) {
|
|
756
|
+
if (!loaderOpts?.preload) {
|
|
757
|
+
match.route.options.onLoadError?.(err)
|
|
758
|
+
}
|
|
768
759
|
|
|
769
|
-
|
|
770
|
-
// This might be completion, error, or a pending state
|
|
771
|
-
await match.__.loadPromise
|
|
760
|
+
throw err
|
|
772
761
|
}
|
|
773
|
-
})
|
|
762
|
+
}),
|
|
763
|
+
)
|
|
774
764
|
|
|
775
|
-
|
|
765
|
+
const matchPromises = resolvedMatches.map(async (match, index) => {
|
|
766
|
+
const prevMatch = resolvedMatches[(index = 1)]
|
|
767
|
+
const search = match.store.state.search as { __data?: any }
|
|
776
768
|
|
|
777
|
-
|
|
778
|
-
|
|
769
|
+
if (search.__data?.matchId && search.__data.matchId !== match.id) {
|
|
770
|
+
return
|
|
771
|
+
}
|
|
779
772
|
|
|
780
|
-
|
|
781
|
-
const next = router.buildNext(opts)
|
|
782
|
-
const unloadedMatchIds = router
|
|
783
|
-
.matchRoutes(next.pathname)
|
|
784
|
-
.map((d) => d.matchId)
|
|
785
|
-
;[
|
|
786
|
-
...router.state.matches,
|
|
787
|
-
...(router.state.pending?.matches ?? []),
|
|
788
|
-
].forEach((match) => {
|
|
789
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
790
|
-
match.invalidate()
|
|
791
|
-
}
|
|
792
|
-
})
|
|
793
|
-
},
|
|
773
|
+
match.load(loaderOpts)
|
|
794
774
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
search: true,
|
|
800
|
-
}),
|
|
775
|
+
if (match.store.state.status !== 'success' && match.__loadPromise) {
|
|
776
|
+
// Wait for the first sign of activity from the match
|
|
777
|
+
await match.__loadPromise
|
|
778
|
+
}
|
|
801
779
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
780
|
+
if (prevMatch) {
|
|
781
|
+
await prevMatch.__loadPromise
|
|
782
|
+
}
|
|
783
|
+
})
|
|
805
784
|
|
|
806
|
-
|
|
807
|
-
|
|
785
|
+
await Promise.all(matchPromises)
|
|
786
|
+
}
|
|
808
787
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
788
|
+
loadMatchData = async (
|
|
789
|
+
routeMatch: RouteMatch<any, any>,
|
|
790
|
+
): Promise<Record<string, unknown>> => {
|
|
791
|
+
if (isServer || !this.options.useServerData) {
|
|
792
|
+
return (
|
|
793
|
+
(await routeMatch.route.options.loader?.({
|
|
794
|
+
// parentLoaderPromise: routeMatch.parentMatch.dataPromise,
|
|
795
|
+
params: routeMatch.params,
|
|
796
|
+
search: routeMatch.store.state.routeSearch,
|
|
797
|
+
signal: routeMatch.abortController.signal,
|
|
798
|
+
})) || {}
|
|
799
|
+
)
|
|
800
|
+
} else {
|
|
801
|
+
// Refresh:
|
|
802
|
+
// '/dashboard'
|
|
803
|
+
// '/dashboard/invoices/'
|
|
804
|
+
// '/dashboard/invoices/123'
|
|
815
805
|
|
|
816
|
-
|
|
806
|
+
// New:
|
|
807
|
+
// '/dashboard/invoices/456'
|
|
817
808
|
|
|
818
|
-
|
|
819
|
-
if (!router.state.pending?.location) {
|
|
820
|
-
return false
|
|
821
|
-
}
|
|
822
|
-
return !!matchPathname(router.state.pending.location.pathname, {
|
|
823
|
-
...opts,
|
|
824
|
-
to: next.pathname,
|
|
825
|
-
})
|
|
826
|
-
}
|
|
809
|
+
// TODO: batch requests when possible
|
|
827
810
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
811
|
+
const res = await this.options.fetchServerDataFn!({
|
|
812
|
+
router: this,
|
|
813
|
+
routeMatch,
|
|
831
814
|
})
|
|
832
|
-
},
|
|
833
815
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
816
|
+
return res
|
|
817
|
+
}
|
|
818
|
+
}
|
|
837
819
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
820
|
+
invalidateRoute = async (opts: MatchLocation) => {
|
|
821
|
+
const next = this.buildNext(opts)
|
|
822
|
+
const unloadedMatchIds = this.matchRoutes(next.pathname).map((d) => d.id)
|
|
823
|
+
|
|
824
|
+
await Promise.allSettled(
|
|
825
|
+
[
|
|
826
|
+
...this.store.state.currentMatches,
|
|
827
|
+
...(this.store.state.pendingMatches ?? []),
|
|
828
|
+
].map(async (match) => {
|
|
829
|
+
if (unloadedMatchIds.includes(match.id)) {
|
|
830
|
+
return match.invalidate()
|
|
831
|
+
}
|
|
832
|
+
}),
|
|
833
|
+
)
|
|
834
|
+
}
|
|
842
835
|
|
|
843
|
-
|
|
836
|
+
reload = () => {
|
|
837
|
+
this.navigate({
|
|
838
|
+
fromCurrent: true,
|
|
839
|
+
replace: true,
|
|
840
|
+
search: true,
|
|
841
|
+
} as any)
|
|
842
|
+
}
|
|
844
843
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
} catch (e) {}
|
|
844
|
+
resolvePath = (from: string, path: string) => {
|
|
845
|
+
return resolvePath(this.basepath!, from, cleanPath(path))
|
|
846
|
+
}
|
|
849
847
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
848
|
+
navigate = async <
|
|
849
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
850
|
+
TTo extends string = '.',
|
|
851
|
+
>({
|
|
852
|
+
from,
|
|
853
|
+
to = '.' as any,
|
|
854
|
+
search,
|
|
855
|
+
hash,
|
|
856
|
+
replace,
|
|
857
|
+
params,
|
|
858
|
+
}: NavigateOptions<TAllRouteInfo, TFrom, TTo>) => {
|
|
859
|
+
// If this link simply reloads the current route,
|
|
860
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
861
|
+
|
|
862
|
+
// If this `to` is a valid external URL, return
|
|
863
|
+
// null for LinkUtils
|
|
864
|
+
const toString = String(to)
|
|
865
|
+
const fromString = String(from)
|
|
866
|
+
|
|
867
|
+
let isExternal
|
|
868
|
+
|
|
869
|
+
try {
|
|
870
|
+
new URL(`${toString}`)
|
|
871
|
+
isExternal = true
|
|
872
|
+
} catch (e) {}
|
|
873
|
+
|
|
874
|
+
invariant(
|
|
875
|
+
!isExternal,
|
|
876
|
+
'Attempting to navigate to external url with this.navigate!',
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
return this.#commitLocation({
|
|
880
|
+
from: fromString,
|
|
881
|
+
to: toString,
|
|
882
|
+
search,
|
|
883
|
+
hash,
|
|
884
|
+
replace,
|
|
885
|
+
params,
|
|
886
|
+
})
|
|
887
|
+
}
|
|
854
888
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
889
|
+
matchRoute = <
|
|
890
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
891
|
+
TTo extends string = '.',
|
|
892
|
+
>(
|
|
893
|
+
location: ToOptions<TAllRouteInfo, TFrom, TTo>,
|
|
894
|
+
opts?: MatchRouteOptions,
|
|
895
|
+
):
|
|
896
|
+
| false
|
|
897
|
+
| TAllRouteInfo['routeInfoById'][ResolveRelativePath<
|
|
898
|
+
TFrom,
|
|
899
|
+
NoInfer<TTo>
|
|
900
|
+
>]['allParams'] => {
|
|
901
|
+
location = {
|
|
902
|
+
...location,
|
|
903
|
+
to: location.to
|
|
904
|
+
? this.resolvePath(location.from ?? '', location.to)
|
|
905
|
+
: undefined,
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const next = this.buildNext(location)
|
|
909
|
+
|
|
910
|
+
if (opts?.pending) {
|
|
911
|
+
if (!this.store.state.pendingLocation) {
|
|
912
|
+
return false
|
|
913
|
+
}
|
|
864
914
|
|
|
865
|
-
|
|
915
|
+
return matchPathname(
|
|
916
|
+
this.basepath,
|
|
917
|
+
this.store.state.pendingLocation!.pathname,
|
|
918
|
+
{
|
|
919
|
+
...opts,
|
|
920
|
+
to: next.pathname,
|
|
921
|
+
},
|
|
922
|
+
) as any
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
return matchPathname(
|
|
926
|
+
this.basepath,
|
|
927
|
+
this.store.state.currentLocation.pathname,
|
|
928
|
+
{
|
|
929
|
+
...opts,
|
|
930
|
+
to: next.pathname,
|
|
931
|
+
},
|
|
932
|
+
) as any
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
buildLink = <
|
|
936
|
+
TFrom extends ValidFromPath<TAllRouteInfo> = '/',
|
|
937
|
+
TTo extends string = '.',
|
|
938
|
+
>({
|
|
939
|
+
from,
|
|
940
|
+
to = '.' as any,
|
|
941
|
+
search,
|
|
942
|
+
params,
|
|
943
|
+
hash,
|
|
944
|
+
target,
|
|
945
|
+
replace,
|
|
946
|
+
activeOptions,
|
|
947
|
+
preload,
|
|
948
|
+
preloadMaxAge: userPreloadMaxAge,
|
|
949
|
+
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
950
|
+
preloadDelay: userPreloadDelay,
|
|
951
|
+
disabled,
|
|
952
|
+
}: LinkOptions<TAllRouteInfo, TFrom, TTo>): LinkInfo => {
|
|
953
|
+
// If this link simply reloads the current route,
|
|
954
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
955
|
+
|
|
956
|
+
// If this `to` is a valid external URL, return
|
|
957
|
+
// null for LinkUtils
|
|
958
|
+
|
|
959
|
+
try {
|
|
960
|
+
new URL(`${to}`)
|
|
961
|
+
return {
|
|
962
|
+
type: 'external',
|
|
963
|
+
href: to,
|
|
964
|
+
}
|
|
965
|
+
} catch (e) {}
|
|
966
|
+
|
|
967
|
+
const nextOpts = {
|
|
866
968
|
from,
|
|
867
|
-
to
|
|
969
|
+
to,
|
|
868
970
|
search,
|
|
869
971
|
params,
|
|
870
972
|
hash,
|
|
871
|
-
target,
|
|
872
973
|
replace,
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const next = this.buildNext(nextOpts)
|
|
977
|
+
|
|
978
|
+
preload = preload ?? this.options.defaultPreload
|
|
979
|
+
const preloadDelay =
|
|
980
|
+
userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0
|
|
981
|
+
|
|
982
|
+
// Compare path/hash for matches
|
|
983
|
+
const pathIsEqual =
|
|
984
|
+
this.store.state.currentLocation.pathname === next.pathname
|
|
985
|
+
const currentPathSplit =
|
|
986
|
+
this.store.state.currentLocation.pathname.split('/')
|
|
987
|
+
const nextPathSplit = next.pathname.split('/')
|
|
988
|
+
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
989
|
+
(d, i) => d === currentPathSplit[i],
|
|
990
|
+
)
|
|
991
|
+
const hashIsEqual = this.store.state.currentLocation.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
|
+
this.invalidateRoute(nextOpts)
|
|
891
1011
|
}
|
|
892
|
-
} catch (e) {}
|
|
893
|
-
|
|
894
|
-
const nextOpts = {
|
|
895
|
-
from,
|
|
896
|
-
to,
|
|
897
|
-
search,
|
|
898
|
-
params,
|
|
899
|
-
hash,
|
|
900
|
-
replace,
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const next = router.buildNext(nextOpts)
|
|
904
1012
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1013
|
+
// All is well? Navigate!
|
|
1014
|
+
this.#commitLocation(nextOpts as any)
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// The click handler
|
|
1019
|
+
const handleFocus = (e: MouseEvent) => {
|
|
1020
|
+
if (preload) {
|
|
1021
|
+
this.preloadRoute(nextOpts, {
|
|
1022
|
+
maxAge: userPreloadMaxAge,
|
|
1023
|
+
gcMaxAge: userPreloadGcMaxAge,
|
|
1024
|
+
}).catch((err) => {
|
|
1025
|
+
console.warn(err)
|
|
1026
|
+
console.warn('Error preloading route! ☝️')
|
|
1027
|
+
})
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
908
1030
|
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
const currentPathSplit = router.state.location.pathname.split('/')
|
|
912
|
-
const nextPathSplit = next.pathname.split('/')
|
|
913
|
-
const pathIsFuzzyEqual = nextPathSplit.every(
|
|
914
|
-
(d, i) => d === currentPathSplit[i],
|
|
915
|
-
)
|
|
916
|
-
const hashIsEqual = router.state.location.hash === next.hash
|
|
917
|
-
// Combine the matches based on user options
|
|
918
|
-
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual
|
|
919
|
-
const hashTest = activeOptions?.includeHash ? hashIsEqual : true
|
|
920
|
-
|
|
921
|
-
// The final "active" test
|
|
922
|
-
const isActive = pathTest && hashTest
|
|
923
|
-
|
|
924
|
-
// The click handler
|
|
925
|
-
const handleClick = (e: MouseEvent) => {
|
|
926
|
-
if (
|
|
927
|
-
!disabled &&
|
|
928
|
-
!isCtrlEvent(e) &&
|
|
929
|
-
!e.defaultPrevented &&
|
|
930
|
-
(!target || target === '_self') &&
|
|
931
|
-
e.button === 0
|
|
932
|
-
) {
|
|
933
|
-
e.preventDefault()
|
|
934
|
-
if (pathIsEqual && !search && !hash) {
|
|
935
|
-
router.invalidateRoute(nextOpts)
|
|
936
|
-
}
|
|
1031
|
+
const handleEnter = (e: MouseEvent) => {
|
|
1032
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
937
1033
|
|
|
938
|
-
|
|
939
|
-
|
|
1034
|
+
if (preload) {
|
|
1035
|
+
if (target.preloadTimeout) {
|
|
1036
|
+
return
|
|
940
1037
|
}
|
|
941
|
-
}
|
|
942
1038
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
router.preloadRoute(nextOpts, {
|
|
1039
|
+
target.preloadTimeout = setTimeout(() => {
|
|
1040
|
+
target.preloadTimeout = null
|
|
1041
|
+
this.preloadRoute(nextOpts, {
|
|
947
1042
|
maxAge: userPreloadMaxAge,
|
|
948
1043
|
gcMaxAge: userPreloadGcMaxAge,
|
|
1044
|
+
}).catch((err) => {
|
|
1045
|
+
console.warn(err)
|
|
1046
|
+
console.warn('Error preloading route! ☝️')
|
|
949
1047
|
})
|
|
950
|
-
}
|
|
1048
|
+
}, preloadDelay)
|
|
951
1049
|
}
|
|
1050
|
+
}
|
|
952
1051
|
|
|
953
|
-
|
|
954
|
-
|
|
1052
|
+
const handleLeave = (e: MouseEvent) => {
|
|
1053
|
+
const target = (e.target || {}) as LinkCurrentTargetElement
|
|
955
1054
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
target.preloadTimeout = setTimeout(() => {
|
|
962
|
-
target.preloadTimeout = null
|
|
963
|
-
router.preloadRoute(nextOpts, {
|
|
964
|
-
maxAge: userPreloadMaxAge,
|
|
965
|
-
gcMaxAge: userPreloadGcMaxAge,
|
|
966
|
-
})
|
|
967
|
-
}, preloadDelay)
|
|
968
|
-
}
|
|
1055
|
+
if (target.preloadTimeout) {
|
|
1056
|
+
clearTimeout(target.preloadTimeout)
|
|
1057
|
+
target.preloadTimeout = null
|
|
969
1058
|
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
type: 'internal',
|
|
1063
|
+
next,
|
|
1064
|
+
handleFocus,
|
|
1065
|
+
handleClick,
|
|
1066
|
+
handleEnter,
|
|
1067
|
+
handleLeave,
|
|
1068
|
+
isActive,
|
|
1069
|
+
disabled,
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
970
1072
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1073
|
+
dehydrate = (): DehydratedRouter<TRouterContext> => {
|
|
1074
|
+
return {
|
|
1075
|
+
state: {
|
|
1076
|
+
...pick(this.store.state, [
|
|
1077
|
+
'latestLocation',
|
|
1078
|
+
'currentLocation',
|
|
1079
|
+
'status',
|
|
1080
|
+
'lastUpdated',
|
|
1081
|
+
]),
|
|
1082
|
+
currentMatches: this.store.state.currentMatches.map((match) => ({
|
|
1083
|
+
id: match.id,
|
|
1084
|
+
state: {
|
|
1085
|
+
...pick(match.store.state, [
|
|
1086
|
+
'status',
|
|
1087
|
+
'routeLoaderData',
|
|
1088
|
+
'invalidAt',
|
|
1089
|
+
'invalid',
|
|
1090
|
+
]),
|
|
1091
|
+
},
|
|
1092
|
+
})),
|
|
1093
|
+
},
|
|
1094
|
+
context: this.options.context as TRouterContext,
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
979
1097
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
handleClick,
|
|
985
|
-
handleEnter,
|
|
986
|
-
handleLeave,
|
|
987
|
-
isActive,
|
|
988
|
-
disabled,
|
|
989
|
-
}
|
|
990
|
-
},
|
|
991
|
-
buildNext: (opts: BuildNextOptions) => {
|
|
992
|
-
const next = router.__.buildLocation(opts)
|
|
1098
|
+
hydrate = (dehydratedRouter: DehydratedRouter<TRouterContext>) => {
|
|
1099
|
+
this.store.setState((s) => {
|
|
1100
|
+
// Update the context TODO: make this part of state?
|
|
1101
|
+
this.options.context = dehydratedRouter.context
|
|
993
1102
|
|
|
994
|
-
|
|
1103
|
+
// Match the routes
|
|
1104
|
+
const currentMatches = this.matchRoutes(
|
|
1105
|
+
dehydratedRouter.state.latestLocation.pathname,
|
|
1106
|
+
{
|
|
1107
|
+
strictParseParams: true,
|
|
1108
|
+
},
|
|
1109
|
+
)
|
|
995
1110
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1111
|
+
currentMatches.forEach((match, index) => {
|
|
1112
|
+
const dehydratedMatch = dehydratedRouter.state.currentMatches[index]
|
|
1113
|
+
invariant(
|
|
1114
|
+
dehydratedMatch && dehydratedMatch.id === match.id,
|
|
1115
|
+
'Oh no! There was a hydration mismatch when attempting to rethis.store the state of the router! 😬',
|
|
1116
|
+
)
|
|
1117
|
+
Object.assign(match, dehydratedMatch)
|
|
1118
|
+
})
|
|
1000
1119
|
|
|
1001
|
-
|
|
1002
|
-
.map((match) => match.options.postSearchFilters ?? [])
|
|
1003
|
-
.flat()
|
|
1004
|
-
.filter(Boolean)
|
|
1120
|
+
currentMatches.forEach((match) => match.__validate())
|
|
1005
1121
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
__postSearchFilters,
|
|
1010
|
-
})
|
|
1011
|
-
},
|
|
1122
|
+
Object.assign(s, { ...dehydratedRouter.state, currentMatches })
|
|
1123
|
+
})
|
|
1124
|
+
}
|
|
1012
1125
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1126
|
+
getLoader = <TFrom extends keyof TAllRouteInfo['routeInfoById'] = '/'>(opts: {
|
|
1127
|
+
from: TFrom
|
|
1128
|
+
}): unknown extends TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1129
|
+
?
|
|
1130
|
+
| Loader<
|
|
1131
|
+
LoaderContext<
|
|
1132
|
+
TAllRouteInfo['routeInfoById'][TFrom]['fullSearchSchema'],
|
|
1133
|
+
TAllRouteInfo['routeInfoById'][TFrom]['allParams']
|
|
1134
|
+
>,
|
|
1135
|
+
TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1136
|
+
>
|
|
1137
|
+
| undefined
|
|
1138
|
+
: Loader<
|
|
1139
|
+
TAllRouteInfo['routeInfoById'][TFrom]['fullSearchSchema'],
|
|
1140
|
+
TAllRouteInfo['routeInfoById'][TFrom]['allParams'],
|
|
1141
|
+
TAllRouteInfo['routeInfoById'][TFrom]['routeLoaderData']
|
|
1142
|
+
> => {
|
|
1143
|
+
const id = opts.from || ('/' as any)
|
|
1144
|
+
|
|
1145
|
+
const route = this.getRoute(id)
|
|
1146
|
+
|
|
1147
|
+
if (!route) return undefined as any
|
|
1148
|
+
|
|
1149
|
+
let loader =
|
|
1150
|
+
this.store.state.loaders[id] ||
|
|
1151
|
+
(() => {
|
|
1152
|
+
this.store.setState((s) => {
|
|
1153
|
+
s.loaders[id] = {
|
|
1154
|
+
pending: [],
|
|
1155
|
+
fetch: (async (loaderContext: LoaderContext<any, any>) => {
|
|
1156
|
+
if (!route) {
|
|
1157
|
+
return
|
|
1037
1158
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1159
|
+
const loaderState: LoaderState<any, any> = {
|
|
1160
|
+
loadedAt: Date.now(),
|
|
1161
|
+
loaderContext,
|
|
1162
|
+
}
|
|
1163
|
+
this.store.setState((s) => {
|
|
1164
|
+
s.loaders[id]!.current = loaderState
|
|
1165
|
+
s.loaders[id]!.latest = loaderState
|
|
1166
|
+
s.loaders[id]!.pending.push(loaderState)
|
|
1167
|
+
})
|
|
1168
|
+
try {
|
|
1169
|
+
return await route.options.loader?.(loaderContext)
|
|
1170
|
+
} finally {
|
|
1171
|
+
this.store.setState((s) => {
|
|
1172
|
+
s.loaders[id]!.pending = s.loaders[id]!.pending.filter(
|
|
1173
|
+
(d) => d !== loaderState,
|
|
1174
|
+
)
|
|
1175
|
+
})
|
|
1176
|
+
}
|
|
1177
|
+
}) as any,
|
|
1178
|
+
}
|
|
1179
|
+
})
|
|
1180
|
+
return this.store.state.loaders[id]!
|
|
1181
|
+
})()
|
|
1040
1182
|
|
|
1041
|
-
|
|
1183
|
+
return loader as any
|
|
1184
|
+
}
|
|
1042
1185
|
|
|
1043
|
-
|
|
1186
|
+
#buildRouteTree = (rootRouteConfig: RouteConfig) => {
|
|
1187
|
+
const recurseRoutes = (
|
|
1188
|
+
routeConfigs: RouteConfig[],
|
|
1189
|
+
parent?: Route<TAllRouteInfo, any, any>,
|
|
1190
|
+
): Route<TAllRouteInfo, any, any>[] => {
|
|
1191
|
+
return routeConfigs.map((routeConfig, i) => {
|
|
1192
|
+
const routeOptions = routeConfig.options
|
|
1193
|
+
const route = new Route(routeConfig, routeOptions, i, parent, this)
|
|
1194
|
+
const existingRoute = (this.routesById as any)[route.id]
|
|
1195
|
+
|
|
1196
|
+
if (existingRoute) {
|
|
1197
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1198
|
+
console.warn(
|
|
1199
|
+
`Duplicate routes found with id: ${String(route.id)}`,
|
|
1200
|
+
this.routesById,
|
|
1201
|
+
route,
|
|
1202
|
+
)
|
|
1203
|
+
}
|
|
1204
|
+
throw new Error()
|
|
1205
|
+
}
|
|
1044
1206
|
|
|
1045
|
-
|
|
1046
|
-
? recurseRoutes(children, route)
|
|
1047
|
-
: undefined
|
|
1207
|
+
;(this.routesById as any)[route.id] = route
|
|
1048
1208
|
|
|
1049
|
-
|
|
1050
|
-
})
|
|
1051
|
-
}
|
|
1209
|
+
const children = routeConfig.children as RouteConfig[]
|
|
1052
1210
|
|
|
1053
|
-
|
|
1211
|
+
route.childRoutes = children.length
|
|
1212
|
+
? recurseRoutes(children, route)
|
|
1213
|
+
: undefined
|
|
1054
1214
|
|
|
1055
|
-
return
|
|
1056
|
-
}
|
|
1215
|
+
return route
|
|
1216
|
+
})
|
|
1217
|
+
}
|
|
1057
1218
|
|
|
1058
|
-
|
|
1059
|
-
location: History['location'],
|
|
1060
|
-
previousLocation?: Location,
|
|
1061
|
-
): Location => {
|
|
1062
|
-
const parsedSearch = router.options.parseSearch(location.search)
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
pathname: location.pathname,
|
|
1066
|
-
searchStr: location.search,
|
|
1067
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1068
|
-
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
1069
|
-
href: `${location.pathname}${location.search}${location.hash}`,
|
|
1070
|
-
state: location.state as LocationState,
|
|
1071
|
-
key: location.key,
|
|
1072
|
-
}
|
|
1073
|
-
},
|
|
1219
|
+
const routes = recurseRoutes([rootRouteConfig])
|
|
1074
1220
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return router.__.commitLocation(next, location.replace)
|
|
1078
|
-
},
|
|
1221
|
+
return routes[0]!
|
|
1222
|
+
}
|
|
1079
1223
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
// ...router.location,
|
|
1083
|
-
const fromPathname = dest.fromCurrent
|
|
1084
|
-
? router.location.pathname
|
|
1085
|
-
: dest.from ?? router.location.pathname
|
|
1086
|
-
|
|
1087
|
-
let pathname = resolvePath(
|
|
1088
|
-
router.basepath ?? '/',
|
|
1089
|
-
fromPathname,
|
|
1090
|
-
`${dest.to ?? '.'}`,
|
|
1091
|
-
)
|
|
1224
|
+
#parseLocation = (previousLocation?: ParsedLocation): ParsedLocation => {
|
|
1225
|
+
let { pathname, search, hash, state } = this.history.location
|
|
1092
1226
|
|
|
1093
|
-
|
|
1094
|
-
strictParseParams: true,
|
|
1095
|
-
})
|
|
1227
|
+
const parsedSearch = this.options.parseSearch(search)
|
|
1096
1228
|
|
|
1097
|
-
|
|
1229
|
+
return {
|
|
1230
|
+
pathname: pathname,
|
|
1231
|
+
searchStr: search,
|
|
1232
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1233
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1234
|
+
href: `${pathname}${search}${hash}`,
|
|
1235
|
+
state: state as LocationState,
|
|
1236
|
+
key: state?.key || '__init__',
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1098
1239
|
|
|
1099
|
-
|
|
1240
|
+
#onFocus = () => {
|
|
1241
|
+
this.load()
|
|
1242
|
+
}
|
|
1100
1243
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1244
|
+
#buildLocation = (dest: BuildNextOptions = {}): ParsedLocation => {
|
|
1245
|
+
const fromPathname = dest.fromCurrent
|
|
1246
|
+
? this.store.state.latestLocation.pathname
|
|
1247
|
+
: dest.from ?? this.store.state.latestLocation.pathname
|
|
1105
1248
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1112
|
-
})
|
|
1113
|
-
}
|
|
1249
|
+
let pathname = resolvePath(
|
|
1250
|
+
this.basepath ?? '/',
|
|
1251
|
+
fromPathname,
|
|
1252
|
+
`${dest.to ?? '.'}`,
|
|
1253
|
+
)
|
|
1114
1254
|
|
|
1115
|
-
|
|
1255
|
+
const fromMatches = this.matchRoutes(
|
|
1256
|
+
this.store.state.latestLocation.pathname,
|
|
1257
|
+
{
|
|
1258
|
+
strictParseParams: true,
|
|
1259
|
+
},
|
|
1260
|
+
)
|
|
1116
1261
|
|
|
1117
|
-
|
|
1118
|
-
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1119
|
-
? dest.__preSearchFilters.reduce(
|
|
1120
|
-
(prev, next) => next(prev),
|
|
1121
|
-
router.location.search,
|
|
1122
|
-
)
|
|
1123
|
-
: router.location.search
|
|
1124
|
-
|
|
1125
|
-
// Then the link/navigate function
|
|
1126
|
-
const destSearch =
|
|
1127
|
-
dest.search === true
|
|
1128
|
-
? preFilteredSearch // Preserve resolvedFrom true
|
|
1129
|
-
: dest.search
|
|
1130
|
-
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1131
|
-
: dest.__preSearchFilters?.length
|
|
1132
|
-
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1133
|
-
: {}
|
|
1134
|
-
|
|
1135
|
-
// Then post filters
|
|
1136
|
-
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1137
|
-
? dest.__postSearchFilters.reduce(
|
|
1138
|
-
(prev, next) => next(prev),
|
|
1139
|
-
destSearch,
|
|
1140
|
-
)
|
|
1141
|
-
: destSearch
|
|
1262
|
+
const toMatches = this.matchRoutes(pathname)
|
|
1142
1263
|
|
|
1143
|
-
|
|
1144
|
-
router.location.search,
|
|
1145
|
-
postFilteredSearch,
|
|
1146
|
-
)
|
|
1264
|
+
const prevParams = { ...last(fromMatches)?.params }
|
|
1147
1265
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
: functionalUpdate(dest.hash!, router.location.hash)
|
|
1153
|
-
hash = hash ? `#${hash}` : ''
|
|
1154
|
-
|
|
1155
|
-
return {
|
|
1156
|
-
pathname,
|
|
1157
|
-
search,
|
|
1158
|
-
searchStr,
|
|
1159
|
-
state: router.location.state,
|
|
1160
|
-
hash,
|
|
1161
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1162
|
-
key: dest.key,
|
|
1163
|
-
}
|
|
1164
|
-
},
|
|
1266
|
+
let nextParams =
|
|
1267
|
+
(dest.params ?? true) === true
|
|
1268
|
+
? prevParams
|
|
1269
|
+
: functionalUpdate(dest.params!, prevParams)
|
|
1165
1270
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1271
|
+
if (nextParams) {
|
|
1272
|
+
toMatches
|
|
1273
|
+
.map((d) => d.route.options.stringifyParams)
|
|
1274
|
+
.filter(Boolean)
|
|
1275
|
+
.forEach((fn) => {
|
|
1276
|
+
Object.assign({}, nextParams!, fn!(nextParams!))
|
|
1277
|
+
})
|
|
1278
|
+
}
|
|
1168
1279
|
|
|
1169
|
-
|
|
1280
|
+
pathname = interpolatePath(pathname, nextParams ?? {})
|
|
1170
1281
|
|
|
1171
|
-
|
|
1282
|
+
// Pre filters first
|
|
1283
|
+
const preFilteredSearch = dest.__preSearchFilters?.length
|
|
1284
|
+
? dest.__preSearchFilters?.reduce(
|
|
1285
|
+
(prev, next) => next(prev),
|
|
1286
|
+
this.store.state.latestLocation.search,
|
|
1287
|
+
)
|
|
1288
|
+
: this.store.state.latestLocation.search
|
|
1289
|
+
|
|
1290
|
+
// Then the link/navigate function
|
|
1291
|
+
const destSearch =
|
|
1292
|
+
dest.search === true
|
|
1293
|
+
? preFilteredSearch // Preserve resolvedFrom true
|
|
1294
|
+
: dest.search
|
|
1295
|
+
? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1296
|
+
: dest.__preSearchFilters?.length
|
|
1297
|
+
? preFilteredSearch // Preserve resolvedFrom filters
|
|
1298
|
+
: {}
|
|
1299
|
+
|
|
1300
|
+
// Then post filters
|
|
1301
|
+
const postFilteredSearch = dest.__postSearchFilters?.length
|
|
1302
|
+
? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch)
|
|
1303
|
+
: destSearch
|
|
1304
|
+
|
|
1305
|
+
const search = replaceEqualDeep(
|
|
1306
|
+
this.store.state.latestLocation.search,
|
|
1307
|
+
postFilteredSearch,
|
|
1308
|
+
)
|
|
1309
|
+
|
|
1310
|
+
const searchStr = this.options.stringifySearch(search)
|
|
1311
|
+
let hash =
|
|
1312
|
+
dest.hash === true
|
|
1313
|
+
? this.store.state.latestLocation.hash
|
|
1314
|
+
: functionalUpdate(dest.hash!, this.store.state.latestLocation.hash)
|
|
1315
|
+
hash = hash ? `#${hash}` : ''
|
|
1316
|
+
|
|
1317
|
+
return {
|
|
1318
|
+
pathname,
|
|
1319
|
+
search,
|
|
1320
|
+
searchStr,
|
|
1321
|
+
state: this.store.state.latestLocation.state,
|
|
1322
|
+
hash,
|
|
1323
|
+
href: `${pathname}${searchStr}${hash}`,
|
|
1324
|
+
key: dest.key,
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1172
1327
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1328
|
+
#commitLocation = (location: BuildNextOptions & { replace?: boolean }) => {
|
|
1329
|
+
const next = this.buildNext(location)
|
|
1330
|
+
const id = '' + Date.now() + Math.random()
|
|
1176
1331
|
|
|
1177
|
-
|
|
1178
|
-
router.__.parseLocation(history.location).href === next.href
|
|
1332
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
|
|
1179
1333
|
|
|
1180
|
-
|
|
1181
|
-
nextAction = 'replace'
|
|
1182
|
-
}
|
|
1334
|
+
let nextAction: 'push' | 'replace' = 'replace'
|
|
1183
1335
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
pathname: next.pathname,
|
|
1188
|
-
hash: next.hash,
|
|
1189
|
-
search: next.searchStr,
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
id,
|
|
1193
|
-
},
|
|
1194
|
-
)
|
|
1195
|
-
} else {
|
|
1196
|
-
history.push(
|
|
1197
|
-
{
|
|
1198
|
-
pathname: next.pathname,
|
|
1199
|
-
hash: next.hash,
|
|
1200
|
-
search: next.searchStr,
|
|
1201
|
-
},
|
|
1202
|
-
{
|
|
1203
|
-
id,
|
|
1204
|
-
},
|
|
1205
|
-
)
|
|
1206
|
-
}
|
|
1336
|
+
if (!location.replace) {
|
|
1337
|
+
nextAction = 'push'
|
|
1338
|
+
}
|
|
1207
1339
|
|
|
1208
|
-
|
|
1209
|
-
const previousNavigationResolve = router.resolveNavigation
|
|
1340
|
+
const isSameUrl = this.store.state.latestLocation.href === next.href
|
|
1210
1341
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
})
|
|
1342
|
+
if (isSameUrl && !next.key) {
|
|
1343
|
+
nextAction = 'replace'
|
|
1344
|
+
}
|
|
1216
1345
|
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1346
|
+
const href = `${next.pathname}${next.searchStr}${
|
|
1347
|
+
next.hash ? `#${next.hash}` : ''
|
|
1348
|
+
}`
|
|
1221
1349
|
|
|
1222
|
-
|
|
1350
|
+
this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
|
|
1351
|
+
id,
|
|
1352
|
+
...next.state,
|
|
1353
|
+
})
|
|
1223
1354
|
|
|
1224
|
-
|
|
1225
|
-
router.options.createRouter?.(router)
|
|
1355
|
+
// this.load(this.#parseLocation(this.store.state.latestLocation))
|
|
1226
1356
|
|
|
1227
|
-
|
|
1357
|
+
return (this.navigationPromise = new Promise((resolve) => {
|
|
1358
|
+
const previousNavigationResolve = this.resolveNavigation
|
|
1359
|
+
|
|
1360
|
+
this.resolveNavigation = () => {
|
|
1361
|
+
previousNavigationResolve()
|
|
1362
|
+
resolve()
|
|
1363
|
+
}
|
|
1364
|
+
}))
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Detect if we're in the DOM
|
|
1369
|
+
const isServer = typeof window === 'undefined' || !window.document.createElement
|
|
1370
|
+
|
|
1371
|
+
function getInitialRouterState(): RouterStore {
|
|
1372
|
+
return {
|
|
1373
|
+
status: 'idle',
|
|
1374
|
+
latestLocation: null!,
|
|
1375
|
+
currentLocation: null!,
|
|
1376
|
+
currentMatches: [],
|
|
1377
|
+
loaders: {},
|
|
1378
|
+
lastUpdated: Date.now(),
|
|
1379
|
+
matchCache: {},
|
|
1380
|
+
get isFetching() {
|
|
1381
|
+
return (
|
|
1382
|
+
this.status === 'loading' ||
|
|
1383
|
+
this.currentMatches.some((d) => d.store.state.isFetching)
|
|
1384
|
+
)
|
|
1385
|
+
},
|
|
1386
|
+
get isPreloading() {
|
|
1387
|
+
return Object.values(this.matchCache).some(
|
|
1388
|
+
(d) =>
|
|
1389
|
+
d.match.store.state.isFetching &&
|
|
1390
|
+
!this.currentMatches.find((dd) => dd.id === d.match.id),
|
|
1391
|
+
)
|
|
1392
|
+
},
|
|
1393
|
+
}
|
|
1228
1394
|
}
|
|
1229
1395
|
|
|
1230
1396
|
function isCtrlEvent(e: MouseEvent) {
|
|
1231
1397
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
|
|
1232
1398
|
}
|
|
1399
|
+
|
|
1400
|
+
function linkMatches(matches: RouteMatch<any, any>[]) {
|
|
1401
|
+
matches.forEach((match, index) => {
|
|
1402
|
+
const parent = matches[index - 1]
|
|
1403
|
+
|
|
1404
|
+
if (parent) {
|
|
1405
|
+
match.__setParentMatch(parent)
|
|
1406
|
+
}
|
|
1407
|
+
})
|
|
1408
|
+
}
|