/** * @wrnexus/csr — the browser reactive runtime. * * Components are `.wrn` files rendered on the SERVER (see @wrnexus/dev-server) * and hydrated in the browser by this single, generic runtime — served once at * `/__wrnexus/reactive.js` for any page that contains a `data-scope`. There are * no per-component browser bundles: SSR stays cleanly separated from CSR. */ import { REACTIVE_RUNTIME } from "./reactive-runtime.ts"; import { NAV_RUNTIME } from "./nav-runtime.ts"; import { REALTIME_RUNTIME } from "./realtime-runtime.ts"; import { ACTION_RUNTIME } from "./action-runtime.ts"; export { REACTIVE_RUNTIME } from "./reactive-runtime.ts"; export { NAV_RUNTIME } from "./nav-runtime.ts"; export { REALTIME_RUNTIME } from "./realtime-runtime.ts"; export { ACTION_RUNTIME } from "./action-runtime.ts"; const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const; function runtimeForMode(development: boolean): string { if (development) { return REACTIVE_RUNTIME.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "").replace( /\/\*__WRNEXUS_DEV_END__\*\//g, "", ); } return REACTIVE_RUNTIME.replace( /\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g, "", ); } function controllerSection(source: string, name: (typeof CONTROLLER_SECTIONS)[number]): string { const pattern = new RegExp( `/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/([\\s\\S]*?)/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`, ); const match = pattern.exec(source); if (!match?.[1]) throw new Error(`Missing reactive runtime controller section: ${name}`); return match[1]; } function stripControllerSections(source: string): string { for (const name of CONTROLLER_SECTIONS) { source = source.replace( new RegExp( `/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/[\\s\\S]*?/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`, ), "", ); } return source; } const CONTROLLER_LOADER = String.raw` var componentControllerSelector = "[data-wrn-anchored],[data-wrn-dialog],[data-wrn-roving],[data-wrn-scrollspy],[data-wrn-splitter],[data-wrn-navbar],[data-wrn-preferences],[data-wrn-select],[data-wrn-pin-input]"; var componentControllerPromise = null; var componentControllerUrl = (document.currentScript && document.currentScript.src) ? new URL("./controllers.js", document.currentScript.src).href : "/__wrnexus/controllers.js"; function needsComponentControllers(root) { var host = root || document; return !!((host.matches && host.matches(componentControllerSelector)) || (host.querySelector && host.querySelector(componentControllerSelector))); } function loadComponentControllers(root) { if (!needsComponentControllers(root)) return; if (window.__wrnexusComponentControllers) { window.__wrnexusComponentControllers.hydrate(root || document); return; } if (!componentControllerPromise) { componentControllerPromise = new Promise(function (resolve, reject) { var script = document.createElement("script"); script.src = componentControllerUrl; script.defer = true; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } componentControllerPromise.then(function () { if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.hydrate(root || document); }).catch(function (error) { console.error("[wrnexus] failed to load component controllers", error); }); } `; /** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */ export function getReactiveRuntime(development = false): string { let source = stripControllerSections(runtimeForMode(development)); source = source.replace( " function hydrateScopes(root) {", `${CONTROLLER_LOADER}\n function hydrateScopes(root) {`, ); source = source.replace( " hydrateNavbarControllers(host);\n hydratePreferenceControllers(host);\n hydrateSelectControllers(host);\n hydratePinInputControllers(host);", " loadComponentControllers(host);", ); source = source.replace(" startDocumentWatch();\n", ""); source = source.replace( " setupAnchoredOverlays();\n setupModalDialogs();\n setupRovingFocus();\n setupScrollspy();\n setupSplitters();", " loadComponentControllers(document);", ); source = source.replace( " window.__wrnexusRepositionAnchored = repositionAnchored;", " window.__wrnexusRepositionAnchored = function (root) { if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.reposition(root); };", ); source = source.replace( " window.__wrnexusMountClientRoots = mountClientRoots;", ` window.__wrnexusControllerBridge = { invokeComponentOutput: invokeComponentOutput, callServerFunction: callServerFunction, dispatchComponentEvent: dispatchComponentEvent, emitPinInputEvent: emitPinInputEvent, parseScopeDecl: parseScopeDecl, warn: ${development ? "warnOnce" : "function () {}"} };\n window.__wrnexusMountClientRoots = mountClientRoots;`, ); return source; } /** Component-specific controllers, loaded only when their marker is present. */ export function getComponentControllerRuntime(development = false): string { const source = runtimeForMode(development); const sections = CONTROLLER_SECTIONS.map((name) => controllerSection(source, name)).join("\n"); return ` (function () { "use strict"; var bridge = window.__wrnexusControllerBridge || {}; var invokeComponentOutput = bridge.invokeComponentOutput; var callServerFunction = bridge.callServerFunction; var dispatchComponentEvent = bridge.dispatchComponentEvent; var emitPinInputEvent = bridge.emitPinInputEvent; var parseScopeDecl = bridge.parseScopeDecl; var warnOnce = bridge.warn || function () {}; ${sections} function hydrate(root) { var host = root || document; hydrateNavbarControllers(host); hydratePreferenceControllers(host); hydrateSelectControllers(host); hydratePinInputControllers(host); syncRovingGroups(host); } setupAnchoredOverlays(); setupModalDialogs(); setupRovingFocus(); setupScrollspy(); setupSplitters(); window.__wrnexusComponentControllers = { hydrate: hydrate, reposition: repositionAnchored }; hydrate(document); })(); `.trim(); } /** The client-side navigation runtime served at `/__wrnexus/nav.js`. */ export function getNavRuntime(): string { return NAV_RUNTIME; } /** The realtime client runtime served at `/__wrnexus/realtime.js`. */ export function getRealtimeRuntime(): string { return REALTIME_RUNTIME; } export function getActionRuntime(): string { return ACTION_RUNTIME; } export * from "./outputs.ts"; export * from "./server-client.ts"; export * from "./refs.ts"; export * from "./client-functions.ts"; export * from "./actions.ts"; export type * from "./types.ts";