@useavalon/avalon 0.3.2-canary.14 → 0.3.2-canary.16
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/src/build/island-manifest.d.ts +3 -0
- package/dist/src/build/island-manifest.js +1 -1
- package/dist/src/client/custom-directives.js +1 -1
- package/dist/src/client/hydrate-runtime.d.ts +29 -0
- package/dist/src/client/hydrate-runtime.js +1 -0
- package/dist/src/client/hydration-mode.d.ts +11 -0
- package/dist/src/client/hydration-mode.js +1 -0
- package/dist/src/client/main-slim.js +1 -0
- package/dist/src/client/main.js +2 -15
- package/dist/src/client/router/a11y.d.ts +9 -0
- package/dist/src/client/router/a11y.js +1 -0
- package/dist/src/client/router/eligibility.d.ts +32 -0
- package/dist/src/client/router/eligibility.js +1 -0
- package/dist/src/client/router/events.d.ts +33 -0
- package/dist/src/client/router/events.js +1 -0
- package/dist/src/client/router/forms.d.ts +25 -0
- package/dist/src/client/router/forms.js +1 -0
- package/dist/src/client/router/index.d.ts +20 -0
- package/dist/src/client/router/index.js +1 -0
- package/dist/src/client/router/opt-out.d.ts +30 -0
- package/dist/src/client/router/opt-out.js +1 -0
- package/dist/src/client/router/persist.d.ts +21 -0
- package/dist/src/client/router/persist.js +1 -0
- package/dist/src/client/router/prefetch.d.ts +25 -0
- package/dist/src/client/router/prefetch.js +1 -0
- package/dist/src/client/router/swap.d.ts +21 -0
- package/dist/src/client/router/swap.js +1 -0
- package/dist/src/client/router/transitions.d.ts +17 -0
- package/dist/src/client/router/transitions.js +1 -0
- package/dist/src/client/server-islands-boot.d.ts +12 -0
- package/dist/src/client/server-islands-boot.js +1 -0
- package/dist/src/client/types/vite-virtual-modules.d.ts +7 -0
- package/dist/src/islands/island.d.ts +15 -3
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/types.d.ts +2 -0
- package/dist/src/nitro/renderer.d.ts +2 -1
- package/dist/src/nitro/renderer.js +20 -20
- package/dist/src/nitro/types.d.ts +5 -0
- package/dist/src/post-build/cloudflare-worker-patch.js +1 -1
- package/dist/src/post-build/index.d.ts +6 -0
- package/dist/src/post-build/index.js +3 -3
- package/dist/src/schemas/routing/index.d.ts +2 -0
- package/dist/src/schemas/routing.d.ts +1 -0
- package/dist/src/schemas/routing.js +1 -1
- package/dist/src/server-islands/encryption.js +1 -1
- package/dist/src/server-islands/endpoint.js +3 -3
- package/dist/src/server-islands/renderer.js +6 -5
- package/dist/src/server-islands/route.d.ts +3 -1
- package/dist/src/server-islands/route.js +1 -1
- package/dist/src/types/island-prop.d.ts +12 -3
- package/dist/src/types/virtual-modules.d.ts +17 -6
- package/dist/src/types/vite-env.d.ts +4 -0
- package/dist/src/vite-plugin/config.js +1 -1
- package/dist/src/vite-plugin/nitro-integration.js +18 -18
- package/dist/src/vite-plugin/plugin.d.ts +2 -0
- package/dist/src/vite-plugin/plugin.js +1 -1
- package/dist/src/vite-plugin/server-islands-plugin.js +1 -1
- package/dist/src/vite-plugin/ssr-dom-shim-module.d.ts +20 -2
- package/dist/src/vite-plugin/ssr-dom-shim-module.js +90 -19
- package/dist/src/vite-plugin/types.d.ts +12 -0
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as e}from"node:fs/promises";import{createIslandRegistry as t,getQualifiedIslandName as n}from"../islands/discovery/index.js";export async function generateIslandManifest(){let r={},s=process.cwd();try{let c=await t(s),l=c.getAllIslands(),u=c.directories,d=c.collisions;for(let t of l){let s=n(t),c=`/${t.relativePath}`,l=await e(t.filePath,`utf-8`),u=i(t.framework),d=a(l),f=await o(l);r[s]={src:c,bundle:t.namespace===``?`/islands/${t.name}.${f}.js`:`/islands/${s}.${f}.js`,hash:f,framework:u,deps:d,namespace:t.namespace,qualifiedName:s,sourceDirectory:t.directory.relativePath}}return{islands:r,directories:u,collisions:d,version:`1.0.0`,buildTime:Date.now()}}catch(e){return console.warn(`Failed to generate island manifest:`,e),{islands:{},directories:[],collisions:[],version:`1.0.0`,buildTime:Date.now()}}}function i(e){switch(e){case`preact`:return`preact`;case`react`:return`react`;case`solid`:return`solid`;case`vue`:return`vue`;case`svelte`:return`svelte`;case`lit`:return`lit`;case`qwik`:return`qwik`;default:return`unknown`}}function a(e){let t=[],n=/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;for(let r=n.exec(e);r!==null;r=n.exec(e)){let e=r[1];!e.startsWith(`.`)&&!e.startsWith(`/`)&&t.push(e)}return[...new Set(t)]}async function o(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``).slice(0,8)}export async function loadIslandManifest(){try{let t=await e(`dist/island-manifest.json`,`utf-8`);return JSON.parse(t)}catch(e){return console.warn(`Failed to load island manifest:`,e),null}}export function getIslandBundlePath(e,t){let n=globalThis.
|
|
1
|
+
import{readFile as e}from"node:fs/promises";import{createIslandRegistry as t,getQualifiedIslandName as n}from"../islands/discovery/index.js";export async function generateIslandManifest(){let r={},s=process.cwd();try{let c=await t(s),l=c.getAllIslands(),u=c.directories,d=c.collisions;for(let t of l){let s=n(t),c=`/${t.relativePath}`,l=await e(t.filePath,`utf-8`),u=i(t.framework),d=a(l),f=await o(l);r[s]={src:c,bundle:t.namespace===``?`/islands/${t.name}.${f}.js`:`/islands/${s}.${f}.js`,hash:f,framework:u,deps:d,namespace:t.namespace,qualifiedName:s,sourceDirectory:t.directory.relativePath}}return{islands:r,directories:u,collisions:d,version:`1.0.0`,buildTime:Date.now()}}catch(e){return console.warn(`Failed to generate island manifest:`,e),{islands:{},directories:[],collisions:[],version:`1.0.0`,buildTime:Date.now()}}}function i(e){switch(e){case`preact`:return`preact`;case`react`:return`react`;case`solid`:return`solid`;case`vue`:return`vue`;case`svelte`:return`svelte`;case`lit`:return`lit`;case`qwik`:return`qwik`;default:return`unknown`}}function a(e){let t=[],n=/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;for(let r=n.exec(e);r!==null;r=n.exec(e)){let e=r[1];!e.startsWith(`.`)&&!e.startsWith(`/`)&&t.push(e)}return[...new Set(t)]}async function o(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``).slice(0,8)}export async function loadIslandManifest(){try{let t=await e(`dist/island-manifest.json`,`utf-8`);return JSON.parse(t)}catch(e){return console.warn(`Failed to load island manifest:`,e),null}}export function getIslandBundlePath(e,t){let n=typeof globalThis.__avalonIsDev==`boolean`?globalThis.__avalonIsDev:process.env.NODE_ENV!==`production`,r=process.cwd();if(r&&e.startsWith(r+`/`)&&(e=e.slice(r.length)),t){let n=l(e),r=t;if(r.islands[n])return r.islands[n].bundle;let i=e.replace(/^\/islands\//,``).replace(/\.(tsx?|jsx?)$/,``),a=t.islands[i];if(a)return a.bundle}return n?e.startsWith(`/islands/`)?e.replaceAll(`/islands/`,`/src/islands/`):e.startsWith(`/src/`)||e.startsWith(`/app/`)||e.startsWith(`/`)?e:`/src/${e}`:`/islands/${l(e)}.js`}function l(e){let t=e.replace(/^\//,``);t=t.replace(/\.(tsx?|jsx?)$/,``);let n=new RegExp(/^src\/(.+)\/islands\/([^/]+)$/).exec(t);if(n){let[,e,t]=n;return`${e}/${t}`}let r=new RegExp(/^(?:src\/)?islands\/([^/]+)$/).exec(t);return r?r[1]:t}export function getIslandEntry(e,t){if(t.islands[e])return t.islands[e];if(t.directories){for(let[n,r]of Object.entries(t.islands))if(n.split(`/`).pop()===e)return r}return null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=new Map;export function registerClientDirective(t,n){e.set(t,n)}export function hasClientDirective(t){return e.has(t)}export function executeCustomDirective(t,n,r){let i=t.dataset.conditionArg
|
|
1
|
+
const e=new Map;export function registerClientDirective(t,n){e.set(t,n)}export function hasClientDirective(t){return e.has(t)}export function executeCustomDirective(t,n,r){let i=e.get(n),a=t.dataset.conditionArg;if(i)return i(t,r,a),!0;let o=t.dataset.directiveScript;if(!o)return!1;try{let e=Function(`return (${o})`)();return typeof e==`function`?(e(t,r,a),!0):!1}catch(e){return console.warn(`[avalon] Failed to execute custom directive "${n}":`,e),!1}}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-entrant island hydrator.
|
|
3
|
+
*
|
|
4
|
+
* First load and client navigations both call {@link scanAndHydrate}.
|
|
5
|
+
* {@link disposeIslands} tears down every island in a root before a DOM swap.
|
|
6
|
+
*/
|
|
7
|
+
export type HydrateFn = (el: HTMLElement, Component: unknown, props: Record<string, unknown>) => void | Promise<void>;
|
|
8
|
+
export type UnmountFn = (el: HTMLElement) => void | Promise<void>;
|
|
9
|
+
export interface IntegrationModule {
|
|
10
|
+
hydrate?: HydrateFn;
|
|
11
|
+
unmount?: UnmountFn;
|
|
12
|
+
preLitHydration?: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface HydrationLoader {
|
|
15
|
+
loadIntegrationModule: (framework: string) => Promise<IntegrationModule>;
|
|
16
|
+
preLitHydration?: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Test seam — production uses `virtual:avalon/integration-loader`. */
|
|
19
|
+
export declare function setHydrationLoader(loader: HydrationLoader | null): void;
|
|
20
|
+
/**
|
|
21
|
+
* Discover `[data-framework]` islands under `root` and hydrate them.
|
|
22
|
+
* Safe to call after a body swap. Already-hydrated islands are skipped.
|
|
23
|
+
*/
|
|
24
|
+
export declare function scanAndHydrate(root?: ParentNode): void;
|
|
25
|
+
/**
|
|
26
|
+
* Dispose every island under `root`: observers, idle timers, and framework roots.
|
|
27
|
+
* No skip list — the client router extracts persist nodes *before* calling this.
|
|
28
|
+
*/
|
|
29
|
+
export declare function disposeIslands(root?: ParentNode): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{executeCustomDirective as e,hasClientDirective as t}from"./custom-directives.js";const n=new WeakMap;let r=null;export function setHydrationLoader(e){r=e}async function a(){return r||import(`virtual:avalon/integration-loader`)}function o(e,t){let r=n.get(e)??[];r.push(t),n.set(e,r)}function s(e){let t=n.get(e);if(t){for(let e of t)try{e()}catch(e){console.warn(`[avalon] Island cleanup failed:`,e)}n.delete(e)}}function c(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function l(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src;if(!n){console.warn(`Island missing data-src attribute`);return}try{let r=e.dataset.props?JSON.parse(e.dataset.props):{},i=await a();t===`lit`&&await(i.preLitHydration?.()??i.loadIntegrationModule(`lit`).then(e=>e.preLitHydration?.()));let o=c(await import(n),n),s=await i.loadIntegrationModule(t);if(!s.hydrate||typeof s.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);await s.hydrate(e,o,r),e.dataset.hydrated=`true`}catch(r){let i=r instanceof Error?r.message:String(r);console.error(`Hydration error for ${t} island ${n}:`,r),e.dataset.hydrationStatus=`failed`,e.dataset.renderStrategy=`ssr-only`,e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:i,timestamp:Date.now()},bubbles:!0}))}}function u(e,t){try{let n=new IntersectionObserver(r=>{r[0]?.isIntersecting&&(l(e,t),n.disconnect())},{rootMargin:`50px`,threshold:0});n.observe(e.firstElementChild||e),o(e,()=>n.disconnect())}catch(n){console.error(`Failed to setup intersection observer:`,n),l(e,t)}}function d(e,t){let n=[`click`,`touchstart`,`mouseenter`,`focusin`],r=!1,i=e.firstElementChild||e,a=()=>{if(!r){r=!0;for(let e of n)i.removeEventListener(e,a);l(e,t)}};try{for(let e of n)i.addEventListener(e,a,{once:!0,passive:!0});o(e,()=>{r=!0;for(let e of n)i.removeEventListener(e,a)})}catch(n){console.error(`Failed to setup interaction observer:`,n),l(e,t)}}function f(e,t){try{if(`requestIdleCallback`in globalThis){let n=globalThis.requestIdleCallback(()=>{l(e,t)},{timeout:5e3});o(e,()=>globalThis.cancelIdleCallback?.(n));return}let n=setTimeout(()=>{l(e,t)},200);o(e,()=>clearTimeout(n))}catch(n){console.error(`Failed to setup idle callback:`,n),l(e,t)}}function p(e,t,n){try{let r=globalThis.matchMedia(n);if(r.matches){l(e,t);return}let i=n=>{n.matches&&(l(e,t),r.removeEventListener(`change`,i))};r.addEventListener(`change`,i),o(e,()=>r.removeEventListener(`change`,i))}catch(r){console.error(`Failed to setup media query:`,n,r),l(e,t)}}function m(n){let r=n.dataset.framework,i=n.dataset.condition||`on:client`,a=n.dataset.renderStrategy;if(r&&a!==`ssr-only`&&!n.dataset.hydrated){if(i===`on:client`){l(n,r);return}if(i===`on:visible`){u(n,r);return}if(i===`on:interaction`){d(n,r);return}if(i===`on:idle`){f(n,r);return}if(i.startsWith(`media:`)){p(n,r,i.slice(6));return}if(n.dataset.customDirective||n.dataset.directiveScript||t(i)){e(n,i,()=>{l(n,r)})||(console.warn(`[avalon] Unknown hydration condition: "${i}". Hydrating immediately.`),l(n,r));return}l(n,r)}}export function scanAndHydrate(e=document){let t=e.querySelectorAll(`[data-framework]`);for(let e of t)try{m(e)}catch(e){console.error(`Error processing island:`,e)}}export async function disposeIslands(e=document){let t=[...e.querySelectorAll(`[data-framework]`)],n=t.length>0?await a():null;for(let e of t){s(e);let t=e.dataset.framework;if(t&&n)try{let r=await n.loadIntegrationModule(t);typeof r.unmount==`function`&&await r.unmount(e)}catch(e){console.warn(`[avalon] Failed to unmount ${t} island:`,e)}delete e.dataset.hydrated,delete e.dataset.hydrationStatus,delete e.dataset.hydrationError,delete e.dataset.litHydrated}}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vs per-island hydration.
|
|
3
|
+
*
|
|
4
|
+
* Dev always uses a long-lived entry-client runtime (HMR). Production uses
|
|
5
|
+
* per-island inline scripts unless `clientRouter` is on — the router must
|
|
6
|
+
* re-scan islands after a DOM swap, which inline scripts cannot do.
|
|
7
|
+
*/
|
|
8
|
+
export type HydrationMode = "entry-client" | "per-island";
|
|
9
|
+
export declare function resolveHydrationMode(isDev: boolean, clientRouter: boolean): HydrationMode;
|
|
10
|
+
/** Import lines for `virtual:avalon/client-entry` based on hydration mode. */
|
|
11
|
+
export declare function clientEntryImportLines(isDev: boolean, clientRouter: boolean): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveHydrationMode(e,t){return e||t?`entry-client`:`per-island`}export function clientEntryImportLines(t,n){let r=[];if(resolveHydrationMode(t,n)===`entry-client`){let e=t?`@useavalon/avalon/client/main`:`@useavalon/avalon/client/main-slim`;r.push(`import '${e}';`)}else r.push(`// Per-island hydration mode — no shared runtime needed`);return n&&r.push(`import '@useavalon/avalon/client/router';`),r}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{scanAndHydrate as e}from"./hydrate-runtime.ts";import{bootServerIslands as t}from"./server-islands-boot.ts";function n(){e(),t()}document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,n):n();
|
package/dist/src/client/main.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import{loadHMRAdapter as e,loadIntegrationModule as t,preLitHydration as n}from"virtual:avalon/integration-loader";import{
|
|
2
|
-
position: absolute;
|
|
3
|
-
top: 0;
|
|
4
|
-
right: 0;
|
|
5
|
-
background: #ff4444;
|
|
6
|
-
color: white;
|
|
7
|
-
padding: 4px 8px;
|
|
8
|
-
font-size: 11px;
|
|
9
|
-
font-family: monospace;
|
|
10
|
-
border-radius: 0 0 0 4px;
|
|
11
|
-
z-index: 9999;
|
|
12
|
-
cursor: pointer;
|
|
13
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
14
|
-
`,i.textContent=`❌ ${t}`,i.title=`Hydration failed: ${n}\n${r.message}\nClick for details`,i.addEventListener(`click`,()=>{alert(`Hydration Error\n\nFramework: ${t}\nComponent: ${n}\n\nError: ${r.message}\n\nStack:\n${r.stack}`)}),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(i)}function h(){return import.meta.env?.DEV||import.meta.env?.MODE===`development`||globalThis.location?.hostname===`localhost`||globalThis.location?.hostname===`127.0.0.1`}function g(e){let t=e.dataset.framework,n=e.dataset.src;if(!n)return null;let r={framework:t,src:n,props:e.dataset.props,scrollPosition:{x:globalThis.scrollX,y:globalThis.scrollY},focusedElement:document.activeElement?.id||null};try{t===`vue`&&e.__vue__?r.vueData=structuredClone(e.__vue__.$data||{}):t===`svelte`&&e.__svelte__?r.svelteState=e.__svelte__:t===`lit`&&e.tagName?.includes(`-`)&&(e.querySelector(`[data-lit-element]`)||e)._$litElement$&&(r.litProperties={})}catch(e){console.warn(`Failed to preserve island state:`,e)}return r}function _(e,t){if(t)try{if(t.scrollPosition&&globalThis.scrollTo(t.scrollPosition.x,t.scrollPosition.y),t.focusedElement){let e=document.getElementById(t.focusedElement);e&&e.focus()}e.dataset.framework===`vue`&&t.vueData&&e.__vue__&&Object.assign(e.__vue__.$data,t.vueData)}catch(e){console.warn(`Failed to restore island state:`,e)}}async function v(e,r,i,a){let o=e.dataset.props,s=o?JSON.parse(o):{};r===`lit`&&await n();let c=d(await import(i),a),l=await t(r);if(!l.hydrate||typeof l.hydrate!=`function`)throw Error(`Integration ${r} does not export a hydrate function`);l.hydrate(e,c,s),e.dataset.hydrated=`true`}function y(e,t,n,r){let i=e.querySelector(`.hmr-error-indicator`);i&&i.remove();let a=document.createElement(`div`);a.className=`hmr-error-indicator`,a.style.cssText=`
|
|
1
|
+
import{loadHMRAdapter as e,loadIntegrationModule as t,preLitHydration as n}from"virtual:avalon/integration-loader";import{scanAndHydrate as r,setHydrationLoader as i}from"./hydrate-runtime.ts";import{bootServerIslands as a}from"./server-islands-boot.ts";i({loadIntegrationModule:t,preLitHydration:n}),document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,o):o();function o(){r(),a()}function s(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`&&t.prototype){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}function c(){return import.meta.env?.DEV||import.meta.env?.MODE===`development`||globalThis.location?.hostname===`localhost`||globalThis.location?.hostname===`127.0.0.1`}function l(e){let t=e.dataset.framework,n=e.dataset.src;if(!n)return null;let r={framework:t,src:n,props:e.dataset.props,scrollPosition:{x:globalThis.scrollX,y:globalThis.scrollY},focusedElement:document.activeElement?.id||null};try{t===`vue`&&e.__vue__?r.vueData=structuredClone(e.__vue__.$data||{}):t===`svelte`&&e.__svelte__?r.svelteState=e.__svelte__:t===`lit`&&e.tagName?.includes(`-`)&&(e.querySelector(`[data-lit-element]`)||e)._$litElement$&&(r.litProperties={})}catch(e){console.warn(`Failed to preserve island state:`,e)}return r}function u(e,t){if(t)try{if(t.scrollPosition&&globalThis.scrollTo(t.scrollPosition.x,t.scrollPosition.y),t.focusedElement){let e=document.getElementById(t.focusedElement);e&&e.focus()}e.dataset.framework===`vue`&&t.vueData&&e.__vue__&&Object.assign(e.__vue__.$data,t.vueData)}catch(e){console.warn(`Failed to restore island state:`,e)}}async function d(e,r,i,a){let o=e.dataset.props,c=o?JSON.parse(o):{};r===`lit`&&await n();let l=s(await import(i),a),u=await t(r);if(!u.hydrate||typeof u.hydrate!=`function`)throw Error(`Integration ${r} does not export a hydrate function`);u.hydrate(e,l,c),e.dataset.hydrated=`true`}function f(e,t,n,r){let i=e.querySelector(`.hmr-error-indicator`);i&&i.remove();let a=document.createElement(`div`);a.className=`hmr-error-indicator`,a.style.cssText=`
|
|
15
2
|
position: absolute;
|
|
16
3
|
top: 0;
|
|
17
4
|
left: 0;
|
|
@@ -36,4 +23,4 @@ import{loadHMRAdapter as e,loadIntegrationModule as t,preLitHydration as n}from"
|
|
|
36
23
|
cursor: pointer;
|
|
37
24
|
font-size: 14px;
|
|
38
25
|
line-height: 1;
|
|
39
|
-
`,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),
|
|
26
|
+
`,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),await p(),h());async function p(){try{let{initializeHMR:t,getHMRCoordinator:n}=await import(`./hmr-coordinator.js`);t();let r=n(),i=new Set;document.querySelectorAll(`[data-framework]`).forEach(e=>{let t=e.dataset.framework;t&&i.add(t)});for(let t of i)try{let n=await e(t);n&&r.registerAdapter(t,n)}catch(e){console.warn(`[HMR] Failed to load adapter for ${t}:`,e)}}catch(e){console.error(`[HMR] Failed to initialize:`,e)}}async function m(e,t,n,r){try{let{showHMRErrorOverlay:e}=await import(`./hmr-error-overlay.js`);e({framework:t,src:n,error:r,filePath:n})}catch{f(e,t,n,r)}}function h(){if(!import.meta.hot)return;let e=new Map;async function t(e){let t=e.replaceAll(`\\`,`/`),r=document.querySelectorAll(`[data-src*="${t}"], [data-src$="${t}"]`);if(r.length===0){let e=document.querySelectorAll(`[data-src]`);for(let r of e){let e=r.dataset.src;e&&(e.includes(t)||t.includes(e.replace(/^\//,``)))&&await n(r)}return}for(let e of r)await n(e)}async function n(t){let n=t.dataset.framework,r=t.dataset.src;if(!(!r||!n))try{let i=l(t);e.set(r,i),delete t.dataset.hydrated,delete t.dataset.hydrationStatus;let a=t.querySelector(`.hydration-error-indicator`);a&&a.remove();let o=Date.now();await d(t,n,r.includes(`?`)?`${r}&t=${o}`:`${r}?t=${o}`,r);let s=e.get(r);s&&(u(t,s),e.delete(r)),t.dispatchEvent(new CustomEvent(`hmr-update`,{detail:{framework:n,src:r,timestamp:Date.now(),success:!0},bubbles:!0}))}catch(e){console.error(`[HMR] Failed for ${n} island ${r}:`,e),t.dispatchEvent(new CustomEvent(`hmr-error`,{detail:{framework:n,src:r,error:e.message,timestamp:Date.now()},bubbles:!0})),c()&&m(t,n,r,e)}}import.meta.hot.on(`vite:beforeUpdate`,e=>{for(let n of e.updates||[]){let e=n.path||n.acceptedPath;e&&(e.includes(`/islands/`)||e.includes(`\\islands\\`))&&t(e)}}),import.meta.hot.on(`vite:beforeFullReload`,()=>{let e=document.querySelectorAll(`[data-hydrated="true"]`),t={};for(let n of e){let e=n.dataset.src;e&&(t[e]=l(n))}try{sessionStorage.setItem(`__avalon_hmr_states__`,JSON.stringify(t))}catch{}});try{let e=sessionStorage.getItem(`__avalon_hmr_states__`);if(e){let t=JSON.parse(e);sessionStorage.removeItem(`__avalon_hmr_states__`),setTimeout(()=>{for(let[e,n]of Object.entries(t)){let t=document.querySelector(`[data-src="${e}"]`);t&&n&&u(t,n)}},100)}}catch{}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ensureRouteAnnouncer(doc?: Document): HTMLElement;
|
|
2
|
+
export declare function announceRoute(title: string, doc?: Document): void;
|
|
3
|
+
export declare function restoreFocus(doc?: Document): void;
|
|
4
|
+
export interface ScrollPosition {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function readScroll(): ScrollPosition;
|
|
9
|
+
export declare function applyScroll(position: ScrollPosition | "top", hash?: string): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`avalon-router-announce`;export function ensureRouteAnnouncer(t=document){let n=t.getElementById(e);return n||(n=t.createElement(`p`),n.id=e,n.setAttribute(`aria-live`,`polite`),n.setAttribute(`aria-atomic`,`true`),n.style.cssText=`position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0`,t.body.appendChild(n),n)}export function announceRoute(e,n=document){let r=ensureRouteAnnouncer(n);r.textContent=``,r.textContent=e}export function restoreFocus(e=document){let t=e.querySelector(`[autofocus]`);if(t){t.focus();return}let n=e.querySelector(`main`);if(n){n.hasAttribute(`tabindex`)||(n.tabIndex=-1),n.focus({preventScroll:!0});return}e.body.tabIndex=-1,e.body.focus({preventScroll:!0})}export function readScroll(){return{x:window.scrollX,y:window.scrollY}}export function applyScroll(e,t){if(t){let e=decodeURIComponent(t.slice(1)),n=document.getElementById(e);if(n){n.scrollIntoView();return}}if(e===`top`){window.scrollTo(0,0);return}window.scrollTo(e.x,e.y)}
|
|
@@ -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 } 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{extractPersisted as w,leftoverPersisted as T,restorePersisted as E}from"./persist.js";import{getCachedOrInflight as D,installPrefetchListeners as O,putCachedDocument as k,ROUTER_FETCH_HEADERS as A}from"./prefetch.js";import{copyHtmlAttributes as j,isHtmlResponse as M,parseHtmlDocument as N,reconcileHead as P,replaceBody as F}from"./swap.js";import{viewTransitionFromDataset as I,withViewTransition as L}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}from"./swap.js";let R=!1,z=!1,B=null;function V(){return location.pathname+location.search+location.hash}function H(){let e={...history.state,avalon:!0,scroll:a()};history.replaceState(e,``)}async function U(t,n){let r=T(t,n);if(r.length===0)return;let i=document.createDocumentFragment();for(let e of r)i.appendChild(e);await e(i)}function W(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 G(e,t){return I(t?.dataset.routerTransition)??I(e.dataset.routerTransition)}async function K(i,a,s,c,l){let d=V(),p=a.pathname+a.search+a.hash;if(x(i))throw new y(p);f({from:d,to:p,newDocument:i});let h=w(document.body);await e(document.body),await L(async()=>{P(document,i),j(document,i),F(document,i),await U(h,E(document.body,h)),W(s,p,c)},l),u({from:d,to:p}),t(document.body),await n(document.body),r(document.title),o(),m(p)}async function q(e,t){let n=new URL(e,location.href),r=D(n),i=r instanceof Promise?await r:r;if(i){if(C(i.html))throw new y(i.finalUrl);return{doc:N(i.html),finalUrl:new URL(i.finalUrl,location.href)}}let a=await fetch(e,{headers:A,redirect:`follow`,signal:t}),o=await Y(a),s=new URL(a.url||e,location.href);return k(n,o,s.href),{doc:N(o),finalUrl:s}}async function J(e,t,n){let r=await fetch(e.href,{method:`POST`,body:t,headers:A,redirect:`follow`,signal:n}),i=await Y(r),a=new URL(r.url||e.href,location.href);return{doc:N(i),finalUrl:a}}async function Y(e){if(S(e.headers))throw new y(e.url);if(!M(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 X(e,t,n,r){if(z&&B?.abort(),b())throw new y(e.href);let i=t.history??(n?`auto`:`push`),a=V(),o=e.pathname+e.search+e.hash;if(!n&&!d({from:a,to:o,history:i}))return;n||H(),z=!0,h(!0),B=new AbortController;let s=B.signal;try{let{doc:a,finalUrl:o}=await(r?r(s):q(e.href,s));await K(a,o,i===`auto`?`push`:i,n,t.viewTransition),Z(e,n,t.scroll)}catch(e){if(s.aborted)return;throw e instanceof y||p(o,e),e}finally{s.aborted||(z=!1,h(!1))}}function Z(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 X(n,t,!1)}catch{Q(n.href)}}function $(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:G(n)}).catch(()=>Q(r.href)))}function ee(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=G(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);X(r.url,{history:`push`,viewTransition:i},!1,e=>J(r.url,a,e)).catch(()=>{t.submit()})}function te(){let e=new URL(location.href);X(e,{history:`auto`},!0).catch(()=>Q(e.href))}function ne(e){return c(l(e),{defaultPrevented:!1,button:0,metaKey:!1,ctrlKey:!1,shiftKey:!1,altKey:!1},location)}export function installClientRouter(){R||typeof document>`u`||(R=!0,history.scrollRestoration=`manual`,document.addEventListener(`click`,$),document.addEventListener(`submit`,ee),window.addEventListener(`popstate`,te),O(ne))}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,21 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* Detach persist nodes from `root` so a body swap does not destroy them.
|
|
12
|
+
* Duplicate keys keep the first node. Nested persist nodes are ignored
|
|
13
|
+
* (the outer wrapper is the unit that moves).
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractPersisted(root: ParentNode): Map<string, HTMLElement>;
|
|
16
|
+
/**
|
|
17
|
+
* Replace matching stubs in the swapped document with the live nodes.
|
|
18
|
+
* Returns keys that were restored; leftover saved nodes should be disposed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function restorePersisted(root: ParentNode, saved: Map<string, HTMLElement>): Set<string>;
|
|
21
|
+
export declare function leftoverPersisted(saved: Map<string, HTMLElement>, restored: Set<string>): HTMLElement[];
|
|
@@ -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 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)}
|
|
@@ -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,{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}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,21 @@
|
|
|
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
|
+
* Script tags in the next document are not executed — server islands are
|
|
9
|
+
* booted by the shared runtime after the swap.
|
|
10
|
+
*/
|
|
11
|
+
export declare function reconcileHead(current: Document, next: Document): void;
|
|
12
|
+
export declare function copyHtmlAttributes(current: Document, next: Document): void;
|
|
13
|
+
/**
|
|
14
|
+
* Attach leftover `<template shadowrootmode>` nodes (older browsers, or a
|
|
15
|
+
* parser that did not instantiate Declarative Shadow DOM). Native DSD already
|
|
16
|
+
* consumed those templates; this is a no-op when `host.shadowRoot` exists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function attachDeclarativeShadowRoots(root: ParentNode): void;
|
|
19
|
+
export declare function replaceBody(current: Document, next: Document): void;
|
|
20
|
+
export declare function parseHtmlDocument(html: string): Document;
|
|
21
|
+
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 e instanceof HTMLElement}function c(e){return`dataset`in e?e.dataset.avalonCss??``:``}function l(t){if(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 u(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 d(e,t){let n=e.head.querySelectorAll(`meta`),r=[...t.head.querySelectorAll(`meta`)],a=new Set(r.map(e=>u(e)));for(let e of n)i(e,`charset`)||a.has(u(e))||e.remove();let o=new Set([...e.head.querySelectorAll(`meta`)].map(e=>u(e)));for(let t of r){if(i(t,`charset`))continue;let n=u(t);if(o.has(n)){[...e.head.querySelectorAll(`meta`)].find(e=>u(e)===n)?.replaceWith(e.importNode(t,!0));continue}e.head.appendChild(e.importNode(t,!0))}}function f(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 p(e,t){s(e)&&e.dataset.avalonCss!=null||t.has(a(e))||e.remove()}function m(e,t){return t.some(t=>o(t)===`modulepreload`&&a(t)===e)}function h(e,t,n){for(let r of e.head.querySelectorAll(`link`)){let e=o(r);if(e===`stylesheet`){p(r,t);continue}l(r)||e===`modulepreload`&&!m(a(r),n)&&r.remove()}}function g(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 _(e,n){for(let n of e.head.querySelectorAll(`link`)){let e=o(n);e===`stylesheet`||e===`modulepreload`||l(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,d(e,t);let{nextStyles:n,nextOther:r,nextLinks:i}=f(t);h(e,n,i),g(e,n),_(e,r),v(e,t)}function v(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 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,17 @@
|
|
|
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
|
+
export type ViewTransitionMode = boolean | string;
|
|
9
|
+
export interface ResolvedViewTransition {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
type?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function prefersReducedMotion(): boolean;
|
|
14
|
+
/** `data-router-transition` on a link, form, or submitter. Absent → default. */
|
|
15
|
+
export declare function viewTransitionFromDataset(raw: string | undefined): ViewTransitionMode | undefined;
|
|
16
|
+
export declare function resolveViewTransition(mode?: ViewTransitionMode): ResolvedViewTransition;
|
|
17
|
+
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}}function r(e,t){let n=document,r=n.startViewTransition?.bind(n);if(r){if(t)try{return r({update:e,types:[t]})}catch{}return r(e)}}export async function withViewTransition(t,i){let{enabled:a,type:o}=resolveViewTransition(i);if(!a||prefersReducedMotion()){await t();return}let s=!1,c=async()=>{s||(s=!0,await t())},l=document.documentElement,u=o?l.dataset.routerTransition:void 0;o&&(l.dataset.routerTransition=o);try{let e=r(c,o);if(!e){await c();return}await e.finished}catch{await c()}finally{o&&(u===void 0?delete l.dataset.routerTransition:l.dataset.routerTransition=u)}}
|
|
@@ -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
|
|
|
@@ -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,7 +29,7 @@ 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?:
|
|
32
|
+
children?: IslandChildren;
|
|
29
33
|
/** Whether to render server-side (default: true unless condition is 'on:client') */
|
|
30
34
|
ssr?: boolean;
|
|
31
35
|
/** Framework hint for client hydration */
|
|
@@ -42,6 +46,13 @@ export interface IslandProps {
|
|
|
42
46
|
server?: ServerIslandProp;
|
|
43
47
|
/** Island directive for combined server + client islands */
|
|
44
48
|
island?: IslandDirective;
|
|
49
|
+
/** Explicit HTML id for this island instance. Unique per instance when omitted. */
|
|
50
|
+
id?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Persist key for client navigation (`data-router-persist`).
|
|
53
|
+
* `true` uses `src` as the key so layout islands match across pages.
|
|
54
|
+
*/
|
|
55
|
+
persist?: string | true;
|
|
45
56
|
/** React/Preact list key — set on the host VNode, never serialized into props */
|
|
46
57
|
key?: string | number;
|
|
47
58
|
}
|
|
@@ -56,7 +67,7 @@ export declare function withListKey(key: unknown, vnode: JSX.Element): JSX.Eleme
|
|
|
56
67
|
* Uses custom elements instead of div wrappers for cleaner, more semantic markup.
|
|
57
68
|
* Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
|
|
58
69
|
*/
|
|
59
|
-
export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
|
|
70
|
+
export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, id, persist, }: IslandProps): JSX.Element;
|
|
60
71
|
/**
|
|
61
72
|
* Universal renderIsland function – auto-detects framework and handles SSR + hydration.
|
|
62
73
|
*
|
|
@@ -73,4 +84,5 @@ export default function Island({ src, condition, conditionArg, props, children,
|
|
|
73
84
|
* Error isolation: If SSR fails, returns an error placeholder instead of throwing,
|
|
74
85
|
* allowing the page to continue rendering other islands.
|
|
75
86
|
*/
|
|
76
|
-
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, key, }: IslandProps): Promise<JSX.Element>;
|
|
87
|
+
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, key, id, persist, }: IslandProps): Promise<JSX.Element>;
|
|
88
|
+
export {};
|