@useavalon/avalon 0.3.2-canary.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/mod.d.ts +1 -1
  2. package/dist/mod.js +1 -1
  3. package/dist/src/build/integration-config.js +1 -1
  4. package/dist/src/build/island-client-bundler.js +1 -1
  5. package/dist/src/build/island-manifest.d.ts +3 -0
  6. package/dist/src/build/island-manifest.js +1 -1
  7. package/dist/src/build/island-ssr-flag.d.ts +14 -0
  8. package/dist/src/build/island-ssr-flag.js +1 -0
  9. package/dist/src/build/mdx-island-transform.js +2 -2
  10. package/dist/src/build/mdx-plugin.js +1 -1
  11. package/dist/src/build/page-island-await.d.ts +18 -0
  12. package/dist/src/build/page-island-await.js +2 -0
  13. package/dist/src/build/page-island-transform.d.ts +3 -1
  14. package/dist/src/build/page-island-transform.js +2 -2
  15. package/dist/src/client/css-hmr-handler.d.ts +7 -90
  16. package/dist/src/client/css-hmr-handler.js +1 -1
  17. package/dist/src/client/custom-directives.js +1 -1
  18. package/dist/src/client/hmr-coordinator.d.ts +0 -1
  19. package/dist/src/client/hmr-coordinator.js +1 -1
  20. package/dist/src/client/hmr-error-overlay.d.ts +18 -0
  21. package/dist/src/client/hydrate-runtime.d.ts +33 -0
  22. package/dist/src/client/hydrate-runtime.js +1 -0
  23. package/dist/src/client/hydration-mode.d.ts +11 -0
  24. package/dist/src/client/hydration-mode.js +1 -0
  25. package/dist/src/client/main-slim.js +1 -0
  26. package/dist/src/client/main.js +2 -15
  27. package/dist/src/client/router/a11y.d.ts +9 -0
  28. package/dist/src/client/router/a11y.js +1 -0
  29. package/dist/src/client/router/eligibility.d.ts +32 -0
  30. package/dist/src/client/router/eligibility.js +1 -0
  31. package/dist/src/client/router/events.d.ts +33 -0
  32. package/dist/src/client/router/events.js +1 -0
  33. package/dist/src/client/router/forms.d.ts +25 -0
  34. package/dist/src/client/router/forms.js +1 -0
  35. package/dist/src/client/router/index.d.ts +20 -0
  36. package/dist/src/client/router/index.js +1 -0
  37. package/dist/src/client/router/opt-out.d.ts +30 -0
  38. package/dist/src/client/router/opt-out.js +1 -0
  39. package/dist/src/client/router/persist.d.ts +24 -0
  40. package/dist/src/client/router/persist.js +1 -0
  41. package/dist/src/client/router/prefetch.d.ts +25 -0
  42. package/dist/src/client/router/prefetch.js +1 -0
  43. package/dist/src/client/router/swap.d.ts +31 -0
  44. package/dist/src/client/router/swap.js +1 -0
  45. package/dist/src/client/router/transitions.d.ts +22 -0
  46. package/dist/src/client/router/transitions.js +1 -0
  47. package/dist/src/client/server-islands-boot.d.ts +12 -0
  48. package/dist/src/client/server-islands-boot.js +1 -0
  49. package/dist/src/client/types/vite-virtual-modules.d.ts +7 -0
  50. package/dist/src/islands/builtin-directives.d.ts +1 -0
  51. package/dist/src/islands/builtin-directives.js +1 -1
  52. package/dist/src/islands/critical-css.js +1 -1
  53. package/dist/src/islands/framework-base-css.d.ts +5 -1
  54. package/dist/src/islands/framework-base-css.js +1 -1
  55. package/dist/src/islands/hydration-directives.js +1 -1
  56. package/dist/src/islands/island.d.ts +24 -3
  57. package/dist/src/islands/island.js +1 -1
  58. package/dist/src/islands/per-island-script.js +1 -1
  59. package/dist/src/islands/types.d.ts +6 -0
  60. package/dist/src/nitro/renderer.d.ts +2 -1
  61. package/dist/src/nitro/renderer.js +20 -20
  62. package/dist/src/nitro/types.d.ts +5 -0
  63. package/dist/src/post-build/cloudflare-worker-patch.d.ts +32 -0
  64. package/dist/src/post-build/cloudflare-worker-patch.js +1 -0
  65. package/dist/src/post-build/fetch-handler-wrapper.d.ts +1 -0
  66. package/dist/src/post-build/fetch-handler-wrapper.js +56 -0
  67. package/dist/src/post-build/index.d.ts +16 -0
  68. package/dist/src/post-build/index.js +6 -51
  69. package/dist/src/post-build/isolated-island-builder.js +2 -2
  70. package/dist/src/render/collect-css.d.ts +29 -0
  71. package/dist/src/render/collect-css.js +3 -3
  72. package/dist/src/render/dev-css-select.d.ts +40 -0
  73. package/dist/src/render/dev-css-select.js +5 -0
  74. package/dist/src/schemas/routing/index.d.ts +2 -0
  75. package/dist/src/schemas/routing.d.ts +1 -0
  76. package/dist/src/schemas/routing.js +1 -1
  77. package/dist/src/server-islands/encryption.js +1 -1
  78. package/dist/src/server-islands/endpoint.js +3 -3
  79. package/dist/src/server-islands/renderer.js +6 -5
  80. package/dist/src/server-islands/route.d.ts +3 -1
  81. package/dist/src/server-islands/route.js +1 -1
  82. package/dist/src/types/island-prop.d.ts +28 -9
  83. package/dist/src/types/virtual-modules.d.ts +26 -6
  84. package/dist/src/types/vite-env.d.ts +4 -0
  85. package/dist/src/vite-plugin/config.js +1 -1
  86. package/dist/src/vite-plugin/dev-css-graph.d.ts +41 -0
  87. package/dist/src/vite-plugin/dev-css-graph.js +1 -0
  88. package/dist/src/vite-plugin/dev-css-hmr.d.ts +42 -0
  89. package/dist/src/vite-plugin/dev-css-hmr.js +2 -0
  90. package/dist/src/vite-plugin/dev-css-modules.d.ts +8 -0
  91. package/dist/src/vite-plugin/dev-css-modules.js +1 -0
  92. package/dist/src/vite-plugin/framework-vite-defaults.d.ts +29 -0
  93. package/dist/src/vite-plugin/framework-vite-defaults.js +1 -0
  94. package/dist/src/vite-plugin/nitro-integration.d.ts +4 -0
  95. package/dist/src/vite-plugin/nitro-integration.js +20 -20
  96. package/dist/src/vite-plugin/plugin.d.ts +2 -0
  97. package/dist/src/vite-plugin/plugin.js +1 -1
  98. package/dist/src/vite-plugin/server-islands-plugin.js +1 -1
  99. package/dist/src/vite-plugin/ssr-dom-shim-module.d.ts +27 -0
  100. package/dist/src/vite-plugin/ssr-dom-shim-module.js +121 -0
  101. package/dist/src/vite-plugin/stub-build-time-packages.d.ts +16 -0
  102. package/dist/src/vite-plugin/stub-build-time-packages.js +2 -0
  103. package/dist/src/vite-plugin/types.d.ts +12 -0
  104. package/package.json +13 -7
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Click / URL eligibility for client navigation.
3
+ * Pure functions so they can be unit-tested without a DOM.
4
+ */
5
+ export interface ClickModifiers {
6
+ defaultPrevented: boolean;
7
+ button: number;
8
+ metaKey: boolean;
9
+ ctrlKey: boolean;
10
+ shiftKey: boolean;
11
+ altKey: boolean;
12
+ }
13
+ export interface LinkAttributes {
14
+ href: string | null;
15
+ download: boolean;
16
+ target: string | null;
17
+ reload: boolean;
18
+ }
19
+ export interface LocationLike {
20
+ origin: string;
21
+ href: string;
22
+ pathname: string;
23
+ search: string;
24
+ }
25
+ export declare function isModifiedClick(event: ClickModifiers): boolean;
26
+ /**
27
+ * Returns the destination URL when this link may use client navigation,
28
+ * or null when the browser should handle it.
29
+ */
30
+ export declare function eligibleNavigationUrl(link: LinkAttributes, click: ClickModifiers, location: LocationLike): URL | null;
31
+ export declare function linkAttributesFromAnchor(anchor: HTMLAnchorElement): LinkAttributes;
32
+ export declare function clickModifiersFromEvent(event: MouseEvent): ClickModifiers;
@@ -0,0 +1 @@
1
+ export function isModifiedClick(e){return e.defaultPrevented||e.button!==0?!0:e.metaKey||e.ctrlKey||e.shiftKey||e.altKey}export function eligibleNavigationUrl(t,n,r){if(isModifiedClick(n)||t.reload||t.download)return null;let i=t.target;if(i&&i!==``&&i!==`_self`)return null;let a=t.href;if(!a||a.startsWith(`#`))return null;let o;try{o=new URL(a,r.href)}catch{return null}return o.origin!==r.origin||o.protocol!==`http:`&&o.protocol!==`https:`||o.pathname===r.pathname&&o.search===r.search&&o.hash?null:o}export function linkAttributesFromAnchor(e){return{href:e.getAttribute(`href`),download:e.hasAttribute(`download`),target:e.getAttribute(`target`),reload:e.dataset.routerReload!=null}}export function clickModifiersFromEvent(e){return{defaultPrevented:e.defaultPrevented,button:e.button,metaKey:e.metaKey,ctrlKey:e.ctrlKey,shiftKey:e.shiftKey,altKey:e.altKey}}
@@ -0,0 +1,33 @@
1
+ import type { ViewTransitionMode } from "./transitions.ts";
2
+ export declare const ROUTER_EVENTS: {
3
+ readonly beforeNavigate: "avalon:before-navigate";
4
+ readonly beforeSwap: "avalon:before-swap";
5
+ readonly afterSwap: "avalon:after-swap";
6
+ readonly pageLoad: "avalon:page-load";
7
+ readonly navigationError: "avalon:navigation-error";
8
+ };
9
+ export type RouterEventName = (typeof ROUTER_EVENTS)[keyof typeof ROUTER_EVENTS];
10
+ export type HistoryKind = "push" | "replace" | "auto";
11
+ export interface NavigateOptions {
12
+ history?: HistoryKind;
13
+ scroll?: boolean;
14
+ /** `false` skips the animation. A string is a type name for CSS selectors. */
15
+ viewTransition?: ViewTransitionMode;
16
+ }
17
+ export interface BeforeNavigateDetail {
18
+ from: string;
19
+ to: string;
20
+ history: HistoryKind;
21
+ }
22
+ export interface SwapDetail {
23
+ from: string;
24
+ to: string;
25
+ }
26
+ export declare function dispatchBeforeNavigate(detail: BeforeNavigateDetail): boolean;
27
+ export declare function dispatchBeforeSwap(detail: SwapDetail & {
28
+ newDocument: Document;
29
+ }): void;
30
+ export declare function dispatchAfterSwap(detail: SwapDetail): void;
31
+ export declare function dispatchPageLoad(url: string): void;
32
+ export declare function dispatchNavigationError(url: string, error: unknown): void;
33
+ export declare function setNavigating(active: boolean): void;
@@ -0,0 +1 @@
1
+ export const ROUTER_EVENTS={beforeNavigate:`avalon:before-navigate`,beforeSwap:`avalon:before-swap`,afterSwap:`avalon:after-swap`,pageLoad:`avalon:page-load`,navigationError:`avalon:navigation-error`};function t(e,t,n=!1){return document.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,cancelable:n}))}export function dispatchBeforeNavigate(n){return t(ROUTER_EVENTS.beforeNavigate,n,!0)}export function dispatchBeforeSwap(n){t(ROUTER_EVENTS.beforeSwap,n)}export function dispatchAfterSwap(n){t(ROUTER_EVENTS.afterSwap,n)}export function dispatchPageLoad(n){t(ROUTER_EVENTS.pageLoad,{url:n})}export function dispatchNavigationError(n,r){t(ROUTER_EVENTS.navigationError,{url:n,error:r})}export function setNavigating(e){if(e){document.documentElement.dataset.routerNavigating=`true`;return}delete document.documentElement.dataset.routerNavigating}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Form eligibility for client navigation.
3
+ *
4
+ * Native GET/POST still work as full navigations. When the router is on,
5
+ * same-origin submissions without file inputs swap the returned HTML document.
6
+ */
7
+ export interface FormSubmitLike {
8
+ action: string;
9
+ method: string;
10
+ enctype: string | null;
11
+ target: string | null;
12
+ reload: boolean;
13
+ hasFiles: boolean;
14
+ }
15
+ export interface LocationLike {
16
+ origin: string;
17
+ href: string;
18
+ }
19
+ export interface EligibleFormNavigation {
20
+ url: URL;
21
+ method: "GET" | "POST";
22
+ }
23
+ export declare function eligibleFormNavigation(form: FormSubmitLike, location: LocationLike): EligibleFormNavigation | null;
24
+ export declare function formSubmitFromEvent(form: HTMLFormElement, submitter: HTMLElement | null): FormSubmitLike;
25
+ export declare function applyGetSearchParams(url: URL, form: HTMLFormElement, submitter: HTMLElement | null): URL;
@@ -0,0 +1 @@
1
+ export function eligibleFormNavigation(e,t){if(e.reload||e.hasFiles)return null;let n=e.target;if(n&&n!==``&&n!==`_self`)return null;let r=e.method.trim().toUpperCase();if(r!==`GET`&&r!==`POST`)return null;let i;try{i=new URL(e.action||t.href,t.href)}catch{return null}return i.origin!==t.origin||i.protocol!==`http:`&&i.protocol!==`https:`?null:{url:i,method:r}}export function formSubmitFromEvent(e,t){let n=t instanceof HTMLButtonElement||t instanceof HTMLInputElement?t:null,r=n?.getAttribute(`formaction`)||e.getAttribute(`action`)||e.action||``,i=n?.getAttribute(`formmethod`)||e.getAttribute(`method`)||e.method||`get`,a=n?.getAttribute(`formtarget`)||e.getAttribute(`target`),o=e.dataset.routerReload!=null||n?.dataset.routerReload!=null,s=!1;for(let t of e.elements)if(t instanceof HTMLInputElement&&t.type===`file`&&t.files&&t.files.length>0){s=!0;break}return{action:r,method:i,enctype:e.getAttribute(`enctype`),target:a,reload:o,hasFiles:s}}export function applyGetSearchParams(e,t,n){let r=new URL(e.href),i=n instanceof HTMLButtonElement||n instanceof HTMLInputElement?new FormData(t,n):new FormData(t);r.search=``;for(let[e,t]of i)typeof t==`string`&&r.searchParams.append(e,t);return r}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Optional client-side navigation over Avalon SSR.
3
+ *
4
+ * Fetches the next server-rendered HTML document, swaps it into the current
5
+ * page, disposes old islands, restores persist slots, and hydrates the rest.
6
+ * On failure, falls back to a full browser navigation.
7
+ */
8
+ import { type NavigateOptions } from "./events.ts";
9
+ export { disposeIslands, scanAndHydrate } from "../hydrate-runtime.ts";
10
+ export { eligibleNavigationUrl, isModifiedClick } from "./eligibility.ts";
11
+ export type { HistoryKind, NavigateOptions } from "./events.ts";
12
+ export { ROUTER_EVENTS } from "./events.ts";
13
+ export { prefetch } from "./prefetch.ts";
14
+ export { reconcileHead, replaceBody, replaceOutlets } from "./swap.ts";
15
+ export type { ViewTransitionMode } from "./transitions.ts";
16
+ /**
17
+ * Programmatic client navigation. Falls back to a full page load on failure.
18
+ */
19
+ export declare function navigate(url: string | URL, options?: NavigateOptions): Promise<void>;
20
+ export declare function installClientRouter(): void;
@@ -0,0 +1 @@
1
+ import{disposeIslands as e,scanAndHydrate as t}from"../hydrate-runtime.js";import{bootServerIslands as n}from"../server-islands-boot.js";import{announceRoute as r,applyScroll as i,readScroll as a,restoreFocus as o}from"./a11y.js";import{clickModifiersFromEvent as s,eligibleNavigationUrl as c,linkAttributesFromAnchor as l}from"./eligibility.js";import{dispatchAfterSwap as u,dispatchBeforeNavigate as d,dispatchBeforeSwap as f,dispatchNavigationError as p,dispatchPageLoad as m,setNavigating as h}from"./events.js";import{applyGetSearchParams as g,eligibleFormNavigation as _,formSubmitFromEvent as v}from"./forms.js";import{ClientNavigationDisabledError as y,currentDocumentDisablesClientNavigation as b,documentDisablesClientNavigation as x,headerDisablesClientNavigation as S,htmlDisablesClientNavigation as C}from"./opt-out.js";import{clearViewTransitionGeometry as w,extractPersisted as T,leftoverPersisted as E,restorePersisted as ee}from"./persist.js";import{getCachedOrInflight as te,installPrefetchListeners as ne,putCachedDocument as re,ROUTER_FETCH_HEADERS as D}from"./prefetch.js";import{copyHtmlAttributes as O,isHtmlResponse as k,parseHtmlDocument as A,reconcileHead as j,replaceBody as M,replaceOutlets as N}from"./swap.js";import{skipInFlightViewTransition as P,viewTransitionFromDataset as F,withViewTransition as I}from"./transitions.js";export{disposeIslands,scanAndHydrate}from"../hydrate-runtime.js";export{eligibleNavigationUrl,isModifiedClick}from"./eligibility.js";export{ROUTER_EVENTS}from"./events.js";export{prefetch}from"./prefetch.js";export{reconcileHead,replaceBody,replaceOutlets}from"./swap.js";let L=!1,R=!1,z=null,B=0,V=Promise.resolve();function H(){return location.pathname+location.search+location.hash}function ie(){let e={...history.state,avalon:!0,scroll:a()};history.replaceState(e,``)}function U(e){let t=document.createDocumentFragment();for(let n of e)t.appendChild(n);return t}async function W(t,n){let r=E(t,n);if(r.length===0)return;let i=document.createDocumentFragment();for(let e of r)i.appendChild(e);await e(i)}function G(e,t,n){if(n)return;let r={avalon:!0,scroll:{x:0,y:0}};if(e===`replace`){history.replaceState(r,``,t);return}history.pushState(r,``,t)}function K(e,t){return e.history??(t?`auto`:`push`)}function q(e,t){return F(t?.dataset.routerTransition)??F(e.dataset.routerTransition)}async function J(e,t,n,r,i){let a=H(),o=t.pathname+t.search+t.hash;if(x(e))throw new y(o);let s=++B;P();let c,l=V;V=new Promise(e=>{c=e});try{if(await l,s!==B)return;await Y(e,{from:a,to:o,historyKind:n,isPop:r,viewTransition:i,generation:s})}finally{c()}}async function Y(i,a){let{from:s,to:c,historyKind:l,isPop:d,viewTransition:p,generation:h}=a;if(f({from:s,to:c,newDocument:i}),h!==B)return;let g=null;await I(()=>{if(h!==B)return;j(document,i),O(document,i);let e=N(document,i);if(e)g=e;else{g=document.body;let e=T(document.body);M(document,i),W(e,ee(document.body,e))}G(l,c,d)},p),h===B&&(g&&await e(Array.isArray(g)?U(g):g),w(document.body),u({from:s,to:c}),t(document.body),await n(document.body),r(document.title),o(),m(c))}async function ae(e,t){let n=new URL(e,location.href),r=te(n),i=r instanceof Promise?await r:r;if(i){if(C(i.html))throw new y(i.finalUrl);return{doc:A(i.html),finalUrl:new URL(i.finalUrl,location.href)}}let a=await fetch(e,{cache:`no-store`,headers:D,redirect:`follow`,signal:t}),o=await X(a),s=new URL(a.url||e,location.href);return re(n,o,s.href),{doc:A(o),finalUrl:s}}async function oe(e,t,n){let r=await fetch(e.href,{method:`POST`,body:t,headers:D,redirect:`follow`,signal:n}),i=await X(r),a=new URL(r.url||e.href,location.href);return{doc:A(i),finalUrl:a}}async function X(e){if(S(e.headers))throw new y(e.url);if(!k(e.headers.get(`content-type`)))throw Error(`Non-HTML response (${e.headers.get(`content-type`)??`unknown`})`);let t=await e.text();if(C(t))throw new y(e.url);return t}async function Z(e,t,n,r){if(R&&z?.abort(),b())throw new y(e.href);let i=K(t,n),a=H(),o=e.pathname+e.search+e.hash;if(!n&&!d({from:a,to:o,history:i}))return;n||ie(),R=!0,h(!0),z=new AbortController;let s=z.signal;try{let{doc:a,finalUrl:o}=await(r?r(s):ae(e.href,s));if(s.aborted||(await J(a,o,i===`auto`?`push`:i,n,t.viewTransition),s.aborted))return;se(e,n,t.scroll)}catch(e){if(s.aborted)return;throw e instanceof y||p(o,e),e}finally{s.aborted||(R=!1,h(!1))}}function se(e,t,n){if(t){let t=history.state;i(t?.scroll??`top`,e.hash);return}n!==!1&&i(`top`,e.hash)}function Q(e){location.assign(e)}export async function navigate(e,t={}){let n=typeof e==`string`?new URL(e,location.href):e;if(n.origin!==location.origin){Q(n.href);return}try{await Z(n,t,!1)}catch{Q(n.href)}}function ce(e){if(e.defaultPrevented||b())return;let t=e.target;if(!(t instanceof Element))return;let n=t.closest(`a`);if(!(n instanceof HTMLAnchorElement)||n.closest(`svg`))return;let r=c(l(n),s(e),location);r&&(e.preventDefault(),navigate(r,{viewTransition:q(n)}).catch(()=>Q(r.href)))}function le(e){if(e.defaultPrevented||b())return;let t=e.target;if(!(t instanceof HTMLFormElement))return;let n=e.submitter instanceof HTMLElement?e.submitter:null,r=_(v(t,n),location);if(!r)return;e.preventDefault();let i=q(t,n);if(r.method===`GET`){navigate(g(r.url,t,n),{viewTransition:i}).catch(()=>{t.submit()});return}let a=n instanceof HTMLButtonElement||n instanceof HTMLInputElement?new FormData(t,n):new FormData(t);Z(r.url,{history:`push`,viewTransition:i},!1,e=>oe(r.url,a,e)).catch(()=>{t.submit()})}function $(){let e=new URL(location.href);Z(e,{history:`auto`},!0).catch(()=>Q(e.href))}function ue(e){return c(l(e),{defaultPrevented:!1,button:0,metaKey:!1,ctrlKey:!1,shiftKey:!1,altKey:!1},location)}export function installClientRouter(){L||typeof document>`u`||(L=!0,history.scrollRestoration=`manual`,document.addEventListener(`click`,ce),document.addEventListener(`submit`,le),window.addEventListener(`popstate`,$),ne(ue))}installClientRouter();
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Route-level client navigation opt-out.
3
+ *
4
+ * Pages export `clientNavigation = false` (or MDX `clientNavigation: false`
5
+ * in frontmatter). SSR stamps `data-client-navigation="false"` on `<html>`
6
+ * and sends `Avalon-Client-Navigation: false` so the client router falls
7
+ * back to a full load instead of swapping that document.
8
+ */
9
+ export declare const CLIENT_NAVIGATION_HEADER = "Avalon-Client-Navigation";
10
+ export declare const CLIENT_NAVIGATION_ATTR = "data-client-navigation";
11
+ export declare class ClientNavigationDisabledError extends Error {
12
+ constructor(url: string);
13
+ }
14
+ export declare function isClientNavigationDisabled(pageModule: {
15
+ clientNavigation?: boolean;
16
+ frontmatter?: Record<string, unknown>;
17
+ }): boolean;
18
+ export declare function headerDisablesClientNavigation(headers: {
19
+ get(name: string): string | null;
20
+ }): boolean;
21
+ export declare function htmlDisablesClientNavigation(html: string): boolean;
22
+ export declare function documentDisablesClientNavigation(doc: {
23
+ documentElement: {
24
+ dataset: DOMStringMap;
25
+ };
26
+ }): boolean;
27
+ /** True when the live document opted this route out of client navigation. */
28
+ export declare function currentDocumentDisablesClientNavigation(): boolean;
29
+ export declare function stampClientNavigationOptOut(html: string): string;
30
+ export declare function clientNavigationResponseHeaders(disabled: boolean): Record<string, string>;
@@ -0,0 +1 @@
1
+ export const CLIENT_NAVIGATION_HEADER=`Avalon-Client-Navigation`;export const CLIENT_NAVIGATION_ATTR=`data-client-navigation`;export class ClientNavigationDisabledError extends Error{constructor(e){super(`Client navigation disabled for ${e}`),this.name=`ClientNavigationDisabledError`}}export function isClientNavigationDisabled(e){return e.clientNavigation===!1?!0:e.frontmatter?.clientNavigation===!1}export function headerDisablesClientNavigation(t){let n=t.get(CLIENT_NAVIGATION_HEADER);return n==null?!1:n.trim().toLowerCase()===`false`}export function htmlDisablesClientNavigation(e){let t=/<html\b[^>]*>/i.exec(e)?.[0];return t?/\sdata-client-navigation\s*=\s*(['"]?)false\1/i.test(t):!1}export function documentDisablesClientNavigation(e){return e.documentElement.dataset.clientNavigation===`false`}export function currentDocumentDisablesClientNavigation(){return typeof document>`u`?!1:documentDisablesClientNavigation(document)}export function stampClientNavigationOptOut(e){return htmlDisablesClientNavigation(e)?e:e.replace(/<html\b/i,`<html data-client-navigation="false"`)}export function clientNavigationResponseHeaders(t){return t?{[CLIENT_NAVIGATION_HEADER]:`false`}:{}}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Selective island persistence across client navigations.
3
+ *
4
+ * Mark a wrapper or `<avalon-island>` with `data-router-persist="key"`.
5
+ * Matching keys are moved (not cloned) from the old document into the new
6
+ * one so the live framework instance survives. Qwik is skipped — its loader
7
+ * is document-oriented.
8
+ */
9
+ export declare function persistKeyOf(el: HTMLElement): string | null;
10
+ export declare function hasPersistedNodes(root: ParentNode): boolean;
11
+ /**
12
+ * Detach persist nodes from `root` so a body swap does not destroy them.
13
+ * Duplicate keys keep the first node. Nested persist nodes are ignored
14
+ * (the outer wrapper is the unit that moves).
15
+ */
16
+ export declare function extractPersisted(root: ParentNode): Map<string, HTMLElement>;
17
+ /**
18
+ * Replace matching stubs in the swapped document with the live nodes.
19
+ * Returns keys that were restored; leftover saved nodes should be disposed.
20
+ */
21
+ export declare function restorePersisted(root: ParentNode, saved: Map<string, HTMLElement>): Set<string>;
22
+ export declare function leftoverPersisted(saved: Map<string, HTMLElement>, restored: Set<string>): HTMLElement[];
23
+ /** View Transitions can leave captured geometry on persist chrome. */
24
+ export declare function clearViewTransitionGeometry(root: ParentNode): void;
@@ -0,0 +1 @@
1
+ const e=`[data-router-persist]`,t=new Set([`qwik`]);export function persistKeyOf(e){let t=e.dataset.routerPersist;if(t==null)return null;let n=t.trim();return n.length>0?n:null}function r(t){return!!t.parentElement?.closest(e)}function i(e){return e.dataset.framework&&t.has(e.dataset.framework)?!0:!!e.querySelector(`[data-framework='qwik']`)}function a(t){return[...t.querySelectorAll(e)].filter(e=>!r(e)&&persistKeyOf(e))}export function hasPersistedNodes(e){return a(e).length>0}export function extractPersisted(e){let t=new Map;for(let r of a(e)){let e=persistKeyOf(r);!e||t.has(e)||i(r)||(t.set(e,r),r.remove())}return t}export function restorePersisted(e,t){let r=new Set;if(t.size===0)return r;for(let i of a(e)){let e=persistKeyOf(i);if(!e)continue;let a=t.get(e);!a||a===i||(i.replaceWith(a),r.add(e))}return r}export function leftoverPersisted(e,t){return[...e].filter(([e])=>!t.has(e)).map(([,e])=>e)}const o=[`width`,`height`,`transform`,`translate`,`scale`,`top`,`left`,`right`,`bottom`,`inset`,`opacity`];export function clearViewTransitionGeometry(e){for(let t of a(e)){for(let e of o)t.style.getPropertyValue(e)&&t.style.removeProperty(e);t.getAttribute(`style`)===``&&t.removeAttribute(`style`)}}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Prefetch cache for client navigation.
3
+ *
4
+ * Hover/focus on an eligible link fetches the SSR HTML document so a later
5
+ * click can swap without waiting on the network. Honors Save-Data and
6
+ * `data-router-prefetch="false"`.
7
+ */
8
+ export declare const ROUTER_FETCH_HEADERS: {
9
+ readonly Accept: "text/html";
10
+ readonly "Avalon-Router": "1";
11
+ };
12
+ export interface PrefetchCacheEntry {
13
+ html: string;
14
+ expiresAt: number;
15
+ finalUrl: string;
16
+ }
17
+ export declare function cacheKeyFromUrl(url: URL): string;
18
+ export declare function clearPrefetchCache(): void;
19
+ export declare function getCachedDocument(url: URL, now?: number): PrefetchCacheEntry | null;
20
+ /** Cache hit, or the in-flight prefetch for this URL (if any). */
21
+ export declare function getCachedOrInflight(url: URL): PrefetchCacheEntry | Promise<PrefetchCacheEntry | null> | null;
22
+ export declare function putCachedDocument(url: URL, html: string, finalUrl: string, ttlMs?: number, now?: number): void;
23
+ /** Programmatic prefetch of a same-origin HTML document. */
24
+ export declare function prefetch(url: string | URL): Promise<void>;
25
+ export declare function installPrefetchListeners(eligibleUrl: (anchor: HTMLAnchorElement) => URL | null): () => void;
@@ -0,0 +1 @@
1
+ import{currentDocumentDisablesClientNavigation as e,headerDisablesClientNavigation as t,htmlDisablesClientNavigation as n}from"./opt-out.js";import{isHtmlResponse as r}from"./swap.js";const i=new Set([`slow-2g`,`2g`]);export const ROUTER_FETCH_HEADERS={Accept:`text/html`,"Avalon-Router":`1`};const o=new Map,s=new Map;export function cacheKeyFromUrl(e){return`${e.origin}${e.pathname}${e.search}`}export function clearPrefetchCache(){o.clear(),s.clear()}function l(){let e=navigator.connection;return e?e.saveData?!1:!i.has(e.effectiveType??``):!0}export function getCachedDocument(e,t=Date.now()){let n=cacheKeyFromUrl(e),r=o.get(n);return r?r.expiresAt<=t?(o.delete(n),null):r:null}export function getCachedOrInflight(e){return getCachedDocument(e)||(s.get(cacheKeyFromUrl(e))??null)}export function putCachedDocument(e,t,n,r=3e4,i=Date.now()){let a={html:t,expiresAt:i+r,finalUrl:n};if(o.set(cacheKeyFromUrl(e),a),n&&n!==e.href)try{o.set(cacheKeyFromUrl(new URL(n)),a)}catch{}}async function d(e,i){let o=cacheKeyFromUrl(e),c=getCachedDocument(e);if(c)return c;let l=s.get(o);if(l!==void 0)return l;let u=(async()=>{try{let o=await fetch(e.href,{cache:`no-store`,headers:ROUTER_FETCH_HEADERS,redirect:`follow`,signal:i});if(!r(o.headers.get(`content-type`))||t(o.headers))return null;let s=await o.text();return n(s)?null:(putCachedDocument(e,s,o.url||e.href),getCachedDocument(e))}catch{return null}finally{s.delete(o)}})();return s.set(o,u),u}export async function prefetch(t){if(e()||!l())return;let n=typeof t==`string`?new URL(t,location.href):t;n.origin===location.origin&&await d(n)}function f(e){return!(e.dataset.routerPrefetch===`false`||e.dataset.routerReload!=null)}function p(e){let t=e.target;if(!(t instanceof Element))return null;let n=t.closest(`a`);return n instanceof HTMLAnchorElement?n:null}import.meta.hot&&import.meta.hot.on(`vite:beforeUpdate`,()=>{clearPrefetchCache()});export function installPrefetchListeners(t){let n,r=null;function i(i){if(e()||!l()||!f(i))return;let a=t(i);if(!a)return;r?.abort(),n!==void 0&&clearTimeout(n),r=new AbortController;let o=r.signal;n=setTimeout(()=>{d(a,o)},80)}function a(){n!==void 0&&clearTimeout(n),n=void 0,r?.abort(),r=null}function o(e){let t=p(e);t&&i(t)}function s(e){let t=p(e);if(!t)return;let n=e.relatedTarget;n instanceof Node&&t.contains(n)||a()}return document.addEventListener(`pointerenter`,o,!0),document.addEventListener(`focusin`,o,!0),document.addEventListener(`pointerleave`,s,!0),()=>{a(),document.removeEventListener(`pointerenter`,o,!0),document.removeEventListener(`focusin`,o,!0),document.removeEventListener(`pointerleave`,s,!0)}}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Head reconciliation and body replacement for client navigation.
3
+ *
4
+ * Stylesheets already present (same href) are reused to avoid FOUC.
5
+ * Page-specific SSR `<style>` tags (`data-universal-ssr`, `data-avalon-ssr-css`,
6
+ * hashed `style[data-avalon-css]`) are copied from the next document.
7
+ * Framework/dev markers (`data-avalon-*`, Vite client) are kept.
8
+ * Runtime-injected stylesheets marked `data-router-persist` stay in head —
9
+ * they are not in the next SSR document (Pagefind UI, etc.).
10
+ * Script tags in the next document are not executed — server islands are
11
+ * booted by the shared runtime after the swap.
12
+ */
13
+ export declare function reconcileHead(current: Document, next: Document): void;
14
+ export declare function copyHtmlAttributes(current: Document, next: Document): void;
15
+ /**
16
+ * Attach leftover `<template shadowrootmode>` nodes (older browsers, or a
17
+ * parser that did not instantiate Declarative Shadow DOM). Native DSD already
18
+ * consumed those templates; this is a no-op when `host.shadowRoot` exists.
19
+ */
20
+ export declare function attachDeclarativeShadowRoots(root: ParentNode): void;
21
+ export declare function outletKeyOf(el: HTMLElement): string | null;
22
+ /**
23
+ * Swap `[data-router-outlet]` regions when both documents share the same keys.
24
+ * Shared chrome (sidebar, header) stays in place so view transitions can
25
+ * crossfade only the page. Returns the detached outgoing nodes, or `null`
26
+ * when a full body replace is required.
27
+ */
28
+ export declare function replaceOutlets(current: Document, next: Document): HTMLElement[] | null;
29
+ export declare function replaceBody(current: Document, next: Document): void;
30
+ export declare function parseHtmlDocument(html: string): Document;
31
+ export declare function isHtmlResponse(contentType: string | null): boolean;
@@ -0,0 +1 @@
1
+ const e=/\/@vite\/client|\/src\/client\/main|entry-client|client\/router/,t=new Set([`icon`,`canonical`,`alternate`,`preload`]),n=`style[data-universal-ssr], style[data-avalon-ssr-css]`,r=new Set([`lang`,`data-router-navigating`,`style`,`data-router-transition`]);function i(e,t){return e.getAttribute(t)}function a(e){return e.getAttribute(`href`)||e.href}function o(e){return(i(e,`rel`)||``).toLowerCase()}function s(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function c(e){return`dataset`in e?e.dataset.avalonCss??``:``}function l(e){let t=i(e,`data-router-persist`);if(t==null)return null;let n=t.trim();return n.length>0?n:null}function u(t){if(l(t)||s(t)&&(t.dataset.avalonCss!=null||t.dataset.avalonBase!=null))return!0;if(t.tagName===`SCRIPT`){let n=i(t,`src`)??``;return e.test(n)}return t.tagName===`STYLE`&&s(t)&&t.dataset.avalonSsrCss!=null}function d(e){if(i(e,`charset`))return`charset`;let t=i(e,`name`);if(t)return`name:${t}`;let n=i(e,`property`);if(n)return`property:${n}`;let r=i(e,`http-equiv`);return r?`http-equiv:${r}`:`misc:${e.outerHTML}`}function f(e,t){let n=e.head.querySelectorAll(`meta`),r=[...t.head.querySelectorAll(`meta`)],a=new Set(r.map(e=>d(e)));for(let e of n)i(e,`charset`)||a.has(d(e))||e.remove();let o=new Set([...e.head.querySelectorAll(`meta`)].map(e=>d(e)));for(let t of r){if(i(t,`charset`))continue;let n=d(t);if(o.has(n)){[...e.head.querySelectorAll(`meta`)].find(e=>d(e)===n)?.replaceWith(e.importNode(t,!0));continue}e.head.appendChild(e.importNode(t,!0))}}function p(e){let t=[...e.head.querySelectorAll(`link`)],n=new Map,r=[];for(let e of t){let t=o(e);t===`stylesheet`||t===`preload`&&i(e,`as`)===`style`?n.set(a(e),e):r.push(e)}return{nextStyles:n,nextOther:r,nextLinks:t}}function m(e,t){u(e)||t.has(a(e))||e.remove()}function h(e,t){return t.some(t=>o(t)===`modulepreload`&&a(t)===e)}function g(e,t,n){for(let r of e.head.querySelectorAll(`link`)){let e=o(r);if(e===`stylesheet`){m(r,t);continue}u(r)||e===`modulepreload`&&!h(a(r),n)&&r.remove()}}function _(e,t){let n=new Set([...e.head.querySelectorAll(`link[rel="stylesheet"]`)].map(e=>a(e)));for(let[r,i]of t)n.has(r)||e.head.appendChild(e.importNode(i,!0))}function v(e,n){for(let n of e.head.querySelectorAll(`link`)){let e=o(n);e===`stylesheet`||e===`modulepreload`||u(n)||t.has(e)&&n.remove()}for(let r of n){let n=o(r);if(n===`modulepreload`){let t=a(r);[...e.head.querySelectorAll(`link[rel="modulepreload"]`)].some(e=>a(e)===t)||e.head.appendChild(e.importNode(r,!0));continue}t.has(n)&&e.head.appendChild(e.importNode(r,!0))}}export function reconcileHead(e,t){e.title=t.title,f(e,t);let{nextStyles:n,nextOther:r,nextLinks:i}=p(t);g(e,n,i),_(e,n),v(e,r),y(e,t)}function y(e,t){for(let t of e.head.querySelectorAll(n))t.remove();for(let r of t.head.querySelectorAll(n))e.head.appendChild(e.importNode(r,!0));let r=new Set([...e.head.querySelectorAll(`style[data-avalon-css]`)].map(e=>c(e)));for(let n of t.head.querySelectorAll(`style[data-avalon-css]`)){let t=c(n);!t||r.has(t)||(e.head.appendChild(e.importNode(n,!0)),r.add(t))}}export function copyHtmlAttributes(e,t){let n=t.documentElement,i=e.documentElement;i.lang=n.lang;for(let e of n.getAttributeNames()){if(r.has(e))continue;let t=n.getAttribute(e);t!==null&&i.setAttribute(e,t)}n.dataset.clientNavigation!==`false`&&delete i.dataset.clientNavigation}export function attachDeclarativeShadowRoots(e){let t=[...e.querySelectorAll(`template[shadowrootmode]`)];for(let e of t){let t=e.parentElement;if(!t)continue;if(t.shadowRoot){e.remove();continue}let n=e.getAttribute(`shadowrootmode`)===`closed`?`closed`:`open`;try{t.attachShadow({mode:n}).appendChild(e.content),e.remove()}catch{}}}export function outletKeyOf(e){let t=e.dataset.routerOutlet;if(t==null)return null;let n=t.trim();return n.length>0?n:null}function b(e){let t=new Map;for(let n of e.querySelectorAll(`[data-router-outlet]`)){let e=outletKeyOf(n);if(!e||t.has(e))return null;t.set(e,n)}return t.size>0?t:null}export function replaceOutlets(e,t){let n=b(e),r=b(t.body);if(!n||!r||n.size!==r.size)return null;for(let e of n.keys())if(!r.has(e))return null;let i=[];for(let[t,a]of n){let n=r.get(t);if(!n)return null;i.push(a),a.replaceWith(e.adoptNode(n))}return attachDeclarativeShadowRoots(e.body),i}export function replaceBody(e,t){e.body.replaceWith(e.adoptNode(t.body)),attachDeclarativeShadowRoots(e.body)}export function parseHtmlDocument(e){return new DOMParser().parseFromString(e,`text/html`)}export function isHtmlResponse(e){return e?e.toLowerCase().includes(`text/html`):!1}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * View Transitions around the client-navigation DOM swap.
3
+ *
4
+ * On by default when the API exists. `false` skips the animation; a string
5
+ * names it for CSS (`html[data-router-transition]` and, where supported,
6
+ * `startViewTransition({ types })`). Reduced motion always skips the API.
7
+ *
8
+ * Only one transition may be active. A new navigation skips the in-flight
9
+ * one so `startViewTransition` is not called in an invalid state.
10
+ */
11
+ export type ViewTransitionMode = boolean | string;
12
+ export interface ResolvedViewTransition {
13
+ enabled: boolean;
14
+ type?: string;
15
+ }
16
+ export declare function prefersReducedMotion(): boolean;
17
+ /** `data-router-transition` on a link, form, or submitter. Absent → default. */
18
+ export declare function viewTransitionFromDataset(raw: string | undefined): ViewTransitionMode | undefined;
19
+ export declare function resolveViewTransition(mode?: ViewTransitionMode): ResolvedViewTransition;
20
+ /** Abort the active view transition so a newer navigation can swap. */
21
+ export declare function skipInFlightViewTransition(): void;
22
+ export declare function withViewTransition(update: () => void | Promise<void>, mode?: ViewTransitionMode): Promise<void>;
@@ -0,0 +1 @@
1
+ export function prefersReducedMotion(){return!!globalThis.matchMedia?.(`(prefers-reduced-motion: reduce)`)?.matches}export function viewTransitionFromDataset(e){if(!(e==null||e===``))return e===`false`?!1:e===`true`?!0:e}export function resolveViewTransition(e){return e===!1||e===`false`?{enabled:!1}:typeof e==`string`&&e!==``&&e!==`true`?{enabled:!0,type:e}:{enabled:!0}}let r=null,i=!1;const a=[];function o(){if(!i){i=!0;return}return new Promise(e=>{a.push(()=>{i=!0,e()})})}function s(){let e=a.shift();if(e){e();return}i=!1}function c(){let e=r;if(!e)return null;r=null;try{e.skipTransition?.()}catch{}return e}export function skipInFlightViewTransition(){let e=c();e&&e.finished.catch(()=>{})}function u(){let e=c();if(e)return e.finished.catch(()=>{})}function d(e,t){let n=document,r=n.startViewTransition?.bind(n);if(r)try{if(t)try{return r({update:e,types:[t]})}catch{}return r(e)}catch{return}}export async function withViewTransition(t,i){let{enabled:a,type:c}=resolveViewTransition(i);if(!a||prefersReducedMotion()){await t();return}let l=!1,f=async()=>{l||(l=!0,await t())},p=document.documentElement,m=c?p.dataset.routerTransition:void 0;c&&(p.dataset.routerTransition=c);let h=o();h&&await h;let g;try{let e=u();e&&await e,g=d(f,c),g&&(r=g,g.finished.catch(()=>{}))}finally{s()}try{if(!g){await f();return}try{await g.finished}catch{await f()}finally{r===g&&(r=null)}}catch{await f()}finally{c&&(m===void 0?delete p.dataset.routerTransition:p.dataset.routerTransition=m)}}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Boot deferred server islands after first load or a client-navigation swap.
3
+ *
4
+ * Inline fetch scripts in the SSR HTML do not run when the document is swapped
5
+ * via DOMParser. The shared runtime discovers `<avalon-server-island data-p>`
6
+ * nodes and performs the same fetch the inline script would.
7
+ */
8
+ /**
9
+ * Fetch and inject every server island under `root` that has not started yet.
10
+ * Combined islands re-execute module scripts in the injected HTML so they hydrate.
11
+ */
12
+ export declare function bootServerIslands(root?: ParentNode): Promise<void>;
@@ -0,0 +1 @@
1
+ function e(e){if(e.dataset.endpoint)return e.dataset.endpoint;let t=/^si-(.+)-(\d+)$/.exec(e.id);return t?`/_server-islands/${t[1]}`:null}function t(e){let t=[...e.querySelectorAll(`script[type="module"]`)];for(let e of t){if(e.getAttribute(`src`))continue;let t=document.createElement(`script`);t.type=`module`,t.textContent=e.textContent,e.replaceWith(t)}}async function n(e,t,n){let r=e.dataset.p;if(!r)return null;let i=`${t}?p=${encodeURIComponent(r)}`,a=i.length>2048?await fetch(t,{method:`POST`,body:r,headers:{"content-type":`text/plain`},signal:n}):await fetch(i,{signal:n});return a.ok?a.text():null}export async function bootServerIslands(r=document){let i=[...r.querySelectorAll(`avalon-server-island[data-p]`)];await Promise.all(i.map(async r=>{if(r.dataset.siStarted)return;let i=e(r);if(!i)return;r.dataset.siStarted=`1`;let a=Number(r.dataset.timeout)||1e4,o=new AbortController,s=setTimeout(()=>o.abort(),a);try{let e=await n(r,i,o.signal);if(clearTimeout(s),e==null)return;r.innerHTML=e,t(r)}catch{clearTimeout(s)}}))}
@@ -11,6 +11,7 @@ declare module "/@useavalon/preact/client" {
11
11
  component: unknown,
12
12
  props?: Record<string, unknown>,
13
13
  ): void;
14
+ export function unmount(container: Element): void;
14
15
  export function getHydrationScript(): string;
15
16
  }
16
17
 
@@ -20,6 +21,7 @@ declare module "/@useavalon/react/client" {
20
21
  component: unknown,
21
22
  props?: Record<string, unknown>,
22
23
  ): void;
24
+ export function unmount(container: Element): void;
23
25
  export function getHydrationScript(): string;
24
26
  }
25
27
 
@@ -29,6 +31,7 @@ declare module "/@useavalon/vue/client" {
29
31
  component: unknown,
30
32
  props?: Record<string, unknown>,
31
33
  ): void;
34
+ export function unmount(container: Element): void;
32
35
  export function getHydrationScript(): string;
33
36
  }
34
37
 
@@ -38,6 +41,7 @@ declare module "/@useavalon/svelte/client" {
38
41
  component: unknown,
39
42
  props?: Record<string, unknown>,
40
43
  ): void;
44
+ export function unmount(container: Element): void;
41
45
  export function getHydrationScript(): string;
42
46
  }
43
47
 
@@ -47,6 +51,7 @@ declare module "/@useavalon/solid/client" {
47
51
  component: unknown,
48
52
  props?: Record<string, unknown>,
49
53
  ): void;
54
+ export function unmount(container: Element): void;
50
55
  export function getHydrationScript(): string;
51
56
  }
52
57
 
@@ -56,6 +61,7 @@ declare module "/@useavalon/lit/client" {
56
61
  component: unknown,
57
62
  props?: Record<string, unknown>,
58
63
  ): void;
64
+ export function unmount(container: Element): void;
59
65
  export function getHydrationScript(): string;
60
66
  }
61
67
 
@@ -65,6 +71,7 @@ declare module "/@useavalon/qwik/client" {
65
71
  component: unknown,
66
72
  props?: Record<string, unknown>,
67
73
  ): void;
74
+ export function unmount(container: Element): void;
68
75
  export function getHydrationScript(): string;
69
76
  }
70
77
 
@@ -11,5 +11,6 @@
11
11
  */
12
12
  /**
13
13
  * Register all built-in custom directives.
14
+ * Safe to call more than once — later calls are a no-op.
14
15
  */
15
16
  export declare function registerBuiltinDirectives(): void;
@@ -1 +1 @@
1
- import{registerHydrationDirective as e}from"./hydration-directives.js";export function registerBuiltinDirectives(){e(`on:delay`,{name:`on:delay`,script:(e,t,n)=>{setTimeout(t,Number.parseInt(n||`1000`,10))}}),e(`on:event`,{name:`on:event`,script:(e,t,n)=>{if(!n){t();return}let r=()=>{document.removeEventListener(n,r),t()};document.addEventListener(n,r,{once:!0})}}),e(`on:scroll`,{name:`on:scroll`,script:(e,t,n)=>{let r=Number.parseInt(n||`100`,10),i=()=>{globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())};globalThis.addEventListener(`scroll`,i,{passive:!0}),globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())}}),e(`on:match`,{name:`on:match`,script:(e,t,n)=>{if(!n){t();return}let r=globalThis.matchMedia(n);if(r.matches){t();return}let i=e=>{e.matches&&(r.removeEventListener(`change`,i),t())};r.addEventListener(`change`,i)}})}
1
+ import{isCustomDirective as e,registerHydrationDirective as t}from"./hydration-directives.js";export function registerBuiltinDirectives(){e(`on:delay`)||(t(`on:delay`,{name:`on:delay`,script:(e,t,n)=>{setTimeout(t,Number.parseInt(n||`1000`,10))}}),t(`on:event`,{name:`on:event`,script:(e,t,n)=>{if(!n){t();return}let r=()=>{document.removeEventListener(n,r),t()};document.addEventListener(n,r,{once:!0})}}),t(`on:scroll`,{name:`on:scroll`,script:(e,t,n)=>{let r=Number.parseInt(n||`100`,10),i=()=>{globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())};globalThis.addEventListener(`scroll`,i,{passive:!0}),globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())}}),t(`on:match`,{name:`on:match`,script:(e,t,n)=>{if(!n){t();return}let r=globalThis.matchMedia(n);if(r.matches){t();return}let i=e=>{e.matches&&(r.removeEventListener(`change`,i),t())};r.addEventListener(`change`,i)}}))}
@@ -1,4 +1,4 @@
1
1
  import{minifyCSS as e}from"./css-utils.js";import{injectFrameworkBaseCSS as t}from"./framework-base-css.js";import{getUniversalCSS as n}from"./universal-css-collector.js";function r(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t}function i(e,t,n){let r=e.replaceAll(/\s+/g,` `).trim();if(r.startsWith(`/*`)&&r.endsWith(`*/`)){n.push(e.trimEnd());return}t.has(r)||(t.add(r),n.push(e.trimEnd()))}export function deduplicateCSSRules(e){if(!e.trim())return``;let t=new Set,n=e.split(`
2
2
  `),a=[],o=``,s=0;for(let e of n)s+=r(e),o+=`${e}\n`,s<=0&&o.trim()&&(i(o,t,a),o=``,s=0);return o.trim()&&a.push(o.trimEnd()),a.join(`
3
- `)}export function extractCriticalCSS(t=!0){let r=n(t);if(!r.trim())return``;let i=e(deduplicateCSSRules(r));return i.trim()?`<style data-critical-css="true">${i}</style>`:``}export function deferNonCriticalStylesheets(e){let t=/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,n=[/syntax-highlight/i,/hljs/i,/prism/i,/highlight\.js/i];return e.replaceAll(t,(e,t)=>{if(/\bmedia\s*=/i.test(t)||/data-critical/i.test(t))return e;let r=/href=["']([^"']+)["']/i.exec(t);if(!r)return e;let i=r[1],a=i.startsWith(`https://`)||i.startsWith(`http://`),o=/data-defer/i.test(t),s=!a&&n.some(e=>e.test(i));return!a&&!o&&!s?e:`${`<link ${t.replace(/\s*data-defer(?:=["'][^"']*["'])?\s*/gi,` `).trim()} media="print" onload="this.media='all'">`}\n${`<noscript><link rel="stylesheet" href="${i}"></noscript>`}`})}export function inlineCriticalCSS(e){let n=t(e),r=extractCriticalCSS(!0);return r&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${r}\n</head>`)),n=deferNonCriticalStylesheets(n),n=c(n),n}function c(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
3
+ `)}export function extractCriticalCSS(t=!0){let r=n(t);if(!r.trim())return``;let i=e(deduplicateCSSRules(r));return i.trim()?`<style data-critical-css="true">${i}</style>`:``}export function deferNonCriticalStylesheets(e){return e.replaceAll(/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,(t,n,r)=>{if(e.slice(Math.max(0,r-32),r).toLowerCase().includes(`<noscript`))return t;let i=n.replace(/\s*\/\s*$/,``).trim();if(/\bmedia\s*=/i.test(i)||/data-critical/i.test(i))return t;let a=/href=["']([^"']+)["']/i.exec(i);if(!a)return t;let o=a[1],s=o.startsWith(`https://`)||o.startsWith(`http://`),c=/data-defer/i.test(i);return!s&&!c?t:`${`<link ${i.replace(/\s*data-defer(?:=["'][^"']*["'])?\s*/gi,` `).trim()} media="print" onload="this.media='all'">`}\n${`<noscript><link rel="stylesheet" href="${o}"></noscript>`}`})}export function inlineCriticalCSS(e){let n=t(e),r=extractCriticalCSS(!0);return r&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${r}\n</head>`)),n=deferNonCriticalStylesheets(n),n=l(n),n}function l(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
4
4
  `);return e.includes(`</title>`)?e.replace(`</title>`,`</title>\n${a}`):e}
@@ -14,6 +14,10 @@
14
14
  * devtools. This mirrors the pattern Astro, Lit, and Elder.js use for
15
15
  * island-style hydration anchors.
16
16
  *
17
+ * Client-only islands have no SSR children, so `display: contents` would
18
+ * leave a host with no box. Those use `display: block` so deferred
19
+ * conditions (`on:visible`, `on:interaction`) can attach to the host.
20
+ *
17
21
  * The CSS is intentionally low-specificity (single-element selectors)
18
22
  * so any user CSS that sets a different `display` value naturally wins.
19
23
  */
@@ -33,4 +37,4 @@
33
37
  */
34
38
  export declare function injectFrameworkBaseCSS(html: string): string;
35
39
  /** Exposed for tests */
36
- export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}";
40
+ export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}avalon-island[data-render-strategy=client-only]{display:block}";
@@ -1 +1 @@
1
- const e=`avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
1
+ const e=`avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}avalon-island[data-render-strategy=client-only]{display:block}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
@@ -1 +1 @@
1
- const e=new Map;export function registerHydrationDirective(t,n){e.has(t)&&console.warn(`[avalon] Hydration directive "${t}" is already registered. Overwriting.`),e.set(t,n)}export function unregisterHydrationDirective(t){return e.delete(t)}export function isCustomDirective(t){return e.has(t)}export function getDirective(t){return e.get(t)}export function getRegisteredDirectives(){return[...e.keys()]}export function serializeDirectiveScript(t){let n=e.get(t);return n?typeof n.script==`string`?n.script:n.script.toString():null}export function clearDirectives(){e.clear()}
1
+ const e=new Map;export function registerHydrationDirective(t,n){e.has(t)||e.set(t,n)}export function unregisterHydrationDirective(t){return e.delete(t)}export function isCustomDirective(t){return e.has(t)}export function getDirective(t){return e.get(t)}export function getRegisteredDirectives(){return[...e.keys()]}export function serializeDirectiveScript(t){let n=e.get(t);return n?typeof n.script==`string`?n.script:n.script.toString():null}export function clearDirectives(){e.clear()}
@@ -8,6 +8,8 @@ declare global {
8
8
  var __viteDevServer: ViteDevServer | undefined;
9
9
  /** Hydration mode — automatically set: "entry-client" in dev (HMR), "per-island" in production */
10
10
  var __avalonHydrationMode: "entry-client" | "per-island" | undefined;
11
+ /** True when Vite is serving (dev). Independent of hydration mode so production + clientRouter still uses hashed island URLs. */
12
+ var __avalonIsDev: boolean | undefined;
11
13
  /** Compile-time constant set by Vite define — true in production, false in dev */
12
14
  var __AVALON_PER_ISLAND__: boolean;
13
15
  }
@@ -15,6 +17,8 @@ declare global {
15
17
  export type HydrationCondition = "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}` | `on:${string}`;
16
18
  /** Supported framework identifiers (without "unknown") */
17
19
  export type FrameworkId = Exclude<Framework, "unknown">;
20
+ /** Preact's `ComponentChildren` already includes `undefined`; keep `?` without repeating it. */
21
+ type IslandChildren = Exclude<import("preact").ComponentChildren, undefined>;
18
22
  export interface IslandProps {
19
23
  /** Path to the island component (e.g., "/islands/Counter.tsx") */
20
24
  src: string;
@@ -25,9 +29,11 @@ export interface IslandProps {
25
29
  /** Props to pass to the island component */
26
30
  props?: Record<string, unknown>;
27
31
  /** Children to render inside the island (for SSR) */
28
- children?: import("preact").ComponentChildren;
32
+ children?: IslandChildren;
29
33
  /** Whether to render server-side (default: true unless condition is 'on:client') */
30
34
  ssr?: boolean;
35
+ /** Skip component SSR and mount on the client. Forces `ssr: false`. */
36
+ clientOnly?: boolean;
31
37
  /** Framework hint for client hydration */
32
38
  framework?: FrameworkId;
33
39
  /** Force SSR-only rendering without hydration */
@@ -42,14 +48,28 @@ export interface IslandProps {
42
48
  server?: ServerIslandProp;
43
49
  /** Island directive for combined server + client islands */
44
50
  island?: IslandDirective;
51
+ /** Explicit HTML id for this island instance. Unique per instance when omitted. */
52
+ id?: string;
53
+ /**
54
+ * Persist key for client navigation (`data-router-persist`).
55
+ * `true` uses `src` as the key so layout islands match across pages.
56
+ */
57
+ persist?: string | true;
58
+ /** React/Preact list key — set on the host VNode, never serialized into props */
59
+ key?: string | number;
45
60
  }
61
+ /**
62
+ * Attach a list key to a VNode returned from a rewritten helper call.
63
+ * Pages import this as `__pageKeyed` from the island transform.
64
+ */
65
+ export declare function withListKey(key: unknown, vnode: JSX.Element): JSX.Element;
46
66
  /**
47
67
  * Universal Island component – renders `<avalon-island>` custom elements for better DOM structure.
48
68
  *
49
69
  * Uses custom elements instead of div wrappers for cleaner, more semantic markup.
50
70
  * Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
51
71
  */
52
- export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
72
+ export default function Island({ src, condition, conditionArg, props, children, ssr: ssrProp, clientOnly, framework, ssrOnly, renderOptions, hydrationData, id, persist, }: IslandProps): JSX.Element;
53
73
  /**
54
74
  * Universal renderIsland function – auto-detects framework and handles SSR + hydration.
55
75
  *
@@ -66,4 +86,5 @@ export default function Island({ src, condition, conditionArg, props, children,
66
86
  * Error isolation: If SSR fails, returns an error placeholder instead of throwing,
67
87
  * allowing the page to continue rendering other islands.
68
88
  */
69
- export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, }: IslandProps): Promise<JSX.Element>;
89
+ export declare function renderIsland({ src, condition, conditionArg, props, children, ssr: ssrProp, clientOnly, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, key, id, persist, }: IslandProps): Promise<JSX.Element>;
90
+ export {};
@@ -1 +1 @@
1
- import{getIslandBundlePath as e}from"../build/island-manifest.js";import{shellH as t,shellFragment as n}from"../render/shell-engine.js";import{addToManifest as r,generateComponentId as i}from"../server-islands/manifest.js";import{renderServerIsland as a}from"../server-islands/renderer.js";import{devError as o,devLog as s,devWarn as c,isDev as l,logRenderTiming as u}from"../utils/dev-logger.js";import{analyzeComponentFile as d,renderComponentSSROnly as f}from"./component-analysis.js";import{detectFramework as p}from"./framework-detection.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";import{detectFrameworkFromPath as g,loadIntegration as _}from"./integration-loader.js";import{addModulepreload as v}from"./modulepreload-collector.js";import{generatePerIslandScript as y}from"./per-island-script.js";import{addUniversalCSS as b}from"./universal-css-collector.js";import{addUniversalHead as x}from"./universal-head-collector.js";function S(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function C(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function w(r,i){if(!C()||i.shouldSkipHydration)return r;let a=e(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0,c=y({islandId:i.islandId,componentSrc:a,framework:i.framework,condition:i.condition,conditionArg:i.conditionArg,propsJson:JSON.stringify(i.props),isCustomDirective:o,directiveScript:s??void 0});return t(n(),null,r,t(`div`,{dangerouslySetInnerHTML:{__html:c},"data-island-script":``}))}function T(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function E(t,n,r,i,a){let o={"data-condition":n,"data-src":e(t),"data-props":JSON.stringify(r),"data-render-strategy":`hydrate`,...T(i)};if(m(n)){o[`data-custom-directive`]=n;let e=h(n);e&&(o[`data-directive-script`]=e)}return a&&(o[`data-condition-arg`]=a),o}function D(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function O(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function k(e,t,n,r){if(e.css&&b(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=D(i);if(a===`style`){let a=O(i);a&&(s(`${r} Extracting CSS from head <style> tag`),b(a,t,n,e.scopeId));return}x(e.head,t,n,a)}}function A(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:c,props:l,hydrationData:u,children:d}=e,f={id:n,"data-framework":r},p=i?{"data-render-strategy":`ssr-only`}:E(a,o,l,u,c);r===`lit`&&s(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let m={...f,...p},h;return h=typeof d==`string`?t(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,m,d),w(h,{islandId:n,src:a,condition:o,conditionArg:c,props:l,framework:r,shouldSkipHydration:i})}function j(n){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u}=n;if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i});let d={id:r,"data-condition":s,"data-src":e(o),"data-props":JSON.stringify(c),"data-render-strategy":`hydrate`,"data-framework":i,...T(l)};if(m(s)){d[`data-custom-directive`]=s;let e=h(s);e&&(d[`data-directive-script`]=e)}return u&&(d[`data-condition-arg`]=u),w(t(`avalon-island`,d),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function M({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:o,ssrOnly:l=!1,renderOptions:u={},hydrationData:d={}}){let f=S(e),p=l||!!u.forceSSROnly,m=o||g(e),h=i!=null&&i!==``;return s(`🔍 [Island Component] ${e}`,{ssr:a,ssrOnly:l,hasChildren:h,framework:o,condition:t}),a&&h?A({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,conditionArg:n,props:r,hydrationData:d,children:i}):(a&&!h&&p&&c(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),j({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,props:r,hydrationData:d,conditionArg:n}))}function N(n,r){let i=r instanceof Error?r.message:String(r);return o(`🚨 Island SSR failed for ${n}:`,r),r instanceof Error&&r.stack&&o(`Stack trace:`,r.stack),t(`avalon-island`,{id:S(n),"data-src":e(n),"data-ssr-error":i,"data-render-strategy":`client-only`})}async function P({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,component:d}){let f=`🏝️ [${e}]`;if(!a||i)return M({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u});let p;try{p=await _(s)}catch(i){return o(`${f} Failed to load ${s} integration:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}try{let i=await p.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return k(i,e,s,f),M({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData})}catch(i){return o(`${f} Fast path SSR failed:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}}async function F(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await d(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)c(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return c(`${r} Component analysis failed:`,e),t}}async function I(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?p(e):`unknown`}async function L(e,t,n,r,i,a,o){let s=await I(e),c=s,u=await(await R(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return k(u,e,s,a),M({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function R(e,t){try{s(`${t} Loading integration for framework: ${e}`);let n=await _(e);return s(`${t} ✅ Integration loaded successfully`),n}catch(n){throw o(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:o,condition:s=`on:client`,conditionArg:c,props:d={},children:f,ssr:p=s!==`on:client`,framework:m,ssrOnly:h=!1,renderOptions:_={},component:v,server:y,island:b}){let x=l()?performance.now():0,S=`🏝️ [${o}]`;try{if(y){let s=i(o);r(s,o);let c=m||g(o),l=a(s,d,y,b,b?e(o):void 0,o,c);return t(n(),null,t(`div`,{dangerouslySetInnerHTML:{__html:l},"data-server-island-wrapper":``}))}return h&&!p&&(p=!0),m?await P({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,framework:m,ssrOnly:h,renderOptions:_,component:v}):await z({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,ssrOnly:h,renderOptions:_,logPrefix:S,component:v})}catch(e){return N(o,e)}finally{l()&&u(o,performance.now()-x)}}async function z(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f}=e;if(s(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!a,condition:n}),await F(t,l,u,d))return B(t,n,i,a,c,u,d);if(!c||a)return M({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,renderOptions:u});try{return await L(t,n,i,l,u,d,f)}catch(e){let a=await I(t);return o(`${d} Framework rendering failed:`,e),M({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:u})}}function B(e,t,n,r,i,a,s){return i&&!r?f({src:e,condition:t,props:n,renderOptions:a}).catch(r=>(o(`${s} SSR failed for SSR-only component:`,r),M({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:a}))):M({src:e,condition:t,props:n,children:r,ssr:i,ssrOnly:!0,renderOptions:a})}
1
+ import{getIslandBundlePath as e}from"../build/island-manifest.js";import{shellH as t,shellFragment as n}from"../render/shell-engine.js";import{addToManifest as r,generateComponentId as i}from"../server-islands/manifest.js";import{renderServerIsland as a}from"../server-islands/renderer.js";import{devError as o,devLog as s,devWarn as c,isDev as l,logRenderTiming as u}from"../utils/dev-logger.js";import{analyzeComponentFile as d,renderComponentSSROnly as f}from"./component-analysis.js";import{detectFramework as p}from"./framework-detection.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";import{detectFrameworkFromPath as g,loadIntegration as _}from"./integration-loader.js";import{addModulepreload as v}from"./modulepreload-collector.js";import{generatePerIslandScript as y}from"./per-island-script.js";import{addUniversalCSS as b}from"./universal-css-collector.js";import{addUniversalHead as x}from"./universal-head-collector.js";function S(e,r){return r==null?e:t(n(),{key:r},e)}export function withListKey(e,t){return S(t,e)}let w=0;function T(e,t){return t||`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}-${w++}`}function E(e,t){if(e===void 0)return;if(e===!0)return t;let n=e.trim();return n.length>0?n:void 0}function D(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function O(r,i){if(!D()||i.shouldSkipHydration)return r;let a=e(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0,c=y({islandId:i.islandId,componentSrc:a,framework:i.framework,condition:i.condition,conditionArg:i.conditionArg,propsJson:JSON.stringify(i.props),isCustomDirective:o,directiveScript:s??void 0});return t(n(),null,r,t(`div`,{dangerouslySetInnerHTML:{__html:c},"data-island-script":``}))}function k(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function A(e){return JSON.stringify(e)}function j(t,n,r,i,a,o=`hydrate`){let s={"data-condition":n,"data-src":e(t),"data-props":A(r),"data-render-strategy":o,...k(i)};if(m(n)){s[`data-custom-directive`]=n;let e=h(n);e&&(s[`data-directive-script`]=e)}return a&&(s[`data-condition-arg`]=a),s}function M(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function N(e){let t=/<style[^>]*>([\s\S]*?)<\/style>/i.exec(e);return t?t[1].trim():null}function P(e,t,n,r){if(e.css&&b(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=M(i);if(a===`style`){let a=N(i);a&&(s(`${r} Extracting CSS from head <style> tag`),b(a,t,n,e.scopeId));return}x(e.head,t,n,a)}}function F(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:c,props:l,hydrationData:u,children:d,persist:f}=e,p={id:n,"data-framework":r},m=E(f,a);m&&(p[`data-router-persist`]=m);let h=i?{"data-render-strategy":`ssr-only`}:j(a,o,l,u,c);r===`lit`&&s(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let g={...p,...h},_;return _=typeof d==`string`?t(`avalon-island`,{...g,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,g,d),O(_,{islandId:n,src:a,condition:o,conditionArg:c,props:l,framework:r,shouldSkipHydration:i})}function I(n){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u,persist:d,clientOnly:f}=n,p=E(d,o);if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i,...p?{"data-router-persist":p}:{}});let g={id:r,"data-condition":s,"data-src":e(o),"data-props":A(c),"data-render-strategy":f?`client-only`:`hydrate`,"data-framework":i,...k(l)};if(p&&(g[`data-router-persist`]=p),m(s)){g[`data-custom-directive`]=s;let e=h(s);e&&(g[`data-directive-script`]=e)}return u&&(g[`data-condition-arg`]=u),O(t(`avalon-island`,g),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function L({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a,clientOnly:o,framework:l,ssrOnly:u=!1,renderOptions:d={},hydrationData:f={},id:p,persist:m}){let h=o===!0?!1:a??t!==`on:client`,_=T(e,p),v=u||!!d.forceSSROnly,y=l||g(e),b=i!=null&&i!==``;return s(`🔍 [Island Component] ${e}`,{ssr:h,ssrOnly:u,hasChildren:b,framework:l,condition:t}),h&&b?F({islandId:_,detectedFramework:y,shouldSkipHydration:v,src:e,condition:t,conditionArg:n,props:r,hydrationData:f,children:i,persist:m}):(h&&!b&&v&&c(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),I({islandId:_,detectedFramework:y,shouldSkipHydration:v,src:e,condition:t,props:r,hydrationData:f,conditionArg:n,persist:m,clientOnly:!h}))}function R(n,r){let i=r instanceof Error?r.message:String(r);return o(`🚨 Island SSR failed for ${n}:`,r),r instanceof Error&&r.stack&&o(`Stack trace:`,r.stack),t(`avalon-island`,{id:T(n),"data-src":e(n),"data-ssr-error":i,"data-render-strategy":`client-only`})}async function z({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,component:d,id:f,persist:p}){let m=`🏝️ [${e}]`;if(!a||i)return L({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p});let h;try{h=await _(s)}catch(i){return o(`${m} Failed to load ${s} integration:`,i),L({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p})}try{let i=await h.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return P(i,e,s,m),L({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData,id:f,persist:p})}catch(i){return o(`${m} Fast path SSR failed:`,i),L({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p})}}async function B(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await d(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)c(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return c(`${r} Component analysis failed:`,e),t}}async function V(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?p(e):`unknown`}async function H(e){let{src:t,condition:n,props:r,ssrOnly:i,renderOptions:a,logPrefix:o,preloadedComponent:s,id:c,persist:u}=e,d=await V(t),f=d,p=await(await U(d,o)).render({component:s??null,props:r,src:t,condition:n,ssrOnly:i,viteServer:globalThis.__viteDevServer,isDev:l()});return P(p,t,d,o),L({src:t,condition:n,props:r,children:p.html,ssr:!0,framework:f,ssrOnly:i,renderOptions:a,hydrationData:i?void 0:p.hydrationData,id:c,persist:u})}async function U(e,t){try{s(`${t} Loading integration for framework: ${e}`);let n=await _(e);return s(`${t} ✅ Integration loaded successfully`),n}catch(n){throw o(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:o,condition:s=`on:client`,conditionArg:c,props:d={},children:f,ssr:p,clientOnly:m,framework:h,ssrOnly:_=!1,renderOptions:v={},component:y,server:b,island:x,key:C,id:w,persist:T}){let E=m===!0?!1:p??s!==`on:client`,D=l()?performance.now():0,O=`🏝️ [${o}]`;try{if(b){let s=i(o);r(s,o);let c=h||g(o),l=a(s,d,b,x,x?e(o):void 0,o,c);return S(t(n(),null,t(`div`,{dangerouslySetInnerHTML:{__html:l},"data-server-island-wrapper":``})),C)}return _&&!E&&m!==!0&&(E=!0),S(h?await z({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:E,framework:h,ssrOnly:_,renderOptions:v,component:y,id:w,persist:T}):await W({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:E,ssrOnly:_,renderOptions:v,logPrefix:O,component:y,id:w,persist:T}),C)}catch(e){return S(R(o,e),C)}finally{l()&&u(o,performance.now()-D)}}async function W(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f,id:p,persist:m}=e;if(s(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!a,condition:n}),await B(t,l,u,d))return G({src:t,condition:n,props:i,children:a,ssr:c,renderOptions:u,logPrefix:d,id:p,persist:m});if(!c||a)return L({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,renderOptions:u,id:p,persist:m});try{return await H({src:t,condition:n,props:i,ssrOnly:l,renderOptions:u,logPrefix:d,preloadedComponent:f,id:p,persist:m})}catch(e){let a=await V(t);return o(`${d} Framework rendering failed:`,e),L({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:u,id:p,persist:m})}}function G(e){let{src:t,condition:n,props:r,children:i,ssr:a,renderOptions:s,logPrefix:c,id:l,persist:u}=e;return a&&!i?f({src:t,condition:n,props:r,renderOptions:s}).catch(e=>(o(`${c} SSR failed for SSR-only component:`,e),L({src:t,condition:n,props:r,ssr:!1,ssrOnly:!0,renderOptions:s,id:l,persist:u}))):L({src:t,condition:n,props:r,children:i,ssr:a,ssrOnly:!0,renderOptions:s,id:l,persist:u})}
@@ -1 +1 @@
1
- export function escapeJsonForScript(e){return e.replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`).replaceAll(`\u2028`,String.raw`\u2028`).replaceAll(`\u2029`,String.raw`\u2029`)}export function generatePerIslandScript(e){let{islandId:t,componentSrc:i,framework:a,condition:o,conditionArg:s,propsJson:c,isCustomDirective:l,directiveScript:u}=e;return`<script type="module">${r(t,o,n(t,i,a,c),s,l,u)}<\/script>`}function n(t,n,r,i){return[`async function h(){`,`var e=document.getElementById(${JSON.stringify(t)});`,`if(!e||e.dataset.hydrated)return;`,`try{`,`var p=${escapeJsonForScript(i)};`,`var m=await import(${JSON.stringify(n)});`,`var C=m.default||Object.values(m).find(function(v){return typeof v==="function"&&v.prototype})||m;`,`if(m.__hydrateIsland){await m.__hydrateIsland(e,C,p)}`,`else if(m.loadIntegrationModule){var i=await m.loadIntegrationModule(${JSON.stringify(r)});if(i.hydrate)await i.hydrate(e,C,p)}`,`e.dataset.hydrated="true";`,`}catch(err){console.error("Hydration error:",err)}`,`}`].join(``)}function r(e,t,n,r,i,a){if(t===`on:client`)return`${n}h();`;if(t===`on:visible`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){try{var t=e.firstElementChild||e;var o=new IntersectionObserver(function(n){`,`if(n[0].isIntersecting){h();o.disconnect()}`,`},{rootMargin:"50px",threshold:0});o.observe(t)}catch(_){h()}}`].join(``);if(t===`on:idle`)return[n,`if("requestIdleCallback" in globalThis){`,`globalThis.requestIdleCallback(function(){h()},{timeout:5000})`,`}else if(document.readyState==="complete"){`,`setTimeout(function(){h()},200)`,`}else{globalThis.addEventListener("load",function(){setTimeout(function(){h()},200)},{once:true})}`].join(``);if(t===`on:interaction`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var t=e.firstElementChild||e;var d=false;var ev=["click","touchstart","mouseenter","focusin"];`,`var fn=function(){if(d)return;d=true;ev.forEach(function(n){t.removeEventListener(n,fn)});h()};`,`ev.forEach(function(n){t.addEventListener(n,fn,{once:true,passive:true})})}`].join(``);if(t.startsWith(`media:`)){let e=t.slice(6);return[n,`try{var mq=globalThis.matchMedia(${JSON.stringify(e)});`,`if(mq.matches){h()}else{mq.addEventListener("change",function x(ev){`,`if(ev.matches){h();mq.removeEventListener("change",x)}},{once:true})}}catch(_){h()}`].join(``)}if(i&&a){let t=r?`,${JSON.stringify(r)}`:``;return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var dir=(${a});`,`dir(e,function(){h()}${t})}`].join(``)}return`${n}h();`}
1
+ export function escapeJsonForScript(e){return e.replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`).replaceAll(`\u2028`,String.raw`\u2028`).replaceAll(`\u2029`,String.raw`\u2029`)}export function generatePerIslandScript(e){let{islandId:t,componentSrc:i,framework:a,condition:o,conditionArg:s,propsJson:c,isCustomDirective:l,directiveScript:u}=e;return`<script type="module">${r(t,o,n(t,i,a,c),s,l,u)}<\/script>`}function n(t,n,r,i){return[`async function h(){`,`var e=document.getElementById(${JSON.stringify(t)});`,`if(!e||e.dataset.hydrated)return;`,`try{`,`var p=${escapeJsonForScript(i)};`,`var m=await import(${JSON.stringify(n)});`,`var C=m.default||Object.values(m).find(function(v){return typeof v==="function"&&v.prototype})||m;`,`var i;if(!m.__hydrateIsland&&!m.__mountIsland&&m.loadIntegrationModule){i=await m.loadIntegrationModule(${JSON.stringify(r)})}`,`var fn=e.dataset.renderStrategy==="client-only"?(m.__mountIsland||(i&&i.mount)):(m.__hydrateIsland||(i&&i.hydrate));`,`if(!fn)throw new Error("Integration does not export "+(e.dataset.renderStrategy==="client-only"?"mount":"hydrate"));`,`await fn(e,C,p)`,`e.dataset.hydrated="true";`,`}catch(err){console.error("Hydration error:",err)}`,`}`].join(``)}function r(e,t,n,r,i,a){if(t===`on:client`)return`${n}h();`;if(t===`on:visible`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){try{var t=e.firstElementChild||e;var o=new IntersectionObserver(function(n){`,`if(n[0].isIntersecting){h();o.disconnect()}`,`},{rootMargin:"50px",threshold:0});o.observe(t)}catch(_){h()}}`].join(``);if(t===`on:idle`)return[n,`if("requestIdleCallback" in globalThis){`,`globalThis.requestIdleCallback(function(){h()},{timeout:5000})`,`}else if(document.readyState==="complete"){`,`setTimeout(function(){h()},200)`,`}else{globalThis.addEventListener("load",function(){setTimeout(function(){h()},200)},{once:true})}`].join(``);if(t===`on:interaction`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var t=e.firstElementChild||e;var d=false;var ev=["click","touchstart","mouseenter","focusin"];`,`var fn=function(){if(d)return;d=true;ev.forEach(function(n){t.removeEventListener(n,fn)});h()};`,`ev.forEach(function(n){t.addEventListener(n,fn,{once:true,passive:true})})}`].join(``);if(t.startsWith(`media:`)){let e=t.slice(6);return[n,`try{var mq=globalThis.matchMedia(${JSON.stringify(e)});`,`if(mq.matches){h()}else{mq.addEventListener("change",function x(ev){`,`if(ev.matches){h();mq.removeEventListener("change",x)}},{once:true})}}catch(_){h()}`].join(``)}if(i&&a){let t=r?`,${JSON.stringify(r)}`:``;return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var dir=(${a});`,`dir(e,function(){h()}${t})}`].join(``)}return`${n}h();`}
@@ -23,6 +23,8 @@ export interface IslandProps {
23
23
  children?: JSX.Element | JSX.Element[] | string;
24
24
  /** Whether to render server-side (default: true unless condition is 'on:client') */
25
25
  ssr?: boolean;
26
+ /** Skip component SSR and mount on the client. Forces `ssr: false`. */
27
+ clientOnly?: boolean;
26
28
  /** Framework hint for client hydration */
27
29
  framework?: "solid" | "vue" | "preact" | "react" | "svelte" | "lit" | "qwik";
28
30
  /** Force SSR-only rendering without hydration */
@@ -31,6 +33,10 @@ export interface IslandProps {
31
33
  renderOptions?: AnalyzerOptions;
32
34
  /** Server island configuration — defers rendering to a server endpoint after page load */
33
35
  server?: ServerIslandProp;
36
+ /** Explicit HTML id for this island instance. Unique per instance when omitted. */
37
+ id?: string;
38
+ /** React/Preact list key — set on the host VNode, never serialized into props */
39
+ key?: string | number;
34
40
  }
35
41
  /**
36
42
  * Parameters for framework-specific render functions