import { signal, computed, effect, batch, type Signal, type ReadonlySignal } from "@preact/signals-core"; import type { UNode, UElement, UniversalProps, UComponent } from "./schema.js"; export interface ReactiveNode { readonly type: string; readonly signal: ReadonlySignal; readonly dispose: () => void; } export function reactiveComponent

( component: UComponent

, propsSignal: Signal

, ): ReactiveNode { const nodeSignal = computed(() => { const props = propsSignal.value; return component(props); }); return { get type() { return component.displayName ?? "anonymous"; }, signal: nodeSignal, dispose: () => {}, }; } export function reactiveElement( type: string, propsSignal: Signal, childrenSignals: ReadonlySignal[], ): ReactiveNode { const nodeSignal = computed(() => { const children = childrenSignals.map((s) => s.value); return { type, props: propsSignal.value, children, } as UElement; }); return { type, signal: nodeSignal, dispose: () => {}, }; } export class ReactiveRoot { private root: Signal; private renderDisposer: (() => void) | null = null; constructor(initial: UNode) { this.root = signal(initial); } get value(): ReadonlySignal { return this.root; } update(fn: (current: UNode) => UNode): void { batch(() => { this.root.value = fn(this.root.value); }); } subscribe(listener: (node: UNode) => void): () => void { return effect(() => { listener(this.root.value); }); } render(emit: (event: { type: string; id: string; payload: unknown }) => void): () => void { this.renderDisposer = effect(() => { const node = this.root.value; emit({ type: "root.render", id: `root_${Date.now()}`, payload: node, }); }); return () => { if (this.renderDisposer) { this.renderDisposer(); this.renderDisposer = null; } }; } } export { signal, computed, effect, batch }; export type { Signal, ReadonlySignal };