import type { ActionBinding, VisibilityCondition, } from "@json-render/core"; // --------------------------------------------------------------------------- // JsonsxNode — intermediate representation produced by the JSX factory // --------------------------------------------------------------------------- /** * Sentinel symbol identifying a JsonsxNode (prevents plain objects from * being mistaken for nodes). */ export const JSONSX_NODE = Symbol.for("jsonsx.node"); /** * Sentinel symbol for Fragment grouping. */ export const FRAGMENT = Symbol.for("jsonsx.fragment"); /** * A node in the intermediate JSX tree. * * Created by the `jsx` / `jsxs` factory functions and consumed by `render()` * which flattens the tree into a json-render `Spec`. */ export interface JsonsxNode { /** Brand symbol — always `JSONSX_NODE` */ $$typeof: typeof JSONSX_NODE; /** * Component type name (e.g. `"Card"`, `"Button"`). * For fragments this is the `FRAGMENT` symbol. */ type: string | typeof FRAGMENT; /** Component props (reserved props already extracted) */ props: Record; /** Child nodes */ children: JsonsxNode[]; // -- Reserved / meta fields (extracted from JSX props) -- /** Explicit element key (overrides auto-generation) */ key: string | undefined; /** Visibility condition */ visible: VisibilityCondition | undefined; /** Event bindings */ on: Record | undefined; /** Repeat configuration */ repeat: { statePath: string; key?: string } | undefined; /** State watchers */ watch: Record | undefined; } // --------------------------------------------------------------------------- // JsonsxComponent — a function usable as a JSX tag that maps to a type string // --------------------------------------------------------------------------- /** * A jsonsx component function. Works like a React function component: * when used as a JSX tag (``), the factory calls the function * with props and gets back a JsonsxNode. */ export type JsonsxComponent = (props: Record) => JsonsxNode; /** * Define a jsonsx component for use as a JSX tag. * * Creates a function that, when called with props, produces a JsonsxNode * with the given type name — just like a React component returns * React elements. * * @example * ```tsx * const Card = component("Card"); * const spec = render(); * ``` */ export function component(typeName: string): JsonsxComponent { // Import createNodeFromString lazily to avoid circular dep // (jsx-runtime imports types). Instead, we build the node inline. return (props: Record) => { return { $$typeof: JSONSX_NODE, type: typeName, props: filterReserved(props), children: normalizeChildrenRaw(props.children), key: props.key != null ? String(props.key) : undefined, visible: props.visible as VisibilityCondition | undefined, on: props.on as Record | undefined, repeat: props.repeat as { statePath: string; key?: string } | undefined, watch: props.watch as Record | undefined, }; }; } const RESERVED = new Set(["key", "children", "visible", "on", "repeat", "watch"]); function filterReserved(props: Record): Record { const out: Record = {}; for (const k of Object.keys(props)) { if (!RESERVED.has(k)) out[k] = props[k]; } return out; } function normalizeChildrenRaw(raw: unknown): JsonsxNode[] { if (raw == null || typeof raw === "boolean") return []; if (Array.isArray(raw)) { const result: JsonsxNode[] = []; for (const child of raw) { if (child == null || typeof child === "boolean") continue; if (Array.isArray(child)) { result.push(...normalizeChildrenRaw(child)); } else { result.push(child as JsonsxNode); } } return result; } return [raw as JsonsxNode]; } // --------------------------------------------------------------------------- // render() options // --------------------------------------------------------------------------- export interface RenderOptions { /** Initial state to include in the Spec output */ state?: Record; } // --------------------------------------------------------------------------- // Type guard // --------------------------------------------------------------------------- export function isJsonsxNode(value: unknown): value is JsonsxNode { return ( typeof value === "object" && value !== null && (value as JsonsxNode).$$typeof === JSONSX_NODE ); }