@zenithbuild/router 0.6.17 → 0.7.0
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/README.md +13 -129
- package/dist/ZenLink.zen +66 -3
- package/dist/events.d.ts +7 -3
- package/dist/events.js +62 -3
- package/index.d.ts +47 -5
- package/package.json +5 -1
- package/template-core.js +422 -0
- package/template-lifecycle.js +127 -0
- package/template-navigation.js +468 -0
- package/template.js +5 -412
package/template-core.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
export function renderRouterCoreSource({ manifest, runtimeSpec, coreSpec }) {
|
|
2
|
+
return `import { hydrate as __zenithHydrate } from '${runtimeSpec}';
|
|
3
|
+
import { zenOnMount as __zenithOnMount } from '${coreSpec}';
|
|
4
|
+
|
|
5
|
+
void __zenithHydrate;
|
|
6
|
+
void __zenithOnMount;
|
|
7
|
+
|
|
8
|
+
const __ZENITH_MANIFEST__ = ${manifest};
|
|
9
|
+
const __ZENITH_ROUTE_EVENT_KEY = "__zenith_route_event_listeners";
|
|
10
|
+
const __ZENITH_ROUTE_EVENT_NAMES = [
|
|
11
|
+
"guard:start",
|
|
12
|
+
"guard:end",
|
|
13
|
+
"route-check:start",
|
|
14
|
+
"route-check:end",
|
|
15
|
+
"route-check:error",
|
|
16
|
+
"route:deny",
|
|
17
|
+
"route:redirect",
|
|
18
|
+
"navigation:request",
|
|
19
|
+
"navigation:before-leave",
|
|
20
|
+
"navigation:leave-complete",
|
|
21
|
+
"navigation:data-ready",
|
|
22
|
+
"navigation:before-swap",
|
|
23
|
+
"navigation:content-swapped",
|
|
24
|
+
"navigation:before-enter",
|
|
25
|
+
"navigation:enter-complete",
|
|
26
|
+
"navigation:abort",
|
|
27
|
+
"navigation:error"
|
|
28
|
+
];
|
|
29
|
+
const __ZENITH_HISTORY_STATE_KEY = "__zenith_router_state";
|
|
30
|
+
const __ZENITH_RUNTIME_ROUTE_HTML_KEY = "__zenith_route_html";
|
|
31
|
+
const __ZENITH_SCROLL_EVENT_NAME = "zx-router-scroll";
|
|
32
|
+
|
|
33
|
+
let activeCleanup = null;
|
|
34
|
+
let navigationToken = 0;
|
|
35
|
+
let activeNavigationController = null;
|
|
36
|
+
let activeNavigationContext = null;
|
|
37
|
+
let currentUrl = null;
|
|
38
|
+
let currentHistoryKey = "";
|
|
39
|
+
let scrollSnapshotQueued = false;
|
|
40
|
+
const scrollPositions = new Map();
|
|
41
|
+
|
|
42
|
+
function splitPath(path) {
|
|
43
|
+
return path.split('/').filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function copyParams(params) {
|
|
47
|
+
if (!params || typeof params !== "object") {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
return { ...params };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function cloneUrl(url) {
|
|
54
|
+
if (!url || typeof url.href !== "string") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return new URL(url.href);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeCatchAll(segments) {
|
|
61
|
+
return segments.filter(Boolean).join('/');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function segmentWeight(segment) {
|
|
65
|
+
if (!segment) return 0;
|
|
66
|
+
if (segment.startsWith('*')) return 1;
|
|
67
|
+
if (segment.startsWith(':')) return 2;
|
|
68
|
+
return 3;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function routeClass(segments) {
|
|
72
|
+
let hasParam = false;
|
|
73
|
+
let hasCatchAll = false;
|
|
74
|
+
for (let i = 0; i < segments.length; i++) {
|
|
75
|
+
const segment = segments[i];
|
|
76
|
+
if (segment.startsWith('*')) {
|
|
77
|
+
hasCatchAll = true;
|
|
78
|
+
} else if (segment.startsWith(':')) {
|
|
79
|
+
hasParam = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!hasParam && !hasCatchAll) return 3;
|
|
83
|
+
if (hasCatchAll) return 1;
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compareRouteSpecificity(a, b) {
|
|
88
|
+
if (a === '/' && b !== '/') return -1;
|
|
89
|
+
if (b === '/' && a !== '/') return 1;
|
|
90
|
+
const aSegs = splitPath(a);
|
|
91
|
+
const bSegs = splitPath(b);
|
|
92
|
+
const aClass = routeClass(aSegs);
|
|
93
|
+
const bClass = routeClass(bSegs);
|
|
94
|
+
if (aClass !== bClass) {
|
|
95
|
+
return bClass - aClass;
|
|
96
|
+
}
|
|
97
|
+
const max = Math.min(aSegs.length, bSegs.length);
|
|
98
|
+
for (let i = 0; i < max; i++) {
|
|
99
|
+
const aWeight = segmentWeight(aSegs[i]);
|
|
100
|
+
const bWeight = segmentWeight(bSegs[i]);
|
|
101
|
+
if (aWeight !== bWeight) {
|
|
102
|
+
return bWeight - aWeight;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (aSegs.length !== bSegs.length) {
|
|
106
|
+
return bSegs.length - aSegs.length;
|
|
107
|
+
}
|
|
108
|
+
return a.localeCompare(b);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveRoute(pathname) {
|
|
112
|
+
if (__ZENITH_MANIFEST__.chunks[pathname]) {
|
|
113
|
+
return { route: pathname, params: {} };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const pathnameSegments = splitPath(pathname);
|
|
117
|
+
const routes = Object.keys(__ZENITH_MANIFEST__.chunks).sort(compareRouteSpecificity);
|
|
118
|
+
for (let i = 0; i < routes.length; i++) {
|
|
119
|
+
const route = routes[i];
|
|
120
|
+
const routeSegments = splitPath(route);
|
|
121
|
+
const params = Object.create(null);
|
|
122
|
+
let routeIndex = 0;
|
|
123
|
+
let pathIndex = 0;
|
|
124
|
+
let matched = true;
|
|
125
|
+
|
|
126
|
+
while (routeIndex < routeSegments.length) {
|
|
127
|
+
const routeSegment = routeSegments[routeIndex];
|
|
128
|
+
if (routeSegment.startsWith('*')) {
|
|
129
|
+
const optionalCatchAll = routeSegment.endsWith('?');
|
|
130
|
+
const key = optionalCatchAll ? routeSegment.slice(1, -1) : routeSegment.slice(1);
|
|
131
|
+
if (routeIndex !== routeSegments.length - 1) {
|
|
132
|
+
matched = false;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
const rest = pathnameSegments.slice(pathIndex);
|
|
136
|
+
const rootRequiredCatchAll = !optionalCatchAll && routeSegments.length === 1;
|
|
137
|
+
if (rest.length === 0 && !optionalCatchAll && !rootRequiredCatchAll) {
|
|
138
|
+
matched = false;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
params[key] = normalizeCatchAll(rest);
|
|
142
|
+
pathIndex = pathnameSegments.length;
|
|
143
|
+
routeIndex = routeSegments.length;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (pathIndex >= pathnameSegments.length) {
|
|
147
|
+
matched = false;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const pathnameSegment = pathnameSegments[pathIndex];
|
|
151
|
+
if (routeSegment.startsWith(':')) {
|
|
152
|
+
params[routeSegment.slice(1)] = pathnameSegment;
|
|
153
|
+
} else if (routeSegment !== pathnameSegment) {
|
|
154
|
+
matched = false;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
routeIndex += 1;
|
|
158
|
+
pathIndex += 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (matched && routeIndex === routeSegments.length && pathIndex === pathnameSegments.length) {
|
|
162
|
+
return { route, params: { ...params } };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function requiresServerReload(route) {
|
|
170
|
+
const routes = __ZENITH_MANIFEST__.server_routes || __ZENITH_MANIFEST__.serverRoutes || [];
|
|
171
|
+
return Array.isArray(routes) && routes.includes(route);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function zenithScope() {
|
|
175
|
+
return typeof globalThis === "object" && globalThis ? globalThis : window;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function toNavigationPath(url) {
|
|
179
|
+
return url.pathname + url.search;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isAbortError(error) {
|
|
183
|
+
return !!error && typeof error === "object" && error.name === "AbortError";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function nextFrame() {
|
|
187
|
+
if (typeof requestAnimationFrame === "function") {
|
|
188
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
189
|
+
}
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function createHistoryKey() {
|
|
194
|
+
return "z" + Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function readHistoryEntry(state) {
|
|
198
|
+
if (!state || typeof state !== "object") return null;
|
|
199
|
+
const entry = state[__ZENITH_HISTORY_STATE_KEY];
|
|
200
|
+
if (!entry || typeof entry !== "object" || typeof entry.key !== "string") {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return entry;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function withHistoryEntry(state, entry) {
|
|
207
|
+
const base = state && typeof state === "object" ? { ...state } : {};
|
|
208
|
+
base[__ZENITH_HISTORY_STATE_KEY] = entry;
|
|
209
|
+
return base;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function ensureHistoryEntry() {
|
|
213
|
+
const existing = readHistoryEntry(history.state);
|
|
214
|
+
if (existing) {
|
|
215
|
+
currentHistoryKey = existing.key;
|
|
216
|
+
return existing;
|
|
217
|
+
}
|
|
218
|
+
const entry = { key: createHistoryKey() };
|
|
219
|
+
history.replaceState(withHistoryEntry(history.state, entry), "", window.location.href);
|
|
220
|
+
currentHistoryKey = entry.key;
|
|
221
|
+
return entry;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function rememberScrollForKey(key, position) {
|
|
225
|
+
if (!key) return;
|
|
226
|
+
scrollPositions.set(key, position || { x: window.scrollX || 0, y: window.scrollY || 0 });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function queueScrollSnapshot() {
|
|
230
|
+
if (!currentHistoryKey || scrollSnapshotQueued) return;
|
|
231
|
+
scrollSnapshotQueued = true;
|
|
232
|
+
const flush = () => {
|
|
233
|
+
scrollSnapshotQueued = false;
|
|
234
|
+
rememberScrollForKey(currentHistoryKey);
|
|
235
|
+
};
|
|
236
|
+
if (typeof requestAnimationFrame === "function") {
|
|
237
|
+
requestAnimationFrame(flush);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
setTimeout(flush, 0);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readStoredScroll(state) {
|
|
244
|
+
const entry = readHistoryEntry(state);
|
|
245
|
+
if (!entry) return { x: 0, y: 0 };
|
|
246
|
+
return scrollPositions.get(entry.key) || { x: 0, y: 0 };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function pushHistoryEntry(targetUrl) {
|
|
250
|
+
const entry = { key: createHistoryKey() };
|
|
251
|
+
history.pushState(withHistoryEntry(history.state, entry), "", targetUrl.href);
|
|
252
|
+
currentHistoryKey = entry.key;
|
|
253
|
+
return entry;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function syncHistoryEntry(state) {
|
|
257
|
+
const existing = readHistoryEntry(state);
|
|
258
|
+
if (existing) {
|
|
259
|
+
currentHistoryKey = existing.key;
|
|
260
|
+
return existing;
|
|
261
|
+
}
|
|
262
|
+
const entry = { key: createHistoryKey() };
|
|
263
|
+
history.replaceState(withHistoryEntry(history.state, entry), "", window.location.href);
|
|
264
|
+
currentHistoryKey = entry.key;
|
|
265
|
+
return entry;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function teardownRuntime() {
|
|
269
|
+
if (typeof activeCleanup === "function") {
|
|
270
|
+
activeCleanup();
|
|
271
|
+
activeCleanup = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function mountRoute(route, params, token, payload) {
|
|
276
|
+
if (token !== navigationToken) return false;
|
|
277
|
+
teardownRuntime();
|
|
278
|
+
if (token !== navigationToken) return false;
|
|
279
|
+
|
|
280
|
+
const scope = zenithScope();
|
|
281
|
+
if (payload && payload.ssrData && typeof payload.ssrData === "object") {
|
|
282
|
+
scope.__zenith_ssr_data = payload.ssrData;
|
|
283
|
+
}
|
|
284
|
+
if (payload && typeof payload.html === "string") {
|
|
285
|
+
scope[__ZENITH_RUNTIME_ROUTE_HTML_KEY] = payload.html;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const pageModule = await import(__ZENITH_MANIFEST__.chunks[route]);
|
|
290
|
+
if (token !== navigationToken) return false;
|
|
291
|
+
const mountFn = pageModule.__zenith_mount || pageModule.default;
|
|
292
|
+
if (typeof mountFn === "function") {
|
|
293
|
+
const cleanup = mountFn(document, params);
|
|
294
|
+
activeCleanup = typeof cleanup === "function" ? cleanup : null;
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
} finally {
|
|
298
|
+
if (Object.prototype.hasOwnProperty.call(scope, __ZENITH_RUNTIME_ROUTE_HTML_KEY)) {
|
|
299
|
+
delete scope[__ZENITH_RUNTIME_ROUTE_HTML_KEY];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function beginNavigation(targetUrl, resolved, navigationType) {
|
|
305
|
+
navigationToken += 1;
|
|
306
|
+
if (activeNavigationContext && !activeNavigationContext.abortReason) {
|
|
307
|
+
activeNavigationContext.abortReason = {
|
|
308
|
+
reason: "superseded",
|
|
309
|
+
abortedStage: activeNavigationContext.stage
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (activeNavigationController && typeof activeNavigationController.abort === "function") {
|
|
313
|
+
activeNavigationController.abort();
|
|
314
|
+
}
|
|
315
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
316
|
+
const context = {
|
|
317
|
+
token: navigationToken,
|
|
318
|
+
controller,
|
|
319
|
+
signal: controller ? controller.signal : undefined,
|
|
320
|
+
navigationType,
|
|
321
|
+
toUrl: cloneUrl(targetUrl),
|
|
322
|
+
fromUrl: currentUrl ? cloneUrl(currentUrl) : new URL(window.location.href),
|
|
323
|
+
routeId: resolved.route,
|
|
324
|
+
params: copyParams(resolved.params),
|
|
325
|
+
stage: "request",
|
|
326
|
+
abortReason: null,
|
|
327
|
+
abortDispatched: false
|
|
328
|
+
};
|
|
329
|
+
activeNavigationController = controller;
|
|
330
|
+
activeNavigationContext = context;
|
|
331
|
+
return context;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function completeNavigation(context) {
|
|
335
|
+
if (activeNavigationController === context.controller) {
|
|
336
|
+
activeNavigationController = null;
|
|
337
|
+
}
|
|
338
|
+
if (activeNavigationContext === context) {
|
|
339
|
+
activeNavigationContext = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function dispatchScrollEvent(phase, detail, cancelable) {
|
|
344
|
+
if (typeof document !== "object" || !document || typeof CustomEvent === "undefined") {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
const event = new CustomEvent(__ZENITH_SCROLL_EVENT_NAME, {
|
|
348
|
+
detail: { ...detail, phase },
|
|
349
|
+
cancelable: cancelable === true
|
|
350
|
+
});
|
|
351
|
+
return document.dispatchEvent(event);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function decodeHash(hash) {
|
|
355
|
+
if (!hash || hash === "#") return "";
|
|
356
|
+
try {
|
|
357
|
+
return decodeURIComponent(hash.slice(1));
|
|
358
|
+
} catch {
|
|
359
|
+
return hash.slice(1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function findHashTarget(hash) {
|
|
364
|
+
const decoded = decodeHash(hash);
|
|
365
|
+
if (!decoded) return null;
|
|
366
|
+
const byId = typeof document.getElementById === "function" ? document.getElementById(decoded) : null;
|
|
367
|
+
if (byId) return byId;
|
|
368
|
+
if (typeof document.getElementsByName === "function") {
|
|
369
|
+
const named = document.getElementsByName(decoded);
|
|
370
|
+
if (named && named.length > 0) return named[0];
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function resolveScrollTarget(targetUrl, historyMode, popstateState) {
|
|
376
|
+
const hashTarget = findHashTarget(targetUrl.hash);
|
|
377
|
+
if (hashTarget) {
|
|
378
|
+
return {
|
|
379
|
+
mode: "hash",
|
|
380
|
+
x: 0,
|
|
381
|
+
y: hashTarget.getBoundingClientRect().top + (window.scrollY || window.pageYOffset || 0),
|
|
382
|
+
focusTarget: hashTarget
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (historyMode === "pop") {
|
|
386
|
+
const saved = readStoredScroll(popstateState);
|
|
387
|
+
return { mode: "restore", x: saved.x, y: saved.y, focusTarget: null };
|
|
388
|
+
}
|
|
389
|
+
return { mode: "top", x: 0, y: 0, focusTarget: null };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function applyNativeScroll(target) {
|
|
393
|
+
if (typeof window.scrollTo === "function") {
|
|
394
|
+
window.scrollTo(target.x, target.y);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function focusAfterNavigation(target) {
|
|
399
|
+
const focusTarget = target.focusTarget || document.querySelector("main") || document.getElementById("app");
|
|
400
|
+
if (!focusTarget || typeof focusTarget.focus !== "function") return;
|
|
401
|
+
const shouldRestoreTabIndex =
|
|
402
|
+
focusTarget instanceof HTMLElement &&
|
|
403
|
+
!focusTarget.hasAttribute("tabindex") &&
|
|
404
|
+
focusTarget !== document.body;
|
|
405
|
+
if (shouldRestoreTabIndex) {
|
|
406
|
+
focusTarget.setAttribute("tabindex", "-1");
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
focusTarget.focus({ preventScroll: true });
|
|
410
|
+
} catch {
|
|
411
|
+
focusTarget.focus();
|
|
412
|
+
}
|
|
413
|
+
if (shouldRestoreTabIndex) {
|
|
414
|
+
focusTarget.addEventListener("blur", function cleanup() {
|
|
415
|
+
focusTarget.removeEventListener("blur", cleanup);
|
|
416
|
+
if (focusTarget.getAttribute("tabindex") === "-1") {
|
|
417
|
+
focusTarget.removeAttribute("tabindex");
|
|
418
|
+
}
|
|
419
|
+
}, { once: true });
|
|
420
|
+
}
|
|
421
|
+
}`;
|
|
422
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export function renderRouterLifecycleSource() {
|
|
2
|
+
return `function routeEventListeners() {
|
|
3
|
+
const scope = zenithScope();
|
|
4
|
+
let listeners = scope[__ZENITH_ROUTE_EVENT_KEY];
|
|
5
|
+
if (!listeners || typeof listeners !== "object") {
|
|
6
|
+
listeners = Object.create(null);
|
|
7
|
+
scope[__ZENITH_ROUTE_EVENT_KEY] = listeners;
|
|
8
|
+
}
|
|
9
|
+
for (let i = 0; i < __ZENITH_ROUTE_EVENT_NAMES.length; i++) {
|
|
10
|
+
const name = __ZENITH_ROUTE_EVENT_NAMES[i];
|
|
11
|
+
if (!(listeners[name] instanceof Set)) {
|
|
12
|
+
listeners[name] = new Set();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return listeners;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function reportRouteEventError(eventName, payload, error) {
|
|
19
|
+
console.error("[Zenith Router] route event handler failed", error);
|
|
20
|
+
if (
|
|
21
|
+
eventName === "navigation:error" ||
|
|
22
|
+
!payload ||
|
|
23
|
+
typeof payload !== "object" ||
|
|
24
|
+
typeof payload.navigationId !== "number"
|
|
25
|
+
) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
dispatchRouteEvent("navigation:error", {
|
|
30
|
+
navigationId: payload.navigationId,
|
|
31
|
+
navigationType: payload.navigationType,
|
|
32
|
+
to: cloneUrl(payload.to),
|
|
33
|
+
from: cloneUrl(payload.from),
|
|
34
|
+
routeId: typeof payload.routeId === "string" ? payload.routeId : "",
|
|
35
|
+
params: copyParams(payload.params),
|
|
36
|
+
stage: typeof payload.stage === "string" ? payload.stage : "listener",
|
|
37
|
+
reason: "listener-error",
|
|
38
|
+
hook: eventName,
|
|
39
|
+
error
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function dispatchRouteEvent(eventName, payload) {
|
|
44
|
+
const listeners = routeEventListeners()[eventName];
|
|
45
|
+
if (!(listeners instanceof Set)) return;
|
|
46
|
+
const handlers = Array.from(listeners);
|
|
47
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
48
|
+
const handler = handlers[i];
|
|
49
|
+
try {
|
|
50
|
+
const result = handler(payload);
|
|
51
|
+
if (result && typeof result.catch === "function") {
|
|
52
|
+
result.catch(function(error) {
|
|
53
|
+
reportRouteEventError(eventName, payload, error);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
reportRouteEventError(eventName, payload, error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function dispatchRouteEventAsync(eventName, payload) {
|
|
63
|
+
const listeners = routeEventListeners()[eventName];
|
|
64
|
+
if (!(listeners instanceof Set)) return;
|
|
65
|
+
const handlers = Array.from(listeners);
|
|
66
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
67
|
+
const handler = handlers[i];
|
|
68
|
+
try {
|
|
69
|
+
await handler(payload);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
reportRouteEventError(eventName, payload, error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildNavigationPayload(context, extra) {
|
|
77
|
+
const payload = {
|
|
78
|
+
navigationId: context.token,
|
|
79
|
+
navigationType: context.navigationType,
|
|
80
|
+
to: cloneUrl(context.toUrl),
|
|
81
|
+
from: cloneUrl(context.fromUrl),
|
|
82
|
+
routeId: context.routeId,
|
|
83
|
+
params: copyParams(context.params),
|
|
84
|
+
stage: context.stage
|
|
85
|
+
};
|
|
86
|
+
if (extra && typeof extra === "object") {
|
|
87
|
+
Object.assign(payload, extra);
|
|
88
|
+
}
|
|
89
|
+
return payload;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function emitNavigationError(context, extra) {
|
|
93
|
+
if (!context) return;
|
|
94
|
+
dispatchRouteEvent("navigation:error", buildNavigationPayload(context, extra));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function emitNavigationEvent(context, eventName, extra, awaitHandlers) {
|
|
98
|
+
const payload = buildNavigationPayload(context, extra);
|
|
99
|
+
if (awaitHandlers) {
|
|
100
|
+
await dispatchRouteEventAsync(eventName, payload);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
dispatchRouteEvent(eventName, payload);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function emitNavigationAbort(context, extra) {
|
|
107
|
+
if (!context || context.abortDispatched) return;
|
|
108
|
+
context.abortDispatched = true;
|
|
109
|
+
dispatchRouteEvent("navigation:abort", buildNavigationPayload(context, extra));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureCurrentNavigation(context) {
|
|
113
|
+
if (context && context.token === navigationToken) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (context) {
|
|
117
|
+
if (!context.abortReason) {
|
|
118
|
+
context.abortReason = {
|
|
119
|
+
reason: "superseded",
|
|
120
|
+
abortedStage: context.stage
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
emitNavigationAbort(context, context.abortReason);
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}`;
|
|
127
|
+
}
|