import { render, VNode } from "@commontools/html";
import { Cell, UI } from "@commontools/runner";
import { vdomSchema } from "@commontools/runner/schemas";
import { loadManager } from "./charm.ts";
import { CharmsController } from "@commontools/charm/ops";
import type { CharmConfig } from "./charm.ts";
import { getLogger } from "@commontools/utils/logger";
import { MockDoc } from "../../html/src/mock-doc.ts";
const logger = getLogger("charm-render", { level: "info", enabled: false });
export interface RenderOptions {
watch?: boolean;
onUpdate?: (html: string) => void;
}
/**
* Renders a charm's UI to HTML using htmlparser2.
* Supports both static and reactive rendering with --watch mode.
*/
export async function renderCharm(
config: CharmConfig,
options: RenderOptions = {},
): Promise void)> {
const mock = new MockDoc(
'',
);
const { document, renderOptions } = mock;
// 2. Get charm controller to access the Cell
const manager = await loadManager(config);
const charms = new CharmsController(manager);
const charm = await charms.get(config.charm, true);
const cell = charm.getCell().asSchema({
type: "object",
properties: {
[UI]: vdomSchema,
},
required: [UI],
});
// Check if charm has UI
const staticValue = cell.get();
if (!staticValue?.[UI]) {
throw new Error(`Charm ${config.charm} has no UI`);
}
// 3. Get the root container
const container = document.getElementById("root");
if (!container) {
throw new Error("Could not find root container");
}
if (options.watch) {
// 4a. Reactive rendering - pass the Cell directly
const uiCell = cell.key(UI);
const cancel = render(container, uiCell as Cell, renderOptions); // FIXME: types
// 5a. Set up monitoring for changes
let updateCount = 0;
const unsubscribe = cell.sink((value) => {
if (value?.[UI]) {
updateCount++;
// Wait for all runtime computations to complete
manager.runtime.idle().then(() => {
const html = container.innerHTML;
logger.info(
"charm-render",
() => `[Update ${updateCount}] UI changed`,
);
if (options.onUpdate) {
options.onUpdate(html);
}
});
}
});
// Return cleanup function
return () => {
cancel();
unsubscribe();
};
} else {
// 4b. Static rendering - render once with current value
const vnode = staticValue[UI];
render(container, vnode as VNode, renderOptions); // FIXME: types
// 5b. Return the rendered HTML
return container.innerHTML;
}
}