@zenithbuild/router 0.7.3 → 0.7.5
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 +2 -1
- package/dist/history.js +31 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/navigate.d.ts +2 -0
- package/dist/navigate.js +16 -0
- package/dist/navigation-shell.d.ts +38 -0
- package/dist/navigation-shell.js +392 -0
- package/dist/router.js +9 -2
- package/index.d.ts +46 -4
- package/package.json +3 -2
- package/template-core.js +10 -1
- package/template-document.js +49 -0
- package/template-form.js +174 -0
- package/template-navigation.js +130 -151
- package/template.js +5 -2
package/template-navigation.js
CHANGED
|
@@ -1,53 +1,8 @@
|
|
|
1
1
|
export function renderRouterNavigationSource() {
|
|
2
|
-
return `function
|
|
3
|
-
if (!
|
|
4
|
-
|
|
5
|
-
if (!ssrScript) return {};
|
|
6
|
-
const source = typeof ssrScript.textContent === "string" ? ssrScript.textContent : "";
|
|
7
|
-
const marker = "window.__zenith_ssr_data =";
|
|
8
|
-
const markerIndex = source.indexOf(marker);
|
|
9
|
-
if (markerIndex === -1) return {};
|
|
10
|
-
const jsonText = source.slice(markerIndex + marker.length).trim().replace(/;$/, "");
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(jsonText);
|
|
13
|
-
} catch {
|
|
14
|
-
return {};
|
|
2
|
+
return `async function requestRouteCheck(context, resolved, targetUrl, signal) {
|
|
3
|
+
if (!__ZENITH_ROUTE_CHECK_ENABLED__) {
|
|
4
|
+
return { kind: "allow" };
|
|
15
5
|
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseDocumentPayload(html) {
|
|
19
|
-
if (typeof DOMParser === "undefined") return null;
|
|
20
|
-
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
21
|
-
return {
|
|
22
|
-
html,
|
|
23
|
-
title: parsed.title || "",
|
|
24
|
-
ssrData: extractSsrData(parsed)
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isHtmlResponse(response) {
|
|
29
|
-
const contentType = String(response.headers.get("content-type") || "");
|
|
30
|
-
return /text\\/html|application\\/xhtml\\+xml/i.test(contentType);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createDocumentDetail(payload, response) {
|
|
34
|
-
return {
|
|
35
|
-
title: payload && typeof payload.title === "string" ? payload.title : "",
|
|
36
|
-
hasSsrData: !!(payload && payload.ssrData && typeof payload.ssrData === "object"),
|
|
37
|
-
status: response && typeof response.status === "number" ? response.status : 200
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createScrollDetail(targetUrl, scrollTarget) {
|
|
42
|
-
return {
|
|
43
|
-
mode: scrollTarget.mode,
|
|
44
|
-
x: scrollTarget.x,
|
|
45
|
-
y: scrollTarget.y,
|
|
46
|
-
hash: targetUrl.hash || ""
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function requestRouteCheck(context, resolved, targetUrl, signal) {
|
|
51
6
|
if (!requiresServerReload(resolved.route)) {
|
|
52
7
|
return { kind: "allow" };
|
|
53
8
|
}
|
|
@@ -143,10 +98,117 @@ function navigateViaBrowser(targetUrl, replace) {
|
|
|
143
98
|
window.location.assign(targetUrl.href);
|
|
144
99
|
}
|
|
145
100
|
|
|
101
|
+
function shouldReplaceBrowserNavigation(historyMode) {
|
|
102
|
+
return historyMode === "pop";
|
|
103
|
+
}
|
|
104
|
+
|
|
146
105
|
function dispatchNavigationFallback(context, detail) {
|
|
147
106
|
emitNavigationAbort(context, detail);
|
|
148
107
|
}
|
|
149
108
|
|
|
109
|
+
async function commitNavigationDocument(context, resolved, targetUrl, historyMode, popstateState, payload, response) {
|
|
110
|
+
const documentDetail = createDocumentDetail(payload, response);
|
|
111
|
+
let historyCommitted = false;
|
|
112
|
+
|
|
113
|
+
context.stage = "data-ready";
|
|
114
|
+
emitNavigationEvent(context, "navigation:data-ready", {
|
|
115
|
+
document: documentDetail
|
|
116
|
+
}, false);
|
|
117
|
+
|
|
118
|
+
dispatchScrollEvent("before", {
|
|
119
|
+
navigationType: historyMode,
|
|
120
|
+
to: targetUrl.href,
|
|
121
|
+
from: context.fromUrl ? context.fromUrl.href : window.location.href
|
|
122
|
+
}, false);
|
|
123
|
+
|
|
124
|
+
context.stage = "before-leave";
|
|
125
|
+
await emitNavigationEvent(context, "navigation:before-leave", {
|
|
126
|
+
document: documentDetail
|
|
127
|
+
}, true);
|
|
128
|
+
if (!ensureCurrentNavigation(context)) return { committed: false, documentDetail, historyCommitted };
|
|
129
|
+
|
|
130
|
+
context.stage = "leave-complete";
|
|
131
|
+
emitNavigationEvent(context, "navigation:leave-complete", {
|
|
132
|
+
document: documentDetail
|
|
133
|
+
}, false);
|
|
134
|
+
|
|
135
|
+
context.stage = "before-swap";
|
|
136
|
+
await emitNavigationEvent(context, "navigation:before-swap", {
|
|
137
|
+
document: documentDetail
|
|
138
|
+
}, true);
|
|
139
|
+
if (!ensureCurrentNavigation(context)) return { committed: false, documentDetail, historyCommitted };
|
|
140
|
+
|
|
141
|
+
if (historyMode === "push") {
|
|
142
|
+
rememberScrollForKey(currentHistoryKey);
|
|
143
|
+
pushHistoryEntry(targetUrl);
|
|
144
|
+
historyCommitted = true;
|
|
145
|
+
} else if (historyMode === "pop") {
|
|
146
|
+
syncHistoryEntry(popstateState);
|
|
147
|
+
historyCommitted = true;
|
|
148
|
+
}
|
|
149
|
+
currentUrl = new URL(targetUrl.href);
|
|
150
|
+
|
|
151
|
+
if (payload.title) {
|
|
152
|
+
document.title = payload.title;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const mounted = await mountRoute(resolved.route, resolved.params, context.token, payload);
|
|
156
|
+
if (!mounted || !ensureCurrentNavigation(context)) {
|
|
157
|
+
return { committed: false, documentDetail, historyCommitted };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
context.stage = "content-swapped";
|
|
161
|
+
emitNavigationEvent(context, "navigation:content-swapped", {
|
|
162
|
+
document: documentDetail,
|
|
163
|
+
historyCommitted
|
|
164
|
+
}, false);
|
|
165
|
+
|
|
166
|
+
await nextFrame();
|
|
167
|
+
if (!ensureCurrentNavigation(context)) return { committed: false, documentDetail, historyCommitted };
|
|
168
|
+
|
|
169
|
+
const scrollTarget = resolveScrollTarget(targetUrl, historyMode, popstateState);
|
|
170
|
+
const scrollDetail = createScrollDetail(targetUrl, scrollTarget);
|
|
171
|
+
const defaultScrollAllowed = dispatchScrollEvent("apply", {
|
|
172
|
+
navigationType: historyMode,
|
|
173
|
+
mode: scrollDetail.mode,
|
|
174
|
+
x: scrollDetail.x,
|
|
175
|
+
y: scrollDetail.y,
|
|
176
|
+
hash: scrollDetail.hash
|
|
177
|
+
}, true);
|
|
178
|
+
if (defaultScrollAllowed) {
|
|
179
|
+
applyNativeScroll(scrollTarget);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
focusAfterNavigation(scrollTarget);
|
|
183
|
+
rememberScrollForKey(currentHistoryKey, { x: scrollTarget.x, y: scrollTarget.y });
|
|
184
|
+
|
|
185
|
+
context.stage = "before-enter";
|
|
186
|
+
await emitNavigationEvent(context, "navigation:before-enter", {
|
|
187
|
+
document: documentDetail,
|
|
188
|
+
scroll: scrollDetail
|
|
189
|
+
}, true);
|
|
190
|
+
if (!ensureCurrentNavigation(context)) return { committed: false, documentDetail, historyCommitted };
|
|
191
|
+
|
|
192
|
+
dispatchScrollEvent("after", {
|
|
193
|
+
navigationType: historyMode,
|
|
194
|
+
mode: scrollDetail.mode,
|
|
195
|
+
x: scrollDetail.x,
|
|
196
|
+
y: scrollDetail.y,
|
|
197
|
+
hash: scrollDetail.hash
|
|
198
|
+
}, false);
|
|
199
|
+
|
|
200
|
+
await nextFrame();
|
|
201
|
+
if (!ensureCurrentNavigation(context)) return { committed: false, documentDetail, historyCommitted };
|
|
202
|
+
|
|
203
|
+
context.stage = "enter-complete";
|
|
204
|
+
emitNavigationEvent(context, "navigation:enter-complete", {
|
|
205
|
+
document: documentDetail,
|
|
206
|
+
scroll: scrollDetail
|
|
207
|
+
}, false);
|
|
208
|
+
|
|
209
|
+
return { committed: true, documentDetail, historyCommitted };
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
151
213
|
const resolved = resolveRoute(targetUrl.pathname);
|
|
152
214
|
if (!resolved) {
|
|
@@ -173,7 +235,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
173
235
|
location: redirectUrl.href,
|
|
174
236
|
status: checkResult.status
|
|
175
237
|
});
|
|
176
|
-
navigateViaBrowser(redirectUrl, historyMode
|
|
238
|
+
navigateViaBrowser(redirectUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
177
239
|
return true;
|
|
178
240
|
}
|
|
179
241
|
if (checkResult.kind === "deny") {
|
|
@@ -181,7 +243,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
181
243
|
reason: "server-deny",
|
|
182
244
|
status: checkResult.status
|
|
183
245
|
});
|
|
184
|
-
navigateViaBrowser(targetUrl, historyMode
|
|
246
|
+
navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
185
247
|
return true;
|
|
186
248
|
}
|
|
187
249
|
|
|
@@ -201,7 +263,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
201
263
|
location: redirectUrl.href,
|
|
202
264
|
status: response.status
|
|
203
265
|
});
|
|
204
|
-
navigateViaBrowser(redirectUrl, historyMode
|
|
266
|
+
navigateViaBrowser(redirectUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
205
267
|
return true;
|
|
206
268
|
}
|
|
207
269
|
|
|
@@ -212,7 +274,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
212
274
|
reason: "http-status",
|
|
213
275
|
status: response.status
|
|
214
276
|
});
|
|
215
|
-
navigateViaBrowser(targetUrl, historyMode
|
|
277
|
+
navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
216
278
|
return true;
|
|
217
279
|
}
|
|
218
280
|
if (!isHtmlResponse(response)) {
|
|
@@ -220,7 +282,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
220
282
|
reason: "non-html",
|
|
221
283
|
status: response.status
|
|
222
284
|
});
|
|
223
|
-
navigateViaBrowser(targetUrl, historyMode
|
|
285
|
+
navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
224
286
|
return true;
|
|
225
287
|
}
|
|
226
288
|
|
|
@@ -229,105 +291,21 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
229
291
|
dispatchNavigationFallback(context, {
|
|
230
292
|
reason: "document-parse"
|
|
231
293
|
});
|
|
232
|
-
navigateViaBrowser(targetUrl, historyMode
|
|
294
|
+
navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode));
|
|
233
295
|
return true;
|
|
234
296
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}, false);
|
|
248
|
-
|
|
249
|
-
context.stage = "before-leave";
|
|
250
|
-
await emitNavigationEvent(context, "navigation:before-leave", {
|
|
251
|
-
document: documentDetail
|
|
252
|
-
}, true);
|
|
253
|
-
if (!ensureCurrentNavigation(context)) return false;
|
|
254
|
-
|
|
255
|
-
context.stage = "leave-complete";
|
|
256
|
-
emitNavigationEvent(context, "navigation:leave-complete", {
|
|
257
|
-
document: documentDetail
|
|
258
|
-
}, false);
|
|
259
|
-
|
|
260
|
-
context.stage = "before-swap";
|
|
261
|
-
await emitNavigationEvent(context, "navigation:before-swap", {
|
|
262
|
-
document: documentDetail
|
|
263
|
-
}, true);
|
|
264
|
-
if (!ensureCurrentNavigation(context)) return false;
|
|
265
|
-
|
|
266
|
-
if (historyMode === "push") {
|
|
267
|
-
rememberScrollForKey(currentHistoryKey);
|
|
268
|
-
pushHistoryEntry(targetUrl);
|
|
269
|
-
historyCommitted = true;
|
|
270
|
-
} else if (historyMode === "pop") {
|
|
271
|
-
syncHistoryEntry(popstateState);
|
|
272
|
-
historyCommitted = true;
|
|
273
|
-
}
|
|
274
|
-
currentUrl = new URL(targetUrl.href);
|
|
275
|
-
|
|
276
|
-
if (payload.title) {
|
|
277
|
-
document.title = payload.title;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const mounted = await mountRoute(resolved.route, resolved.params, context.token, payload);
|
|
281
|
-
if (!mounted || !ensureCurrentNavigation(context)) return false;
|
|
282
|
-
|
|
283
|
-
context.stage = "content-swapped";
|
|
284
|
-
emitNavigationEvent(context, "navigation:content-swapped", {
|
|
285
|
-
document: documentDetail,
|
|
286
|
-
historyCommitted
|
|
287
|
-
}, false);
|
|
288
|
-
|
|
289
|
-
await nextFrame();
|
|
290
|
-
if (!ensureCurrentNavigation(context)) return false;
|
|
291
|
-
|
|
292
|
-
const scrollTarget = resolveScrollTarget(targetUrl, historyMode, popstateState);
|
|
293
|
-
const scrollDetail = createScrollDetail(targetUrl, scrollTarget);
|
|
294
|
-
const defaultScrollAllowed = dispatchScrollEvent("apply", {
|
|
295
|
-
navigationType: historyMode,
|
|
296
|
-
mode: scrollDetail.mode,
|
|
297
|
-
x: scrollDetail.x,
|
|
298
|
-
y: scrollDetail.y,
|
|
299
|
-
hash: scrollDetail.hash
|
|
300
|
-
}, true);
|
|
301
|
-
if (defaultScrollAllowed) {
|
|
302
|
-
applyNativeScroll(scrollTarget);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
focusAfterNavigation(scrollTarget);
|
|
306
|
-
rememberScrollForKey(currentHistoryKey, { x: scrollTarget.x, y: scrollTarget.y });
|
|
307
|
-
|
|
308
|
-
context.stage = "before-enter";
|
|
309
|
-
await emitNavigationEvent(context, "navigation:before-enter", {
|
|
310
|
-
document: documentDetail,
|
|
311
|
-
scroll: scrollDetail
|
|
312
|
-
}, true);
|
|
313
|
-
if (!ensureCurrentNavigation(context)) return false;
|
|
314
|
-
|
|
315
|
-
dispatchScrollEvent("after", {
|
|
316
|
-
navigationType: historyMode,
|
|
317
|
-
mode: scrollDetail.mode,
|
|
318
|
-
x: scrollDetail.x,
|
|
319
|
-
y: scrollDetail.y,
|
|
320
|
-
hash: scrollDetail.hash
|
|
321
|
-
}, false);
|
|
322
|
-
|
|
323
|
-
await nextFrame();
|
|
324
|
-
if (!ensureCurrentNavigation(context)) return false;
|
|
325
|
-
|
|
326
|
-
context.stage = "enter-complete";
|
|
327
|
-
emitNavigationEvent(context, "navigation:enter-complete", {
|
|
328
|
-
document: documentDetail,
|
|
329
|
-
scroll: scrollDetail
|
|
330
|
-
}, false);
|
|
297
|
+
const committed = await commitNavigationDocument(
|
|
298
|
+
context,
|
|
299
|
+
resolved,
|
|
300
|
+
targetUrl,
|
|
301
|
+
historyMode,
|
|
302
|
+
popstateState,
|
|
303
|
+
payload,
|
|
304
|
+
response
|
|
305
|
+
);
|
|
306
|
+
documentDetail = committed.documentDetail;
|
|
307
|
+
historyCommitted = committed.historyCommitted;
|
|
308
|
+
if (!committed.committed) return false;
|
|
331
309
|
|
|
332
310
|
return true;
|
|
333
311
|
} catch (error) {
|
|
@@ -343,7 +321,7 @@ async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
|
343
321
|
reason: "runtime-failure",
|
|
344
322
|
historyCommitted
|
|
345
323
|
});
|
|
346
|
-
navigateViaBrowser(targetUrl, historyMode
|
|
324
|
+
navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode) || historyCommitted);
|
|
347
325
|
return true;
|
|
348
326
|
}
|
|
349
327
|
dispatchNavigationFallback(context, context.abortReason || {
|
|
@@ -387,6 +365,7 @@ function start() {
|
|
|
387
365
|
currentUrl = new URL(window.location.href);
|
|
388
366
|
queueScrollSnapshot();
|
|
389
367
|
});
|
|
368
|
+
installEnhancedFormHandling();
|
|
390
369
|
|
|
391
370
|
document.addEventListener("click", function(event) {
|
|
392
371
|
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
package/template.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { renderRouterCoreSource } from './template-core.js';
|
|
2
|
+
import { renderRouterDocumentSource } from './template-document.js';
|
|
3
|
+
import { renderRouterFormSource } from './template-form.js';
|
|
2
4
|
import { renderRouterLifecycleSource } from './template-lifecycle.js';
|
|
3
5
|
import { renderRouterNavigationSource } from './template-navigation.js';
|
|
6
|
+
import { renderRouterRefreshSource } from './template-refresh.js';
|
|
4
7
|
|
|
5
8
|
function normalizeManifestJson(manifestJson) {
|
|
6
9
|
return manifestJson.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
@@ -19,7 +22,7 @@ export function renderRouterModule(opts) {
|
|
|
19
22
|
throw new Error('renderRouterModule(opts) requires an options object');
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const { manifestJson, runtimeImport, coreImport } = opts;
|
|
25
|
+
const { manifestJson, runtimeImport, coreImport, routeCheck = false } = opts;
|
|
23
26
|
if (typeof manifestJson !== 'string' || manifestJson.length === 0) {
|
|
24
27
|
throw new Error('renderRouterModule(opts) requires opts.manifestJson string');
|
|
25
28
|
}
|
|
@@ -34,5 +37,5 @@ export function renderRouterModule(opts) {
|
|
|
34
37
|
const runtimeSpec = sanitizeImportSpecifier(runtimeImport);
|
|
35
38
|
const coreSpec = sanitizeImportSpecifier(coreImport);
|
|
36
39
|
|
|
37
|
-
return `${renderRouterCoreSource({ manifest, runtimeSpec, coreSpec })}\n\n${renderRouterLifecycleSource()}\n\n${renderRouterNavigationSource()}\n`;
|
|
40
|
+
return `${renderRouterCoreSource({ manifest, runtimeSpec, coreSpec, routeCheck })}\n\n${renderRouterDocumentSource()}\n\n${renderRouterLifecycleSource()}\n\n${renderRouterRefreshSource()}\n\n${renderRouterNavigationSource()}\n\n${renderRouterFormSource()}\n`;
|
|
38
41
|
}
|