/* Client-side hydration runtime. * * Scans the document for `[data-wompo-island]` markers emitted by the SSR serializer and * schedules each island's hydration based on its `data-wompo-mode`: * - `load` → immediately * - `idle` → requestIdleCallback (fallback setTimeout 1ms) * - `visible` → IntersectionObserver with a 200px rootMargin * * The component class for each island must already be registered via `defineWompo` (i.e. the * JS chunk that defines the component has been loaded). For lazy island chunks the framework * registers a global `__WOMPRO_ISLANDS` map of `tagName -> moduleUrl` which we dynamic-import * on demand. Without that map (plain library usage), an unknown island is logged and skipped. */ import { registeredComponents } from './public-api.js'; import { parse as devalueParse } from '../ssr/devalue.js'; import type { WompoElement, WompoProps } from './types.js'; type IslandMode = 'load' | 'idle' | 'visible'; declare global { interface Window { __WOMPRO_ISLANDS?: Record; } } /** Hydrate every island found under `root`. Idempotent: re-running on a tree that's already * hydrated is a no-op (the `data-wompo-island` attribute is removed once hydration completes). */ export function hydrate(root: Document | Element = document): void { const els = (root as Document | Element).querySelectorAll('[data-wompo-island]'); for (const el of Array.from(els)) { const tagName = el.tagName.toLowerCase(); const mode = (el.getAttribute('data-wompo-mode') as IslandMode | null) || 'load'; scheduleHydrate(el as HTMLElement, tagName, mode); } } function scheduleHydrate(el: HTMLElement, tagName: string, mode: IslandMode): void { const run = () => hydrateOne(el, tagName); if (mode === 'load') { run(); } else if (mode === 'idle') { if (typeof (window as any).requestIdleCallback === 'function') { (window as any).requestIdleCallback(run, { timeout: 2000 }); } else { setTimeout(run, 1); } } else if (mode === 'visible') { if (typeof IntersectionObserver === 'function') { const io = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { io.disconnect(); run(); break; } } }, { rootMargin: '200px' }, ); io.observe(el); } else { run(); } } else { run(); } } function hydrateOne(el: HTMLElement, tagName: string): void { // Read props payload from the first