mirror of
https://github.com/kennethnym/jrx.git
synced 2026-03-20 03:41:18 +00:00
@@ -1,3 +1,3 @@
|
||||
export { render } from "./render";
|
||||
export { isJsonsxNode, JSONSX_NODE, FRAGMENT } from "./types";
|
||||
export type { JsonsxNode, JsonsxComponent, RenderOptions } from "./types";
|
||||
export { isJrxNode, JRX_NODE, FRAGMENT } from "./types";
|
||||
export type { JrxNode, JrxComponent, RenderOptions } from "./types";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/** @jsxImportSource react */
|
||||
|
||||
/**
|
||||
* Integration tests: verify that Specs produced by jsonsx are consumable
|
||||
* Integration tests: verify that Specs produced by jrx are consumable
|
||||
* by @json-render/react's Renderer.
|
||||
*
|
||||
* This file uses React JSX (via the pragma above) for the React component
|
||||
* tree, and jsonsx's jsx()/jsxs() via the component wrappers for building Specs.
|
||||
* tree, and jrx's jsx()/jsxs() via the component wrappers for building Specs.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, mock } from "bun:test";
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@json-render/react";
|
||||
import { useStateStore } from "@json-render/react";
|
||||
import { jsx, jsxs } from "./jsx-runtime";
|
||||
import { render as jsonsxRender } from "./render";
|
||||
import { render as jrxRender } from "./render";
|
||||
import {
|
||||
Stack as JStack,
|
||||
Card as JCard,
|
||||
@@ -64,7 +64,7 @@ function StateProbe() {
|
||||
const registry = { Button, Text, Stack, Card };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: render a jsonsx spec with @json-render/react
|
||||
// Helper: render a jrx spec with @json-render/react
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function renderSpec(spec: Spec, handlers?: Record<string, (...args: unknown[]) => void>) {
|
||||
@@ -80,15 +80,15 @@ function renderSpec(spec: Spec, handlers?: Record<string, (...args: unknown[]) =
|
||||
// Basic rendering
|
||||
// =============================================================================
|
||||
|
||||
describe("jsonsx → @json-render/react round-trip", () => {
|
||||
describe("jrx → @json-render/react round-trip", () => {
|
||||
it("renders a single element", () => {
|
||||
const spec = jsonsxRender(jsx(JText, { content: "Hello from jsonsx" }));
|
||||
const spec = jrxRender(jsx(JText, { content: "Hello from jrx" }));
|
||||
renderSpec(spec);
|
||||
expect(screen.getByTestId("text").textContent).toBe("Hello from jsonsx");
|
||||
expect(screen.getByTestId("text").textContent).toBe("Hello from jrx");
|
||||
});
|
||||
|
||||
it("renders nested elements with children", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JCard, {
|
||||
title: "My Card",
|
||||
children: [jsx(JText, { content: "Inside card" })],
|
||||
@@ -101,7 +101,7 @@ describe("jsonsx → @json-render/react round-trip", () => {
|
||||
});
|
||||
|
||||
it("renders a tree with multiple children", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsx(JText, { content: "First" }),
|
||||
@@ -116,7 +116,7 @@ describe("jsonsx → @json-render/react round-trip", () => {
|
||||
});
|
||||
|
||||
it("renders a deep tree", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsxs(JCard, {
|
||||
@@ -136,9 +136,9 @@ describe("jsonsx → @json-render/react round-trip", () => {
|
||||
// State + actions (adapted from chained-actions.test.tsx)
|
||||
// =============================================================================
|
||||
|
||||
describe("jsonsx specs with state and actions", () => {
|
||||
describe("jrx specs with state and actions", () => {
|
||||
it("renders with initial state", () => {
|
||||
const spec = jsonsxRender(jsx(JText, { content: "Stateful" }), {
|
||||
const spec = jrxRender(jsx(JText, { content: "Stateful" }), {
|
||||
state: { count: 42 },
|
||||
});
|
||||
renderSpec(spec);
|
||||
@@ -148,7 +148,7 @@ describe("jsonsx specs with state and actions", () => {
|
||||
});
|
||||
|
||||
it("setState action updates state on button press", async () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsx(JButton, {
|
||||
label: "Set",
|
||||
on: {
|
||||
@@ -172,7 +172,7 @@ describe("jsonsx specs with state and actions", () => {
|
||||
});
|
||||
|
||||
it("chained pushState + setState resolves correctly", async () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsx(JButton, {
|
||||
label: "Chain",
|
||||
on: {
|
||||
@@ -206,7 +206,7 @@ describe("jsonsx specs with state and actions", () => {
|
||||
});
|
||||
|
||||
it("multiple pushState chain resolves correctly", async () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsx(JButton, {
|
||||
label: "Go",
|
||||
on: {
|
||||
@@ -242,9 +242,9 @@ describe("jsonsx specs with state and actions", () => {
|
||||
// Spec structural validity
|
||||
// =============================================================================
|
||||
|
||||
describe("jsonsx spec structural validity", () => {
|
||||
describe("jrx spec structural validity", () => {
|
||||
it("all child references resolve to existing elements", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsxs(JCard, {
|
||||
@@ -272,12 +272,12 @@ describe("jsonsx spec structural validity", () => {
|
||||
});
|
||||
|
||||
it("root element exists in elements map", () => {
|
||||
const spec = jsonsxRender(jsx(JCard, { title: "Root" }));
|
||||
const spec = jrxRender(jsx(JCard, { title: "Root" }));
|
||||
expect(spec.elements[spec.root]).toBeDefined();
|
||||
});
|
||||
|
||||
it("element count matches node count", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsx(JCard, { title: "A" }),
|
||||
@@ -294,9 +294,9 @@ describe("jsonsx spec structural validity", () => {
|
||||
// Dynamic features (ported from json-render's dynamic-forms.test.tsx)
|
||||
// =============================================================================
|
||||
|
||||
describe("jsonsx specs with dynamic features", () => {
|
||||
describe("jrx specs with dynamic features", () => {
|
||||
it("$state prop expressions resolve at render time", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsx(JText, { content: { $state: "/message" } }),
|
||||
{ state: { message: "Dynamic hello" } },
|
||||
);
|
||||
@@ -306,7 +306,7 @@ describe("jsonsx specs with dynamic features", () => {
|
||||
});
|
||||
|
||||
it("visibility condition hides element when false", () => {
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsx(JText, {
|
||||
@@ -324,7 +324,7 @@ describe("jsonsx specs with dynamic features", () => {
|
||||
|
||||
it("visibility condition shows element when true", () => {
|
||||
cleanup();
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsx(JText, {
|
||||
@@ -343,7 +343,7 @@ describe("jsonsx specs with dynamic features", () => {
|
||||
it("watchers fire when watched state changes", async () => {
|
||||
const loadCities = mock();
|
||||
|
||||
const spec = jsonsxRender(
|
||||
const spec = jrxRender(
|
||||
jsxs(JStack, {
|
||||
children: [
|
||||
jsx(JButton, {
|
||||
|
||||
@@ -2,12 +2,12 @@ import type {
|
||||
ActionBinding,
|
||||
VisibilityCondition,
|
||||
} from "@json-render/core";
|
||||
import { JSONSX_NODE, FRAGMENT, type JsonsxNode } from "./types";
|
||||
import type { JsonsxComponent } from "./types";
|
||||
import { JRX_NODE, FRAGMENT, type JrxNode } from "./types";
|
||||
import type { JrxComponent } from "./types";
|
||||
|
||||
export { FRAGMENT as Fragment };
|
||||
|
||||
/** Props reserved by jsonsx — extracted from JSX props and placed on the UIElement level. */
|
||||
/** Props reserved by jrx — extracted from JSX props and placed on the UIElement level. */
|
||||
const RESERVED_PROPS = new Set([
|
||||
"key",
|
||||
"children",
|
||||
@@ -18,26 +18,26 @@ const RESERVED_PROPS = new Set([
|
||||
]);
|
||||
|
||||
/**
|
||||
* Normalize a raw `children` value from JSX props into a flat array of JsonsxNodes.
|
||||
* Normalize a raw `children` value from JSX props into a flat array of JrxNodes.
|
||||
* Handles: undefined, single node, nested arrays, and filters out nulls/booleans.
|
||||
*/
|
||||
function normalizeChildren(raw: unknown): JsonsxNode[] {
|
||||
function normalizeChildren(raw: unknown): JrxNode[] {
|
||||
if (raw == null || typeof raw === "boolean") return [];
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
const result: JsonsxNode[] = [];
|
||||
const result: JrxNode[] = [];
|
||||
for (const child of raw) {
|
||||
if (child == null || typeof child === "boolean") continue;
|
||||
if (Array.isArray(child)) {
|
||||
result.push(...normalizeChildren(child));
|
||||
} else {
|
||||
result.push(child as JsonsxNode);
|
||||
result.push(child as JrxNode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return [raw as JsonsxNode];
|
||||
return [raw as JrxNode];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,20 +56,20 @@ function extractProps(
|
||||
}
|
||||
|
||||
/** Accepted tag types: string literal, Fragment symbol, or a function component. */
|
||||
type JsxType = string | typeof FRAGMENT | JsonsxComponent;
|
||||
type JsxType = string | typeof FRAGMENT | JrxComponent;
|
||||
|
||||
/**
|
||||
* Core factory — shared by `jsx` and `jsxs`.
|
||||
*
|
||||
* If `type` is a function, it is called with props (like React calls
|
||||
* function components). The function returns a JsonsxNode directly.
|
||||
* function components). The function returns a JrxNode directly.
|
||||
*
|
||||
* If `type` is a string or Fragment, a JsonsxNode is constructed inline.
|
||||
* If `type` is a string or Fragment, a JrxNode is constructed inline.
|
||||
*/
|
||||
function createNode(
|
||||
type: JsxType,
|
||||
rawProps: Record<string, unknown> | null,
|
||||
): JsonsxNode {
|
||||
): JrxNode {
|
||||
const p = rawProps ?? {};
|
||||
|
||||
// Function component — call it, just like React does.
|
||||
@@ -78,7 +78,7 @@ function createNode(
|
||||
}
|
||||
|
||||
return {
|
||||
$$typeof: JSONSX_NODE,
|
||||
$$typeof: JRX_NODE,
|
||||
type,
|
||||
props: extractProps(p),
|
||||
children: normalizeChildren(p.children),
|
||||
@@ -102,7 +102,7 @@ export function jsx(
|
||||
type: JsxType,
|
||||
props: Record<string, unknown> | null,
|
||||
key?: string,
|
||||
): JsonsxNode {
|
||||
): JrxNode {
|
||||
const node = createNode(type, props);
|
||||
if (key != null) node.key = String(key);
|
||||
return node;
|
||||
@@ -116,7 +116,7 @@ export function jsxs(
|
||||
type: JsxType,
|
||||
props: Record<string, unknown> | null,
|
||||
key?: string,
|
||||
): JsonsxNode {
|
||||
): JrxNode {
|
||||
const node = createNode(type, props);
|
||||
if (key != null) node.key = String(key);
|
||||
return node;
|
||||
@@ -133,7 +133,7 @@ export namespace JSX {
|
||||
}
|
||||
|
||||
/** The type returned by JSX expressions. */
|
||||
export type Element = JsonsxNode;
|
||||
export type Element = JrxNode;
|
||||
|
||||
export interface ElementChildrenAttribute {
|
||||
children: {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { render } from "./render";
|
||||
import { isJsonsxNode, FRAGMENT } from "./types";
|
||||
import { isJrxNode, FRAGMENT } from "./types";
|
||||
import { jsx, jsxs, Fragment } from "./jsx-runtime";
|
||||
import {
|
||||
Stack,
|
||||
@@ -18,25 +18,25 @@ import {
|
||||
// =============================================================================
|
||||
|
||||
describe("jsx factory", () => {
|
||||
it("jsx() with string type returns a JsonsxNode", () => {
|
||||
it("jsx() with string type returns a JrxNode", () => {
|
||||
const node = jsx("Card", { title: "Hello" });
|
||||
expect(isJsonsxNode(node)).toBe(true);
|
||||
expect(isJrxNode(node)).toBe(true);
|
||||
expect(node.type).toBe("Card");
|
||||
expect(node.props).toEqual({ title: "Hello" });
|
||||
});
|
||||
|
||||
it("jsx() with component function resolves typeName", () => {
|
||||
const node = jsx(Card, { title: "Hello" });
|
||||
expect(isJsonsxNode(node)).toBe(true);
|
||||
expect(isJrxNode(node)).toBe(true);
|
||||
expect(node.type).toBe("Card");
|
||||
expect(node.props).toEqual({ title: "Hello" });
|
||||
});
|
||||
|
||||
it("jsxs() returns a JsonsxNode with children", () => {
|
||||
it("jsxs() returns a JrxNode with children", () => {
|
||||
const node = jsxs(Stack, {
|
||||
children: [jsx(Text, { content: "A" }), jsx(Text, { content: "B" })],
|
||||
});
|
||||
expect(isJsonsxNode(node)).toBe(true);
|
||||
expect(isJrxNode(node)).toBe(true);
|
||||
expect(node.children).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("jsx factory", () => {
|
||||
|
||||
it("jsx() handles null props", () => {
|
||||
const node = jsx("Divider", null);
|
||||
expect(isJsonsxNode(node)).toBe(true);
|
||||
expect(isJrxNode(node)).toBe(true);
|
||||
expect(node.props).toEqual({});
|
||||
expect(node.children).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { render } from "./render";
|
||||
import { FRAGMENT, type JsonsxNode } from "./types";
|
||||
import { FRAGMENT, type JrxNode } from "./types";
|
||||
import { jsx } from "./jsx-runtime";
|
||||
import {
|
||||
Stack,
|
||||
@@ -300,7 +300,7 @@ describe("state passthrough", () => {
|
||||
// =============================================================================
|
||||
|
||||
describe("error handling", () => {
|
||||
it("throws for non-JsonsxNode input", () => {
|
||||
expect(() => render({} as JsonsxNode)).toThrow(/expects a JsonsxNode/);
|
||||
it("throws for non-JrxNode input", () => {
|
||||
expect(() => render({} as JrxNode)).toThrow(/expects a JrxNode/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { Spec, UIElement } from "@json-render/core";
|
||||
import { FRAGMENT, type JsonsxNode, type RenderOptions, isJsonsxNode } from "./types";
|
||||
import { FRAGMENT, type JrxNode, type RenderOptions, isJrxNode } from "./types";
|
||||
|
||||
/**
|
||||
* Flatten a JsonsxNode tree into a json-render `Spec`.
|
||||
* Flatten a JrxNode tree into a json-render `Spec`.
|
||||
*
|
||||
* Analogous to `ReactDOM.render` but produces JSON instead of DOM mutations.
|
||||
*
|
||||
* @param node - Root JsonsxNode (produced by JSX)
|
||||
* @param node - Root JrxNode (produced by JSX)
|
||||
* @param options - Optional render configuration (e.g. initial state)
|
||||
* @returns A json-render `Spec` ready for any renderer
|
||||
*
|
||||
@@ -20,9 +20,9 @@ import { FRAGMENT, type JsonsxNode, type RenderOptions, isJsonsxNode } from "./t
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function render(node: JsonsxNode, options?: RenderOptions): Spec {
|
||||
if (!isJsonsxNode(node)) {
|
||||
throw new Error("render() expects a JsonsxNode produced by JSX.");
|
||||
export function render(node: JrxNode, options?: RenderOptions): Spec {
|
||||
if (!isJrxNode(node)) {
|
||||
throw new Error("render() expects a JrxNode produced by JSX.");
|
||||
}
|
||||
|
||||
if (node.type === FRAGMENT) {
|
||||
@@ -66,12 +66,12 @@ function generateKey(
|
||||
|
||||
/**
|
||||
* Resolve the children of a node, expanding fragments inline.
|
||||
* Returns an array of concrete (non-fragment) JsonsxNodes.
|
||||
* Returns an array of concrete (non-fragment) JrxNodes.
|
||||
*/
|
||||
function expandChildren(children: JsonsxNode[]): JsonsxNode[] {
|
||||
const result: JsonsxNode[] = [];
|
||||
function expandChildren(children: JrxNode[]): JrxNode[] {
|
||||
const result: JrxNode[] = [];
|
||||
for (const child of children) {
|
||||
if (!isJsonsxNode(child)) continue;
|
||||
if (!isJrxNode(child)) continue;
|
||||
if (child.type === FRAGMENT) {
|
||||
// Recursively expand nested fragments
|
||||
result.push(...expandChildren(child.children));
|
||||
@@ -83,11 +83,11 @@ function expandChildren(children: JsonsxNode[]): JsonsxNode[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively flatten a JsonsxNode into the elements map.
|
||||
* Recursively flatten a JrxNode into the elements map.
|
||||
* Returns the key assigned to this node.
|
||||
*/
|
||||
function flattenNode(
|
||||
node: JsonsxNode,
|
||||
node: JrxNode,
|
||||
elements: Record<string, UIElement>,
|
||||
counters: Map<string, number>,
|
||||
usedKeys: Set<string>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Ported from json-render's core/src/spec-validator.test.ts.
|
||||
*
|
||||
* Runs @json-render/core's validateSpec against Specs produced by jsonsx
|
||||
* Runs @json-render/core's validateSpec against Specs produced by jrx
|
||||
* to prove structural correctness.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
Select,
|
||||
} from "./test-components";
|
||||
|
||||
describe("validateSpec on jsonsx-produced specs", () => {
|
||||
describe("validateSpec on jrx-produced specs", () => {
|
||||
it("validates a simple single-element spec", () => {
|
||||
const spec = render(<Text text="hello" />);
|
||||
const result = validateSpec(spec);
|
||||
@@ -90,7 +90,7 @@ describe("validateSpec on jsonsx-produced specs", () => {
|
||||
expect(result.issues.some((i) => i.code === "watch_in_props")).toBe(false);
|
||||
});
|
||||
|
||||
it("no orphaned elements in jsonsx output", () => {
|
||||
it("no orphaned elements in jrx output", () => {
|
||||
const spec = render(
|
||||
<Stack>
|
||||
<Text text="A" />
|
||||
@@ -101,7 +101,7 @@ describe("validateSpec on jsonsx-produced specs", () => {
|
||||
expect(result.issues.some((i) => i.code === "orphaned_element")).toBe(false);
|
||||
});
|
||||
|
||||
it("no missing children in jsonsx output", () => {
|
||||
it("no missing children in jrx output", () => {
|
||||
const spec = render(
|
||||
<Stack>
|
||||
<Card title="X">
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { jsx } from "./jsx-runtime";
|
||||
import type { JsonsxNode } from "./types";
|
||||
import type { JrxNode } from "./types";
|
||||
|
||||
export function Stack(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Stack(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Stack", props);
|
||||
}
|
||||
|
||||
export function Card(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Card(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Card", props);
|
||||
}
|
||||
|
||||
export function Text(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Text(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Text", props);
|
||||
}
|
||||
|
||||
export function Button(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Button(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Button", props);
|
||||
}
|
||||
|
||||
export function Badge(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Badge(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Badge", props);
|
||||
}
|
||||
|
||||
export function List(props: Record<string, unknown>): JsonsxNode {
|
||||
export function List(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("List", props);
|
||||
}
|
||||
|
||||
export function ListItem(props: Record<string, unknown>): JsonsxNode {
|
||||
export function ListItem(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("ListItem", props);
|
||||
}
|
||||
|
||||
export function Select(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Select(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Select", props);
|
||||
}
|
||||
|
||||
export function Input(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Input(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Input", props);
|
||||
}
|
||||
|
||||
export function Divider(props: Record<string, unknown>): JsonsxNode {
|
||||
export function Divider(props: Record<string, unknown>): JrxNode {
|
||||
return jsx("Divider", props);
|
||||
}
|
||||
|
||||
44
src/types.ts
44
src/types.ts
@@ -4,19 +4,19 @@ import type {
|
||||
} from "@json-render/core";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JsonsxNode — intermediate representation produced by the JSX factory
|
||||
// JrxNode — intermediate representation produced by the JSX factory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sentinel symbol identifying a JsonsxNode (prevents plain objects from
|
||||
* Sentinel symbol identifying a JrxNode (prevents plain objects from
|
||||
* being mistaken for nodes).
|
||||
*/
|
||||
export const JSONSX_NODE = Symbol.for("jsonsx.node");
|
||||
export const JRX_NODE = Symbol.for("jrx.node");
|
||||
|
||||
/**
|
||||
* Sentinel symbol for Fragment grouping.
|
||||
*/
|
||||
export const FRAGMENT = Symbol.for("jsonsx.fragment");
|
||||
export const FRAGMENT = Symbol.for("jrx.fragment");
|
||||
|
||||
/**
|
||||
* A node in the intermediate JSX tree.
|
||||
@@ -24,9 +24,9 @@ export const FRAGMENT = Symbol.for("jsonsx.fragment");
|
||||
* 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;
|
||||
export interface JrxNode {
|
||||
/** Brand symbol — always `JRX_NODE` */
|
||||
$$typeof: typeof JRX_NODE;
|
||||
|
||||
/**
|
||||
* Component type name (e.g. `"Card"`, `"Button"`).
|
||||
@@ -38,7 +38,7 @@ export interface JsonsxNode {
|
||||
props: Record<string, unknown>;
|
||||
|
||||
/** Child nodes */
|
||||
children: JsonsxNode[];
|
||||
children: JrxNode[];
|
||||
|
||||
// -- Reserved / meta fields (extracted from JSX props) --
|
||||
|
||||
@@ -59,20 +59,20 @@ export interface JsonsxNode {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JsonsxComponent — a function usable as a JSX tag that maps to a type string
|
||||
// JrxComponent — a function usable as a JSX tag that maps to a type string
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A jsonsx component function. Works like a React function component:
|
||||
* A jrx component function. Works like a React function component:
|
||||
* when used as a JSX tag (`<Card />`), the factory calls the function
|
||||
* with props and gets back a JsonsxNode.
|
||||
* with props and gets back a JrxNode.
|
||||
*/
|
||||
export type JsonsxComponent = (props: Record<string, unknown>) => JsonsxNode;
|
||||
export type JrxComponent = (props: Record<string, unknown>) => JrxNode;
|
||||
|
||||
/**
|
||||
* Define a jsonsx component for use as a JSX tag.
|
||||
* Define a jrx component for use as a JSX tag.
|
||||
*
|
||||
* Creates a function that, when called with props, produces a JsonsxNode
|
||||
* Creates a function that, when called with props, produces a JrxNode
|
||||
* with the given type name — just like a React component returns
|
||||
* React elements.
|
||||
*
|
||||
@@ -82,12 +82,12 @@ export type JsonsxComponent = (props: Record<string, unknown>) => JsonsxNode;
|
||||
* const spec = render(<Card title="Hello"><Text content="World" /></Card>);
|
||||
* ```
|
||||
*/
|
||||
export function component(typeName: string): JsonsxComponent {
|
||||
export function component(typeName: string): JrxComponent {
|
||||
// Import createNodeFromString lazily to avoid circular dep
|
||||
// (jsx-runtime imports types). Instead, we build the node inline.
|
||||
return (props: Record<string, unknown>) => {
|
||||
return {
|
||||
$$typeof: JSONSX_NODE,
|
||||
$$typeof: JRX_NODE,
|
||||
type: typeName,
|
||||
props: filterReserved(props),
|
||||
children: normalizeChildrenRaw(props.children),
|
||||
@@ -110,21 +110,21 @@ function filterReserved(props: Record<string, unknown>): Record<string, unknown>
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeChildrenRaw(raw: unknown): JsonsxNode[] {
|
||||
function normalizeChildrenRaw(raw: unknown): JrxNode[] {
|
||||
if (raw == null || typeof raw === "boolean") return [];
|
||||
if (Array.isArray(raw)) {
|
||||
const result: JsonsxNode[] = [];
|
||||
const result: JrxNode[] = [];
|
||||
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);
|
||||
result.push(child as JrxNode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return [raw as JsonsxNode];
|
||||
return [raw as JrxNode];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -140,10 +140,10 @@ export interface RenderOptions {
|
||||
// Type guard
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function isJsonsxNode(value: unknown): value is JsonsxNode {
|
||||
export function isJrxNode(value: unknown): value is JrxNode {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
(value as JsonsxNode).$$typeof === JSONSX_NODE
|
||||
(value as JrxNode).$$typeof === JRX_NODE
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user