@vesk/runtime 0.1.1 → 0.2.8
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/dist/form.js +1 -1
- package/dist/headless.d.ts +30 -0
- package/dist/headless.d.ts.map +1 -0
- package/dist/headless.js +39 -0
- package/dist/hmr-client.js +5 -2
- package/dist/index-client.d.ts +5 -0
- package/dist/index-client.d.ts.map +1 -1
- package/dist/index-client.js +3 -0
- package/dist/index-router.d.ts +16 -0
- package/dist/index-router.d.ts.map +1 -0
- package/dist/index-router.js +15 -0
- package/dist/index-server.d.ts +3 -0
- package/dist/index-server.d.ts.map +1 -1
- package/dist/index-server.js +2 -0
- package/dist/loading-indicator.d.ts +127 -0
- package/dist/loading-indicator.d.ts.map +1 -0
- package/dist/loading-indicator.js +342 -0
- package/dist/md.d.ts +112 -3
- package/dist/md.d.ts.map +1 -1
- package/dist/md.js +1977 -198
- package/dist/network.d.ts +28 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +62 -0
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +22 -13
- package/dist/resource.d.ts +3 -1
- package/dist/resource.d.ts.map +1 -1
- package/dist/resource.js +36 -1
- package/dist/ripple-runtime.d.ts +11 -6
- package/dist/ripple-runtime.d.ts.map +1 -1
- package/dist/ripple-runtime.js +12 -0
- package/dist/router-components.d.ts +29 -3
- package/dist/router-components.d.ts.map +1 -1
- package/dist/router-components.js +73 -0
- package/dist/router-match.d.ts +2 -0
- package/dist/router-match.d.ts.map +1 -1
- package/dist/router-match.js +6 -3
- package/dist/router.d.ts +27 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +770 -254
- package/package.json +6 -2
- package/dist/package.json +0 -23
package/dist/router.js
CHANGED
|
@@ -2,7 +2,9 @@ import { get, set, scope, set_active_block } from '@vesk/runtime/src/ripple-runt
|
|
|
2
2
|
import { root } from '@vesk/runtime/src/ripple-blocks';
|
|
3
3
|
import { createHydrateWalker, hydrateViewport, hydrateIdle, hydrateOnInteraction } from '@vesk/runtime/src/hydrate';
|
|
4
4
|
import { matchRoute, flattenLayoutChain, buildTreeFromMap } from '@vesk/runtime/src/router-match';
|
|
5
|
-
import { setIsHydrating, _state, _scrollPositions, _isPopStateNavigation, setIsPopStateNavigation, setCurrentRouter, showLoadingInContainer, handleScroll, applyHead, findLoadingComponent, findErrorComponent, findNotFoundComponent, Outlet, Link, NavLink, useNavigate, useParams, usePathname, useSearchParams, useRouter, Redirect, redirect, permanentRedirect, NotFoundError, notFound, } from '@vesk/runtime/src/router-components';
|
|
5
|
+
import { setIsHydrating, _state, _scrollPositions, _isPopStateNavigation, setIsPopStateNavigation, setCurrentRouter, showLoadingInContainer, handleScroll, applyHead, findLoadingComponent, findErrorComponent, findNotFoundComponent, findOfflineComponent, findNetworkComponent, Outlet, Link, NavLink, useNavigate, useParams, usePathname, useSearchParams, useRouter, Redirect, redirect, permanentRedirect, NotFoundError, notFound, } from '@vesk/runtime/src/router-components';
|
|
6
|
+
import { getNetworkState, watchNetwork } from '@vesk/runtime/src/network';
|
|
7
|
+
import { loadingStart, loadingFinish, getLoadingState } from '@vesk/runtime/src/loading-indicator';
|
|
6
8
|
export { Outlet, Link, NavLink, useNavigate, useParams, usePathname, useSearchParams, useRouter, Redirect, redirect, permanentRedirect, NotFoundError, notFound, ensureChunk, };
|
|
7
9
|
export { matchRoute } from '@vesk/runtime/src/router-match';
|
|
8
10
|
const loadedChunks = new Set();
|
|
@@ -35,7 +37,18 @@ function ensureChunk(chunkUrl) {
|
|
|
35
37
|
}
|
|
36
38
|
/** Loads pending chunks without letting a single failed chunk abort the flow. */
|
|
37
39
|
function loadChunksQuietly(urls) {
|
|
38
|
-
|
|
40
|
+
navDebug('loadChunks start', urls);
|
|
41
|
+
return Promise.all(urls.map((u) => ensureChunk(u).catch(() => undefined))).then(() => {
|
|
42
|
+
navDebug('loadChunks done', urls);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Set window.__veskNavDebug = [] in the page to capture router breadcrumbs.
|
|
46
|
+
function navDebug(...parts) {
|
|
47
|
+
if (typeof window === 'undefined')
|
|
48
|
+
return;
|
|
49
|
+
const log = window.__veskNavDebug;
|
|
50
|
+
if (log)
|
|
51
|
+
log.push(parts.map(p => Array.isArray(p) ? p.join(',') : String(p)).join(' '));
|
|
39
52
|
}
|
|
40
53
|
function hasPendingChunks(nodes) {
|
|
41
54
|
const urls = [];
|
|
@@ -48,6 +61,17 @@ function hasPendingChunks(nodes) {
|
|
|
48
61
|
nodes.forEach(walk);
|
|
49
62
|
return urls;
|
|
50
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Classifies a data-fetch failure as a connectivity problem rather than a
|
|
66
|
+
* server problem: fetch rejects with TypeError on network failure, and
|
|
67
|
+
* `navigator.onLine === false` confirms it even where the error shape is
|
|
68
|
+
* environment-specific.
|
|
69
|
+
*/
|
|
70
|
+
function isConnectivityFailure(err) {
|
|
71
|
+
if (typeof navigator !== 'undefined' && navigator && navigator.onLine === false)
|
|
72
|
+
return true;
|
|
73
|
+
return err instanceof TypeError;
|
|
74
|
+
}
|
|
51
75
|
const _dataPromises = new Map();
|
|
52
76
|
async function fetchRouteData(path) {
|
|
53
77
|
try {
|
|
@@ -83,7 +107,13 @@ async function fetchRouteData(path) {
|
|
|
83
107
|
return null;
|
|
84
108
|
return (await res.json());
|
|
85
109
|
}
|
|
86
|
-
catch {
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (isConnectivityFailure(err))
|
|
112
|
+
return { offline: true };
|
|
113
|
+
// Passive signals can miss (proxies, emulation) — actively probe
|
|
114
|
+
// before classifying as a plain failure.
|
|
115
|
+
if (await looksOffline())
|
|
116
|
+
return { offline: true };
|
|
87
117
|
return null;
|
|
88
118
|
}
|
|
89
119
|
}
|
|
@@ -129,7 +159,27 @@ function chunkErrorForNode(node) {
|
|
|
129
159
|
return new Error(String(node._chunkError));
|
|
130
160
|
return undefined;
|
|
131
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Active connectivity check for failure paths where passive signals
|
|
164
|
+
* (`navigator.onLine`) are unreliable (proxies, CDP emulation, some
|
|
165
|
+
* browsers lag the flag). Any HTTP response — even 404/500 — proves the
|
|
166
|
+
* origin is reachable; only a fetch-level rejection counts as offline.
|
|
167
|
+
*/
|
|
168
|
+
async function looksOffline() {
|
|
169
|
+
if (typeof navigator !== 'undefined' && navigator && navigator.onLine === false)
|
|
170
|
+
return true;
|
|
171
|
+
if (typeof fetch !== 'function')
|
|
172
|
+
return false;
|
|
173
|
+
try {
|
|
174
|
+
await fetch('/__vesk_connectivity_' + Date.now(), { method: 'HEAD', cache: 'no-store' });
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
return err instanceof TypeError;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
132
181
|
async function renderNotFound(router, match, container) {
|
|
182
|
+
clearOfflineFlag(router, container);
|
|
133
183
|
const chain = match.matchChain;
|
|
134
184
|
const paramValues = match.params;
|
|
135
185
|
const notFoundFn = findNotFoundComponent(chain);
|
|
@@ -158,7 +208,142 @@ async function renderNotFound(router, match, container) {
|
|
|
158
208
|
container.innerHTML = '<h1>404 — Not Found</h1>';
|
|
159
209
|
router._currentMatch = match;
|
|
160
210
|
}
|
|
211
|
+
function defaultOfflineHtml(url) {
|
|
212
|
+
return '<div data-vesk-offline style="all:initial;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;font-family:system-ui,-apple-system,sans-serif;color:#1f2937;text-align:center;padding:24px">' +
|
|
213
|
+
'<div style="font-size:40px;line-height:1;margin-bottom:16px" aria-hidden="true">📡</div>' +
|
|
214
|
+
'<h1 style="font-size:22px;font-weight:700;margin:0 0 8px">You\u2019re offline</h1>' +
|
|
215
|
+
'<p style="margin:0 0 20px;color:#6b7280;max-width:34em">Check your connection. <span style="color:#9ca3af">' + escapeOfflineUrl(url) + '</span> will load automatically once you\u2019re back online.</p>' +
|
|
216
|
+
'<button type="button" data-vesk-offline-retry style="all:unset;cursor:pointer;background:#2563eb;color:#fff;padding:10px 22px;border-radius:8px;font-size:14px;font-weight:600">Retry now</button>' +
|
|
217
|
+
'</div>';
|
|
218
|
+
}
|
|
219
|
+
function escapeOfflineUrl(url) {
|
|
220
|
+
return String(url).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Renders the connectivity experience for a navigation that failed due to
|
|
224
|
+
* loss of (or degraded) network. Precedence:
|
|
225
|
+
* 1. nearest route `offline.vsk` — dedicated offline UI (when offline)
|
|
226
|
+
* 2. nearest route `network.vsk` — state-aware UI receiving
|
|
227
|
+
* `{ url, params, retry, online, effectiveType, downlink, rtt, saveData }`
|
|
228
|
+
* 3. router `offline` option (component or HTML string)
|
|
229
|
+
* 4. nearest route `error.vsk`, invoked with an `offline: true` error so
|
|
230
|
+
* existing error boundaries can style the offline case themselves
|
|
231
|
+
* 5. built-in default panel
|
|
232
|
+
*
|
|
233
|
+
* A network failure never renders the not-found page. While a boundary from
|
|
234
|
+
* steps 1–3 or 5 is displayed it re-renders on connectivity changes
|
|
235
|
+
* (3g→4g→offline…), and the router re-navigates automatically when the
|
|
236
|
+
* browser comes back online.
|
|
237
|
+
*/
|
|
238
|
+
async function renderOfflinePage(router, match, container) {
|
|
239
|
+
const url = match.pathname || (typeof window !== 'undefined' ? window.location.pathname : '');
|
|
240
|
+
const chain = match.matchChain;
|
|
241
|
+
const navTokenAtRender = router._navToken;
|
|
242
|
+
// Debounced retry: a component that calls retry() while rendering must
|
|
243
|
+
// not trigger a navigate→fail→render→retry microtask loop.
|
|
244
|
+
const safeRetry = () => {
|
|
245
|
+
const r = router;
|
|
246
|
+
const now = Date.now();
|
|
247
|
+
if (now - (r._lastOfflineRetry || 0) < 300)
|
|
248
|
+
return;
|
|
249
|
+
r._lastOfflineRetry = now;
|
|
250
|
+
router.navigate(url, { replace: true });
|
|
251
|
+
};
|
|
252
|
+
const mountComponent = async (entry) => {
|
|
253
|
+
if (typeof entry === 'string') {
|
|
254
|
+
container.innerHTML = entry;
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
const tempRoot = document.createDocumentFragment();
|
|
258
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
259
|
+
const props = { url, params: match.params, retry: safeRetry, ...getNetworkState() };
|
|
260
|
+
const dom = await runInBlockWindow(() => entry(props, new Map(), walker));
|
|
261
|
+
if (dom && typeof dom === 'object' && dom.nodeType) {
|
|
262
|
+
if (container.replaceChildren)
|
|
263
|
+
container.replaceChildren(dom);
|
|
264
|
+
else {
|
|
265
|
+
container.innerHTML = '';
|
|
266
|
+
container.appendChild(dom);
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
if (typeof dom === 'string') {
|
|
271
|
+
container.innerHTML = dom;
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
};
|
|
276
|
+
let mounted = false;
|
|
277
|
+
let reactive = false; // re-render on connectivity change
|
|
278
|
+
const offlineEntry = findOfflineComponent(chain);
|
|
279
|
+
if (!mounted && offlineEntry && !(getNetworkState().online)) {
|
|
280
|
+
mounted = await mountComponent(offlineEntry);
|
|
281
|
+
reactive = mounted;
|
|
282
|
+
}
|
|
283
|
+
const networkEntry = findNetworkComponent(chain);
|
|
284
|
+
if (!mounted && networkEntry) {
|
|
285
|
+
mounted = await mountComponent(networkEntry);
|
|
286
|
+
reactive = mounted;
|
|
287
|
+
}
|
|
288
|
+
const optionUI = router._offlineUI;
|
|
289
|
+
if (!mounted && optionUI !== undefined && optionUI !== null) {
|
|
290
|
+
mounted = typeof optionUI === 'function'
|
|
291
|
+
? await mountComponent(optionUI)
|
|
292
|
+
: ((container.innerHTML = String(optionUI)), true);
|
|
293
|
+
reactive = typeof optionUI === 'function';
|
|
294
|
+
}
|
|
295
|
+
const errorFn = findErrorComponent(chain);
|
|
296
|
+
if (!mounted && errorFn) {
|
|
297
|
+
// Existing error boundary opts in via `props.offline` /
|
|
298
|
+
// `props.networkState`.
|
|
299
|
+
const err = new Error('You are offline');
|
|
300
|
+
err.statusCode = 0;
|
|
301
|
+
err.offline = true;
|
|
302
|
+
await renderErrorPage(router, match, container, err);
|
|
303
|
+
mounted = true;
|
|
304
|
+
}
|
|
305
|
+
if (!mounted) {
|
|
306
|
+
container.innerHTML = defaultOfflineHtml(url);
|
|
307
|
+
const retryBtn = (container.querySelector ? container.querySelector('[data-vesk-offline-retry]') : null);
|
|
308
|
+
if (retryBtn)
|
|
309
|
+
retryBtn.addEventListener('click', safeRetry);
|
|
310
|
+
reactive = true;
|
|
311
|
+
}
|
|
312
|
+
router._showingOffline = true;
|
|
313
|
+
registerOnlineRecovery(router);
|
|
314
|
+
// Live-swap the displayed boundary when connectivity changes (e.g.
|
|
315
|
+
// offline → 3g → 4g), but stop as soon as a newer navigation takes over.
|
|
316
|
+
if (reactive && typeof window !== 'undefined') {
|
|
317
|
+
container.__veskNetworkUI = true;
|
|
318
|
+
const unsubscribe = watchNetwork(() => {
|
|
319
|
+
const stillCurrent = router._navToken === navTokenAtRender &&
|
|
320
|
+
container.__veskNetworkUI === true &&
|
|
321
|
+
router._showingOffline === true;
|
|
322
|
+
if (!stillCurrent) {
|
|
323
|
+
unsubscribe();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
void renderOfflinePage(router, match, container);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function registerOnlineRecovery(router) {
|
|
331
|
+
const r = router;
|
|
332
|
+
if (r._onlineHandler || typeof window === 'undefined' || typeof window.addEventListener !== 'function')
|
|
333
|
+
return;
|
|
334
|
+
r._onlineHandler = () => {
|
|
335
|
+
if (!r._showingOffline)
|
|
336
|
+
return;
|
|
337
|
+
r._showingOffline = false;
|
|
338
|
+
router.navigate(window.location.pathname + window.location.search, { replace: true });
|
|
339
|
+
};
|
|
340
|
+
window.addEventListener('online', r._onlineHandler);
|
|
341
|
+
}
|
|
161
342
|
async function applyRouteData(router, match, data, container, render = renderMatch) {
|
|
343
|
+
if (data.offline) {
|
|
344
|
+
await renderOfflinePage(router, match, container);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
162
347
|
if (data.notFound) {
|
|
163
348
|
await renderNotFound(router, match, container);
|
|
164
349
|
return;
|
|
@@ -229,7 +414,7 @@ function shouldFetchData(router, match) {
|
|
|
229
414
|
return Date.now() - fetchedAt >= ttl;
|
|
230
415
|
}
|
|
231
416
|
function hasRealPageData(data) {
|
|
232
|
-
if (data.notFound || data.redirect || data.error)
|
|
417
|
+
if (data.offline || data.notFound || data.redirect || data.error)
|
|
233
418
|
return true;
|
|
234
419
|
if (data.head && data.head.trim().length > 0)
|
|
235
420
|
return true;
|
|
@@ -339,7 +524,27 @@ function replacePageContent(container, newContent) {
|
|
|
339
524
|
}
|
|
340
525
|
return true;
|
|
341
526
|
}
|
|
342
|
-
function
|
|
527
|
+
function storePageInstance(router, pageNode, pageProps, result) {
|
|
528
|
+
if (!router || !pageNode._pageName || !result || result.nodeType !== 1)
|
|
529
|
+
return;
|
|
530
|
+
if (!router.__componentInstances)
|
|
531
|
+
router.__componentInstances = new Map();
|
|
532
|
+
const name = pageNode._pageName;
|
|
533
|
+
if (!router.__componentInstances.has(name))
|
|
534
|
+
router.__componentInstances.set(name, []);
|
|
535
|
+
router.__componentInstances.get(name).push({ root: result, props: pageProps, node: pageNode, type: 'page' });
|
|
536
|
+
}
|
|
537
|
+
function storeLayoutInstance(router, node, layoutProps, result) {
|
|
538
|
+
if (!router || !node._layoutName || !result || result.nodeType !== 1)
|
|
539
|
+
return;
|
|
540
|
+
if (!router.__componentInstances)
|
|
541
|
+
router.__componentInstances = new Map();
|
|
542
|
+
const name = node._layoutName;
|
|
543
|
+
if (!router.__componentInstances.has(name))
|
|
544
|
+
router.__componentInstances.set(name, []);
|
|
545
|
+
router.__componentInstances.get(name).push({ root: result, props: layoutProps, node, type: 'layout' });
|
|
546
|
+
}
|
|
547
|
+
function renderMatch(router, match, container, navToken) {
|
|
343
548
|
const chain = match.matchChain;
|
|
344
549
|
const paramValues = match.params;
|
|
345
550
|
let pageNode = findPageNodeOrFailed(match);
|
|
@@ -379,11 +584,25 @@ function renderMatch(router, match, container) {
|
|
|
379
584
|
return;
|
|
380
585
|
}
|
|
381
586
|
// The route matched and has a page name, but its component chunk failed
|
|
382
|
-
// to load or compile
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
587
|
+
// to load or compile (or its component ref is still an unwired name).
|
|
588
|
+
// While offline that is a connectivity problem, not a broken route —
|
|
589
|
+
// show the offline experience instead of an error page. The connectivity
|
|
590
|
+
// probe only runs when a chunk actually failed or the browser reports
|
|
591
|
+
// offline, so ordinary lazy-loading never pays for it.
|
|
592
|
+
if (pageNode._pageName && (!pageNode.page || typeof pageNode.page === 'string')) {
|
|
593
|
+
const suspectOffline = failedChunks.has(pageNode._chunk)
|
|
594
|
+
|| (typeof navigator !== 'undefined' && !!navigator && navigator.onLine === false);
|
|
595
|
+
navDebug('unresolved page guard', String(pageNode._pageName), 'suspect=', String(suspectOffline));
|
|
596
|
+
if (!suspectOffline) {
|
|
597
|
+
const chunkErr0 = chunkErrorForNode(pageNode);
|
|
598
|
+
return renderErrorPage(router, match, container, chunkErr0 || new Error(`Component "${pageNode._pageName}" is unavailable`));
|
|
599
|
+
}
|
|
600
|
+
return looksOffline().then((offline) => {
|
|
601
|
+
if (offline)
|
|
602
|
+
return renderOfflinePage(router, match, container);
|
|
603
|
+
const chunkErr = chunkErrorForNode(pageNode);
|
|
604
|
+
return renderErrorPage(router, match, container, chunkErr || new Error(`Component "${pageNode._pageName}" is unavailable`));
|
|
605
|
+
});
|
|
387
606
|
}
|
|
388
607
|
const layoutNodes = chain.filter(n => n.layout);
|
|
389
608
|
const tempRoot = document.createDocumentFragment();
|
|
@@ -434,7 +653,10 @@ function renderMatch(router, match, container) {
|
|
|
434
653
|
const pageStep = () => {
|
|
435
654
|
set(_state.params, paramValues);
|
|
436
655
|
set(_state.path, match.pathname || window.location.pathname);
|
|
437
|
-
|
|
656
|
+
{
|
|
657
|
+
const s = window.location.search || '';
|
|
658
|
+
set(_state.search, s.startsWith('?') ? s.slice(1) : s);
|
|
659
|
+
}
|
|
438
660
|
const pageProps = { params: paramValues, ...pageNode.props };
|
|
439
661
|
const result = pageNode.page(pageProps, new Map(), clientWalker);
|
|
440
662
|
if (result && typeof result.then === 'function') {
|
|
@@ -457,7 +679,7 @@ function renderMatch(router, match, container) {
|
|
|
457
679
|
router.navigate(window.location.pathname, { replace: true });
|
|
458
680
|
}
|
|
459
681
|
};
|
|
460
|
-
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
682
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '', offline: error?.offline === true, networkState: getNetworkState() };
|
|
461
683
|
return runInBlockWindow(() => renderLayoutWith(0, () => {
|
|
462
684
|
const result = errorFn(errorProps, new Map(), clientWalker);
|
|
463
685
|
if (result && typeof result.then === 'function') {
|
|
@@ -478,7 +700,7 @@ function renderMatch(router, match, container) {
|
|
|
478
700
|
router.navigate(window.location.pathname, { replace: true });
|
|
479
701
|
}
|
|
480
702
|
};
|
|
481
|
-
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
703
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '', offline: error?.offline === true, networkState: getNetworkState() };
|
|
482
704
|
const errDom = runInBlockWindow(() => errorFn(errorProps, new Map(), clientWalker));
|
|
483
705
|
if (errDom && typeof errDom.then === 'function') {
|
|
484
706
|
errDom.then(mountDom);
|
|
@@ -487,27 +709,14 @@ function renderMatch(router, match, container) {
|
|
|
487
709
|
mountDom(errDom);
|
|
488
710
|
}
|
|
489
711
|
}
|
|
490
|
-
function storePageInstance(router, pageNode, pageProps, result) {
|
|
491
|
-
if (!router || !pageNode._pageName || !result || result.nodeType !== 1)
|
|
492
|
-
return;
|
|
493
|
-
if (!router.__componentInstances)
|
|
494
|
-
router.__componentInstances = new Map();
|
|
495
|
-
const name = pageNode._pageName;
|
|
496
|
-
if (!router.__componentInstances.has(name))
|
|
497
|
-
router.__componentInstances.set(name, []);
|
|
498
|
-
router.__componentInstances.get(name).push({ root: result, props: pageProps, node: pageNode, type: 'page' });
|
|
499
|
-
}
|
|
500
|
-
function storeLayoutInstance(router, node, layoutProps, result) {
|
|
501
|
-
if (!router || !node._layoutName || !result || result.nodeType !== 1)
|
|
502
|
-
return;
|
|
503
|
-
if (!router.__componentInstances)
|
|
504
|
-
router.__componentInstances = new Map();
|
|
505
|
-
const name = node._layoutName;
|
|
506
|
-
if (!router.__componentInstances.has(name))
|
|
507
|
-
router.__componentInstances.set(name, []);
|
|
508
|
-
router.__componentInstances.get(name).push({ root: result, props: layoutProps, node, type: 'layout' });
|
|
509
|
-
}
|
|
510
712
|
const mountDom = (dom) => {
|
|
713
|
+
// Staleness guard: a suspended (async) page whose render resolves after
|
|
714
|
+
// a NEWER navigation must not clobber the newer page's DOM. The file
|
|
715
|
+
// router stamps the container with the latest nav token per navigation.
|
|
716
|
+
if (navToken !== undefined &&
|
|
717
|
+
container.__veskMountToken !== navToken) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
511
720
|
if (dom && typeof dom === 'object' && dom.nodeType) {
|
|
512
721
|
if (container.replaceChildren)
|
|
513
722
|
container.replaceChildren(dom);
|
|
@@ -566,7 +775,13 @@ function makeSsrError(message) {
|
|
|
566
775
|
// failure or when the SSR output is already an error page). The error
|
|
567
776
|
// component renders in place of the page inside the layout chain so the error
|
|
568
777
|
// page keeps the site nav.
|
|
778
|
+
function clearOfflineFlag(router, container) {
|
|
779
|
+
router._showingOffline = false;
|
|
780
|
+
if (container)
|
|
781
|
+
container.__veskNetworkUI = false;
|
|
782
|
+
}
|
|
569
783
|
async function renderErrorPage(router, match, container, error) {
|
|
784
|
+
clearOfflineFlag(router, container);
|
|
570
785
|
const chain = match.matchChain;
|
|
571
786
|
const paramValues = match.params;
|
|
572
787
|
const errorFn = findErrorComponent(chain);
|
|
@@ -586,7 +801,7 @@ async function renderErrorPage(router, match, container, error) {
|
|
|
586
801
|
router.navigate(window.location.pathname, { replace: true });
|
|
587
802
|
}
|
|
588
803
|
};
|
|
589
|
-
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
804
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '', offline: error?.offline === true, networkState: getNetworkState() };
|
|
590
805
|
const renderErrorChain = (index) => {
|
|
591
806
|
if (index >= layoutNodes.length) {
|
|
592
807
|
const result = errorFn(errorProps, new Map(), walker);
|
|
@@ -660,7 +875,10 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
660
875
|
})();
|
|
661
876
|
set(_state.params, paramValues);
|
|
662
877
|
set(_state.path, match.pathname || window.location.pathname);
|
|
663
|
-
|
|
878
|
+
{
|
|
879
|
+
const s = window.location.search || '';
|
|
880
|
+
set(_state.search, s.startsWith('?') ? s.slice(1) : s);
|
|
881
|
+
}
|
|
664
882
|
if (ssrErrorMarker !== null) {
|
|
665
883
|
let message = 'Internal Server Error';
|
|
666
884
|
if (ssrErrorMarker.startsWith(':')) {
|
|
@@ -677,8 +895,13 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
677
895
|
const layoutNodes = chain.filter(n => n.layout);
|
|
678
896
|
const hydrators = router.__hydrators;
|
|
679
897
|
if (!pageNode.page && !(hydrators && pageNode._pageName && hydrators[pageNode._pageName])) {
|
|
680
|
-
// The route's component chunk failed to load or compile.
|
|
681
|
-
//
|
|
898
|
+
// The route's component chunk failed to load or compile. While
|
|
899
|
+
// offline that is a connectivity problem — show the offline
|
|
900
|
+
// experience; otherwise render the error boundary for this route.
|
|
901
|
+
if (await looksOffline()) {
|
|
902
|
+
await renderOfflinePage(router, match, container);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
682
905
|
const chunkErr = chunkErrorForNode(pageNode);
|
|
683
906
|
await renderErrorPage(router, match, container, chunkErr || new Error(`Component "${pageNode._pageName}" is unavailable`));
|
|
684
907
|
return;
|
|
@@ -698,7 +921,10 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
698
921
|
if (!strategy || strategy === 'full') {
|
|
699
922
|
const walker = createHydrateWalker(container);
|
|
700
923
|
setIsHydrating(true);
|
|
701
|
-
await runInBlockWindow(() =>
|
|
924
|
+
await runInBlockWindow(() => {
|
|
925
|
+
const result = hydPage(pageProps, new Map(), walker);
|
|
926
|
+
storePageInstance(router, pageNode, pageProps, result);
|
|
927
|
+
});
|
|
702
928
|
setIsHydrating(false);
|
|
703
929
|
}
|
|
704
930
|
else if (strategy === 'viewport') {
|
|
@@ -723,7 +949,10 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
723
949
|
if (index >= layoutNodes.length) {
|
|
724
950
|
return (subWalker) => {
|
|
725
951
|
if (!strategy || strategy === 'full') {
|
|
726
|
-
|
|
952
|
+
const pageProps = { params: paramValues, ...pageNode.props };
|
|
953
|
+
const result = hydPage(pageProps, new Map(), subWalker);
|
|
954
|
+
storePageInstance(router, pageNode, pageProps, result);
|
|
955
|
+
return result;
|
|
727
956
|
}
|
|
728
957
|
else if (strategy === 'viewport') {
|
|
729
958
|
hydrateViewport(subWalker.root, hydPage, { params: paramValues, ...pageNode.props });
|
|
@@ -741,7 +970,9 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
741
970
|
const hydLayout = hydLayouts[index];
|
|
742
971
|
const childHydrator = renderLayoutChain(index + 1);
|
|
743
972
|
const layoutProps = { children: childHydrator, params: paramValues };
|
|
744
|
-
|
|
973
|
+
const result = hydLayout(layoutProps, new Map(), walker);
|
|
974
|
+
storeLayoutInstance(router, node, layoutProps, result);
|
|
975
|
+
return result;
|
|
745
976
|
}
|
|
746
977
|
const walker = createHydrateWalker(container);
|
|
747
978
|
await runInBlockWindow(() => renderLayoutChain(0));
|
|
@@ -764,6 +995,20 @@ async function hydrateInitial(router, match, container, strategy) {
|
|
|
764
995
|
}
|
|
765
996
|
}
|
|
766
997
|
}
|
|
998
|
+
function scrollToHash() {
|
|
999
|
+
const h = typeof window !== 'undefined' ? window.location.hash : '';
|
|
1000
|
+
if (!h || h === '#')
|
|
1001
|
+
return;
|
|
1002
|
+
const id = h.slice(1);
|
|
1003
|
+
setTimeout(() => {
|
|
1004
|
+
try {
|
|
1005
|
+
const el = document.getElementById(id);
|
|
1006
|
+
if (el && typeof el.scrollIntoView === 'function')
|
|
1007
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1008
|
+
}
|
|
1009
|
+
catch { /* element may not exist */ }
|
|
1010
|
+
}, 80);
|
|
1011
|
+
}
|
|
767
1012
|
export function createRouter(routes, options = {}) {
|
|
768
1013
|
const container = options.container || document.getElementById('root');
|
|
769
1014
|
const prefetch = options.prefetch !== false;
|
|
@@ -777,6 +1022,51 @@ export function createRouter(routes, options = {}) {
|
|
|
777
1022
|
_currentSegments: null,
|
|
778
1023
|
_depth: 0,
|
|
779
1024
|
_routeDataCache: options.routeDataCache ?? 0,
|
|
1025
|
+
_offlineUI: (options.offline ?? null),
|
|
1026
|
+
_beforeGuards: [],
|
|
1027
|
+
_viewTransitions: options.viewTransitions === true,
|
|
1028
|
+
beforeEach(fn) {
|
|
1029
|
+
this._beforeGuards.push(fn);
|
|
1030
|
+
let active = true;
|
|
1031
|
+
return () => {
|
|
1032
|
+
if (!active)
|
|
1033
|
+
return;
|
|
1034
|
+
active = false;
|
|
1035
|
+
const guards = this._beforeGuards;
|
|
1036
|
+
const i = guards.indexOf(fn);
|
|
1037
|
+
if (i > -1)
|
|
1038
|
+
guards.splice(i, 1);
|
|
1039
|
+
};
|
|
1040
|
+
},
|
|
1041
|
+
_runGuards(to) {
|
|
1042
|
+
const from = window.location.pathname;
|
|
1043
|
+
for (const g of this._beforeGuards ?? []) {
|
|
1044
|
+
const d = g(to, from);
|
|
1045
|
+
if (d instanceof Promise) {
|
|
1046
|
+
return d.then((r) => {
|
|
1047
|
+
if (r === false || typeof r === 'string')
|
|
1048
|
+
return r;
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (d === false || typeof d === 'string')
|
|
1052
|
+
return d;
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
go(n) {
|
|
1056
|
+
window.history.go(n);
|
|
1057
|
+
},
|
|
1058
|
+
get route() {
|
|
1059
|
+
const m = this._currentMatch;
|
|
1060
|
+
if (!m)
|
|
1061
|
+
return null;
|
|
1062
|
+
const chain = m.matchChain ?? [];
|
|
1063
|
+
const deepest = chain[chain.length - 1];
|
|
1064
|
+
return {
|
|
1065
|
+
pathname: m.pathname || '',
|
|
1066
|
+
params: m.params,
|
|
1067
|
+
pattern: deepest?.fullPath || '/',
|
|
1068
|
+
};
|
|
1069
|
+
},
|
|
780
1070
|
start() {
|
|
781
1071
|
setCurrentRouter(this);
|
|
782
1072
|
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
@@ -795,24 +1085,52 @@ export function createRouter(routes, options = {}) {
|
|
|
795
1085
|
}, 100);
|
|
796
1086
|
}, { passive: true });
|
|
797
1087
|
}
|
|
1088
|
+
// Navigation policy: SPA navigation happens through <Link>/
|
|
1089
|
+
// <NavLink>/navigate() and opt-in plain anchors
|
|
1090
|
+
// `<a href="..." no-reload>`. Plain <a href> without `no-reload`
|
|
1091
|
+
// (or `data-no-reload`) always does native browser navigation.
|
|
1092
|
+
// `no-reload` anchors are intercepted via a delegated document
|
|
1093
|
+
// click listener that respects modifier keys, target="_blank",
|
|
1094
|
+
// download, external origins, hash/mailto/tel schemes.
|
|
1095
|
+
window.addEventListener('popstate', () => {
|
|
1096
|
+
setIsPopStateNavigation(true);
|
|
1097
|
+
this.navigate(window.location.href, { replace: true });
|
|
1098
|
+
});
|
|
798
1099
|
document.addEventListener('click', (e) => {
|
|
799
|
-
const
|
|
800
|
-
if (!
|
|
1100
|
+
const target = e.target;
|
|
1101
|
+
if (!target || target.nodeType !== 1)
|
|
1102
|
+
return;
|
|
1103
|
+
const anchor = target.closest('a[href]');
|
|
1104
|
+
if (!anchor)
|
|
1105
|
+
return;
|
|
1106
|
+
if (!anchor.hasAttribute('no-reload') && !anchor.hasAttribute('data-no-reload'))
|
|
1107
|
+
return;
|
|
1108
|
+
if (e.defaultPrevented)
|
|
1109
|
+
return;
|
|
1110
|
+
const me = e;
|
|
1111
|
+
if (me.metaKey || me.ctrlKey || me.shiftKey || me.altKey || me.button !== 0)
|
|
801
1112
|
return;
|
|
802
|
-
if (
|
|
1113
|
+
if (anchor.target === '_blank')
|
|
803
1114
|
return;
|
|
804
|
-
|
|
805
|
-
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:'))
|
|
1115
|
+
if (anchor.hasAttribute('download'))
|
|
806
1116
|
return;
|
|
807
|
-
if (
|
|
1117
|
+
if (anchor.getAttribute('rel') === 'external')
|
|
808
1118
|
return;
|
|
1119
|
+
const href = anchor.getAttribute('href');
|
|
1120
|
+
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:'))
|
|
1121
|
+
return;
|
|
1122
|
+
try {
|
|
1123
|
+
const url = new URL(href, window.location.origin);
|
|
1124
|
+
if (url.origin !== window.location.origin)
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
catch {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
809
1130
|
e.preventDefault();
|
|
1131
|
+
e.stopPropagation();
|
|
810
1132
|
this.navigate(href);
|
|
811
1133
|
});
|
|
812
|
-
window.addEventListener('popstate', () => {
|
|
813
|
-
setIsPopStateNavigation(true);
|
|
814
|
-
this.navigate(window.location.href, { replace: true });
|
|
815
|
-
});
|
|
816
1134
|
if (prefetch) {
|
|
817
1135
|
document.addEventListener('mouseenter', (e) => {
|
|
818
1136
|
const link = e.target?.nodeType === 1 ? e.target.closest('a[href]') : null;
|
|
@@ -846,74 +1164,133 @@ export function createRouter(routes, options = {}) {
|
|
|
846
1164
|
return;
|
|
847
1165
|
}
|
|
848
1166
|
match.pathname = url.pathname;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
let firstRenderFailed = false;
|
|
856
|
-
const updateUrl = () => {
|
|
857
|
-
if (!opts.replace) {
|
|
858
|
-
window.history.pushState({ path: url.pathname }, '', url.pathname);
|
|
1167
|
+
// Navigation guards (programmatic/Link navigations only).
|
|
1168
|
+
const self = this;
|
|
1169
|
+
const guardResult = self._runGuards(url.pathname);
|
|
1170
|
+
const proceedNav = () => {
|
|
1171
|
+
if (!_isPopStateNavigation) {
|
|
1172
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
859
1173
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1174
|
+
const loadingFn = findLoadingComponent(match.matchChain);
|
|
1175
|
+
this._navToken = (this._navToken || 0) + 1;
|
|
1176
|
+
const navToken = this._navToken;
|
|
1177
|
+
loadingStart();
|
|
1178
|
+
let firstRenderFailed = false;
|
|
1179
|
+
const updateUrl = () => {
|
|
1180
|
+
if (!opts.replace) {
|
|
1181
|
+
window.history.pushState({ path: url.pathname }, '', url.pathname + url.search);
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
window.history.replaceState({ path: url.pathname }, '', url.pathname + url.search);
|
|
1185
|
+
}
|
|
1186
|
+
set(_state.path, url.pathname);
|
|
1187
|
+
set(_state.search, url.search.startsWith('?') ? url.search.slice(1) : url.search);
|
|
1188
|
+
};
|
|
1189
|
+
const renderContent = () => {
|
|
1190
|
+
try {
|
|
1191
|
+
renderMatch(this, match, this.container);
|
|
1192
|
+
}
|
|
1193
|
+
catch (e) {
|
|
1194
|
+
firstRenderFailed = true;
|
|
1195
|
+
throw e;
|
|
1196
|
+
}
|
|
1197
|
+
this._currentMatch = match;
|
|
1198
|
+
clearOfflineFlag(this, container);
|
|
1199
|
+
handleScroll(url.pathname, opts.replace);
|
|
1200
|
+
};
|
|
1201
|
+
const swapView = (fn) => {
|
|
1202
|
+
const d = document;
|
|
1203
|
+
if (this._viewTransitions && typeof d.startViewTransition === 'function')
|
|
1204
|
+
d.startViewTransition(fn);
|
|
1205
|
+
else
|
|
1206
|
+
fn();
|
|
1207
|
+
};
|
|
1208
|
+
const doRender = () => {
|
|
1209
|
+
updateUrl();
|
|
1210
|
+
swapView(() => renderContent());
|
|
1211
|
+
scrollToHash();
|
|
1212
|
+
};
|
|
1213
|
+
const fetchData = () => {
|
|
1214
|
+
if (firstRenderFailed)
|
|
1215
|
+
return;
|
|
1216
|
+
if (!shouldFetchData(this, match)) {
|
|
1217
|
+
loadingFinish();
|
|
1218
|
+
scrollToHash();
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
getRouteData(url.pathname + url.search, 'nav' + navToken).then(async (data) => {
|
|
1222
|
+
if (navToken !== this._navToken)
|
|
1223
|
+
return;
|
|
1224
|
+
if (!data) {
|
|
1225
|
+
loadingFinish();
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (data.redirect) {
|
|
1229
|
+
loadingFinish();
|
|
1230
|
+
this.navigate(data.redirect, { replace: true });
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (!hasRealPageData(data)) {
|
|
1234
|
+
loadingFinish();
|
|
1235
|
+
scrollToHash();
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
await swapView(() => applyRouteData(this, match, data, this.container));
|
|
1239
|
+
scrollToHash();
|
|
1240
|
+
loadingFinish({ error: !!data.error });
|
|
1241
|
+
}).catch(() => loadingFinish({ error: true }));
|
|
1242
|
+
};
|
|
1243
|
+
if (loadingFn) {
|
|
1244
|
+
showLoadingInContainer(this.container, loadingFn, match.params);
|
|
1245
|
+
Promise.resolve().then(() => { try {
|
|
1246
|
+
doRender();
|
|
1247
|
+
}
|
|
1248
|
+
finally {
|
|
1249
|
+
fetchData();
|
|
1250
|
+
} });
|
|
869
1251
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1252
|
+
else {
|
|
1253
|
+
try {
|
|
1254
|
+
doRender();
|
|
1255
|
+
}
|
|
1256
|
+
finally {
|
|
1257
|
+
fetchData();
|
|
1258
|
+
}
|
|
873
1259
|
}
|
|
874
|
-
this._currentMatch = match;
|
|
875
|
-
handleScroll(url.pathname, opts.replace);
|
|
876
|
-
};
|
|
877
|
-
const doRender = () => {
|
|
878
|
-
updateUrl();
|
|
879
|
-
renderContent();
|
|
880
1260
|
};
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (navToken !== this._navToken)
|
|
888
|
-
return;
|
|
889
|
-
if (!data)
|
|
1261
|
+
// If guards return a Promise, chain the rest asynchronously.
|
|
1262
|
+
// If sync (the common case), the navigation completes synchronously,
|
|
1263
|
+
// preserving backward compatibility with callers that don't await.
|
|
1264
|
+
if (guardResult instanceof Promise) {
|
|
1265
|
+
return guardResult.then((decision) => {
|
|
1266
|
+
if (decision === false)
|
|
890
1267
|
return;
|
|
891
|
-
if (
|
|
892
|
-
|
|
1268
|
+
if (typeof decision === 'string' && decision !== url.pathname && (self._guardDepth ?? 0) < 5) {
|
|
1269
|
+
self._guardDepth = (self._guardDepth ?? 0) + 1;
|
|
1270
|
+
try {
|
|
1271
|
+
self.navigate(decision, { replace: true });
|
|
1272
|
+
}
|
|
1273
|
+
finally {
|
|
1274
|
+
self._guardDepth = 0;
|
|
1275
|
+
}
|
|
893
1276
|
return;
|
|
894
1277
|
}
|
|
895
|
-
|
|
896
|
-
return;
|
|
897
|
-
await applyRouteData(this, match, data, this.container);
|
|
1278
|
+
proceedNav();
|
|
898
1279
|
});
|
|
899
|
-
};
|
|
900
|
-
if (loadingFn) {
|
|
901
|
-
showLoadingInContainer(this.container, loadingFn, match.params);
|
|
902
|
-
Promise.resolve().then(() => { try {
|
|
903
|
-
doRender();
|
|
904
|
-
}
|
|
905
|
-
finally {
|
|
906
|
-
fetchData();
|
|
907
|
-
} });
|
|
908
1280
|
}
|
|
909
|
-
|
|
1281
|
+
if (guardResult === false)
|
|
1282
|
+
return;
|
|
1283
|
+
if (typeof guardResult === 'string' && guardResult !== url.pathname && (self._guardDepth ?? 0) < 5) {
|
|
1284
|
+
self._guardDepth = (self._guardDepth ?? 0) + 1;
|
|
910
1285
|
try {
|
|
911
|
-
|
|
1286
|
+
self.navigate(guardResult, { replace: true });
|
|
912
1287
|
}
|
|
913
1288
|
finally {
|
|
914
|
-
|
|
1289
|
+
self._guardDepth = 0;
|
|
915
1290
|
}
|
|
1291
|
+
return;
|
|
916
1292
|
}
|
|
1293
|
+
proceedNav();
|
|
917
1294
|
},
|
|
918
1295
|
prefetch(path) {
|
|
919
1296
|
const url = new URL(path, window.location.origin);
|
|
@@ -933,6 +1310,10 @@ export function createRouter(routes, options = {}) {
|
|
|
933
1310
|
get currentPath() {
|
|
934
1311
|
return get(_state.path);
|
|
935
1312
|
},
|
|
1313
|
+
get isLoading() {
|
|
1314
|
+
const cells = getLoadingState();
|
|
1315
|
+
return Boolean(get(cells.isLoading));
|
|
1316
|
+
},
|
|
936
1317
|
hmrUpdate() {
|
|
937
1318
|
const updated = globalThis.__updatedComponents;
|
|
938
1319
|
if (!updated || updated.size === 0)
|
|
@@ -996,6 +1377,51 @@ export function createFileRouter(routeTree, options = {}) {
|
|
|
996
1377
|
_currentSegments: null,
|
|
997
1378
|
_depth: 0,
|
|
998
1379
|
_routeDataCache: options.routeDataCache ?? 0,
|
|
1380
|
+
_offlineUI: (options.offline ?? null),
|
|
1381
|
+
_beforeGuards: [],
|
|
1382
|
+
_viewTransitions: options.viewTransitions === true,
|
|
1383
|
+
beforeEach(fn) {
|
|
1384
|
+
this._beforeGuards.push(fn);
|
|
1385
|
+
let active = true;
|
|
1386
|
+
return () => {
|
|
1387
|
+
if (!active)
|
|
1388
|
+
return;
|
|
1389
|
+
active = false;
|
|
1390
|
+
const guards = this._beforeGuards;
|
|
1391
|
+
const i = guards.indexOf(fn);
|
|
1392
|
+
if (i > -1)
|
|
1393
|
+
guards.splice(i, 1);
|
|
1394
|
+
};
|
|
1395
|
+
},
|
|
1396
|
+
_runGuards(to) {
|
|
1397
|
+
const from = window.location.pathname;
|
|
1398
|
+
for (const g of this._beforeGuards ?? []) {
|
|
1399
|
+
const d = g(to, from);
|
|
1400
|
+
if (d instanceof Promise) {
|
|
1401
|
+
return d.then((r) => {
|
|
1402
|
+
if (r === false || typeof r === 'string')
|
|
1403
|
+
return r;
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
if (d === false || typeof d === 'string')
|
|
1407
|
+
return d;
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
go(n) {
|
|
1411
|
+
window.history.go(n);
|
|
1412
|
+
},
|
|
1413
|
+
get route() {
|
|
1414
|
+
const m = router._currentMatch;
|
|
1415
|
+
if (!m)
|
|
1416
|
+
return null;
|
|
1417
|
+
const chain = m.matchChain ?? [];
|
|
1418
|
+
const deepest = chain[chain.length - 1];
|
|
1419
|
+
return {
|
|
1420
|
+
pathname: m.pathname || '',
|
|
1421
|
+
params: m.params,
|
|
1422
|
+
pattern: deepest?.fullPath || '/',
|
|
1423
|
+
};
|
|
1424
|
+
},
|
|
999
1425
|
start() {
|
|
1000
1426
|
setCurrentRouter(this);
|
|
1001
1427
|
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
@@ -1019,6 +1445,41 @@ export function createFileRouter(routeTree, options = {}) {
|
|
|
1019
1445
|
setIsPopStateNavigation(true);
|
|
1020
1446
|
router.navigate(window.location.pathname + window.location.search, { replace: true });
|
|
1021
1447
|
});
|
|
1448
|
+
document.addEventListener('click', (e) => {
|
|
1449
|
+
const target = e.target;
|
|
1450
|
+
if (!target || target.nodeType !== 1)
|
|
1451
|
+
return;
|
|
1452
|
+
const anchor = target.closest('a[href]');
|
|
1453
|
+
if (!anchor)
|
|
1454
|
+
return;
|
|
1455
|
+
if (!anchor.hasAttribute('no-reload') && !anchor.hasAttribute('data-no-reload'))
|
|
1456
|
+
return;
|
|
1457
|
+
if (e.defaultPrevented)
|
|
1458
|
+
return;
|
|
1459
|
+
const me = e;
|
|
1460
|
+
if (me.metaKey || me.ctrlKey || me.shiftKey || me.altKey || me.button !== 0)
|
|
1461
|
+
return;
|
|
1462
|
+
if (anchor.target === '_blank')
|
|
1463
|
+
return;
|
|
1464
|
+
if (anchor.hasAttribute('download'))
|
|
1465
|
+
return;
|
|
1466
|
+
if (anchor.getAttribute('rel') === 'external')
|
|
1467
|
+
return;
|
|
1468
|
+
const href = anchor.getAttribute('href');
|
|
1469
|
+
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:'))
|
|
1470
|
+
return;
|
|
1471
|
+
try {
|
|
1472
|
+
const url = new URL(href, window.location.origin);
|
|
1473
|
+
if (url.origin !== window.location.origin)
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
catch {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
e.preventDefault();
|
|
1480
|
+
e.stopPropagation();
|
|
1481
|
+
router.navigate(href);
|
|
1482
|
+
});
|
|
1022
1483
|
if (options.prefetch !== false) {
|
|
1023
1484
|
document.addEventListener('mouseenter', (e) => {
|
|
1024
1485
|
const link = e.target?.nodeType === 1 ? e.target.closest('a[href]') : null;
|
|
@@ -1074,148 +1535,225 @@ export function createFileRouter(routeTree, options = {}) {
|
|
|
1074
1535
|
return;
|
|
1075
1536
|
}
|
|
1076
1537
|
match.pathname = url.pathname;
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const loadingFn = findLoadingComponent(match.matchChain);
|
|
1081
|
-
router._navToken = (router._navToken || 0) + 1;
|
|
1082
|
-
const navToken = router._navToken;
|
|
1083
|
-
const middlewareFns = Array.isArray(middleware) ? middleware : (middleware ? [middleware] : []);
|
|
1084
|
-
let firstRenderFailed = false;
|
|
1085
|
-
const updateUrl = () => {
|
|
1086
|
-
const fullUrl = url.pathname + url.search;
|
|
1087
|
-
if (!opts.replace) {
|
|
1088
|
-
window.history.pushState({ path: fullUrl }, '', fullUrl);
|
|
1089
|
-
}
|
|
1090
|
-
else {
|
|
1091
|
-
window.history.replaceState({ path: fullUrl }, '', fullUrl);
|
|
1092
|
-
}
|
|
1093
|
-
set(_state.path, url.pathname);
|
|
1094
|
-
set(_state.search, url.search);
|
|
1095
|
-
};
|
|
1096
|
-
const renderContent = () => {
|
|
1097
|
-
// Data isolation: route nodes are shared singletons. When the
|
|
1098
|
-
// navigation changes the path a node was last hydrated with, its
|
|
1099
|
-
// cached props must not render on the new path (a params-only or
|
|
1100
|
-
// failed data payload would otherwise merge stale foreign data
|
|
1101
|
-
// into the new page).
|
|
1102
|
-
const propsNode = findPageNode(match);
|
|
1103
|
-
if (propsNode && propsNode._dataPath !== url.pathname) {
|
|
1104
|
-
propsNode.props = undefined;
|
|
1105
|
-
}
|
|
1106
|
-
try {
|
|
1107
|
-
renderFn(router, match, container);
|
|
1108
|
-
}
|
|
1109
|
-
catch (e) {
|
|
1110
|
-
firstRenderFailed = true;
|
|
1111
|
-
throw e;
|
|
1538
|
+
const afterGuards = () => {
|
|
1539
|
+
if (!_isPopStateNavigation) {
|
|
1540
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
1112
1541
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1542
|
+
const swapView = (fn) => {
|
|
1543
|
+
const d = document;
|
|
1544
|
+
if (router._viewTransitions && typeof d.startViewTransition === 'function')
|
|
1545
|
+
d.startViewTransition(fn);
|
|
1546
|
+
else
|
|
1547
|
+
fn();
|
|
1548
|
+
};
|
|
1549
|
+
const loadingFn = findLoadingComponent(match.matchChain);
|
|
1550
|
+
router._navToken = (router._navToken || 0) + 1;
|
|
1551
|
+
navDebug('navigate', url.pathname, 'token=' + router._navToken, 'pendingChunks', hasPendingChunks(match.matchChain));
|
|
1552
|
+
const navToken = router._navToken;
|
|
1553
|
+
loadingStart();
|
|
1554
|
+
const middlewareFns = Array.isArray(middleware) ? middleware : (middleware ? [middleware] : []);
|
|
1555
|
+
let firstRenderFailed = false;
|
|
1556
|
+
const updateUrl = () => {
|
|
1557
|
+
const fullUrl = url.pathname + url.search;
|
|
1558
|
+
if (!opts.replace) {
|
|
1559
|
+
window.history.pushState({ path: fullUrl }, '', fullUrl);
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
window.history.replaceState({ path: fullUrl }, '', fullUrl);
|
|
1563
|
+
}
|
|
1564
|
+
set(_state.path, url.pathname);
|
|
1565
|
+
set(_state.search, url.search.startsWith('?') ? url.search.slice(1) : url.search);
|
|
1566
|
+
};
|
|
1567
|
+
const renderContent = () => {
|
|
1568
|
+
const propsNode = findPageNode(match);
|
|
1569
|
+
if (propsNode && propsNode._dataPath !== url.pathname) {
|
|
1570
|
+
propsNode.props = undefined;
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
container.__veskMountToken = navToken;
|
|
1574
|
+
renderFn(router, match, container, navToken);
|
|
1575
|
+
}
|
|
1576
|
+
catch (e) {
|
|
1577
|
+
firstRenderFailed = true;
|
|
1578
|
+
throw e;
|
|
1579
|
+
}
|
|
1580
|
+
router._currentMatch = match;
|
|
1581
|
+
clearOfflineFlag(router, container);
|
|
1582
|
+
handleScroll(url.pathname, opts.replace);
|
|
1583
|
+
};
|
|
1584
|
+
const doRender = () => {
|
|
1585
|
+
navDebug('doRender', url.pathname, 'token=' + navToken);
|
|
1586
|
+
updateUrl();
|
|
1587
|
+
swapView(() => renderContent());
|
|
1588
|
+
scrollToHash();
|
|
1589
|
+
navDebug('painted', url.pathname);
|
|
1590
|
+
};
|
|
1591
|
+
const fetchData = () => {
|
|
1592
|
+
if (firstRenderFailed)
|
|
1129
1593
|
return;
|
|
1130
|
-
|
|
1131
|
-
|
|
1594
|
+
navDebug('fetchData?', url.pathname, String(shouldFetchData(router, match)));
|
|
1595
|
+
if (!shouldFetchData(router, match)) {
|
|
1596
|
+
loadingFinish();
|
|
1597
|
+
scrollToHash();
|
|
1132
1598
|
return;
|
|
1133
1599
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const pending = hasPendingChunks(match.matchChain);
|
|
1137
|
-
if (pending.length > 0) {
|
|
1138
|
-
await loadChunksQuietly(pending);
|
|
1600
|
+
getRouteData(url.pathname + url.search, 'nav' + navToken).then(async (data) => {
|
|
1601
|
+
navDebug('data arrived', url.pathname, 'token=' + navToken, 'current=' + router._navToken, 'realProps', JSON.stringify(data && hasRealPageData(data)));
|
|
1139
1602
|
if (navToken !== router._navToken)
|
|
1140
1603
|
return;
|
|
1141
|
-
if (
|
|
1142
|
-
|
|
1604
|
+
if (!data) {
|
|
1605
|
+
loadingFinish();
|
|
1606
|
+
return;
|
|
1143
1607
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
};
|
|
1148
|
-
const pendingChunks = hasPendingChunks(match.matchChain);
|
|
1149
|
-
const doRenderWithChunks = pendingChunks.length > 0
|
|
1150
|
-
? () => {
|
|
1151
|
-
updateUrl();
|
|
1152
|
-
return loadChunksQuietly(pendingChunks).then(() => {
|
|
1153
|
-
// A newer navigation (e.g. popstate back while the chunk
|
|
1154
|
-
// was still loading) may have superseded this one; don't
|
|
1155
|
-
// paint the stale route over the current URL.
|
|
1156
|
-
if (navToken !== router._navToken)
|
|
1608
|
+
if (data.redirect) {
|
|
1609
|
+
loadingFinish();
|
|
1610
|
+
router.navigate(data.redirect, { replace: true });
|
|
1157
1611
|
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (!hasRealPageData(data)) {
|
|
1614
|
+
loadingFinish();
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
const pending = hasPendingChunks(match.matchChain);
|
|
1618
|
+
if (pending.length > 0) {
|
|
1619
|
+
await loadChunksQuietly(pending);
|
|
1620
|
+
if (navToken !== router._navToken)
|
|
1621
|
+
return;
|
|
1622
|
+
if (typeof router.__updateComponents === 'function') {
|
|
1623
|
+
router.__updateComponents(match.matchChain);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
await swapView(() => applyRouteData(router, match, data, container, renderFn));
|
|
1627
|
+
scrollToHash();
|
|
1628
|
+
loadingFinish({ error: !!data.error });
|
|
1629
|
+
}).catch(() => loadingFinish({ error: true }));
|
|
1630
|
+
};
|
|
1631
|
+
const pendingChunks = hasPendingChunks(match.matchChain);
|
|
1632
|
+
const handleNavFailure = (err) => {
|
|
1633
|
+
navDebug('handleNavFailure', String(err?.message || err).slice(0, 80));
|
|
1634
|
+
if (navToken !== router._navToken)
|
|
1635
|
+
return;
|
|
1636
|
+
loadingFinish({ error: true });
|
|
1637
|
+
looksOffline().then((offline) => {
|
|
1638
|
+
navDebug('handleNavFailure verdict', offline ? 'offline' : 'online');
|
|
1639
|
+
if (offline)
|
|
1640
|
+
return void renderOfflinePage(router, match, container);
|
|
1641
|
+
return void renderErrorPage(router, match, container, err);
|
|
1642
|
+
}).catch(() => { });
|
|
1643
|
+
};
|
|
1644
|
+
const doRenderWithChunks = pendingChunks.length > 0
|
|
1645
|
+
? () => {
|
|
1646
|
+
updateUrl();
|
|
1647
|
+
return loadChunksQuietly(pendingChunks).then(() => {
|
|
1648
|
+
if (navToken !== router._navToken)
|
|
1649
|
+
return;
|
|
1650
|
+
if (typeof router.__updateComponents === 'function') {
|
|
1651
|
+
router.__updateComponents(match.matchChain);
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
renderContent();
|
|
1655
|
+
loadingFinish();
|
|
1656
|
+
}
|
|
1657
|
+
catch (e) {
|
|
1658
|
+
handleNavFailure(e);
|
|
1659
|
+
}
|
|
1660
|
+
}, (e) => {
|
|
1661
|
+
handleNavFailure(e);
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
: (() => {
|
|
1158
1665
|
if (typeof router.__updateComponents === 'function') {
|
|
1159
1666
|
router.__updateComponents(match.matchChain);
|
|
1160
1667
|
}
|
|
1161
|
-
|
|
1668
|
+
doRender();
|
|
1162
1669
|
});
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (index >= middlewareFns.length) {
|
|
1167
|
-
await doRenderWithChunks();
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
const fn = middlewareFns[index];
|
|
1171
|
-
const ctx = { url: url.pathname, params: match.params, router, locals: {} };
|
|
1172
|
-
async function next(rewrite) {
|
|
1173
|
-
if (rewrite) {
|
|
1174
|
-
match.pathname = rewrite;
|
|
1175
|
-
url.pathname = rewrite;
|
|
1176
|
-
}
|
|
1177
|
-
return runMwChain(index + 1);
|
|
1178
|
-
}
|
|
1179
|
-
try {
|
|
1180
|
-
const result = await fn(ctx, next);
|
|
1181
|
-
if (result && result.redirect) {
|
|
1182
|
-
router.navigate(result.redirect, { replace: true });
|
|
1670
|
+
async function runMwChain(index) {
|
|
1671
|
+
if (index >= middlewareFns.length) {
|
|
1672
|
+
await doRenderWithChunks();
|
|
1183
1673
|
return;
|
|
1184
1674
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1675
|
+
const fn = middlewareFns[index];
|
|
1676
|
+
const ctx = { url: url.pathname, params: match.params, router, locals: {} };
|
|
1677
|
+
async function next(rewrite) {
|
|
1678
|
+
if (rewrite) {
|
|
1679
|
+
match.pathname = rewrite;
|
|
1680
|
+
url.pathname = rewrite;
|
|
1681
|
+
}
|
|
1682
|
+
return runMwChain(index + 1);
|
|
1190
1683
|
}
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
if (middlewareFns.length > 0 || loadingFn) {
|
|
1194
|
-
if (loadingFn) {
|
|
1195
|
-
showLoadingInContainer(container, loadingFn, match.params);
|
|
1196
|
-
}
|
|
1197
|
-
Promise.resolve().then(() => {
|
|
1198
1684
|
try {
|
|
1199
|
-
|
|
1200
|
-
|
|
1685
|
+
const result = await fn(ctx, next);
|
|
1686
|
+
if (result && result.redirect) {
|
|
1687
|
+
router.navigate(result.redirect, { replace: true });
|
|
1688
|
+
return;
|
|
1201
1689
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1690
|
+
}
|
|
1691
|
+
catch (e) {
|
|
1692
|
+
if (e && e.name === 'Redirect') {
|
|
1693
|
+
router.navigate(e.url, { replace: true });
|
|
1694
|
+
return;
|
|
1204
1695
|
}
|
|
1205
1696
|
}
|
|
1697
|
+
}
|
|
1698
|
+
if (middlewareFns.length > 0 || loadingFn) {
|
|
1699
|
+
if (loadingFn) {
|
|
1700
|
+
showLoadingInContainer(container, loadingFn, match.params);
|
|
1701
|
+
}
|
|
1702
|
+
Promise.resolve().then(() => {
|
|
1703
|
+
try {
|
|
1704
|
+
if (middlewareFns.length > 0) {
|
|
1705
|
+
runMwChain(0);
|
|
1706
|
+
}
|
|
1707
|
+
else {
|
|
1708
|
+
doRenderWithChunks();
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
finally {
|
|
1712
|
+
fetchData();
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
try {
|
|
1718
|
+
doRenderWithChunks();
|
|
1719
|
+
}
|
|
1206
1720
|
finally {
|
|
1207
1721
|
fetchData();
|
|
1208
1722
|
}
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
// Navigation guards — sync fast path for sync guard functions.
|
|
1726
|
+
const guardResult = router._runGuards(url.pathname);
|
|
1727
|
+
if (guardResult instanceof Promise) {
|
|
1728
|
+
return guardResult.then((decision) => {
|
|
1729
|
+
if (decision === false)
|
|
1730
|
+
return;
|
|
1731
|
+
if (typeof decision === 'string' && decision !== url.pathname && (router._guardDepth || 0) < 5) {
|
|
1732
|
+
router._guardDepth = (router._guardDepth || 0) + 1;
|
|
1733
|
+
try {
|
|
1734
|
+
router.navigate(decision, { replace: true });
|
|
1735
|
+
}
|
|
1736
|
+
finally {
|
|
1737
|
+
router._guardDepth = 0;
|
|
1738
|
+
}
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
afterGuards();
|
|
1209
1742
|
});
|
|
1210
1743
|
}
|
|
1211
|
-
|
|
1744
|
+
if (guardResult === false)
|
|
1745
|
+
return;
|
|
1746
|
+
if (typeof guardResult === 'string' && guardResult !== url.pathname && (router._guardDepth || 0) < 5) {
|
|
1747
|
+
router._guardDepth = (router._guardDepth || 0) + 1;
|
|
1212
1748
|
try {
|
|
1213
|
-
|
|
1749
|
+
router.navigate(guardResult, { replace: true });
|
|
1214
1750
|
}
|
|
1215
1751
|
finally {
|
|
1216
|
-
|
|
1752
|
+
router._guardDepth = 0;
|
|
1217
1753
|
}
|
|
1754
|
+
return;
|
|
1218
1755
|
}
|
|
1756
|
+
afterGuards();
|
|
1219
1757
|
},
|
|
1220
1758
|
prefetch(path) {
|
|
1221
1759
|
const url = new URL(path, window.location.origin);
|
|
@@ -1237,6 +1775,10 @@ export function createFileRouter(routeTree, options = {}) {
|
|
|
1237
1775
|
get currentPath() {
|
|
1238
1776
|
return get(_state.path);
|
|
1239
1777
|
},
|
|
1778
|
+
get isLoading() {
|
|
1779
|
+
const cells = getLoadingState();
|
|
1780
|
+
return Boolean(get(cells.isLoading));
|
|
1781
|
+
},
|
|
1240
1782
|
hmrUpdate() {
|
|
1241
1783
|
const updated = globalThis.__updatedComponents;
|
|
1242
1784
|
if (!updated || updated.size === 0)
|
|
@@ -1245,43 +1787,15 @@ export function createFileRouter(routeTree, options = {}) {
|
|
|
1245
1787
|
if (typeof router.__updateComponents === 'function') {
|
|
1246
1788
|
router.__updateComponents(router.routeTree);
|
|
1247
1789
|
}
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const isPage = inst.type === 'page';
|
|
1256
|
-
const newFn = isPage ? inst.node.page : inst.node.layout;
|
|
1257
|
-
if (!newFn)
|
|
1258
|
-
continue;
|
|
1259
|
-
const walker = createHydrateWalker(document.createDocumentFragment(), []);
|
|
1260
|
-
let newDom;
|
|
1261
|
-
root(() => {
|
|
1262
|
-
newDom = newFn(inst.props, new Map(), walker);
|
|
1263
|
-
});
|
|
1264
|
-
if (newDom && newDom.nodeType === 1 && inst.root && inst.root.parentNode) {
|
|
1265
|
-
inst.root.parentNode.replaceChild(newDom, inst.root);
|
|
1266
|
-
inst.root = newDom;
|
|
1267
|
-
didUpdate = true;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
catch (e) {
|
|
1271
|
-
console.error('HMR update error:', e);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
if (!didUpdate) {
|
|
1277
|
-
const path = window.location.pathname + window.location.search;
|
|
1278
|
-
router.navigate(path, { replace: true });
|
|
1790
|
+
const path = window.location.pathname + window.location.search;
|
|
1791
|
+
const match = matchRoute(router.routeTree, path);
|
|
1792
|
+
if (match) {
|
|
1793
|
+
match.pathname = path;
|
|
1794
|
+
const result = renderMatch(router, match, container);
|
|
1795
|
+
if (result && typeof result.then === 'function') {
|
|
1796
|
+
result.catch((e) => console.error('HMR renderMatch error:', e));
|
|
1279
1797
|
}
|
|
1280
1798
|
}
|
|
1281
|
-
else {
|
|
1282
|
-
const path = window.location.pathname + window.location.search;
|
|
1283
|
-
router.navigate(path, { replace: true });
|
|
1284
|
-
}
|
|
1285
1799
|
},
|
|
1286
1800
|
};
|
|
1287
1801
|
return router;
|
|
@@ -1306,6 +1820,8 @@ export function buildRouteTree(definitions) {
|
|
|
1306
1820
|
loading: def.loading || null,
|
|
1307
1821
|
error: def.error || null,
|
|
1308
1822
|
notFound: def.notFound || null,
|
|
1823
|
+
offline: def.offline ?? null,
|
|
1824
|
+
network: def.network ?? null,
|
|
1309
1825
|
children: (def.children || []).map(c => {
|
|
1310
1826
|
const cParts = (c.path || '').split('/').filter(Boolean);
|
|
1311
1827
|
return {
|
|
@@ -1318,11 +1834,11 @@ export function buildRouteTree(definitions) {
|
|
|
1318
1834
|
loading: null,
|
|
1319
1835
|
error: null,
|
|
1320
1836
|
notFound: null,
|
|
1321
|
-
segmentCount:
|
|
1837
|
+
segmentCount: cParts.length || 1,
|
|
1322
1838
|
children: [],
|
|
1323
1839
|
};
|
|
1324
1840
|
}),
|
|
1325
|
-
segmentCount:
|
|
1841
|
+
segmentCount: parts.length,
|
|
1326
1842
|
};
|
|
1327
1843
|
tree.push(node);
|
|
1328
1844
|
}
|