@ilha/router 0.8.5 → 0.8.7
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/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/{src-C2_zEYnV.js → src-Dfq3JPRz.js} +201 -36
- package/dist/ssr.js +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,10 @@ export type Loader<T> = (ctx: LoaderContext) => Promise<T> | T;
|
|
|
65
65
|
/**
|
|
66
66
|
* Identity function for declaring a loader. Exists purely as a type anchor and
|
|
67
67
|
* a marker for the Vite plugin to detect by export name.
|
|
68
|
+
*
|
|
69
|
+
* Loaders must read `ctx.params`/`ctx.url` rather than `useRoute()` — the
|
|
70
|
+
* route store still holds the previous route while a navigation's loader is
|
|
71
|
+
* in flight, so `useRoute().params()` inside a loader reads stale params.
|
|
68
72
|
*/
|
|
69
73
|
export declare function loader<T>(fn: Loader<T>): Loader<T>;
|
|
70
74
|
/** Extract the return type of a loader. */
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as wrapLayout, C as routePath, D as src_default, E as serializeHead, M as setHistoryMode, O as useRoute, S as routeParams, T as router, _ as navigating, a as RouterView, b as redirect, c as composeLoaders, d as error, f as head, g as navigate, h as loader, i as RouterLink, j as getHistoryMode, k as wrapError, l as defineLayout, m as isActive, n as LoaderError, o as afterNavigate, p as invalidate, r as Redirect, s as beforeNavigate, t as LOADER_ENDPOINT, u as enableLinkInterception, v as prefetch, w as routeSearch, x as routeHash, y as prime } from "./src-
|
|
1
|
+
import { A as wrapLayout, C as routePath, D as src_default, E as serializeHead, M as setHistoryMode, O as useRoute, S as routeParams, T as router, _ as navigating, a as RouterView, b as redirect, c as composeLoaders, d as error, f as head, g as navigate, h as loader, i as RouterLink, j as getHistoryMode, k as wrapError, l as defineLayout, m as isActive, n as LoaderError, o as afterNavigate, p as invalidate, r as Redirect, s as beforeNavigate, t as LOADER_ENDPOINT, u as enableLinkInterception, v as prefetch, w as routeSearch, x as routeHash, y as prime } from "./src-Dfq3JPRz.js";
|
|
2
2
|
|
|
3
3
|
export { LOADER_ENDPOINT, LoaderError, Redirect, RouterLink, RouterView, afterNavigate, beforeNavigate, composeLoaders, src_default as default, defineLayout, enableLinkInterception, error, getHistoryMode, head, invalidate, isActive, loader, navigate, navigating, prefetch, prime, redirect, routeHash, routeParams, routePath, routeSearch, router, serializeHead, setHistoryMode, useRoute, wrapError, wrapLayout };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ilha, { ISLAND_MOUNT_INTERNAL, context, html, mount } from "ilha";
|
|
1
|
+
import ilha, { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, context, html, mount } from "ilha";
|
|
2
2
|
import { addRoute, createRouter, findRoute } from "rou3";
|
|
3
3
|
|
|
4
4
|
//#region src/hash.ts
|
|
@@ -136,6 +136,10 @@ const isBrowser = typeof window !== "undefined" && typeof document !== "undefine
|
|
|
136
136
|
/**
|
|
137
137
|
* Identity function for declaring a loader. Exists purely as a type anchor and
|
|
138
138
|
* a marker for the Vite plugin to detect by export name.
|
|
139
|
+
*
|
|
140
|
+
* Loaders must read `ctx.params`/`ctx.url` rather than `useRoute()` — the
|
|
141
|
+
* route store still holds the previous route while a navigation's loader is
|
|
142
|
+
* in flight, so `useRoute().params()` inside a loader reads stale params.
|
|
139
143
|
*/
|
|
140
144
|
function loader(fn) {
|
|
141
145
|
return fn;
|
|
@@ -306,7 +310,6 @@ function injectKPageSlot(layoutHtml, slotInnerHtml, which) {
|
|
|
306
310
|
const target = which === "innermost" ? spans[spans.length - 1] : spans[0];
|
|
307
311
|
return layoutHtml.slice(0, target.openEnd) + slotInnerHtml + layoutHtml.slice(target.closeStart);
|
|
308
312
|
}
|
|
309
|
-
/** Layout shell HTML with an empty `k:page` — avoids scanning MDX/twoslash inside `Wrapped.toString()`. */
|
|
310
313
|
function layoutHtmlWithEmptyKPage(wrappedLayout, props) {
|
|
311
314
|
const handler = wrappedLayout[WRAP_LAYOUT_HANDLER];
|
|
312
315
|
if (!handler) return wrappedLayout.toString(props);
|
|
@@ -393,6 +396,43 @@ function wrapLayout(layout, page) {
|
|
|
393
396
|
};
|
|
394
397
|
}
|
|
395
398
|
wrapLeafPageMountHooks(leafPage);
|
|
399
|
+
const pageHandles = /* @__PURE__ */ new Map();
|
|
400
|
+
const pageInternalBase = page[ISLAND_MOUNT_INTERNAL];
|
|
401
|
+
if (typeof pageInternalBase === "function") page[ISLAND_MOUNT_INTERNAL] = (host, props) => {
|
|
402
|
+
const handle = pageInternalBase(host, props);
|
|
403
|
+
const entry = {
|
|
404
|
+
handle,
|
|
405
|
+
mountProps: props
|
|
406
|
+
};
|
|
407
|
+
pageHandles.set(host, entry);
|
|
408
|
+
return {
|
|
409
|
+
unmount: () => {
|
|
410
|
+
if (pageHandles.get(host) === entry) pageHandles.delete(host);
|
|
411
|
+
return handle.unmount();
|
|
412
|
+
},
|
|
413
|
+
updateProps: (p) => {
|
|
414
|
+
entry.mountProps = p;
|
|
415
|
+
handle.updateProps(p);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* In-place prop update for one mounted layout instance: refresh the
|
|
421
|
+
* merged-input ref (so any layout re-render passes fresh props to `k:page`),
|
|
422
|
+
* push the merged loader props directly into the page mounted under *this*
|
|
423
|
+
* layout host (spread over its latest slot props so layout-provided extras
|
|
424
|
+
* survive), then update the layout island's own input. Page-first ordering
|
|
425
|
+
* lets a layout that *does* read its input re-render afterwards and win with
|
|
426
|
+
* its own fresher slot props.
|
|
427
|
+
*/
|
|
428
|
+
const layoutUpdateProps = (layoutHost, coreUpdate) => (p) => {
|
|
429
|
+
setLayoutMergedInput(p);
|
|
430
|
+
for (const [pageHost, entry] of pageHandles) if (layoutHost.contains(pageHost)) entry.handle.updateProps({
|
|
431
|
+
...entry.mountProps ?? {},
|
|
432
|
+
...p ?? {}
|
|
433
|
+
});
|
|
434
|
+
coreUpdate?.(p);
|
|
435
|
+
};
|
|
396
436
|
const layoutMount = Wrapped.mount.bind(Wrapped);
|
|
397
437
|
const layoutInternal = Wrapped[ISLAND_MOUNT_INTERNAL];
|
|
398
438
|
function prepareLayoutMountHost(host) {
|
|
@@ -401,16 +441,27 @@ function wrapLayout(layout, page) {
|
|
|
401
441
|
Wrapped.mount = (host, props) => {
|
|
402
442
|
setLayoutMergedInput(props);
|
|
403
443
|
prepareLayoutMountHost(host);
|
|
404
|
-
|
|
444
|
+
const unmount = layoutMount(host, props);
|
|
445
|
+
const core = ISLAND_MOUNT_HANDLES.get(host);
|
|
446
|
+
ISLAND_MOUNT_HANDLES.set(host, {
|
|
447
|
+
unmount,
|
|
448
|
+
updateProps: layoutUpdateProps(host, core?.updateProps)
|
|
449
|
+
});
|
|
450
|
+
return unmount;
|
|
405
451
|
};
|
|
406
452
|
Wrapped[ISLAND_MOUNT_INTERNAL] = (host, props) => {
|
|
407
453
|
setLayoutMergedInput(props);
|
|
408
454
|
prepareLayoutMountHost(host);
|
|
409
|
-
|
|
410
|
-
return {
|
|
455
|
+
const base = typeof layoutInternal === "function" ? layoutInternal(host, props) : {
|
|
411
456
|
unmount: layoutMount(host, props),
|
|
412
457
|
updateProps: () => {}
|
|
413
458
|
};
|
|
459
|
+
const enhanced = {
|
|
460
|
+
unmount: base.unmount,
|
|
461
|
+
updateProps: layoutUpdateProps(host, base.updateProps)
|
|
462
|
+
};
|
|
463
|
+
ISLAND_MOUNT_HANDLES.set(host, enhanced);
|
|
464
|
+
return enhanced;
|
|
414
465
|
};
|
|
415
466
|
Wrapped.hydratable = async (props, opts) => {
|
|
416
467
|
if (!opts?.name) throw new Error("wrapLayout: hydratable requires options.name");
|
|
@@ -640,6 +691,51 @@ function prefetch(pathWithSearch) {
|
|
|
640
691
|
expires: Date.now() + PREFETCH_TTL_MS
|
|
641
692
|
});
|
|
642
693
|
}
|
|
694
|
+
const noopRouteHandle = () => ({
|
|
695
|
+
unmount: () => {},
|
|
696
|
+
updateProps: null
|
|
697
|
+
});
|
|
698
|
+
/** Mount an island keeping the full internal handle so later same-island
|
|
699
|
+
* navigations can push new loader props instead of remounting. */
|
|
700
|
+
function mountIslandWithHandle(island, host, props) {
|
|
701
|
+
const internal = island[ISLAND_MOUNT_INTERNAL];
|
|
702
|
+
if (typeof internal === "function") {
|
|
703
|
+
const h = internal(host, props);
|
|
704
|
+
return {
|
|
705
|
+
unmount: () => void h.unmount(),
|
|
706
|
+
updateProps: (p) => h.updateProps(p)
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
unmount: island.mount(host, props),
|
|
711
|
+
updateProps: null
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Same-island fast path: fetch fresh loader data and push it into the mounted
|
|
716
|
+
* island via `updateProps` — ilha's fine-grained morph reconciles the DOM, so
|
|
717
|
+
* focus, caret, selection, and scroll survive (the reason persistQuery-driven
|
|
718
|
+
* filter inputs don't blur while typing). Returns "updated" when applied (or
|
|
719
|
+
* redirected), "remount" when the caller must run the full teardown + mount
|
|
720
|
+
* path (loader error / not-found need boundary DOM; the full path re-fetches,
|
|
721
|
+
* accepted for these rare cases). Throws AbortError when superseded.
|
|
722
|
+
*/
|
|
723
|
+
async function updateRouteInPlace(handle, pathWithSearch, signal) {
|
|
724
|
+
if (!handle.updateProps) return "remount";
|
|
725
|
+
const result = !!findRoute(_rou3, "GET", pathWithSearch.split("?")[0] ?? "")?.data?.hasLoader ? await fetchLoaderData(pathWithSearch, signal) : {
|
|
726
|
+
kind: "data",
|
|
727
|
+
data: {}
|
|
728
|
+
};
|
|
729
|
+
signal.throwIfAborted();
|
|
730
|
+
if (result.kind === "redirect") {
|
|
731
|
+
clientRedirect(result.to);
|
|
732
|
+
return "updated";
|
|
733
|
+
}
|
|
734
|
+
if (result.kind !== "data") return "remount";
|
|
735
|
+
if (result.headEntries?.length) applyHeadEntriesToDocument([...result.headEntries]);
|
|
736
|
+
handle.updateProps(result.data);
|
|
737
|
+
return "updated";
|
|
738
|
+
}
|
|
643
739
|
/**
|
|
644
740
|
* Mounts a route island with proper hydration for client-side navigation.
|
|
645
741
|
* Looks up the island in the reverse registry, runs the loader (via fetch),
|
|
@@ -652,13 +748,16 @@ async function mountRouteWithHydration(island, host, pathWithSearch, signal, reg
|
|
|
652
748
|
return withViewSwap(() => {
|
|
653
749
|
host.innerHTML = `<div data-router-view data-router-not-found>${nf.toString()}</div>`;
|
|
654
750
|
const nfHost = host.firstElementChild;
|
|
655
|
-
return
|
|
751
|
+
return {
|
|
752
|
+
unmount: nfHost ? nf.mount(nfHost) : () => {},
|
|
753
|
+
updateProps: null
|
|
754
|
+
};
|
|
656
755
|
});
|
|
657
756
|
}
|
|
658
757
|
await withViewSwap(() => {
|
|
659
758
|
host.innerHTML = `<div data-router-empty></div>`;
|
|
660
759
|
});
|
|
661
|
-
return ()
|
|
760
|
+
return noopRouteHandle();
|
|
662
761
|
}
|
|
663
762
|
const clientMatch = findRoute(_rou3, "GET", pathWithSearch.split("?")[0] ?? "");
|
|
664
763
|
const hasLoader = !!clientMatch?.data?.hasLoader;
|
|
@@ -669,22 +768,25 @@ async function mountRouteWithHydration(island, host, pathWithSearch, signal, reg
|
|
|
669
768
|
};
|
|
670
769
|
if (loaderResult.kind === "redirect") {
|
|
671
770
|
clientRedirect(loaderResult.to);
|
|
672
|
-
return ()
|
|
771
|
+
return noopRouteHandle();
|
|
673
772
|
}
|
|
674
773
|
if (loaderResult.kind === "error") {
|
|
675
774
|
const boundary = clientMatch?.data?.errorHandler;
|
|
676
|
-
if (boundary) return withViewSwap(() =>
|
|
775
|
+
if (boundary) return withViewSwap(() => ({
|
|
776
|
+
unmount: mountLoaderErrorBoundary(boundary, host, loaderResult.status, loaderResult.message),
|
|
777
|
+
updateProps: null
|
|
778
|
+
}));
|
|
677
779
|
const escaped = escapeHtml(loaderResult.message);
|
|
678
780
|
await withViewSwap(() => {
|
|
679
781
|
host.innerHTML = `<div data-router-view data-router-error="${loaderResult.status}">${escaped}</div>`;
|
|
680
782
|
});
|
|
681
|
-
return ()
|
|
783
|
+
return noopRouteHandle();
|
|
682
784
|
}
|
|
683
785
|
if (loaderResult.kind === "not-found") {
|
|
684
786
|
await withViewSwap(() => {
|
|
685
787
|
host.innerHTML = `<div data-router-empty></div>`;
|
|
686
788
|
});
|
|
687
|
-
return ()
|
|
789
|
+
return noopRouteHandle();
|
|
688
790
|
}
|
|
689
791
|
props = loaderResult.data;
|
|
690
792
|
const headStore = { entries: [...loaderResult.headEntries ?? []] };
|
|
@@ -695,7 +797,7 @@ async function mountRouteWithHydration(island, host, pathWithSearch, signal, reg
|
|
|
695
797
|
applyHeadEntriesToDocument(headStore.entries);
|
|
696
798
|
host.innerHTML = `<div data-router-view>${html}</div>`;
|
|
697
799
|
});
|
|
698
|
-
return ()
|
|
800
|
+
return noopRouteHandle();
|
|
699
801
|
}
|
|
700
802
|
const name = reverseRegistry?.get(island) ?? Object.entries(registry).find(([, v]) => v === island)?.[0];
|
|
701
803
|
if (!name) {
|
|
@@ -705,7 +807,7 @@ async function mountRouteWithHydration(island, host, pathWithSearch, signal, reg
|
|
|
705
807
|
applyHeadEntriesToDocument(headStore.entries);
|
|
706
808
|
host.innerHTML = `<div data-router-view>${html}</div>`;
|
|
707
809
|
});
|
|
708
|
-
return ()
|
|
810
|
+
return noopRouteHandle();
|
|
709
811
|
}
|
|
710
812
|
const html = await withHeadStore(headStore, () => island.hydratable(props, {
|
|
711
813
|
name,
|
|
@@ -716,7 +818,7 @@ async function mountRouteWithHydration(island, host, pathWithSearch, signal, reg
|
|
|
716
818
|
applyHeadEntriesToDocument(headStore.entries);
|
|
717
819
|
host.innerHTML = `<div data-router-view>${html}</div>`;
|
|
718
820
|
const islandHost = host.querySelector(`[data-ilha="${name}"]`);
|
|
719
|
-
return islandHost ? island
|
|
821
|
+
return islandHost ? mountIslandWithHandle(island, islandHost) : noopRouteHandle();
|
|
720
822
|
});
|
|
721
823
|
}
|
|
722
824
|
/** Render + mount a `+error` boundary island for a failed loader. */
|
|
@@ -960,7 +1062,12 @@ function navigate(to, opts = {}) {
|
|
|
960
1062
|
}
|
|
961
1063
|
_lastNavKey = currentNavKey();
|
|
962
1064
|
syncRouteFromLocation();
|
|
963
|
-
if (opts.scroll !== false)
|
|
1065
|
+
if (opts.scroll !== false) {
|
|
1066
|
+
const dest = adapter.readLocation();
|
|
1067
|
+
const fromMatch = findRoute(_rou3, "GET", cur.pathname);
|
|
1068
|
+
const destMatch = findRoute(_rou3, "GET", dest.pathname);
|
|
1069
|
+
if (!(fromMatch?.data?.island != null && destMatch?.data?.island === fromMatch.data.island) || dest.hash && dest.hash !== "#") scrollAfterNavigate(dest.hash);
|
|
1070
|
+
}
|
|
964
1071
|
runAfterNavigateHooks({
|
|
965
1072
|
from: current,
|
|
966
1073
|
to,
|
|
@@ -1534,11 +1641,23 @@ function router(options = {}) {
|
|
|
1534
1641
|
if (getHistoryMode() === "hash") console.warn("[ilha-router] mount({ hydrate: true }) was called in hash mode. SSR + hydration assumes the server can render the active route, but in hash mode the server only ever sees the document URL. Use plain SPA mode (`mount(target)` without `hydrate: true`) for hash-mode apps.");
|
|
1535
1642
|
const viewHost = host.querySelector("[data-router-view]") ?? host;
|
|
1536
1643
|
let currentMountedIsland = activeIsland();
|
|
1644
|
+
let currentMountedPath = routePath() + routeSearch();
|
|
1645
|
+
let viewHandle = null;
|
|
1646
|
+
const adoptHydratedHandle = () => {
|
|
1647
|
+
const el = viewHost.querySelector("[data-ilha]");
|
|
1648
|
+
if (!el) return null;
|
|
1649
|
+
const h = ISLAND_MOUNT_HANDLES.get(el);
|
|
1650
|
+
return h ? {
|
|
1651
|
+
unmount: () => void h.unmount(),
|
|
1652
|
+
updateProps: (p) => h.updateProps(p)
|
|
1653
|
+
} : null;
|
|
1654
|
+
};
|
|
1537
1655
|
const reverseRegistry = registry ? buildReverseRegistry(registry) : void 0;
|
|
1538
1656
|
let navVersion = 0;
|
|
1539
1657
|
const NavHandler = ilha.render(() => {
|
|
1540
1658
|
const current = activeIsland();
|
|
1541
|
-
|
|
1659
|
+
const pathWithSearch = routePath() + routeSearch();
|
|
1660
|
+
if (current !== currentMountedIsland || pathWithSearch !== currentMountedPath) {
|
|
1542
1661
|
const thisNav = ++navVersion;
|
|
1543
1662
|
navAbort?.abort();
|
|
1544
1663
|
navAbort = new AbortController();
|
|
@@ -1546,11 +1665,20 @@ function router(options = {}) {
|
|
|
1546
1665
|
queueMicrotask(async () => {
|
|
1547
1666
|
if (thisNav !== navVersion) return;
|
|
1548
1667
|
const settle = beginNavigation();
|
|
1549
|
-
unmountView?.();
|
|
1550
|
-
unmountView = null;
|
|
1551
1668
|
try {
|
|
1552
|
-
|
|
1553
|
-
|
|
1669
|
+
if (current !== null && current === currentMountedIsland) {
|
|
1670
|
+
const handle = viewHandle ?? adoptHydratedHandle();
|
|
1671
|
+
if (handle?.updateProps) {
|
|
1672
|
+
if (await updateRouteInPlace(handle, pathWithSearch, signal) === "updated") {
|
|
1673
|
+
viewHandle = handle;
|
|
1674
|
+
currentMountedPath = pathWithSearch;
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
viewHandle?.unmount();
|
|
1680
|
+
viewHandle = null;
|
|
1681
|
+
viewHandle = await mountRouteWithHydration(current, viewHost, pathWithSearch, signal, registry, reverseRegistry);
|
|
1554
1682
|
} catch (e) {
|
|
1555
1683
|
if (e?.name === "AbortError") return;
|
|
1556
1684
|
console.error("[ilha-router] navigation failed:", e);
|
|
@@ -1560,6 +1688,7 @@ function router(options = {}) {
|
|
|
1560
1688
|
settle();
|
|
1561
1689
|
}
|
|
1562
1690
|
currentMountedIsland = current;
|
|
1691
|
+
currentMountedPath = pathWithSearch;
|
|
1563
1692
|
});
|
|
1564
1693
|
}
|
|
1565
1694
|
return "";
|
|
@@ -1580,8 +1709,8 @@ function router(options = {}) {
|
|
|
1580
1709
|
navAbort = ac;
|
|
1581
1710
|
try {
|
|
1582
1711
|
const um = await mountRouteWithHydration(island, viewHost, pathWithSearch, ac.signal, registry, reverseRegistry);
|
|
1583
|
-
if (thisNav === navVersion)
|
|
1584
|
-
else um();
|
|
1712
|
+
if (thisNav === navVersion) viewHandle = um;
|
|
1713
|
+
else um.unmount();
|
|
1585
1714
|
} catch (e) {
|
|
1586
1715
|
if (e?.name !== "AbortError") console.error("[ilha-router] initial client loader render failed:", e);
|
|
1587
1716
|
}
|
|
@@ -1607,12 +1736,26 @@ function router(options = {}) {
|
|
|
1607
1736
|
const settle = beginNavigation();
|
|
1608
1737
|
try {
|
|
1609
1738
|
const loc = getAdapter().readLocation();
|
|
1610
|
-
const
|
|
1739
|
+
const pathWithSearch = loc.pathname + loc.search;
|
|
1740
|
+
if (island !== null && island === currentMountedIsland) {
|
|
1741
|
+
const handle = viewHandle ?? adoptHydratedHandle();
|
|
1742
|
+
if (handle?.updateProps) {
|
|
1743
|
+
if (await updateRouteInPlace(handle, pathWithSearch, ac.signal) === "updated") {
|
|
1744
|
+
if (thisNav === navVersion) {
|
|
1745
|
+
viewHandle = handle;
|
|
1746
|
+
currentMountedPath = pathWithSearch;
|
|
1747
|
+
}
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
const um = await mountRouteWithHydration(island, viewHost, pathWithSearch, ac.signal, registry, reverseRegistry);
|
|
1611
1753
|
if (thisNav === navVersion) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1754
|
+
viewHandle?.unmount();
|
|
1755
|
+
viewHandle = um;
|
|
1614
1756
|
currentMountedIsland = island;
|
|
1615
|
-
|
|
1757
|
+
currentMountedPath = pathWithSearch;
|
|
1758
|
+
} else um.unmount();
|
|
1616
1759
|
} catch (e) {
|
|
1617
1760
|
if (e?.name !== "AbortError") console.error("[ilha-router] invalidate failed:", e);
|
|
1618
1761
|
} finally {
|
|
@@ -1626,7 +1769,7 @@ function router(options = {}) {
|
|
|
1626
1769
|
navAbort?.abort();
|
|
1627
1770
|
unmountNavHandler();
|
|
1628
1771
|
navHost.remove();
|
|
1629
|
-
|
|
1772
|
+
viewHandle?.unmount();
|
|
1630
1773
|
_linkCleanup?.();
|
|
1631
1774
|
_navChangeCleanup?.();
|
|
1632
1775
|
_linkCleanup = null;
|
|
@@ -1634,8 +1777,10 @@ function router(options = {}) {
|
|
|
1634
1777
|
if (prevScrollRestoration !== null) history.scrollRestoration = prevScrollRestoration;
|
|
1635
1778
|
};
|
|
1636
1779
|
}
|
|
1637
|
-
let
|
|
1780
|
+
let mountedHandle = null;
|
|
1781
|
+
let mountedHandleIsland = null;
|
|
1638
1782
|
let currentMountedIsland = null;
|
|
1783
|
+
let currentMountedPath = null;
|
|
1639
1784
|
let navVersion = 0;
|
|
1640
1785
|
unmountView = RouterView.mount(host);
|
|
1641
1786
|
/**
|
|
@@ -1644,12 +1789,21 @@ function router(options = {}) {
|
|
|
1644
1789
|
* render would have no access to loader data.
|
|
1645
1790
|
*/
|
|
1646
1791
|
async function mountActiveIsland(island, signal) {
|
|
1647
|
-
|
|
1648
|
-
|
|
1792
|
+
const sameIsland = island !== null && island === mountedHandleIsland && mountedHandle?.updateProps != null;
|
|
1793
|
+
const teardown = () => {
|
|
1794
|
+
mountedHandle?.unmount();
|
|
1795
|
+
mountedHandle = null;
|
|
1796
|
+
mountedHandleIsland = null;
|
|
1797
|
+
};
|
|
1798
|
+
if (!sameIsland) teardown();
|
|
1649
1799
|
currentMountedIsland = island;
|
|
1800
|
+
currentMountedPath = routePath() + routeSearch();
|
|
1650
1801
|
if (!island) {
|
|
1651
1802
|
const nfHost = host?.querySelector("[data-router-not-found]");
|
|
1652
|
-
if (_notFound && nfHost)
|
|
1803
|
+
if (_notFound && nfHost) mountedHandle = {
|
|
1804
|
+
unmount: _notFound.mount(nfHost),
|
|
1805
|
+
updateProps: null
|
|
1806
|
+
};
|
|
1653
1807
|
return;
|
|
1654
1808
|
}
|
|
1655
1809
|
const viewHost = host?.querySelector("[data-router-view]");
|
|
@@ -1666,9 +1820,13 @@ function router(options = {}) {
|
|
|
1666
1820
|
return;
|
|
1667
1821
|
}
|
|
1668
1822
|
if (result.kind === "error") {
|
|
1823
|
+
if (sameIsland) teardown();
|
|
1669
1824
|
const boundary = clientMatch?.data?.errorHandler;
|
|
1670
1825
|
if (boundary) {
|
|
1671
|
-
|
|
1826
|
+
mountedHandle = await withViewSwap(() => ({
|
|
1827
|
+
unmount: mountLoaderErrorBoundary(boundary, viewHost, result.status, result.message),
|
|
1828
|
+
updateProps: null
|
|
1829
|
+
}));
|
|
1672
1830
|
return;
|
|
1673
1831
|
}
|
|
1674
1832
|
const escaped = escapeHtml(result.message);
|
|
@@ -1678,13 +1836,19 @@ function router(options = {}) {
|
|
|
1678
1836
|
return;
|
|
1679
1837
|
}
|
|
1680
1838
|
const props = result.kind === "data" ? result.data : {};
|
|
1839
|
+
if (sameIsland && mountedHandle?.updateProps) {
|
|
1840
|
+
if (result.kind === "data" && result.headEntries?.length) applyHeadEntriesToDocument([...result.headEntries]);
|
|
1841
|
+
mountedHandle.updateProps(props);
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1681
1844
|
const headStore = { entries: [...result.kind === "data" ? result.headEntries ?? [] : []] };
|
|
1682
1845
|
const html = await withHeadStore(headStore, () => island.toString(props));
|
|
1683
|
-
|
|
1846
|
+
mountedHandle = await withViewSwap(() => {
|
|
1684
1847
|
applyHeadEntriesToDocument(headStore.entries);
|
|
1685
1848
|
viewHost.innerHTML = html;
|
|
1686
|
-
return island
|
|
1849
|
+
return mountIslandWithHandle(island, viewHost, props);
|
|
1687
1850
|
});
|
|
1851
|
+
mountedHandleIsland = island;
|
|
1688
1852
|
}
|
|
1689
1853
|
navAbort = new AbortController();
|
|
1690
1854
|
mountActiveIsland(activeIsland(), navAbort.signal).catch((e) => {
|
|
@@ -1693,7 +1857,8 @@ function router(options = {}) {
|
|
|
1693
1857
|
});
|
|
1694
1858
|
const NavHandler = ilha.render(() => {
|
|
1695
1859
|
const current = activeIsland();
|
|
1696
|
-
|
|
1860
|
+
const pathWithSearch = routePath() + routeSearch();
|
|
1861
|
+
if (current !== currentMountedIsland || pathWithSearch !== currentMountedPath) {
|
|
1697
1862
|
const thisNav = ++navVersion;
|
|
1698
1863
|
navAbort?.abort();
|
|
1699
1864
|
navAbort = new AbortController();
|
|
@@ -1731,7 +1896,7 @@ function router(options = {}) {
|
|
|
1731
1896
|
++navVersion;
|
|
1732
1897
|
_revalidate = null;
|
|
1733
1898
|
navAbort?.abort();
|
|
1734
|
-
|
|
1899
|
+
mountedHandle?.unmount();
|
|
1735
1900
|
unmountNavHandler();
|
|
1736
1901
|
navHost.remove();
|
|
1737
1902
|
unmountView?.();
|
package/dist/ssr.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilha/router",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "A tiny SPA router for Ilha",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontend",
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
"unplugin": "3.3.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"ilha": "0.9.
|
|
71
|
+
"ilha": "0.9.3",
|
|
72
72
|
"vite": "^8.1.3"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"ilha": ">=0.9.
|
|
75
|
+
"ilha": ">=0.9.3"
|
|
76
76
|
}
|
|
77
77
|
}
|