Files
WRNexusJS/packages/dev-server/src/partial-build.ts
T
ClintchizandClaude Opus 5 949cf78636
Quality / quality (ubuntu-latest) (push) Failing after 13m40s
Quality / quality (windows-latest) (push) Canceled after 0s
feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs
DataTable replaces the 20-line Table scaffold entirely: columns, sorting,
filtering, pagination, selection, bulk actions, comparison layout, sticky
first column, custom HTML cells, and a remote source driven by a `request`
output rather than a function prop (props travel as HTML attributes, so a
function arrives as its own source text).

Toaster replaces the hand-rolled status div: tone icons, actions, hover
pause/resume and a progress bar.

Overlays audit -- Modal and Drawer declared aria-modal="true" but nothing
ever moved focus into the panel, so the @keydown handler on their root
never ran and closeOnEscape did nothing. Focus, focus restore, a Tab trap
and a body scroll lock now live in the reactive runtime, shared by both.

ContextMenu placed pointer menus by subtracting a guessed 340x420 from the
viewport, which pushed every menu that was not that size away from the
pointer; it now positions at the pointer and lets the anchored clamp pull
it back once it can be measured.

The reactive runtime size budget moves 150k -> 175k to cover anchored
overlays, dialog behaviour, the toaster and the DataTable client half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:59:58 +05:30

74 lines
2.7 KiB
TypeScript

import { partialPrerender } from "@wrnexus/ssr";
import {
fillSlots,
normalizeComponentName,
parseComponentProps,
readElementBody,
} from "./runtime.ts";
export interface PartialBuildModule {
default?: unknown;
render?: (props?: Record<string, unknown>) => string | Promise<string>;
layout?: string | { name?: string; render?: (props?: Record<string, unknown>) => string };
__wrnexusBuildStaticShell?: (ctx?: Record<string, unknown>) => string | Promise<string>;
}
export interface PartialBuildEntry {
name: string;
mod: PartialBuildModule;
}
const MOUNT_OPEN_RE =
/<([A-Za-z][A-Za-z0-9-]*)\b([^>]*?\bdata-component="([A-Za-z0-9_-]+)"[^>]*?)(\/?)>/;
/** Expand compiler component mounts at build time using only their pure render exports. */
export async function expandStaticComponents(
html: string,
components: readonly PartialBuildEntry[],
depth = 0,
): Promise<string> {
if (depth > 15) throw new Error("WRN-PARTIAL-STATIC-DEPTH: component nesting exceeds 15");
if (!html.includes("data-component=")) return html;
let output = "";
let cursor = 0;
for (;;) {
const match = MOUNT_OPEN_RE.exec(html.slice(cursor));
if (!match) return output + html.slice(cursor);
const start = cursor + match.index;
output += html.slice(cursor, start);
const [open, tag, attributes, name, selfClosing] = match;
const openEnd = start + open.length;
const body =
selfClosing === "/" ? { inner: "", end: openEnd } : readElementBody(html, tag!, openEnd);
const component = components.find(
(entry) => normalizeComponentName(entry.name) === normalizeComponentName(name!),
);
if (!component || typeof component.mod.render !== "function") {
throw new Error(`WRN-PARTIAL-STATIC-COMPONENT: '${name}' has no build-time renderer`);
}
const rendered = await component.mod.render(parseComponentProps(attributes!));
output += await expandStaticComponents(
fillSlots(String(rendered), body.inner, true),
components,
depth + 1,
);
cursor = body.end;
}
}
/** Produce the body shell stored in dist; dynamic region bodies are never evaluated here. */
export async function precomputePartialStaticShell(
page: PartialBuildModule,
components: readonly PartialBuildEntry[],
): Promise<{ shell: string; regions: number }> {
if (typeof page.__wrnexusBuildStaticShell !== "function") {
throw new Error("WRN-PARTIAL-STATIC-EXPORT: compiler did not emit a static-shell renderer");
}
const body = await expandStaticComponents(
String(await page.__wrnexusBuildStaticShell({})),
components,
);
const result = partialPrerender(body);
return { shell: result.shell, regions: result.regions.length };
}