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