@esmx/router 3.0.0-rc.12 → 3.0.0-rc.122
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/README.md +158 -0
- package/README.zh-CN.md +158 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +64 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +16 -0
- package/dist/index.d.ts +14 -3
- package/dist/index.mjs +13 -3
- package/dist/location.d.ts +22 -0
- package/dist/location.mjs +64 -0
- package/dist/matcher.d.ts +4 -0
- package/dist/matcher.mjs +49 -0
- package/dist/micro-app.d.ts +20 -0
- package/dist/micro-app.mjs +132 -0
- package/dist/navigation.d.ts +45 -0
- package/dist/navigation.mjs +153 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +95 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +77 -0
- package/dist/route-transition.d.ts +54 -0
- package/dist/route-transition.mjs +362 -0
- package/dist/route.d.ts +77 -0
- package/dist/route.mjs +223 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +142 -0
- package/dist/router.d.ts +113 -102
- package/dist/router.mjs +321 -354
- package/dist/scroll.d.ts +33 -0
- package/dist/scroll.mjs +50 -0
- package/dist/types.d.ts +312 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +32 -0
- package/dist/util.mjs +100 -0
- package/package.json +89 -62
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.ts +67 -3
- package/src/location.ts +124 -0
- package/src/matcher.ts +71 -0
- package/src/micro-app.ts +153 -0
- package/src/navigation.ts +202 -0
- package/src/options.ts +136 -0
- package/src/route-task.ts +102 -0
- package/src/route-transition.ts +480 -0
- package/src/route.ts +335 -0
- package/src/router-link.ts +241 -0
- package/src/router.ts +351 -467
- package/src/scroll.ts +106 -0
- package/src/types.ts +415 -0
- package/src/util.ts +184 -0
- package/dist/history/abstract.d.ts +0 -29
- package/dist/history/abstract.mjs +0 -107
- package/dist/history/base.d.ts +0 -79
- package/dist/history/base.mjs +0 -275
- package/dist/history/html.d.ts +0 -22
- package/dist/history/html.mjs +0 -181
- package/dist/history/index.d.ts +0 -7
- package/dist/history/index.mjs +0 -16
- package/dist/matcher/create-matcher.d.ts +0 -5
- package/dist/matcher/create-matcher.mjs +0 -218
- package/dist/matcher/create-matcher.spec.d.ts +0 -1
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +0 -1
- package/dist/matcher/index.mjs +0 -1
- package/dist/task-pipe/index.d.ts +0 -1
- package/dist/task-pipe/index.mjs +0 -1
- package/dist/task-pipe/task.d.ts +0 -30
- package/dist/task-pipe/task.mjs +0 -66
- package/dist/utils/bom.d.ts +0 -5
- package/dist/utils/bom.mjs +0 -10
- package/dist/utils/encoding.d.ts +0 -48
- package/dist/utils/encoding.mjs +0 -44
- package/dist/utils/guards.d.ts +0 -9
- package/dist/utils/guards.mjs +0 -12
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.mjs +0 -27
- package/dist/utils/path.d.ts +0 -60
- package/dist/utils/path.mjs +0 -264
- package/dist/utils/path.spec.d.ts +0 -1
- package/dist/utils/path.spec.mjs +0 -30
- package/dist/utils/scroll.d.ts +0 -25
- package/dist/utils/scroll.mjs +0 -59
- package/dist/utils/utils.d.ts +0 -16
- package/dist/utils/utils.mjs +0 -11
- package/dist/utils/warn.d.ts +0 -2
- package/dist/utils/warn.mjs +0 -12
- package/src/history/abstract.ts +0 -149
- package/src/history/base.ts +0 -408
- package/src/history/html.ts +0 -231
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -293
- package/src/matcher/index.ts +0 -1
- package/src/task-pipe/index.ts +0 -1
- package/src/task-pipe/task.ts +0 -97
- package/src/utils/bom.ts +0 -14
- package/src/utils/encoding.ts +0 -153
- package/src/utils/guards.ts +0 -25
- package/src/utils/index.ts +0 -27
- package/src/utils/path.spec.ts +0 -44
- package/src/utils/path.ts +0 -397
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { Route } from './route';
|
|
2
|
+
import {
|
|
3
|
+
createRouteTask,
|
|
4
|
+
type RouteTask,
|
|
5
|
+
RouteTaskController
|
|
6
|
+
} from './route-task';
|
|
7
|
+
import type { Router } from './router';
|
|
8
|
+
import {
|
|
9
|
+
getSavedScrollPosition,
|
|
10
|
+
saveScrollPosition,
|
|
11
|
+
scrollToPosition
|
|
12
|
+
} from './scroll';
|
|
13
|
+
import type {
|
|
14
|
+
RouteConfirmHook,
|
|
15
|
+
RouteConfirmHookResult,
|
|
16
|
+
RouteHandleHook,
|
|
17
|
+
RouteLocationInput,
|
|
18
|
+
RouteNotifyHook
|
|
19
|
+
} from './types';
|
|
20
|
+
import { RouteType } from './types';
|
|
21
|
+
import {
|
|
22
|
+
isBrowser,
|
|
23
|
+
isRouteMatched,
|
|
24
|
+
isUrlEqual,
|
|
25
|
+
isValidConfirmHookResult,
|
|
26
|
+
removeFromArray
|
|
27
|
+
} from './util';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Route transition hooks responsible for handling different stages of the navigation process.
|
|
31
|
+
* Each hook is responsible for a specific aspect of route transition.
|
|
32
|
+
*/
|
|
33
|
+
export const ROUTE_TRANSITION_HOOKS = {
|
|
34
|
+
async fallback(
|
|
35
|
+
to: Route,
|
|
36
|
+
from: Route | null,
|
|
37
|
+
router: Router
|
|
38
|
+
): Promise<RouteConfirmHookResult> {
|
|
39
|
+
if (to.matched.length === 0) {
|
|
40
|
+
return router.parsedOptions.fallback;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async override(
|
|
45
|
+
to: Route,
|
|
46
|
+
from: Route | null,
|
|
47
|
+
router: Router
|
|
48
|
+
): Promise<RouteConfirmHookResult> {
|
|
49
|
+
const result = await to.config?.override?.(to, from, router);
|
|
50
|
+
if (isValidConfirmHookResult(result)) {
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async asyncComponent(
|
|
56
|
+
to: Route,
|
|
57
|
+
from: Route | null,
|
|
58
|
+
router: Router
|
|
59
|
+
): Promise<RouteConfirmHookResult> {
|
|
60
|
+
await Promise.all(
|
|
61
|
+
to.matched.map(async (matched) => {
|
|
62
|
+
const { asyncComponent, component } = matched;
|
|
63
|
+
if (!component && typeof asyncComponent === 'function') {
|
|
64
|
+
try {
|
|
65
|
+
const result = await asyncComponent();
|
|
66
|
+
matched.component = result;
|
|
67
|
+
} catch (e) {
|
|
68
|
+
const error =
|
|
69
|
+
e instanceof Error ? e : new Error(String(e));
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Async component '${matched.compilePath}' is not a valid component. Original error: ${error.message}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async beforeLeave(
|
|
80
|
+
to: Route,
|
|
81
|
+
from: Route | null,
|
|
82
|
+
router: Router
|
|
83
|
+
): Promise<RouteConfirmHookResult> {
|
|
84
|
+
if (!from?.matched.length) return;
|
|
85
|
+
|
|
86
|
+
// Find routes that need to be left (routes in 'from' but not in 'to').
|
|
87
|
+
const leavingRoutes = from.matched.filter(
|
|
88
|
+
(fromRoute) => !to.matched.some((toRoute) => toRoute === fromRoute)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Execute beforeLeave guards in order from child to parent.
|
|
92
|
+
for (let i = leavingRoutes.length - 1; i >= 0; i--) {
|
|
93
|
+
const route = leavingRoutes[i];
|
|
94
|
+
if (route.beforeLeave) {
|
|
95
|
+
const result = await route.beforeLeave(to, from, router);
|
|
96
|
+
if (isValidConfirmHookResult(result)) {
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async beforeEnter(
|
|
104
|
+
to: Route,
|
|
105
|
+
from: Route | null,
|
|
106
|
+
router: Router
|
|
107
|
+
): Promise<RouteConfirmHookResult> {
|
|
108
|
+
if (!to.matched.length) return;
|
|
109
|
+
|
|
110
|
+
// Find routes that need to be entered (routes in 'to' but not in 'from').
|
|
111
|
+
const enteringRoutes = to.matched.filter(
|
|
112
|
+
(toRoute) =>
|
|
113
|
+
!from?.matched.some((fromRoute) => fromRoute === toRoute)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Execute beforeEnter guards in order from parent to child.
|
|
117
|
+
for (const route of enteringRoutes) {
|
|
118
|
+
if (route.beforeEnter) {
|
|
119
|
+
const result = await route.beforeEnter(to, from, router);
|
|
120
|
+
if (isValidConfirmHookResult(result)) {
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
async beforeUpdate(
|
|
128
|
+
to: Route,
|
|
129
|
+
from: Route | null,
|
|
130
|
+
router: Router
|
|
131
|
+
): Promise<RouteConfirmHookResult> {
|
|
132
|
+
// beforeUpdate is only executed when parameters change within the exact same route combination.
|
|
133
|
+
// Quick check: if the final route configs are different, it's definitely not the same combination.
|
|
134
|
+
if (!isRouteMatched(to, from, 'route')) return;
|
|
135
|
+
|
|
136
|
+
// Detailed check: the 'matched' arrays of both routes must be identical.
|
|
137
|
+
if (!from || to.matched.length !== from.matched.length) return;
|
|
138
|
+
const isSameRouteSet = to.matched.every(
|
|
139
|
+
(toRoute, index) => toRoute === from.matched[index]
|
|
140
|
+
);
|
|
141
|
+
if (!isSameRouteSet) return;
|
|
142
|
+
|
|
143
|
+
// Only execute beforeUpdate when path parameters or query parameters change.
|
|
144
|
+
if (!isRouteMatched(to, from, 'exact')) {
|
|
145
|
+
// Execute beforeUpdate guards in order from parent to child.
|
|
146
|
+
for (const route of to.matched) {
|
|
147
|
+
if (route.beforeUpdate) {
|
|
148
|
+
const result = await route.beforeUpdate(to, from, router);
|
|
149
|
+
if (isValidConfirmHookResult(result)) {
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async beforeEach(
|
|
158
|
+
to: Route,
|
|
159
|
+
from: Route | null,
|
|
160
|
+
router: Router
|
|
161
|
+
): Promise<RouteConfirmHookResult> {
|
|
162
|
+
// Access the transition instance from the router to get guards
|
|
163
|
+
const transition = router.transition;
|
|
164
|
+
for (const guard of transition.guards.beforeEach) {
|
|
165
|
+
const result = await guard(to, from, router);
|
|
166
|
+
if (isValidConfirmHookResult(result)) {
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async confirm(
|
|
173
|
+
to: Route,
|
|
174
|
+
from: Route | null,
|
|
175
|
+
router: Router
|
|
176
|
+
): Promise<RouteConfirmHookResult> {
|
|
177
|
+
if (to.confirm) {
|
|
178
|
+
const result = await to.confirm(to, from, router);
|
|
179
|
+
if (isValidConfirmHookResult(result)) {
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (isBrowser && 'scrollRestoration' in window.history)
|
|
185
|
+
window.history.scrollRestoration = 'manual';
|
|
186
|
+
// handle scroll position
|
|
187
|
+
if (from && isBrowser && !router.isLayer)
|
|
188
|
+
switch (to.type) {
|
|
189
|
+
case RouteType.push:
|
|
190
|
+
case RouteType.replace: {
|
|
191
|
+
if (!to.keepScrollPosition) {
|
|
192
|
+
saveScrollPosition(from.url.href);
|
|
193
|
+
scrollToPosition({ left: 0, top: 0 });
|
|
194
|
+
} else {
|
|
195
|
+
to.applyNavigationState({
|
|
196
|
+
__keepScrollPosition: to.keepScrollPosition
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case RouteType.go:
|
|
202
|
+
case RouteType.forward:
|
|
203
|
+
case RouteType.back:
|
|
204
|
+
// for popstate
|
|
205
|
+
case RouteType.unknown: {
|
|
206
|
+
saveScrollPosition(from.url.href);
|
|
207
|
+
setTimeout(async () => {
|
|
208
|
+
const state = window.history.state;
|
|
209
|
+
if (state?.__keepScrollPosition) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const savedPosition = getSavedScrollPosition(
|
|
213
|
+
to.url.href,
|
|
214
|
+
{ left: 0, top: 0 }
|
|
215
|
+
);
|
|
216
|
+
if (!savedPosition) return;
|
|
217
|
+
await router.parsedOptions.nextTick();
|
|
218
|
+
scrollToPosition(savedPosition);
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (to.type) {
|
|
225
|
+
case RouteType.push:
|
|
226
|
+
return ROUTE_TYPE_HANDLERS.push;
|
|
227
|
+
case RouteType.replace:
|
|
228
|
+
return ROUTE_TYPE_HANDLERS.replace;
|
|
229
|
+
case RouteType.restartApp:
|
|
230
|
+
return ROUTE_TYPE_HANDLERS.restartApp;
|
|
231
|
+
case RouteType.pushWindow:
|
|
232
|
+
return ROUTE_TYPE_HANDLERS.pushWindow;
|
|
233
|
+
case RouteType.replaceWindow:
|
|
234
|
+
return ROUTE_TYPE_HANDLERS.replaceWindow;
|
|
235
|
+
case RouteType.pushLayer:
|
|
236
|
+
return ROUTE_TYPE_HANDLERS.pushLayer;
|
|
237
|
+
default:
|
|
238
|
+
return ROUTE_TYPE_HANDLERS.default;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} satisfies Record<string, RouteConfirmHook>;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Route type handlers configuration.
|
|
245
|
+
* Maps each route type to its corresponding navigation handler function.
|
|
246
|
+
* These handlers perform the actual navigation operations like updating browser state,
|
|
247
|
+
* managing micro-app updates, and handling different navigation patterns.
|
|
248
|
+
*/
|
|
249
|
+
export const ROUTE_TYPE_HANDLERS = {
|
|
250
|
+
push(to, from, router) {
|
|
251
|
+
router.transition.route = to;
|
|
252
|
+
router.microApp._update(router);
|
|
253
|
+
if (!isUrlEqual(to.url, from?.url)) {
|
|
254
|
+
const newState = router.navigation.push(to.state, to.url);
|
|
255
|
+
to.applyNavigationState(newState);
|
|
256
|
+
} else {
|
|
257
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
258
|
+
to.applyNavigationState(newState);
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
replace(to, from, router) {
|
|
262
|
+
router.transition.route = to;
|
|
263
|
+
router.microApp._update(router);
|
|
264
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
265
|
+
to.applyNavigationState(newState);
|
|
266
|
+
},
|
|
267
|
+
restartApp(to, from, router) {
|
|
268
|
+
router.transition.route = to;
|
|
269
|
+
router.microApp._update(router, true);
|
|
270
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
271
|
+
to.applyNavigationState(newState);
|
|
272
|
+
},
|
|
273
|
+
pushWindow(to, from, router) {
|
|
274
|
+
return router.parsedOptions.fallback(to, from, router);
|
|
275
|
+
},
|
|
276
|
+
replaceWindow(to, from, router) {
|
|
277
|
+
return router.parsedOptions.fallback(to, from, router);
|
|
278
|
+
},
|
|
279
|
+
async pushLayer(to, from, router) {
|
|
280
|
+
const { promise } = await router.createLayer(to);
|
|
281
|
+
return promise;
|
|
282
|
+
},
|
|
283
|
+
default(to, from, router) {
|
|
284
|
+
router.transition.route = to;
|
|
285
|
+
router.microApp._update(router);
|
|
286
|
+
if (!isUrlEqual(to.url, from?.url)) {
|
|
287
|
+
const newState = router.navigation.replace(to.state, to.url);
|
|
288
|
+
to.applyNavigationState(newState);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} satisfies Record<string, RouteHandleHook>;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Route transition pipeline configuration.
|
|
295
|
+
* Defines the sequence of hooks and guards that should be executed for each route type.
|
|
296
|
+
* The order matters: hooks are executed sequentially from first to last.
|
|
297
|
+
*
|
|
298
|
+
* Pipeline stages:
|
|
299
|
+
* - fallback: Handle unmatched routes
|
|
300
|
+
* - override: Allow route override logic
|
|
301
|
+
* - beforeLeave: Execute before leaving current route
|
|
302
|
+
* - beforeEach: Global navigation guard
|
|
303
|
+
* - beforeUpdate: Execute before updating route (same component)
|
|
304
|
+
* - beforeEnter: Execute before entering new route
|
|
305
|
+
* - asyncComponent: Load async components
|
|
306
|
+
* - confirm: Final confirmation and navigation execution
|
|
307
|
+
*/
|
|
308
|
+
const ROUTE_TRANSITION_PIPELINE = {
|
|
309
|
+
[RouteType.push]: [
|
|
310
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
311
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
312
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
313
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
314
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
315
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
316
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
317
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
318
|
+
],
|
|
319
|
+
|
|
320
|
+
[RouteType.replace]: [
|
|
321
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
322
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
323
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
324
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
325
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
326
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
327
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
328
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
329
|
+
],
|
|
330
|
+
[RouteType.pushWindow]: [
|
|
331
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
332
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
333
|
+
// ROUTE_TRANSITION_HOOKS.beforeLeave
|
|
334
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
335
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
336
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
337
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
338
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
339
|
+
],
|
|
340
|
+
|
|
341
|
+
[RouteType.replaceWindow]: [
|
|
342
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
343
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
344
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
345
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
346
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
347
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
348
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
349
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
350
|
+
],
|
|
351
|
+
[RouteType.pushLayer]: [
|
|
352
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
353
|
+
ROUTE_TRANSITION_HOOKS.override,
|
|
354
|
+
// ROUTE_TRANSITION_HOOKS.beforeLeave
|
|
355
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
356
|
+
// ROUTE_TRANSITION_HOOKS.beforeUpdate
|
|
357
|
+
// ROUTE_TRANSITION_HOOKS.beforeEnter
|
|
358
|
+
// ROUTE_TRANSITION_HOOKS.asyncComponent
|
|
359
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
360
|
+
],
|
|
361
|
+
[RouteType.restartApp]: [
|
|
362
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
363
|
+
// ROUTE_TRANSITION_HOOKS.override,
|
|
364
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
365
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
366
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
367
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
368
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
369
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
370
|
+
],
|
|
371
|
+
|
|
372
|
+
[RouteType.unknown]: [
|
|
373
|
+
ROUTE_TRANSITION_HOOKS.fallback,
|
|
374
|
+
// ROUTE_TRANSITION_HOOKS.override,
|
|
375
|
+
ROUTE_TRANSITION_HOOKS.beforeLeave,
|
|
376
|
+
ROUTE_TRANSITION_HOOKS.beforeEach,
|
|
377
|
+
ROUTE_TRANSITION_HOOKS.beforeUpdate,
|
|
378
|
+
ROUTE_TRANSITION_HOOKS.beforeEnter,
|
|
379
|
+
ROUTE_TRANSITION_HOOKS.asyncComponent,
|
|
380
|
+
ROUTE_TRANSITION_HOOKS.confirm
|
|
381
|
+
]
|
|
382
|
+
} satisfies Record<string, RouteConfirmHook[]>;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Route Transition Manager
|
|
386
|
+
* Responsible for managing all route transition logic, including guard execution,
|
|
387
|
+
* task processing, and status updates.
|
|
388
|
+
*/
|
|
389
|
+
export class RouteTransition {
|
|
390
|
+
private readonly router: Router;
|
|
391
|
+
|
|
392
|
+
public route: Route | null = null;
|
|
393
|
+
|
|
394
|
+
// Task controller for the current transition.
|
|
395
|
+
private _controller: RouteTaskController | null = null;
|
|
396
|
+
|
|
397
|
+
public readonly guards = {
|
|
398
|
+
beforeEach: [] as RouteConfirmHook[],
|
|
399
|
+
afterEach: [] as RouteNotifyHook[]
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
private destroyed = false;
|
|
403
|
+
|
|
404
|
+
constructor(router: Router) {
|
|
405
|
+
this.router = router;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
public beforeEach(guard: RouteConfirmHook): () => void {
|
|
409
|
+
this.guards.beforeEach.push(guard);
|
|
410
|
+
return () => {
|
|
411
|
+
removeFromArray(this.guards.beforeEach, guard);
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public afterEach(guard: RouteNotifyHook): () => void {
|
|
416
|
+
this.guards.afterEach.push(guard);
|
|
417
|
+
return () => {
|
|
418
|
+
removeFromArray(this.guards.afterEach, guard);
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public destroy(): void {
|
|
423
|
+
this._controller?.abort();
|
|
424
|
+
this._controller = null;
|
|
425
|
+
this.guards.afterEach.length = 0;
|
|
426
|
+
this.guards.beforeEach.length = 0;
|
|
427
|
+
this.destroyed = true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
public async to(
|
|
431
|
+
toType: RouteType,
|
|
432
|
+
toInput: RouteLocationInput
|
|
433
|
+
): Promise<Route> {
|
|
434
|
+
if (this.destroyed) {
|
|
435
|
+
throw new Error('RouteTransition has been destroyed');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const from = this.route;
|
|
439
|
+
const to = await this._runTask(
|
|
440
|
+
new Route({
|
|
441
|
+
options: this.router.parsedOptions,
|
|
442
|
+
toType,
|
|
443
|
+
toInput,
|
|
444
|
+
from: from?.url ?? null
|
|
445
|
+
}),
|
|
446
|
+
from
|
|
447
|
+
);
|
|
448
|
+
if (typeof to.handle === 'function') {
|
|
449
|
+
to.handleResult = await to.handle(to, from, this.router);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (to.handle) {
|
|
453
|
+
for (const guard of this.guards.afterEach) {
|
|
454
|
+
guard(to, from, this.router);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return to;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private async _runTask(to: Route, from: Route | null): Promise<Route> {
|
|
462
|
+
this._controller?.abort();
|
|
463
|
+
this._controller = new RouteTaskController();
|
|
464
|
+
const taskFunctions: RouteConfirmHook[] =
|
|
465
|
+
ROUTE_TRANSITION_PIPELINE[to.type] ||
|
|
466
|
+
ROUTE_TRANSITION_PIPELINE[RouteType.unknown];
|
|
467
|
+
const tasks = taskFunctions.map<RouteTask>((taskFn) => ({
|
|
468
|
+
name: taskFn.name,
|
|
469
|
+
task: taskFn
|
|
470
|
+
}));
|
|
471
|
+
|
|
472
|
+
return createRouteTask({
|
|
473
|
+
to,
|
|
474
|
+
from,
|
|
475
|
+
tasks,
|
|
476
|
+
controller: this._controller,
|
|
477
|
+
router: this.router
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|