2026-02-27 00:31:52 +00:00
|
|
|
import type {
|
|
|
|
|
ActionBinding,
|
|
|
|
|
VisibilityCondition,
|
|
|
|
|
} from "@json-render/core";
|
2026-02-28 01:42:09 +00:00
|
|
|
import { JRX_NODE, FRAGMENT, type JrxNode } from "./types";
|
|
|
|
|
import type { JrxComponent } from "./types";
|
2026-02-27 00:31:52 +00:00
|
|
|
|
|
|
|
|
export { FRAGMENT as Fragment };
|
|
|
|
|
|
2026-02-28 01:42:09 +00:00
|
|
|
/** Props reserved by jrx — extracted from JSX props and placed on the UIElement level. */
|
2026-02-27 00:31:52 +00:00
|
|
|
const RESERVED_PROPS = new Set([
|
|
|
|
|
"key",
|
|
|
|
|
"children",
|
|
|
|
|
"visible",
|
|
|
|
|
"on",
|
|
|
|
|
"repeat",
|
|
|
|
|
"watch",
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-28 01:42:09 +00:00
|
|
|
* Normalize a raw `children` value from JSX props into a flat array of JrxNodes.
|
2026-02-27 00:31:52 +00:00
|
|
|
* Handles: undefined, single node, nested arrays, and filters out nulls/booleans.
|
|
|
|
|
*/
|
2026-02-28 01:42:09 +00:00
|
|
|
function normalizeChildren(raw: unknown): JrxNode[] {
|
2026-02-27 00:31:52 +00:00
|
|
|
if (raw == null || typeof raw === "boolean") return [];
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(raw)) {
|
2026-02-28 01:42:09 +00:00
|
|
|
const result: JrxNode[] = [];
|
2026-02-27 00:31:52 +00:00
|
|
|
for (const child of raw) {
|
|
|
|
|
if (child == null || typeof child === "boolean") continue;
|
|
|
|
|
if (Array.isArray(child)) {
|
|
|
|
|
result.push(...normalizeChildren(child));
|
|
|
|
|
} else {
|
2026-02-28 01:42:09 +00:00
|
|
|
result.push(child as JrxNode);
|
2026-02-27 00:31:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 01:42:09 +00:00
|
|
|
return [raw as JrxNode];
|
2026-02-27 00:31:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract component props, filtering out reserved prop names.
|
|
|
|
|
*/
|
|
|
|
|
function extractProps(
|
|
|
|
|
rawProps: Record<string, unknown>,
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
const props: Record<string, unknown> = {};
|
|
|
|
|
for (const k of Object.keys(rawProps)) {
|
|
|
|
|
if (!RESERVED_PROPS.has(k)) {
|
|
|
|
|
props[k] = rawProps[k];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return props;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Accepted tag types: string literal, Fragment symbol, or a function component. */
|
2026-02-28 01:42:09 +00:00
|
|
|
type JsxType = string | typeof FRAGMENT | JrxComponent;
|
2026-02-27 00:31:52 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Core factory — shared by `jsx` and `jsxs`.
|
|
|
|
|
*
|
|
|
|
|
* If `type` is a function, it is called with props (like React calls
|
2026-02-28 01:42:09 +00:00
|
|
|
* function components). The function returns a JrxNode directly.
|
2026-02-27 00:31:52 +00:00
|
|
|
*
|
2026-02-28 01:42:09 +00:00
|
|
|
* If `type` is a string or Fragment, a JrxNode is constructed inline.
|
2026-02-27 00:31:52 +00:00
|
|
|
*/
|
|
|
|
|
function createNode(
|
|
|
|
|
type: JsxType,
|
|
|
|
|
rawProps: Record<string, unknown> | null,
|
2026-02-28 01:42:09 +00:00
|
|
|
): JrxNode {
|
2026-02-27 00:31:52 +00:00
|
|
|
const p = rawProps ?? {};
|
|
|
|
|
|
|
|
|
|
// Function component — call it, just like React does.
|
|
|
|
|
if (typeof type === "function") {
|
|
|
|
|
return type(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2026-02-28 01:42:09 +00:00
|
|
|
$$typeof: JRX_NODE,
|
2026-02-27 00:31:52 +00:00
|
|
|
type,
|
|
|
|
|
props: extractProps(p),
|
|
|
|
|
children: normalizeChildren(p.children),
|
|
|
|
|
key: p.key != null ? String(p.key) : undefined,
|
|
|
|
|
visible: p.visible as VisibilityCondition | undefined,
|
|
|
|
|
on: p.on as
|
|
|
|
|
| Record<string, ActionBinding | ActionBinding[]>
|
|
|
|
|
| undefined,
|
|
|
|
|
repeat: p.repeat as { statePath: string; key?: string } | undefined,
|
|
|
|
|
watch: p.watch as
|
|
|
|
|
| Record<string, ActionBinding | ActionBinding[]>
|
|
|
|
|
| undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* JSX factory for elements with a single child (or no children).
|
|
|
|
|
* Called by the automatic JSX transform (`react-jsx`).
|
|
|
|
|
*/
|
|
|
|
|
export function jsx(
|
|
|
|
|
type: JsxType,
|
|
|
|
|
props: Record<string, unknown> | null,
|
|
|
|
|
key?: string,
|
2026-02-28 01:42:09 +00:00
|
|
|
): JrxNode {
|
2026-02-27 00:31:52 +00:00
|
|
|
const node = createNode(type, props);
|
|
|
|
|
if (key != null) node.key = String(key);
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* JSX factory for elements with multiple static children.
|
|
|
|
|
* Called by the automatic JSX transform (`react-jsx`).
|
|
|
|
|
*/
|
|
|
|
|
export function jsxs(
|
|
|
|
|
type: JsxType,
|
|
|
|
|
props: Record<string, unknown> | null,
|
|
|
|
|
key?: string,
|
2026-02-28 01:42:09 +00:00
|
|
|
): JrxNode {
|
2026-02-27 00:31:52 +00:00
|
|
|
const node = createNode(type, props);
|
|
|
|
|
if (key != null) node.key = String(key);
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// JSX namespace — tells TypeScript what JSX expressions are valid
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
export namespace JSX {
|
|
|
|
|
/** Any string tag is valid — component types come from the catalog at runtime. */
|
|
|
|
|
export interface IntrinsicElements {
|
|
|
|
|
[tag: string]: Record<string, unknown>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** The type returned by JSX expressions. */
|
2026-02-28 01:42:09 +00:00
|
|
|
export type Element = JrxNode;
|
2026-02-27 00:31:52 +00:00
|
|
|
|
|
|
|
|
export interface ElementChildrenAttribute {
|
|
|
|
|
children: {};
|
|
|
|
|
}
|
|
|
|
|
}
|