@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.
@@ -1,53 +1,8 @@
1
1
  export function renderRouterNavigationSource() {
2
- return `function extractSsrData(parsed) {
3
- if (!parsed || typeof parsed.getElementById !== "function") return {};
4
- const ssrScript = parsed.getElementById("zenith-ssr-data");
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 === "pop");
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 === "pop");
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 === "pop");
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 === "pop");
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 === "pop");
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 === "pop");
294
+ navigateViaBrowser(targetUrl, shouldReplaceBrowserNavigation(historyMode));
233
295
  return true;
234
296
  }
235
-
236
- documentDetail = createDocumentDetail(payload, response);
237
-
238
- context.stage = "data-ready";
239
- emitNavigationEvent(context, "navigation:data-ready", {
240
- document: documentDetail
241
- }, false);
242
-
243
- dispatchScrollEvent("before", {
244
- navigationType: historyMode,
245
- to: targetUrl.href,
246
- from: context.fromUrl ? context.fromUrl.href : window.location.href
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 === "pop" || historyCommitted);
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
  }