@particle-academy/fancy-cms-ui 0.0.1-dev.13 → 0.0.1-dev.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -26,10 +26,13 @@ var DEFAULT_BREAKPOINTS = {
26
26
  md: 768,
27
27
  lg: 1024
28
28
  };
29
- function literal(v) {
30
- if (v === void 0 || v === null) return "";
31
- if (isBinding(v)) return "";
32
- return String(v);
29
+ function getPath(data, path) {
30
+ return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
31
+ }
32
+ function resolveValue(v, data) {
33
+ if (v === void 0 || v === null) return v;
34
+ if (isBinding(v)) return getPath(data, v.$bind);
35
+ return v;
33
36
  }
34
37
  var defaultRegistry = {
35
38
  section: ({ children }) => children,
@@ -38,25 +41,25 @@ var defaultRegistry = {
38
41
  frame: ({ children }) => children,
39
42
  shape: ({ children }) => children,
40
43
  card: ({ children }) => children,
41
- text: ({ node }) => literal(node.props.content),
42
- heading: ({ node }) => literal(node.props.content) || "Heading",
43
- link: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: literal(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: literal(node.props.content) || "link" }),
44
+ text: ({ node, text }) => text(node.props.content),
45
+ heading: ({ node, text }) => text(node.props.content) || "Heading",
46
+ link: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
44
47
  divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
45
- callout: ({ node }) => {
46
- const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[literal(node.props.variant)] ?? "#3b82f6";
47
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: literal(node.props.content) || "Callout" }) });
48
+ callout: ({ node, text }) => {
49
+ const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
50
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: text(node.props.content) || "Callout" }) });
48
51
  },
49
- code: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: literal(node.props.content) }) }),
50
- button: ({ node }) => {
51
- const label = literal(node.props.label) || literal(node.props.content) || "Button";
52
- const variant = literal(node.props.variant) || "primary";
52
+ code: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: text(node.props.content) }) }),
53
+ button: ({ node, text }) => {
54
+ const label = text(node.props.label) || text(node.props.content) || "Button";
55
+ const variant = text(node.props.variant) || "primary";
53
56
  return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label });
54
57
  },
55
- image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
58
+ image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
56
59
  "img",
57
60
  {
58
- src: literal(node.props.src),
59
- alt: literal(node.props.alt),
61
+ src: text(node.props.src),
62
+ alt: text(node.props.alt),
60
63
  style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
61
64
  }
62
65
  )
@@ -78,21 +81,24 @@ function buttonStyle(variant) {
78
81
  if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
79
82
  return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
80
83
  }
81
- function RenderNode({ doc, id, registry = defaultRegistry }) {
84
+ function RenderNode({ doc, id, registry = defaultRegistry, data }) {
82
85
  const node = doc.nodes[id];
83
86
  if (!node) return null;
84
- const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry }, child.id));
87
+ if (node.type === "repeater") {
88
+ const arr = resolveValue(node.props.items, data);
89
+ const items = Array.isArray(arr) ? arr : [];
90
+ const template = childrenOf(doc, id)[0];
91
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, className: node.className, children: template ? items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: template.id, registry, data: { ...data, item, index } }, index)) : null });
92
+ }
93
+ const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry, data }, child.id));
94
+ const resolve = (v) => resolveValue(v, data);
95
+ const text = (v) => {
96
+ const r = resolve(v);
97
+ return r === void 0 || r === null ? "" : String(r);
98
+ };
85
99
  const renderer = registry[node.type];
86
- const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
87
- return /* @__PURE__ */ jsxRuntime.jsx(
88
- "div",
89
- {
90
- "data-cms": node.id,
91
- "data-cms-island": node.island ? node.type : void 0,
92
- className: node.className,
93
- children: inner
94
- }
95
- );
100
+ const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
101
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, className: node.className, children: inner });
96
102
  }
97
103
 
98
104
  // src/render/css.ts
@@ -223,11 +229,12 @@ ${inner}
223
229
  function CmsPage({
224
230
  doc,
225
231
  registry = defaultRegistry,
232
+ data,
226
233
  includeStyles = true
227
234
  }) {
228
235
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
229
236
  includeStyles ? /* @__PURE__ */ jsxRuntime.jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
230
- doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry }, id))
237
+ doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry, data }, id))
231
238
  ] });
232
239
  }
233
240
  function CmsRegion({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/document/reduce.ts","../src/document/types.ts","../src/react/registry.tsx","../src/react/RenderNode.tsx","../src/render/css.ts","../src/react/CmsPage.tsx","../src/react/CmsRegion.tsx"],"names":["jsx","Fragment","jsxs"],"mappings":"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","file":"react.cjs","sourcesContent":["/**\n * The pure, total reducer. `reduce(doc, op)` returns a new document; an invalid\n * op is a **logged no-op** (returns the same doc, `seq` unchanged). That single\n * property is what makes undo, revisions, collaboration, and agent edits all\n * flow through one spine.\n *\n * Ordering: top-level order is the canonical `sections` array; child order is\n * the fractional `order` field among siblings (see `fractional.ts`).\n */\nimport type { Action, Node, NodeId, PageDoc, StyleProps } from \"./types\";\nimport type { PageOp } from \"./ops\";\n\nexport interface ReduceOptions {\n /** Called when an op can't apply. Default: silent. */\n onInvalid?: (op: PageOp, reason: string) => void;\n}\n\n/** Children of a node (or top-level when `parent` is null), in order. */\nexport function childrenOf(doc: PageDoc, parent: NodeId | null): Node[] {\n if (parent === null) {\n return doc.sections.map((id) => doc.nodes[id]).filter((n): n is Node => Boolean(n));\n }\n const out: Node[] = [];\n for (const id of Object.keys(doc.nodes)) {\n const n = doc.nodes[id]!;\n if (n.parent === parent) out.push(n);\n }\n out.sort((a, b) => (a.order < b.order ? -1 : a.order > b.order ? 1 : 0));\n return out;\n}\n\n/** Every transitive descendant id of `id` (excludes `id`). */\nexport function descendantIds(doc: PageDoc, id: NodeId): NodeId[] {\n const out: NodeId[] = [];\n const stack: NodeId[] = [id];\n while (stack.length) {\n const current = stack.pop()!;\n for (const childId of Object.keys(doc.nodes)) {\n const n = doc.nodes[childId]!;\n if (n.parent === current) {\n out.push(childId);\n stack.push(childId);\n }\n }\n }\n return out;\n}\n\nfunction patchNode(doc: PageDoc, id: NodeId, patch: Partial<Node>): PageDoc {\n const node = doc.nodes[id]!;\n return {\n ...doc,\n nodes: { ...doc.nodes, [id]: { ...node, ...patch } },\n seq: doc.seq + 1,\n };\n}\n\nexport function reduce(doc: PageDoc, op: PageOp, opts: ReduceOptions = {}): PageDoc {\n const invalid = (reason: string): PageDoc => {\n opts.onInvalid?.(op, reason);\n return doc;\n };\n\n switch (op.t) {\n case \"insert_node\": {\n if (doc.nodes[op.node.id]) return invalid(\"node id already exists\");\n if (op.node.parent !== null && !doc.nodes[op.node.parent]) return invalid(\"parent not found\");\n return {\n ...doc,\n nodes: { ...doc.nodes, [op.node.id]: op.node },\n sections: op.node.parent === null ? [...doc.sections, op.node.id] : doc.sections,\n seq: doc.seq + 1,\n };\n }\n\n case \"remove_node\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n const dead = new Set<NodeId>([op.id, ...descendantIds(doc, op.id)]);\n const nodes: Record<NodeId, Node> = {};\n for (const id of Object.keys(doc.nodes)) {\n if (!dead.has(id)) nodes[id] = doc.nodes[id]!;\n }\n return {\n ...doc,\n nodes,\n sections: doc.sections.filter((s) => !dead.has(s)),\n seq: doc.seq + 1,\n };\n }\n\n case \"move_node\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n if (op.parent === op.id) return invalid(\"cannot parent a node to itself\");\n if (op.parent !== null && !doc.nodes[op.parent]) return invalid(\"new parent not found\");\n if (op.parent !== null && descendantIds(doc, op.id).includes(op.parent)) {\n return invalid(\"move would create a cycle\");\n }\n const wasTop = node.parent === null;\n const nowTop = op.parent === null;\n let sections = doc.sections;\n if (wasTop && !nowTop) sections = sections.filter((s) => s !== op.id);\n else if (!wasTop && nowTop) sections = [...sections, op.id];\n return {\n ...doc,\n nodes: { ...doc.nodes, [op.id]: { ...node, parent: op.parent, order: op.order } },\n sections,\n seq: doc.seq + 1,\n };\n }\n\n case \"set_props\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n return patchNode(doc, op.id, {\n props: { ...doc.nodes[op.id]!.props, ...op.patch } as unknown as Node[\"props\"],\n });\n }\n\n case \"set_layout\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n return patchNode(doc, op.id, { layout: op.layout });\n }\n\n case \"set_style\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const prev = node.style[op.breakpoint] ?? {};\n return patchNode(doc, op.id, {\n style: { ...node.style, [op.breakpoint]: { ...prev, ...op.patch } } as Node[\"style\"],\n });\n }\n\n case \"set_constraints\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const base = node.constraints ?? { base: { width: \"hug\", height: \"hug\" } };\n const prev = base[op.breakpoint] ?? {};\n return patchNode(doc, op.id, {\n constraints: { ...base, [op.breakpoint]: { ...prev, ...op.patch } } as Node[\"constraints\"],\n });\n }\n\n case \"set_animation\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const list = node.animations ?? [];\n const idx = list.findIndex((a) => a.id === op.anim.id);\n return patchNode(doc, op.id, {\n animations: idx >= 0 ? list.map((a, i) => (i === idx ? op.anim : a)) : [...list, op.anim],\n });\n }\n\n case \"remove_animation\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n return patchNode(doc, op.id, {\n animations: (node.animations ?? []).filter((a) => a.id !== op.animId),\n });\n }\n\n case \"set_action\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const list = node.actions ?? [];\n let actions: Action[];\n if (op.action === null) actions = list.filter((_, i) => i !== op.index);\n else if (op.index >= list.length) actions = [...list, op.action];\n else actions = list.map((a, i) => (i === op.index ? op.action! : a));\n return patchNode(doc, op.id, { actions });\n }\n\n case \"set_meta\":\n return { ...doc, meta: { ...doc.meta, ...op.patch }, seq: doc.seq + 1 };\n\n case \"set_theme\":\n return { ...doc, theme: { ...doc.theme, ...op.patch }, seq: doc.seq + 1 };\n\n case \"reorder_sections\": {\n const current = new Set(doc.sections);\n const same =\n op.order.length === doc.sections.length && op.order.every((id) => current.has(id));\n if (!same) return invalid(\"reorder must be a permutation of current sections\");\n return { ...doc, sections: [...op.order], seq: doc.seq + 1 };\n }\n\n default:\n return invalid(`unknown op \"${(op as PageOp).t}\"`);\n }\n}\n\n/** Apply a sequence of ops, threading the document. */\nexport function reduceAll(doc: PageDoc, ops: PageOp[], opts?: ReduceOptions): PageDoc {\n return ops.reduce((d, op) => reduce(d, op, opts), doc);\n}\n\n/**\n * Inverse op(s) for undo, computed against the **pre-op** document. Structural\n * ops invert cleanly; merge-style ops (`set_props`/`set_style`/`set_constraints`)\n * restore the previous values of the touched keys (keys that did not previously\n * exist can't be deleted via a merge — a known Phase-0 limitation).\n */\nexport function invert(doc: PageDoc, op: PageOp): PageOp[] {\n switch (op.t) {\n case \"insert_node\":\n return [{ t: \"remove_node\", id: op.node.id }];\n\n case \"remove_node\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n // Re-insert the subtree parent-first, then restore section order.\n const ids = [op.id, ...descendantIds(doc, op.id)];\n const inserts: PageOp[] = ids.map((id) => ({ t: \"insert_node\", node: doc.nodes[id]! }));\n if (node.parent === null) inserts.push({ t: \"reorder_sections\", order: [...doc.sections] });\n return inserts;\n }\n\n case \"move_node\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const back: PageOp[] = [{ t: \"move_node\", id: op.id, parent: node.parent, order: node.order }];\n if (node.parent === null) back.push({ t: \"reorder_sections\", order: [...doc.sections] });\n return back;\n }\n\n case \"set_props\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const prev: Record<string, unknown> = {};\n for (const k of Object.keys(op.patch)) prev[k] = node.props[k];\n return [{ t: \"set_props\", id: op.id, patch: prev }];\n }\n\n case \"set_layout\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n return [{ t: \"set_layout\", id: op.id, layout: node.layout }];\n }\n\n case \"set_style\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const prevBp = (node.style[op.breakpoint] ?? {}) as Record<string, unknown>;\n const prev: Partial<StyleProps> = {};\n for (const k of Object.keys(op.patch)) (prev as Record<string, unknown>)[k] = prevBp[k];\n return [{ t: \"set_style\", id: op.id, breakpoint: op.breakpoint, patch: prev }];\n }\n\n case \"set_meta\":\n return [{ t: \"set_meta\", patch: pick(doc.meta, Object.keys(op.patch)) }];\n\n case \"set_theme\":\n return [{ t: \"set_theme\", patch: pick(doc.theme, Object.keys(op.patch)) }];\n\n case \"reorder_sections\":\n return [{ t: \"reorder_sections\", order: [...doc.sections] }];\n\n default:\n return [];\n }\n}\n\nfunction pick<T extends object>(obj: T, keys: string[]): Partial<T> {\n const out: Partial<T> = {};\n for (const k of keys) (out as Record<string, unknown>)[k] = (obj as Record<string, unknown>)[k];\n return out;\n}\n","/**\n * Stages document model — see `fancy-ui/docs/fancy-cms.md` §4.\n *\n * A page is a vertical stack of full-bleed **sections**; inside each, **layers**\n * are positioned by constraints (free + bold stays responsive), with stack/grid\n * containers where structure is wanted. Nodes live in a **flat map** keyed by id\n * (Figma-style) so ops target a stable identity and collab merges stay clean.\n */\n\nexport type NodeId = string;\n\nexport type ScrollMode = \"snap\" | \"smooth\";\n\n/** Ordered, mobile-first. Convention: `\"base\" | \"md\" | \"lg\"`. */\nexport type Breakpoint = string;\n\nexport type Json = string | number | boolean | null | Json[] | { [key: string]: Json };\n\n/** A literal value OR a binding into the page's data context (`{ $bind: \"profile.coins\" }`). */\nexport interface Binding {\n $bind: string;\n}\nexport type Bound<T> = T | Binding;\n\nexport function isBinding(v: unknown): v is Binding {\n return typeof v === \"object\" && v !== null && typeof (v as Binding).$bind === \"string\";\n}\n\n// ── Sizing ────────────────────────────────────────────────────────────────\n\nexport type LengthUnit = \"px\" | \"%\" | \"vw\" | \"vh\" | \"rem\" | \"fr\";\nexport interface Length {\n value: number;\n unit: LengthUnit;\n}\nexport type SizeMode = Length | \"fill\" | \"hug\";\n\nexport interface Constraints {\n /** Pinned offsets within a `free` parent (null = unpinned). */\n left?: Length | null;\n right?: Length | null;\n top?: Length | null;\n bottom?: Length | null;\n centerX?: boolean;\n centerY?: boolean;\n width: SizeMode;\n height: SizeMode;\n}\n\nexport type LayoutMode = \"free\" | \"stack\" | \"grid\";\n\n// ── Style (compiles deterministically to CSS in both emitters) ──────────────\n\nexport interface StyleProps {\n // box\n padding?: Length;\n margin?: Length;\n radius?: Length;\n // color / surface\n background?: string; // color | gradient | url(...)\n color?: string;\n opacity?: number;\n // typography\n fontFamily?: string;\n fontSize?: Length;\n fontWeight?: number;\n lineHeight?: number;\n textAlign?: \"left\" | \"center\" | \"right\" | \"justify\";\n letterSpacing?: Length;\n // border / shadow / effects (static)\n border?: string;\n boxShadow?: string;\n transform?: string;\n filter?: string;\n // container knobs (when layout = stack | grid)\n gap?: Length;\n direction?: \"row\" | \"column\";\n columns?: number;\n align?: \"start\" | \"center\" | \"end\" | \"stretch\";\n justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\";\n}\n\n/** Per-breakpoint overrides; `base` is required, others cascade mobile-first. */\nexport type PerBreakpoint<T> = { base: T } & Partial<Record<Breakpoint, Partial<T>>>;\n\n// ── Animation (typed for Phase 2; ignored by the static CSS emitter) ────────\n\nexport type AnimDriver = \"enter\" | \"scroll\" | \"loop\" | \"hover\" | \"click\" | \"drag\";\nexport interface Keyframe {\n at: number; // 0..1\n props: Record<string, number | string>;\n}\nexport interface Anim {\n id: string;\n driver: AnimDriver;\n keyframes: Keyframe[];\n range?: { start: number; end: number };\n ease?: string;\n duration?: number;\n delay?: number;\n stagger?: number;\n once?: boolean;\n emit?: \"css\" | \"js\";\n}\n\n// ── Actions (typed for Phase 3/4) ───────────────────────────────────────────\n\nexport type ActionEvent = \"click\" | \"submit\" | \"change\" | \"load\" | \"timer\" | \"scroll-into\" | \"hover\";\nexport type ActionHandler =\n | { kind: \"navigate\"; href: Bound<string> }\n | { kind: \"submitForm\"; endpoint: string; method?: \"post\" | \"put\" }\n | { kind: \"mutate\"; target: string; value: Bound<Json> }\n | { kind: \"callEndpoint\"; endpoint: string; method?: string }\n | { kind: \"runFlow\"; flow: string }\n | { kind: \"openModal\"; node: NodeId }\n | { kind: \"openDrawer\"; node: NodeId }\n | { kind: \"playTimeline\"; section: NodeId }\n | { kind: \"toggleNode\"; node: NodeId };\nexport interface Action {\n on: ActionEvent;\n do: ActionHandler;\n}\n\n// ── Nodes & document ────────────────────────────────────────────────────────\n\nexport interface Node {\n id: NodeId;\n /** Addon key: `section | text | image | shape | stack | grid | frame | <addon>`. */\n type: string;\n /** `null` = a top-level section. */\n parent: NodeId | null;\n /** Fractional index — collab-safe ordering among siblings. */\n order: string;\n /** Addon-specific props; any value may be a {@link Binding}. */\n props: Record<string, Bound<Json>>;\n /** How this node arranges its children. */\n layout?: LayoutMode;\n /** How this node positions itself within its parent. */\n constraints?: PerBreakpoint<Constraints>;\n style: PerBreakpoint<StyleProps>;\n animations?: Anim[];\n actions?: Action[];\n /** Repeater: render this node once per item of a bound array. */\n repeat?: { each: Binding; as: string };\n /**\n * Host CSS class(es) applied to the node's element. The migration enabler:\n * reuse the host site's existing stylesheet for pixel-perfect reproduction\n * instead of re-deriving every rule through {@link StyleProps}.\n */\n className?: string;\n /** Derived from the addon manifest — interactive/3rd-party ⇒ hydration island. */\n island?: boolean;\n}\n\nexport interface ThemeTokens {\n name?: string;\n [key: string]: Json | undefined;\n}\n\nexport interface PageMeta {\n title: string;\n slug: string;\n scrollMode: ScrollMode;\n seo?: Record<string, string>;\n}\n\nexport interface PageDoc {\n id: string;\n /** Op counter — collab ordering + revision checkpoints. */\n seq: number;\n meta: PageMeta;\n theme: ThemeTokens;\n /** Ordered, mobile-first. */\n breakpoints: Breakpoint[];\n /** Top-level section order. */\n sections: NodeId[];\n /** Flat map — not a nested tree. */\n nodes: Record<NodeId, Node>;\n}\n\n/** Default breakpoint → min-width (px) map. `base` has no media query. */\nexport const DEFAULT_BREAKPOINTS: Record<string, number> = {\n base: 0,\n md: 768,\n lg: 1024,\n};\n\n/** A minimal empty document. */\nexport function emptyDoc(id: string, slug = \"/\"): PageDoc {\n return {\n id,\n seq: 0,\n meta: { title: \"Untitled\", slug, scrollMode: \"smooth\" },\n theme: { name: \"default\" },\n breakpoints: [\"base\", \"md\", \"lg\"],\n sections: [],\n nodes: {},\n };\n}\n","import type { ReactNode } from \"react\";\nimport { isBinding, type Bound, type Json, type Node, type PageDoc } from \"../document/types\";\n\nexport interface ElementContext {\n node: Node;\n doc: PageDoc;\n /** Pre-rendered child nodes, in order. */\n children: ReactNode;\n}\n\n/**\n * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added\n * by {@link RenderNode}, so a renderer only returns what goes inside.\n */\nexport type ElementRenderer = (ctx: ElementContext) => ReactNode;\n\nexport type ElementRegistry = Record<string, ElementRenderer>;\n\n/** Resolve a literal prop to a string (bindings resolve in Phase 3). */\nfunction literal(v: Bound<Json> | undefined): string {\n if (v === undefined || v === null) return \"\";\n if (isBinding(v)) return \"\";\n return String(v);\n}\n\n/** Built-in native elements. Containers render their children; leaves render content. */\nexport const defaultRegistry: ElementRegistry = {\n section: ({ children }) => children,\n stack: ({ children }) => children,\n grid: ({ children }) => children,\n frame: ({ children }) => children,\n shape: ({ children }) => children,\n card: ({ children }) => children,\n text: ({ node }) => literal(node.props.content),\n heading: ({ node }) => literal(node.props.content) || \"Heading\",\n link: ({ node }) => (\n <a href={literal(node.props.href) || \"#\"} style={{ color: \"inherit\", textDecoration: \"underline\" }}>\n {literal(node.props.content) || \"link\"}\n </a>\n ),\n divider: () => <hr style={{ border: \"none\", borderTop: \"1px solid #e2e8f0\", margin: 0 }} />,\n callout: ({ node }) => {\n const tone = ({ info: \"#3b82f6\", success: \"#10b981\", warning: \"#f59e0b\", danger: \"#ef4444\" } as Record<string, string>)[literal(node.props.variant)] ?? \"#3b82f6\";\n return (\n <div style={{ display: \"flex\", gap: 10, padding: \"12px 14px\", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }}>\n <span style={{ fontSize: 13 }}>{literal(node.props.content) || \"Callout\"}</span>\n </div>\n );\n },\n code: ({ node }) => (\n <pre style={{ margin: 0, padding: \"12px 14px\", borderRadius: 10, background: \"#0b1220\", color: \"#e2e8f0\", fontFamily: \"ui-monospace, monospace\", fontSize: 13, overflowX: \"auto\" }}>\n <code>{literal(node.props.content)}</code>\n </pre>\n ),\n button: ({ node }) => {\n const label = literal(node.props.label) || literal(node.props.content) || \"Button\";\n const variant = literal(node.props.variant) || \"primary\";\n return <span data-cms-button={variant} style={buttonStyle(variant)}>{label}</span>;\n },\n image: ({ node }) => (\n <img\n src={literal(node.props.src)}\n alt={literal(node.props.alt)}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }}\n />\n ),\n};\n\n/**\n * A self-contained button look so the `button` primitive renders sensibly with\n * zero host CSS. Hosts that want their own button classes override the `button`\n * renderer in their registry (e.g. the sandbox maps it to `.btn .btn-primary`).\n */\nfunction buttonStyle(variant: string): import(\"react\").CSSProperties {\n const base: import(\"react\").CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"9px 16px\",\n borderRadius: 8,\n fontWeight: 600,\n fontSize: 14,\n lineHeight: 1,\n cursor: \"pointer\",\n userSelect: \"none\",\n };\n if (variant === \"ghost\") return { ...base, background: \"transparent\", color: \"inherit\", border: \"1px solid currentColor\" };\n if (variant === \"outline\") return { ...base, background: \"transparent\", color: \"#7c3aed\", border: \"1px solid #7c3aed\" };\n return { ...base, background: \"#7c3aed\", color: \"#fff\", border: \"1px solid #7c3aed\" };\n}\n","import type { ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { childrenOf } from \"../document/reduce\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface RenderNodeProps {\n doc: PageDoc;\n id: string;\n registry?: ElementRegistry;\n}\n\n/**\n * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS\n * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /\n * 3rd-party **islands** with no registered renderer become an empty, sized\n * placeholder the host hydrates client-side — mirroring the PHP renderer.\n */\nexport function RenderNode({ doc, id, registry = defaultRegistry }: RenderNodeProps): ReactElement | null {\n const node = doc.nodes[id];\n if (!node) return null;\n\n const kids = childrenOf(doc, id).map((child) => (\n <RenderNode key={child.id} doc={doc} id={child.id} registry={registry} />\n ));\n\n const renderer = registry[node.type];\n const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;\n\n return (\n <div\n data-cms={node.id}\n data-cms-island={node.island ? node.type : undefined}\n className={node.className}\n >\n {inner}\n </div>\n );\n}\n","/**\n * The JS style → CSS emitter (one half of the dual-emitter pair; the PHP\n * `particle-academy/fancy-cms` mirrors it byte-for-byte). Output is\n * **deterministic**: declarations are sorted, selectors and media blocks use a\n * fixed format, and nodes are emitted in sorted-id order — so the parity harness\n * can diff the two engines' CSS exactly.\n *\n * Phase 0 covers the static subset (layout / constraints / style). Motion lands\n * in Phase 2.\n */\nimport {\n DEFAULT_BREAKPOINTS,\n type Constraints,\n type Length,\n type Node,\n type PageDoc,\n type SizeMode,\n type StyleProps,\n} from \"../document/types\";\n\nconst selectorFor = (id: string): string => `[data-cms=\"${id}\"]`;\n\nfunction lengthToCss(len: Length): string {\n return `${len.value}${len.unit}`;\n}\n\nfunction sizeToCss(size: SizeMode): string {\n if (size === \"fill\") return \"100%\";\n if (size === \"hug\") return \"auto\";\n return lengthToCss(size);\n}\n\nconst ALIGN: Record<string, string> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n stretch: \"stretch\",\n};\nconst JUSTIFY: Record<string, string> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n between: \"space-between\",\n around: \"space-around\",\n};\n\n/** Map StyleProps → CSS declarations (property → value). */\nfunction styleDecls(style: Partial<StyleProps>): Record<string, string> {\n const d: Record<string, string> = {};\n if (style.padding) d[\"padding\"] = lengthToCss(style.padding);\n if (style.margin) d[\"margin\"] = lengthToCss(style.margin);\n if (style.radius) d[\"border-radius\"] = lengthToCss(style.radius);\n if (style.background !== undefined) d[\"background\"] = style.background;\n if (style.color !== undefined) d[\"color\"] = style.color;\n if (style.opacity !== undefined) d[\"opacity\"] = String(style.opacity);\n if (style.fontFamily !== undefined) d[\"font-family\"] = style.fontFamily;\n if (style.fontSize) d[\"font-size\"] = lengthToCss(style.fontSize);\n if (style.fontWeight !== undefined) d[\"font-weight\"] = String(style.fontWeight);\n if (style.lineHeight !== undefined) d[\"line-height\"] = String(style.lineHeight);\n if (style.textAlign !== undefined) d[\"text-align\"] = style.textAlign;\n if (style.letterSpacing) d[\"letter-spacing\"] = lengthToCss(style.letterSpacing);\n if (style.border !== undefined) d[\"border\"] = style.border;\n if (style.boxShadow !== undefined) d[\"box-shadow\"] = style.boxShadow;\n if (style.transform !== undefined) d[\"transform\"] = style.transform;\n if (style.filter !== undefined) d[\"filter\"] = style.filter;\n if (style.gap) d[\"gap\"] = lengthToCss(style.gap);\n if (style.align !== undefined) d[\"align-items\"] = ALIGN[style.align] ?? style.align;\n if (style.justify !== undefined) d[\"justify-content\"] = JUSTIFY[style.justify] ?? style.justify;\n return d;\n}\n\n/** Container behaviour from `node.layout`. */\nfunction layoutDecls(node: Node): Record<string, string> {\n const d: Record<string, string> = {};\n if (node.layout === \"stack\") {\n d[\"display\"] = \"flex\";\n d[\"flex-direction\"] = node.style.base.direction ?? \"column\";\n } else if (node.layout === \"grid\") {\n d[\"display\"] = \"grid\";\n d[\"grid-template-columns\"] = `repeat(${node.style.base.columns ?? 1}, 1fr)`;\n } else if (node.layout === \"free\") {\n d[\"position\"] = \"relative\";\n }\n return d;\n}\n\n/** Self-positioning from constraints (absolute pins only inside a `free` parent). */\nfunction constraintDecls(c: Partial<Constraints>, parentFree: boolean): Record<string, string> {\n const d: Record<string, string> = {};\n const transforms: string[] = [];\n if (parentFree) {\n d[\"position\"] = \"absolute\";\n if (c.left) d[\"left\"] = lengthToCss(c.left);\n if (c.right) d[\"right\"] = lengthToCss(c.right);\n if (c.top) d[\"top\"] = lengthToCss(c.top);\n if (c.bottom) d[\"bottom\"] = lengthToCss(c.bottom);\n if (c.centerX) {\n d[\"left\"] = \"50%\";\n transforms.push(\"translateX(-50%)\");\n }\n if (c.centerY) {\n d[\"top\"] = \"50%\";\n transforms.push(\"translateY(-50%)\");\n }\n if (transforms.length) d[\"transform\"] = transforms.join(\" \");\n }\n if (c.width) d[\"width\"] = sizeToCss(c.width);\n if (c.height) d[\"height\"] = sizeToCss(c.height);\n return d;\n}\n\nfunction isParentFree(doc: PageDoc, node: Node): boolean {\n if (node.parent === null) return false; // sections stack in the page flow\n const parent = doc.nodes[node.parent];\n return !parent || parent.layout === undefined || parent.layout === \"free\";\n}\n\nfunction serializeRule(selector: string, decls: Record<string, string>, indent: string): string {\n const props = Object.keys(decls).sort();\n if (props.length === 0) return \"\";\n const body = props.map((p) => `${indent} ${p}: ${decls[p]};`).join(\"\\n\");\n return `${indent}${selector} {\\n${body}\\n${indent}}`;\n}\n\n/** Emit the full stylesheet for a document. Deterministic. */\nexport function emitDocCss(doc: PageDoc): string {\n const breakpointPx = DEFAULT_BREAKPOINTS;\n const blocks: string[] = [];\n\n for (const id of Object.keys(doc.nodes).sort()) {\n const node = doc.nodes[id]!;\n const selector = selectorFor(id);\n const parentFree = isParentFree(doc, node);\n\n // base = style.base + layout + constraints.base\n const base: Record<string, string> = {\n ...layoutDecls(node),\n ...(node.constraints ? constraintDecls(node.constraints.base, parentFree) : {}),\n ...styleDecls(node.style.base),\n };\n const baseRule = serializeRule(selector, base, \"\");\n if (baseRule) blocks.push(baseRule);\n\n // per-breakpoint overrides (mobile-first, in doc order, skipping base)\n for (const bp of doc.breakpoints) {\n if (bp === \"base\") continue;\n const px = breakpointPx[bp];\n if (px === undefined) continue;\n const decls: Record<string, string> = {\n ...(node.constraints?.[bp] ? constraintDecls(node.constraints[bp]!, parentFree) : {}),\n ...(node.style[bp] ? styleDecls(node.style[bp]!) : {}),\n };\n const inner = serializeRule(selector, decls, \" \");\n if (inner) blocks.push(`@media (min-width: ${px}px) {\\n${inner}\\n}`);\n }\n }\n\n return blocks.join(\"\\n\\n\") + (blocks.length ? \"\\n\" : \"\");\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface CmsPageProps {\n doc: PageDoc;\n registry?: ElementRegistry;\n /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */\n includeStyles?: boolean;\n}\n\n/** Render a full page document: the compiled stylesheet + every section. */\nexport function CmsPage({\n doc,\n registry = defaultRegistry,\n includeStyles = true,\n}: CmsPageProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n {doc.sections.map((id) => (\n <RenderNode key={id} doc={doc} id={id} registry={registry} />\n ))}\n </Fragment>\n );\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface CmsRegionProps {\n doc: PageDoc;\n /** Root node id of the region to render (a section, or any node). */\n root: string;\n registry?: ElementRegistry;\n includeStyles?: boolean;\n}\n\n/**\n * Render a single subtree — drop into a hand-coded page so the CMS owns just\n * that region (the \"guest, not host\" mounting mode). Phase 0/1 emits the whole\n * document's CSS; scoping it to the subtree is a later optimization.\n */\nexport function CmsRegion({\n doc,\n root,\n registry = defaultRegistry,\n includeStyles = true,\n}: CmsRegionProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n <RenderNode doc={doc} id={root} registry={registry} />\n </Fragment>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/document/reduce.ts","../src/document/types.ts","../src/react/registry.tsx","../src/react/RenderNode.tsx","../src/render/css.ts","../src/react/CmsPage.tsx","../src/react/CmsRegion.tsx"],"names":["jsx","Fragment","jsxs"],"mappings":";;;;;;AAkBO,SAAS,UAAA,CAAW,KAAc,MAAA,EAA+B;AACtE,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,OAAO,GAAA,CAAI,QAAA,CAAS,GAAA,CAAI,CAAC,OAAO,GAAA,CAAI,KAAA,CAAM,EAAE,CAAC,EAAE,MAAA,CAAO,CAAC,CAAA,KAAiB,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,EACpF;AACA,EAAA,MAAM,MAAc,EAAC;AACrB,EAAA,KAAA,MAAW,EAAA,IAAM,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,EAAG;AACvC,IAAA,MAAM,CAAA,GAAI,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACtB,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,MAAA,EAAQ,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,EACrC;AACA,EAAA,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAO,EAAE,KAAA,GAAQ,CAAA,CAAE,KAAA,GAAQ,EAAA,GAAK,CAAA,CAAE,KAAA,GAAQ,CAAA,CAAE,KAAA,GAAQ,IAAI,CAAE,CAAA;AACvE,EAAA,OAAO,GAAA;AACT;;;ACLO,SAAS,UAAU,CAAA,EAA0B;AAClD,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,MAAM,IAAA,IAAQ,OAAQ,EAAc,KAAA,KAAU,QAAA;AAChF;AA2JO,IAAM,mBAAA,GAA8C;AAAA,EACzD,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,GAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AC7JO,SAAS,OAAA,CAAQ,MAA+B,IAAA,EAAuB;AAC5E,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,OAAgB,CAAC,CAAA,EAAG,CAAA,KAAO,CAAA,IAAK,IAAA,GAAO,MAAA,GAAa,CAAA,CAA8B,CAAC,GAAI,IAAI,CAAA;AACpH;AAGO,SAAS,YAAA,CAAa,GAA4B,IAAA,EAA6B;AACpF,EAAA,IAAI,CAAA,KAAM,MAAA,IAAa,CAAA,KAAM,IAAA,EAAM,OAAO,CAAA;AAC1C,EAAA,IAAI,UAAU,CAAC,CAAA,SAAU,OAAA,CAAQ,IAAA,EAAM,EAAE,KAAK,CAAA;AAC9C,EAAA,OAAO,CAAA;AACT;AAGO,IAAM,eAAA,GAAmC;AAAA,EAC9C,OAAA,EAAS,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EAC3B,KAAA,EAAO,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EACzB,IAAA,EAAM,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EACxB,KAAA,EAAO,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EACzB,KAAA,EAAO,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EACzB,IAAA,EAAM,CAAC,EAAE,QAAA,EAAS,KAAM,QAAA;AAAA,EACxB,IAAA,EAAM,CAAC,EAAE,IAAA,EAAM,MAAK,KAAM,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,EACjD,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,IAAA,OAAW,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,IAAK,SAAA;AAAA,EACzD,IAAA,EAAM,CAAC,EAAE,IAAA,EAAM,IAAA,EAAK,qBAClBA,cAAA,CAAC,GAAA,EAAA,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,IAAK,GAAA,EAAK,KAAA,EAAO,EAAE,KAAA,EAAO,SAAA,EAAW,cAAA,EAAgB,WAAA,EAAY,EAC3F,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,IAAK,MAAA,EAC/B,CAAA;AAAA,EAEF,OAAA,EAAS,sBAAMA,cAAA,CAAC,IAAA,EAAA,EAAG,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAW,mBAAA,EAAqB,MAAA,EAAQ,CAAA,EAAE,EAAG,CAAA;AAAA,EACzF,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,MAAK,KAAM;AAC3B,IAAA,MAAM,OAAQ,EAAE,IAAA,EAAM,SAAA,EAAW,OAAA,EAAS,WAAW,OAAA,EAAS,SAAA,EAAW,MAAA,EAAQ,SAAA,GAAuC,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAC,CAAA,IAAK,SAAA;AACrJ,IAAA,uBACEA,cAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,SAAS,MAAA,EAAQ,GAAA,EAAK,EAAA,EAAI,OAAA,EAAS,aAAa,YAAA,EAAc,EAAA,EAAI,UAAA,EAAY,CAAA,EAAG,IAAI,CAAA,EAAA,CAAA,EAAM,UAAA,EAAY,CAAA,UAAA,EAAa,IAAI,CAAA,CAAA,EAAG,EACvI,QAAA,kBAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,OAAO,EAAE,QAAA,EAAU,EAAA,EAAG,EAAI,eAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,IAAK,WAAU,CAAA,EACxE,CAAA;AAAA,EAEJ,CAAA;AAAA,EACA,IAAA,EAAM,CAAC,EAAE,IAAA,EAAM,MAAK,qBAClBA,cAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,MAAA,EAAQ,CAAA,EAAG,OAAA,EAAS,WAAA,EAAa,cAAc,EAAA,EAAI,UAAA,EAAY,SAAA,EAAW,KAAA,EAAO,SAAA,EAAW,UAAA,EAAY,yBAAA,EAA2B,QAAA,EAAU,IAAI,SAAA,EAAW,MAAA,EAAO,EAC/K,QAAA,kBAAAA,cAAA,CAAC,UAAM,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,GAAE,CAAA,EAClC,CAAA;AAAA,EAEF,MAAA,EAAQ,CAAC,EAAE,IAAA,EAAM,MAAK,KAAM;AAC1B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,KAAK,KAAK,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,IAAK,QAAA;AACpE,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,IAAK,SAAA;AAC5C,IAAA,uBAAOA,cAAA,CAAC,UAAK,iBAAA,EAAiB,OAAA,EAAS,OAAO,WAAA,CAAY,OAAO,GAAI,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,EAC7E,CAAA;AAAA,EACA,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,MAAK,qBACnBA,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAAA,MACxB,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAAA,MACxB,KAAA,EAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAQ,MAAA,EAAQ,SAAA,EAAW,OAAA,EAAS,OAAA,EAAS,OAAA;AAAQ;AAAA;AAGnF;AAOA,SAAS,YAAY,OAAA,EAAgD;AACnE,EAAA,MAAM,IAAA,GAAsC;AAAA,IAC1C,OAAA,EAAS,aAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,CAAA;AAAA,IACL,OAAA,EAAS,UAAA;AAAA,IACT,YAAA,EAAc,CAAA;AAAA,IACd,UAAA,EAAY,GAAA;AAAA,IACZ,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY,CAAA;AAAA,IACZ,MAAA,EAAQ,SAAA;AAAA,IACR,UAAA,EAAY;AAAA,GACd;AACA,EAAA,IAAI,OAAA,KAAY,OAAA,EAAS,OAAO,EAAE,GAAG,IAAA,EAAM,UAAA,EAAY,aAAA,EAAe,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,wBAAA,EAAyB;AACzH,EAAA,IAAI,OAAA,KAAY,SAAA,EAAW,OAAO,EAAE,GAAG,IAAA,EAAM,UAAA,EAAY,aAAA,EAAe,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,mBAAA,EAAoB;AACtH,EAAA,OAAO,EAAE,GAAG,IAAA,EAAM,UAAA,EAAY,WAAW,KAAA,EAAO,MAAA,EAAQ,QAAQ,mBAAA,EAAoB;AACtF;AChFO,SAAS,WAAW,EAAE,GAAA,EAAK,IAAI,QAAA,GAAW,eAAA,EAAiB,MAAK,EAAyC;AAC9G,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACzB,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,EAAA,IAAI,IAAA,CAAK,SAAS,UAAA,EAAY;AAC5B,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,IAAA,CAAK,KAAA,CAAM,OAAkC,IAAI,CAAA;AAC1E,IAAA,MAAM,QAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,MAAM,EAAC;AAC1C,IAAA,MAAM,QAAA,GAAW,UAAA,CAAW,GAAA,EAAK,EAAE,EAAE,CAAC,CAAA;AACtC,IAAA,uBACEA,cAAAA,CAAC,KAAA,EAAA,EAAI,UAAA,EAAU,KAAK,EAAA,EAAI,SAAA,EAAW,IAAA,CAAK,SAAA,EACrC,QAAA,EAAA,QAAA,GACG,KAAA,CAAM,GAAA,CAAI,CAAC,MAAM,KAAA,qBACfA,cAAAA,CAAC,UAAA,EAAA,EAAuB,GAAA,EAAU,EAAA,EAAI,QAAA,CAAS,EAAA,EAAI,UAAoB,IAAA,EAAM,EAAE,GAAG,IAAA,EAAM,MAAM,KAAA,EAAM,EAAA,EAAnF,KAAsF,CACxG,IACD,IAAA,EACN,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,OAAO,UAAA,CAAW,GAAA,EAAK,EAAE,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,qBACpCA,eAAC,UAAA,EAAA,EAA0B,GAAA,EAAU,IAAI,KAAA,CAAM,EAAA,EAAI,UAAoB,IAAA,EAAA,EAAtD,KAAA,CAAM,EAA4D,CACpF,CAAA;AAED,EAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAA+B,YAAA,CAAa,GAAG,IAAI,CAAA;AACpE,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAuC;AACnD,IAAA,MAAM,CAAA,GAAI,QAAQ,CAAC,CAAA;AACnB,IAAA,OAAO,MAAM,MAAA,IAAa,CAAA,KAAM,IAAA,GAAO,EAAA,GAAK,OAAO,CAAC,CAAA;AAAA,EACtD,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AACnC,EAAA,MAAM,KAAA,GAAQ,QAAA,GAAW,QAAA,CAAS,EAAE,MAAM,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,IAAA,EAAM,SAAS,IAAA,EAAM,CAAA,GAAI,IAAA,CAAK,SAAS,IAAA,GAAO,IAAA;AAE7G,EAAA,uBACEA,cAAAA,CAAC,KAAA,EAAA,EAAI,UAAA,EAAU,KAAK,EAAA,EAAI,iBAAA,EAAiB,IAAA,CAAK,MAAA,GAAS,KAAK,IAAA,GAAO,MAAA,EAAW,SAAA,EAAW,IAAA,CAAK,WAC3F,QAAA,EAAA,KAAA,EACH,CAAA;AAEJ;;;ACxCA,IAAM,WAAA,GAAc,CAAC,EAAA,KAAuB,CAAA,WAAA,EAAc,EAAE,CAAA,EAAA,CAAA;AAE5D,SAAS,YAAY,GAAA,EAAqB;AACxC,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,KAAK,CAAA,EAAG,IAAI,IAAI,CAAA,CAAA;AAChC;AAEA,SAAS,UAAU,IAAA,EAAwB;AACzC,EAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,MAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,OAAO,OAAO,MAAA;AAC3B,EAAA,OAAO,YAAY,IAAI,CAAA;AACzB;AAEA,IAAM,KAAA,GAAgC;AAAA,EACpC,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ,QAAA;AAAA,EACR,GAAA,EAAK,UAAA;AAAA,EACL,OAAA,EAAS;AACX,CAAA;AACA,IAAM,OAAA,GAAkC;AAAA,EACtC,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ,QAAA;AAAA,EACR,GAAA,EAAK,UAAA;AAAA,EACL,OAAA,EAAS,eAAA;AAAA,EACT,MAAA,EAAQ;AACV,CAAA;AAGA,SAAS,WAAW,KAAA,EAAoD;AACtE,EAAA,MAAM,IAA4B,EAAC;AACnC,EAAA,IAAI,MAAM,OAAA,EAAS,CAAA,CAAE,SAAS,CAAA,GAAI,WAAA,CAAY,MAAM,OAAO,CAAA;AAC3D,EAAA,IAAI,MAAM,MAAA,EAAQ,CAAA,CAAE,QAAQ,CAAA,GAAI,WAAA,CAAY,MAAM,MAAM,CAAA;AACxD,EAAA,IAAI,MAAM,MAAA,EAAQ,CAAA,CAAE,eAAe,CAAA,GAAI,WAAA,CAAY,MAAM,MAAM,CAAA;AAC/D,EAAA,IAAI,MAAM,UAAA,KAAe,MAAA,EAAW,CAAA,CAAE,YAAY,IAAI,KAAA,CAAM,UAAA;AAC5D,EAAA,IAAI,MAAM,KAAA,KAAU,MAAA,EAAW,CAAA,CAAE,OAAO,IAAI,KAAA,CAAM,KAAA;AAClD,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW,CAAA,CAAE,SAAS,CAAA,GAAI,MAAA,CAAO,MAAM,OAAO,CAAA;AACpE,EAAA,IAAI,MAAM,UAAA,KAAe,MAAA,EAAW,CAAA,CAAE,aAAa,IAAI,KAAA,CAAM,UAAA;AAC7D,EAAA,IAAI,MAAM,QAAA,EAAU,CAAA,CAAE,WAAW,CAAA,GAAI,WAAA,CAAY,MAAM,QAAQ,CAAA;AAC/D,EAAA,IAAI,KAAA,CAAM,eAAe,MAAA,EAAW,CAAA,CAAE,aAAa,CAAA,GAAI,MAAA,CAAO,MAAM,UAAU,CAAA;AAC9E,EAAA,IAAI,KAAA,CAAM,eAAe,MAAA,EAAW,CAAA,CAAE,aAAa,CAAA,GAAI,MAAA,CAAO,MAAM,UAAU,CAAA;AAC9E,EAAA,IAAI,MAAM,SAAA,KAAc,MAAA,EAAW,CAAA,CAAE,YAAY,IAAI,KAAA,CAAM,SAAA;AAC3D,EAAA,IAAI,MAAM,aAAA,EAAe,CAAA,CAAE,gBAAgB,CAAA,GAAI,WAAA,CAAY,MAAM,aAAa,CAAA;AAC9E,EAAA,IAAI,MAAM,MAAA,KAAW,MAAA,EAAW,CAAA,CAAE,QAAQ,IAAI,KAAA,CAAM,MAAA;AACpD,EAAA,IAAI,MAAM,SAAA,KAAc,MAAA,EAAW,CAAA,CAAE,YAAY,IAAI,KAAA,CAAM,SAAA;AAC3D,EAAA,IAAI,MAAM,SAAA,KAAc,MAAA,EAAW,CAAA,CAAE,WAAW,IAAI,KAAA,CAAM,SAAA;AAC1D,EAAA,IAAI,MAAM,MAAA,KAAW,MAAA,EAAW,CAAA,CAAE,QAAQ,IAAI,KAAA,CAAM,MAAA;AACpD,EAAA,IAAI,MAAM,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA,GAAI,WAAA,CAAY,MAAM,GAAG,CAAA;AAC/C,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAW,CAAA,CAAE,aAAa,IAAI,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA,IAAK,KAAA,CAAM,KAAA;AAC9E,EAAA,IAAI,KAAA,CAAM,OAAA,KAAY,MAAA,EAAW,CAAA,CAAE,iBAAiB,IAAI,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,IAAK,KAAA,CAAM,OAAA;AACxF,EAAA,OAAO,CAAA;AACT;AAGA,SAAS,YAAY,IAAA,EAAoC;AACvD,EAAA,MAAM,IAA4B,EAAC;AACnC,EAAA,IAAI,IAAA,CAAK,WAAW,OAAA,EAAS;AAC3B,IAAA,CAAA,CAAE,SAAS,CAAA,GAAI,MAAA;AACf,IAAA,CAAA,CAAE,gBAAgB,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,KAAK,SAAA,IAAa,QAAA;AAAA,EACrD,CAAA,MAAA,IAAW,IAAA,CAAK,MAAA,KAAW,MAAA,EAAQ;AACjC,IAAA,CAAA,CAAE,SAAS,CAAA,GAAI,MAAA;AACf,IAAA,CAAA,CAAE,uBAAuB,CAAA,GAAI,CAAA,OAAA,EAAU,KAAK,KAAA,CAAM,IAAA,CAAK,WAAW,CAAC,CAAA,MAAA,CAAA;AAAA,EACrE,CAAA,MAAA,IAAW,IAAA,CAAK,MAAA,KAAW,MAAA,EAAQ;AACjC,IAAA,CAAA,CAAE,UAAU,CAAA,GAAI,UAAA;AAAA,EAClB;AACA,EAAA,OAAO,CAAA;AACT;AAGA,SAAS,eAAA,CAAgB,GAAyB,UAAA,EAA6C;AAC7F,EAAA,MAAM,IAA4B,EAAC;AACnC,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,CAAA,CAAE,UAAU,CAAA,GAAI,UAAA;AAChB,IAAA,IAAI,EAAE,IAAA,EAAM,CAAA,CAAE,MAAM,CAAA,GAAI,WAAA,CAAY,EAAE,IAAI,CAAA;AAC1C,IAAA,IAAI,EAAE,KAAA,EAAO,CAAA,CAAE,OAAO,CAAA,GAAI,WAAA,CAAY,EAAE,KAAK,CAAA;AAC7C,IAAA,IAAI,EAAE,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA,GAAI,WAAA,CAAY,EAAE,GAAG,CAAA;AACvC,IAAA,IAAI,EAAE,MAAA,EAAQ,CAAA,CAAE,QAAQ,CAAA,GAAI,WAAA,CAAY,EAAE,MAAM,CAAA;AAChD,IAAA,IAAI,EAAE,OAAA,EAAS;AACb,MAAA,CAAA,CAAE,MAAM,CAAA,GAAI,KAAA;AACZ,MAAA,UAAA,CAAW,KAAK,kBAAkB,CAAA;AAAA,IACpC;AACA,IAAA,IAAI,EAAE,OAAA,EAAS;AACb,MAAA,CAAA,CAAE,KAAK,CAAA,GAAI,KAAA;AACX,MAAA,UAAA,CAAW,KAAK,kBAAkB,CAAA;AAAA,IACpC;AACA,IAAA,IAAI,WAAW,MAAA,EAAQ,CAAA,CAAE,WAAW,CAAA,GAAI,UAAA,CAAW,KAAK,GAAG,CAAA;AAAA,EAC7D;AACA,EAAA,IAAI,EAAE,KAAA,EAAO,CAAA,CAAE,OAAO,CAAA,GAAI,SAAA,CAAU,EAAE,KAAK,CAAA;AAC3C,EAAA,IAAI,EAAE,MAAA,EAAQ,CAAA,CAAE,QAAQ,CAAA,GAAI,SAAA,CAAU,EAAE,MAAM,CAAA;AAC9C,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,YAAA,CAAa,KAAc,IAAA,EAAqB;AACvD,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,IAAA,EAAM,OAAO,KAAA;AACjC,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA;AACpC,EAAA,OAAO,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,MAAA,IAAa,OAAO,MAAA,KAAW,MAAA;AACrE;AAEA,SAAS,aAAA,CAAc,QAAA,EAAkB,KAAA,EAA+B,MAAA,EAAwB;AAC9F,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,KAAK,EAAE,IAAA,EAAK;AACtC,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AAC/B,EAAA,MAAM,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,GAAG,MAAM,CAAA,EAAA,EAAK,CAAC,CAAA,EAAA,EAAK,MAAM,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,KAAK,IAAI,CAAA;AACxE,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,QAAQ,CAAA;AAAA,EAAO,IAAI;AAAA,EAAK,MAAM,CAAA,CAAA,CAAA;AACnD;AAGO,SAAS,WAAW,GAAA,EAAsB;AAC/C,EAAA,MAAM,YAAA,GAAe,mBAAA;AACrB,EAAA,MAAM,SAAmB,EAAC;AAE1B,EAAA,KAAA,MAAW,MAAM,MAAA,CAAO,IAAA,CAAK,IAAI,KAAK,CAAA,CAAE,MAAK,EAAG;AAC9C,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACzB,IAAA,MAAM,QAAA,GAAW,YAAY,EAAE,CAAA;AAC/B,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;AAGzC,IAAA,MAAM,IAAA,GAA+B;AAAA,MACnC,GAAG,YAAY,IAAI,CAAA;AAAA,MACnB,GAAI,KAAK,WAAA,GAAc,eAAA,CAAgB,KAAK,WAAA,CAAY,IAAA,EAAM,UAAU,CAAA,GAAI,EAAC;AAAA,MAC7E,GAAG,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAI;AAAA,KAC/B;AACA,IAAA,MAAM,QAAA,GAAW,aAAA,CAAc,QAAA,EAAU,IAAA,EAAM,EAAE,CAAA;AACjD,IAAA,IAAI,QAAA,EAAU,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA;AAGlC,IAAA,KAAA,MAAW,EAAA,IAAM,IAAI,WAAA,EAAa;AAChC,MAAA,IAAI,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,EAAA,GAAK,aAAa,EAAE,CAAA;AAC1B,MAAA,IAAI,OAAO,MAAA,EAAW;AACtB,MAAA,MAAM,KAAA,GAAgC;AAAA,QACpC,GAAI,IAAA,CAAK,WAAA,GAAc,EAAE,CAAA,GAAI,eAAA,CAAgB,IAAA,CAAK,WAAA,CAAY,EAAE,CAAA,EAAI,UAAU,CAAA,GAAI,EAAC;AAAA,QACnF,GAAI,IAAA,CAAK,KAAA,CAAM,EAAE,CAAA,GAAI,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,EAAE,CAAE,CAAA,GAAI;AAAC,OACtD;AACA,MAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,QAAA,EAAU,KAAA,EAAO,IAAI,CAAA;AACjD,MAAA,IAAI,KAAA,EAAO,MAAA,CAAO,IAAA,CAAK,CAAA,mBAAA,EAAsB,EAAE,CAAA;AAAA,EAAU,KAAK;AAAA,CAAA,CAAK,CAAA;AAAA,IACrE;AAAA,EACF;AAEA,EAAA,OAAO,OAAO,IAAA,CAAK,MAAM,CAAA,IAAK,MAAA,CAAO,SAAS,IAAA,GAAO,EAAA,CAAA;AACvD;AC9IO,SAAS,OAAA,CAAQ;AAAA,EACtB,GAAA;AAAA,EACA,QAAA,GAAW,eAAA;AAAA,EACX,IAAA;AAAA,EACA,aAAA,GAAgB;AAClB,CAAA,EAA+B;AAC7B,EAAA,uCACGC,cAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,aAAA,mBAAgBD,eAAC,OAAA,EAAA,EAAM,iBAAA,EAAgB,IAAI,QAAA,EAAA,UAAA,CAAW,GAAG,GAAE,CAAA,GAAW,IAAA;AAAA,IACtE,GAAA,CAAI,QAAA,CAAS,GAAA,CAAI,CAAC,EAAA,qBACjBA,cAAAA,CAAC,UAAA,EAAA,EAAoB,GAAA,EAAU,EAAA,EAAQ,QAAA,EAAoB,IAAA,EAAA,EAA1C,EAAsD,CACxE;AAAA,GAAA,EACH,CAAA;AAEJ;ACXO,SAAS,SAAA,CAAU;AAAA,EACxB,GAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA,GAAW,eAAA;AAAA,EACX,aAAA,GAAgB;AAClB,CAAA,EAAiC;AAC/B,EAAA,uBACEE,eAAAA,CAACD,cAAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,aAAA,mBAAgBD,eAAC,OAAA,EAAA,EAAM,iBAAA,EAAgB,IAAI,QAAA,EAAA,UAAA,CAAW,GAAG,GAAE,CAAA,GAAW,IAAA;AAAA,oBACvEA,cAAAA,CAAC,UAAA,EAAA,EAAW,GAAA,EAAU,EAAA,EAAI,MAAM,QAAA,EAAoB;AAAA,GAAA,EACtD,CAAA;AAEJ","file":"react.cjs","sourcesContent":["/**\n * The pure, total reducer. `reduce(doc, op)` returns a new document; an invalid\n * op is a **logged no-op** (returns the same doc, `seq` unchanged). That single\n * property is what makes undo, revisions, collaboration, and agent edits all\n * flow through one spine.\n *\n * Ordering: top-level order is the canonical `sections` array; child order is\n * the fractional `order` field among siblings (see `fractional.ts`).\n */\nimport type { Action, Node, NodeId, PageDoc, StyleProps } from \"./types\";\nimport type { PageOp } from \"./ops\";\n\nexport interface ReduceOptions {\n /** Called when an op can't apply. Default: silent. */\n onInvalid?: (op: PageOp, reason: string) => void;\n}\n\n/** Children of a node (or top-level when `parent` is null), in order. */\nexport function childrenOf(doc: PageDoc, parent: NodeId | null): Node[] {\n if (parent === null) {\n return doc.sections.map((id) => doc.nodes[id]).filter((n): n is Node => Boolean(n));\n }\n const out: Node[] = [];\n for (const id of Object.keys(doc.nodes)) {\n const n = doc.nodes[id]!;\n if (n.parent === parent) out.push(n);\n }\n out.sort((a, b) => (a.order < b.order ? -1 : a.order > b.order ? 1 : 0));\n return out;\n}\n\n/** Every transitive descendant id of `id` (excludes `id`). */\nexport function descendantIds(doc: PageDoc, id: NodeId): NodeId[] {\n const out: NodeId[] = [];\n const stack: NodeId[] = [id];\n while (stack.length) {\n const current = stack.pop()!;\n for (const childId of Object.keys(doc.nodes)) {\n const n = doc.nodes[childId]!;\n if (n.parent === current) {\n out.push(childId);\n stack.push(childId);\n }\n }\n }\n return out;\n}\n\nfunction patchNode(doc: PageDoc, id: NodeId, patch: Partial<Node>): PageDoc {\n const node = doc.nodes[id]!;\n return {\n ...doc,\n nodes: { ...doc.nodes, [id]: { ...node, ...patch } },\n seq: doc.seq + 1,\n };\n}\n\nexport function reduce(doc: PageDoc, op: PageOp, opts: ReduceOptions = {}): PageDoc {\n const invalid = (reason: string): PageDoc => {\n opts.onInvalid?.(op, reason);\n return doc;\n };\n\n switch (op.t) {\n case \"insert_node\": {\n if (doc.nodes[op.node.id]) return invalid(\"node id already exists\");\n if (op.node.parent !== null && !doc.nodes[op.node.parent]) return invalid(\"parent not found\");\n return {\n ...doc,\n nodes: { ...doc.nodes, [op.node.id]: op.node },\n sections: op.node.parent === null ? [...doc.sections, op.node.id] : doc.sections,\n seq: doc.seq + 1,\n };\n }\n\n case \"remove_node\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n const dead = new Set<NodeId>([op.id, ...descendantIds(doc, op.id)]);\n const nodes: Record<NodeId, Node> = {};\n for (const id of Object.keys(doc.nodes)) {\n if (!dead.has(id)) nodes[id] = doc.nodes[id]!;\n }\n return {\n ...doc,\n nodes,\n sections: doc.sections.filter((s) => !dead.has(s)),\n seq: doc.seq + 1,\n };\n }\n\n case \"move_node\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n if (op.parent === op.id) return invalid(\"cannot parent a node to itself\");\n if (op.parent !== null && !doc.nodes[op.parent]) return invalid(\"new parent not found\");\n if (op.parent !== null && descendantIds(doc, op.id).includes(op.parent)) {\n return invalid(\"move would create a cycle\");\n }\n const wasTop = node.parent === null;\n const nowTop = op.parent === null;\n let sections = doc.sections;\n if (wasTop && !nowTop) sections = sections.filter((s) => s !== op.id);\n else if (!wasTop && nowTop) sections = [...sections, op.id];\n return {\n ...doc,\n nodes: { ...doc.nodes, [op.id]: { ...node, parent: op.parent, order: op.order } },\n sections,\n seq: doc.seq + 1,\n };\n }\n\n case \"set_props\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n return patchNode(doc, op.id, {\n props: { ...doc.nodes[op.id]!.props, ...op.patch } as unknown as Node[\"props\"],\n });\n }\n\n case \"set_layout\": {\n if (!doc.nodes[op.id]) return invalid(\"node not found\");\n return patchNode(doc, op.id, { layout: op.layout });\n }\n\n case \"set_style\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const prev = node.style[op.breakpoint] ?? {};\n return patchNode(doc, op.id, {\n style: { ...node.style, [op.breakpoint]: { ...prev, ...op.patch } } as Node[\"style\"],\n });\n }\n\n case \"set_constraints\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const base = node.constraints ?? { base: { width: \"hug\", height: \"hug\" } };\n const prev = base[op.breakpoint] ?? {};\n return patchNode(doc, op.id, {\n constraints: { ...base, [op.breakpoint]: { ...prev, ...op.patch } } as Node[\"constraints\"],\n });\n }\n\n case \"set_animation\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const list = node.animations ?? [];\n const idx = list.findIndex((a) => a.id === op.anim.id);\n return patchNode(doc, op.id, {\n animations: idx >= 0 ? list.map((a, i) => (i === idx ? op.anim : a)) : [...list, op.anim],\n });\n }\n\n case \"remove_animation\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n return patchNode(doc, op.id, {\n animations: (node.animations ?? []).filter((a) => a.id !== op.animId),\n });\n }\n\n case \"set_action\": {\n const node = doc.nodes[op.id];\n if (!node) return invalid(\"node not found\");\n const list = node.actions ?? [];\n let actions: Action[];\n if (op.action === null) actions = list.filter((_, i) => i !== op.index);\n else if (op.index >= list.length) actions = [...list, op.action];\n else actions = list.map((a, i) => (i === op.index ? op.action! : a));\n return patchNode(doc, op.id, { actions });\n }\n\n case \"set_meta\":\n return { ...doc, meta: { ...doc.meta, ...op.patch }, seq: doc.seq + 1 };\n\n case \"set_theme\":\n return { ...doc, theme: { ...doc.theme, ...op.patch }, seq: doc.seq + 1 };\n\n case \"reorder_sections\": {\n const current = new Set(doc.sections);\n const same =\n op.order.length === doc.sections.length && op.order.every((id) => current.has(id));\n if (!same) return invalid(\"reorder must be a permutation of current sections\");\n return { ...doc, sections: [...op.order], seq: doc.seq + 1 };\n }\n\n default:\n return invalid(`unknown op \"${(op as PageOp).t}\"`);\n }\n}\n\n/** Apply a sequence of ops, threading the document. */\nexport function reduceAll(doc: PageDoc, ops: PageOp[], opts?: ReduceOptions): PageDoc {\n return ops.reduce((d, op) => reduce(d, op, opts), doc);\n}\n\n/**\n * Inverse op(s) for undo, computed against the **pre-op** document. Structural\n * ops invert cleanly; merge-style ops (`set_props`/`set_style`/`set_constraints`)\n * restore the previous values of the touched keys (keys that did not previously\n * exist can't be deleted via a merge — a known Phase-0 limitation).\n */\nexport function invert(doc: PageDoc, op: PageOp): PageOp[] {\n switch (op.t) {\n case \"insert_node\":\n return [{ t: \"remove_node\", id: op.node.id }];\n\n case \"remove_node\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n // Re-insert the subtree parent-first, then restore section order.\n const ids = [op.id, ...descendantIds(doc, op.id)];\n const inserts: PageOp[] = ids.map((id) => ({ t: \"insert_node\", node: doc.nodes[id]! }));\n if (node.parent === null) inserts.push({ t: \"reorder_sections\", order: [...doc.sections] });\n return inserts;\n }\n\n case \"move_node\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const back: PageOp[] = [{ t: \"move_node\", id: op.id, parent: node.parent, order: node.order }];\n if (node.parent === null) back.push({ t: \"reorder_sections\", order: [...doc.sections] });\n return back;\n }\n\n case \"set_props\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const prev: Record<string, unknown> = {};\n for (const k of Object.keys(op.patch)) prev[k] = node.props[k];\n return [{ t: \"set_props\", id: op.id, patch: prev }];\n }\n\n case \"set_layout\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n return [{ t: \"set_layout\", id: op.id, layout: node.layout }];\n }\n\n case \"set_style\": {\n const node = doc.nodes[op.id];\n if (!node) return [];\n const prevBp = (node.style[op.breakpoint] ?? {}) as Record<string, unknown>;\n const prev: Partial<StyleProps> = {};\n for (const k of Object.keys(op.patch)) (prev as Record<string, unknown>)[k] = prevBp[k];\n return [{ t: \"set_style\", id: op.id, breakpoint: op.breakpoint, patch: prev }];\n }\n\n case \"set_meta\":\n return [{ t: \"set_meta\", patch: pick(doc.meta, Object.keys(op.patch)) }];\n\n case \"set_theme\":\n return [{ t: \"set_theme\", patch: pick(doc.theme, Object.keys(op.patch)) }];\n\n case \"reorder_sections\":\n return [{ t: \"reorder_sections\", order: [...doc.sections] }];\n\n default:\n return [];\n }\n}\n\nfunction pick<T extends object>(obj: T, keys: string[]): Partial<T> {\n const out: Partial<T> = {};\n for (const k of keys) (out as Record<string, unknown>)[k] = (obj as Record<string, unknown>)[k];\n return out;\n}\n","/**\n * Stages document model — see `fancy-ui/docs/fancy-cms.md` §4.\n *\n * A page is a vertical stack of full-bleed **sections**; inside each, **layers**\n * are positioned by constraints (free + bold stays responsive), with stack/grid\n * containers where structure is wanted. Nodes live in a **flat map** keyed by id\n * (Figma-style) so ops target a stable identity and collab merges stay clean.\n */\n\nexport type NodeId = string;\n\nexport type ScrollMode = \"snap\" | \"smooth\";\n\n/** Ordered, mobile-first. Convention: `\"base\" | \"md\" | \"lg\"`. */\nexport type Breakpoint = string;\n\nexport type Json = string | number | boolean | null | Json[] | { [key: string]: Json };\n\n/** A literal value OR a binding into the page's data context (`{ $bind: \"profile.coins\" }`). */\nexport interface Binding {\n $bind: string;\n}\nexport type Bound<T> = T | Binding;\n\nexport function isBinding(v: unknown): v is Binding {\n return typeof v === \"object\" && v !== null && typeof (v as Binding).$bind === \"string\";\n}\n\n// ── Sizing ────────────────────────────────────────────────────────────────\n\nexport type LengthUnit = \"px\" | \"%\" | \"vw\" | \"vh\" | \"rem\" | \"fr\";\nexport interface Length {\n value: number;\n unit: LengthUnit;\n}\nexport type SizeMode = Length | \"fill\" | \"hug\";\n\nexport interface Constraints {\n /** Pinned offsets within a `free` parent (null = unpinned). */\n left?: Length | null;\n right?: Length | null;\n top?: Length | null;\n bottom?: Length | null;\n centerX?: boolean;\n centerY?: boolean;\n width: SizeMode;\n height: SizeMode;\n}\n\nexport type LayoutMode = \"free\" | \"stack\" | \"grid\";\n\n// ── Style (compiles deterministically to CSS in both emitters) ──────────────\n\nexport interface StyleProps {\n // box\n padding?: Length;\n margin?: Length;\n radius?: Length;\n // color / surface\n background?: string; // color | gradient | url(...)\n color?: string;\n opacity?: number;\n // typography\n fontFamily?: string;\n fontSize?: Length;\n fontWeight?: number;\n lineHeight?: number;\n textAlign?: \"left\" | \"center\" | \"right\" | \"justify\";\n letterSpacing?: Length;\n // border / shadow / effects (static)\n border?: string;\n boxShadow?: string;\n transform?: string;\n filter?: string;\n // container knobs (when layout = stack | grid)\n gap?: Length;\n direction?: \"row\" | \"column\";\n columns?: number;\n align?: \"start\" | \"center\" | \"end\" | \"stretch\";\n justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\";\n}\n\n/** Per-breakpoint overrides; `base` is required, others cascade mobile-first. */\nexport type PerBreakpoint<T> = { base: T } & Partial<Record<Breakpoint, Partial<T>>>;\n\n// ── Animation (typed for Phase 2; ignored by the static CSS emitter) ────────\n\nexport type AnimDriver = \"enter\" | \"scroll\" | \"loop\" | \"hover\" | \"click\" | \"drag\";\nexport interface Keyframe {\n at: number; // 0..1\n props: Record<string, number | string>;\n}\nexport interface Anim {\n id: string;\n driver: AnimDriver;\n keyframes: Keyframe[];\n range?: { start: number; end: number };\n ease?: string;\n duration?: number;\n delay?: number;\n stagger?: number;\n once?: boolean;\n emit?: \"css\" | \"js\";\n}\n\n// ── Actions (typed for Phase 3/4) ───────────────────────────────────────────\n\nexport type ActionEvent = \"click\" | \"submit\" | \"change\" | \"load\" | \"timer\" | \"scroll-into\" | \"hover\";\nexport type ActionHandler =\n | { kind: \"navigate\"; href: Bound<string> }\n | { kind: \"submitForm\"; endpoint: string; method?: \"post\" | \"put\" }\n | { kind: \"mutate\"; target: string; value: Bound<Json> }\n | { kind: \"callEndpoint\"; endpoint: string; method?: string }\n | { kind: \"runFlow\"; flow: string }\n | { kind: \"openModal\"; node: NodeId }\n | { kind: \"openDrawer\"; node: NodeId }\n | { kind: \"playTimeline\"; section: NodeId }\n | { kind: \"toggleNode\"; node: NodeId };\nexport interface Action {\n on: ActionEvent;\n do: ActionHandler;\n}\n\n// ── Nodes & document ────────────────────────────────────────────────────────\n\nexport interface Node {\n id: NodeId;\n /** Addon key: `section | text | image | shape | stack | grid | frame | <addon>`. */\n type: string;\n /** `null` = a top-level section. */\n parent: NodeId | null;\n /** Fractional index — collab-safe ordering among siblings. */\n order: string;\n /** Addon-specific props; any value may be a {@link Binding}. */\n props: Record<string, Bound<Json>>;\n /** How this node arranges its children. */\n layout?: LayoutMode;\n /** How this node positions itself within its parent. */\n constraints?: PerBreakpoint<Constraints>;\n style: PerBreakpoint<StyleProps>;\n animations?: Anim[];\n actions?: Action[];\n /** Repeater: render this node once per item of a bound array. */\n repeat?: { each: Binding; as: string };\n /**\n * Host CSS class(es) applied to the node's element. The migration enabler:\n * reuse the host site's existing stylesheet for pixel-perfect reproduction\n * instead of re-deriving every rule through {@link StyleProps}.\n */\n className?: string;\n /** Derived from the addon manifest — interactive/3rd-party ⇒ hydration island. */\n island?: boolean;\n}\n\nexport interface ThemeTokens {\n name?: string;\n [key: string]: Json | undefined;\n}\n\nexport interface PageMeta {\n title: string;\n slug: string;\n scrollMode: ScrollMode;\n seo?: Record<string, string>;\n}\n\nexport interface PageDoc {\n id: string;\n /** Op counter — collab ordering + revision checkpoints. */\n seq: number;\n meta: PageMeta;\n theme: ThemeTokens;\n /** Ordered, mobile-first. */\n breakpoints: Breakpoint[];\n /** Top-level section order. */\n sections: NodeId[];\n /** Flat map — not a nested tree. */\n nodes: Record<NodeId, Node>;\n}\n\n/** Default breakpoint → min-width (px) map. `base` has no media query. */\nexport const DEFAULT_BREAKPOINTS: Record<string, number> = {\n base: 0,\n md: 768,\n lg: 1024,\n};\n\n/** A minimal empty document. */\nexport function emptyDoc(id: string, slug = \"/\"): PageDoc {\n return {\n id,\n seq: 0,\n meta: { title: \"Untitled\", slug, scrollMode: \"smooth\" },\n theme: { name: \"default\" },\n breakpoints: [\"base\", \"md\", \"lg\"],\n sections: [],\n nodes: {},\n };\n}\n","import type { ReactNode } from \"react\";\nimport { isBinding, type Bound, type Json, type Node, type PageDoc } from \"../document/types\";\n\n/** The page data context bindings resolve against (e.g. server props, repeater `item`). */\nexport type DataContext = Record<string, unknown>;\n\nexport interface ElementContext {\n node: Node;\n doc: PageDoc;\n /** Pre-rendered child nodes, in order. */\n children: ReactNode;\n /** The data context this node renders in (page data + any repeater scope). */\n data?: DataContext;\n /** Resolve a (possibly bound) value to its raw resolved value. */\n resolve: (v: Bound<Json> | undefined) => unknown;\n /** Resolve a (possibly bound) value to a display string. */\n text: (v: Bound<Json> | undefined) => string;\n}\n\n/**\n * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added\n * by {@link RenderNode}, so a renderer only returns what goes inside.\n */\nexport type ElementRenderer = (ctx: ElementContext) => ReactNode;\n\nexport type ElementRegistry = Record<string, ElementRenderer>;\n\n/** Read a dotted path (`a.b.c`) out of the data context. */\nexport function getPath(data: DataContext | undefined, path: string): unknown {\n return path.split(\".\").reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), data);\n}\n\n/** Resolve a value: a `{ $bind }` reads from the data context; anything else is literal. */\nexport function resolveValue(v: Bound<Json> | undefined, data?: DataContext): unknown {\n if (v === undefined || v === null) return v;\n if (isBinding(v)) return getPath(data, v.$bind);\n return v;\n}\n\n/** Built-in native elements. Containers render their children; leaves render content. */\nexport const defaultRegistry: ElementRegistry = {\n section: ({ children }) => children,\n stack: ({ children }) => children,\n grid: ({ children }) => children,\n frame: ({ children }) => children,\n shape: ({ children }) => children,\n card: ({ children }) => children,\n text: ({ node, text }) => text(node.props.content),\n heading: ({ node, text }) => text(node.props.content) || \"Heading\",\n link: ({ node, text }) => (\n <a href={text(node.props.href) || \"#\"} style={{ color: \"inherit\", textDecoration: \"underline\" }}>\n {text(node.props.content) || \"link\"}\n </a>\n ),\n divider: () => <hr style={{ border: \"none\", borderTop: \"1px solid #e2e8f0\", margin: 0 }} />,\n callout: ({ node, text }) => {\n const tone = ({ info: \"#3b82f6\", success: \"#10b981\", warning: \"#f59e0b\", danger: \"#ef4444\" } as Record<string, string>)[text(node.props.variant)] ?? \"#3b82f6\";\n return (\n <div style={{ display: \"flex\", gap: 10, padding: \"12px 14px\", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }}>\n <span style={{ fontSize: 13 }}>{text(node.props.content) || \"Callout\"}</span>\n </div>\n );\n },\n code: ({ node, text }) => (\n <pre style={{ margin: 0, padding: \"12px 14px\", borderRadius: 10, background: \"#0b1220\", color: \"#e2e8f0\", fontFamily: \"ui-monospace, monospace\", fontSize: 13, overflowX: \"auto\" }}>\n <code>{text(node.props.content)}</code>\n </pre>\n ),\n button: ({ node, text }) => {\n const label = text(node.props.label) || text(node.props.content) || \"Button\";\n const variant = text(node.props.variant) || \"primary\";\n return <span data-cms-button={variant} style={buttonStyle(variant)}>{label}</span>;\n },\n image: ({ node, text }) => (\n <img\n src={text(node.props.src)}\n alt={text(node.props.alt)}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }}\n />\n ),\n};\n\n/**\n * A self-contained button look so the `button` primitive renders sensibly with\n * zero host CSS. Hosts that want their own button classes override the `button`\n * renderer in their registry (e.g. the sandbox maps it to `.btn .btn-primary`).\n */\nfunction buttonStyle(variant: string): import(\"react\").CSSProperties {\n const base: import(\"react\").CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"9px 16px\",\n borderRadius: 8,\n fontWeight: 600,\n fontSize: 14,\n lineHeight: 1,\n cursor: \"pointer\",\n userSelect: \"none\",\n };\n if (variant === \"ghost\") return { ...base, background: \"transparent\", color: \"inherit\", border: \"1px solid currentColor\" };\n if (variant === \"outline\") return { ...base, background: \"transparent\", color: \"#7c3aed\", border: \"1px solid #7c3aed\" };\n return { ...base, background: \"#7c3aed\", color: \"#fff\", border: \"1px solid #7c3aed\" };\n}\n","import type { ReactElement } from \"react\";\nimport type { Bound, Json, PageDoc } from \"../document/types\";\nimport { childrenOf } from \"../document/reduce\";\nimport { defaultRegistry, resolveValue, type DataContext, type ElementRegistry } from \"./registry\";\n\nexport interface RenderNodeProps {\n doc: PageDoc;\n id: string;\n registry?: ElementRegistry;\n /** Data context bindings resolve against (page data + any repeater scope). */\n data?: DataContext;\n}\n\n/**\n * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS\n * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /\n * 3rd-party **islands** with no registered renderer become an empty, sized\n * placeholder the host hydrates client-side — mirroring the PHP renderer.\n *\n * `repeater` nodes are special-cased: their first child is a template rendered\n * once per item of the bound `items` array, each in a scope that exposes `item`\n * and `index` to nested `{ $bind: \"item.…\" }` bindings.\n */\nexport function RenderNode({ doc, id, registry = defaultRegistry, data }: RenderNodeProps): ReactElement | null {\n const node = doc.nodes[id];\n if (!node) return null;\n\n if (node.type === \"repeater\") {\n const arr = resolveValue(node.props.items as Bound<Json> | undefined, data);\n const items = Array.isArray(arr) ? arr : [];\n const template = childrenOf(doc, id)[0];\n return (\n <div data-cms={node.id} className={node.className}>\n {template\n ? items.map((item, index) => (\n <RenderNode key={index} doc={doc} id={template.id} registry={registry} data={{ ...data, item, index }} />\n ))\n : null}\n </div>\n );\n }\n\n const kids = childrenOf(doc, id).map((child) => (\n <RenderNode key={child.id} doc={doc} id={child.id} registry={registry} data={data} />\n ));\n\n const resolve = (v: Bound<Json> | undefined) => resolveValue(v, data);\n const text = (v: Bound<Json> | undefined): string => {\n const r = resolve(v);\n return r === undefined || r === null ? \"\" : String(r);\n };\n\n const renderer = registry[node.type];\n const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;\n\n return (\n <div data-cms={node.id} data-cms-island={node.island ? node.type : undefined} className={node.className}>\n {inner}\n </div>\n );\n}\n","/**\n * The JS style → CSS emitter (one half of the dual-emitter pair; the PHP\n * `particle-academy/fancy-cms` mirrors it byte-for-byte). Output is\n * **deterministic**: declarations are sorted, selectors and media blocks use a\n * fixed format, and nodes are emitted in sorted-id order — so the parity harness\n * can diff the two engines' CSS exactly.\n *\n * Phase 0 covers the static subset (layout / constraints / style). Motion lands\n * in Phase 2.\n */\nimport {\n DEFAULT_BREAKPOINTS,\n type Constraints,\n type Length,\n type Node,\n type PageDoc,\n type SizeMode,\n type StyleProps,\n} from \"../document/types\";\n\nconst selectorFor = (id: string): string => `[data-cms=\"${id}\"]`;\n\nfunction lengthToCss(len: Length): string {\n return `${len.value}${len.unit}`;\n}\n\nfunction sizeToCss(size: SizeMode): string {\n if (size === \"fill\") return \"100%\";\n if (size === \"hug\") return \"auto\";\n return lengthToCss(size);\n}\n\nconst ALIGN: Record<string, string> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n stretch: \"stretch\",\n};\nconst JUSTIFY: Record<string, string> = {\n start: \"flex-start\",\n center: \"center\",\n end: \"flex-end\",\n between: \"space-between\",\n around: \"space-around\",\n};\n\n/** Map StyleProps → CSS declarations (property → value). */\nfunction styleDecls(style: Partial<StyleProps>): Record<string, string> {\n const d: Record<string, string> = {};\n if (style.padding) d[\"padding\"] = lengthToCss(style.padding);\n if (style.margin) d[\"margin\"] = lengthToCss(style.margin);\n if (style.radius) d[\"border-radius\"] = lengthToCss(style.radius);\n if (style.background !== undefined) d[\"background\"] = style.background;\n if (style.color !== undefined) d[\"color\"] = style.color;\n if (style.opacity !== undefined) d[\"opacity\"] = String(style.opacity);\n if (style.fontFamily !== undefined) d[\"font-family\"] = style.fontFamily;\n if (style.fontSize) d[\"font-size\"] = lengthToCss(style.fontSize);\n if (style.fontWeight !== undefined) d[\"font-weight\"] = String(style.fontWeight);\n if (style.lineHeight !== undefined) d[\"line-height\"] = String(style.lineHeight);\n if (style.textAlign !== undefined) d[\"text-align\"] = style.textAlign;\n if (style.letterSpacing) d[\"letter-spacing\"] = lengthToCss(style.letterSpacing);\n if (style.border !== undefined) d[\"border\"] = style.border;\n if (style.boxShadow !== undefined) d[\"box-shadow\"] = style.boxShadow;\n if (style.transform !== undefined) d[\"transform\"] = style.transform;\n if (style.filter !== undefined) d[\"filter\"] = style.filter;\n if (style.gap) d[\"gap\"] = lengthToCss(style.gap);\n if (style.align !== undefined) d[\"align-items\"] = ALIGN[style.align] ?? style.align;\n if (style.justify !== undefined) d[\"justify-content\"] = JUSTIFY[style.justify] ?? style.justify;\n return d;\n}\n\n/** Container behaviour from `node.layout`. */\nfunction layoutDecls(node: Node): Record<string, string> {\n const d: Record<string, string> = {};\n if (node.layout === \"stack\") {\n d[\"display\"] = \"flex\";\n d[\"flex-direction\"] = node.style.base.direction ?? \"column\";\n } else if (node.layout === \"grid\") {\n d[\"display\"] = \"grid\";\n d[\"grid-template-columns\"] = `repeat(${node.style.base.columns ?? 1}, 1fr)`;\n } else if (node.layout === \"free\") {\n d[\"position\"] = \"relative\";\n }\n return d;\n}\n\n/** Self-positioning from constraints (absolute pins only inside a `free` parent). */\nfunction constraintDecls(c: Partial<Constraints>, parentFree: boolean): Record<string, string> {\n const d: Record<string, string> = {};\n const transforms: string[] = [];\n if (parentFree) {\n d[\"position\"] = \"absolute\";\n if (c.left) d[\"left\"] = lengthToCss(c.left);\n if (c.right) d[\"right\"] = lengthToCss(c.right);\n if (c.top) d[\"top\"] = lengthToCss(c.top);\n if (c.bottom) d[\"bottom\"] = lengthToCss(c.bottom);\n if (c.centerX) {\n d[\"left\"] = \"50%\";\n transforms.push(\"translateX(-50%)\");\n }\n if (c.centerY) {\n d[\"top\"] = \"50%\";\n transforms.push(\"translateY(-50%)\");\n }\n if (transforms.length) d[\"transform\"] = transforms.join(\" \");\n }\n if (c.width) d[\"width\"] = sizeToCss(c.width);\n if (c.height) d[\"height\"] = sizeToCss(c.height);\n return d;\n}\n\nfunction isParentFree(doc: PageDoc, node: Node): boolean {\n if (node.parent === null) return false; // sections stack in the page flow\n const parent = doc.nodes[node.parent];\n return !parent || parent.layout === undefined || parent.layout === \"free\";\n}\n\nfunction serializeRule(selector: string, decls: Record<string, string>, indent: string): string {\n const props = Object.keys(decls).sort();\n if (props.length === 0) return \"\";\n const body = props.map((p) => `${indent} ${p}: ${decls[p]};`).join(\"\\n\");\n return `${indent}${selector} {\\n${body}\\n${indent}}`;\n}\n\n/** Emit the full stylesheet for a document. Deterministic. */\nexport function emitDocCss(doc: PageDoc): string {\n const breakpointPx = DEFAULT_BREAKPOINTS;\n const blocks: string[] = [];\n\n for (const id of Object.keys(doc.nodes).sort()) {\n const node = doc.nodes[id]!;\n const selector = selectorFor(id);\n const parentFree = isParentFree(doc, node);\n\n // base = style.base + layout + constraints.base\n const base: Record<string, string> = {\n ...layoutDecls(node),\n ...(node.constraints ? constraintDecls(node.constraints.base, parentFree) : {}),\n ...styleDecls(node.style.base),\n };\n const baseRule = serializeRule(selector, base, \"\");\n if (baseRule) blocks.push(baseRule);\n\n // per-breakpoint overrides (mobile-first, in doc order, skipping base)\n for (const bp of doc.breakpoints) {\n if (bp === \"base\") continue;\n const px = breakpointPx[bp];\n if (px === undefined) continue;\n const decls: Record<string, string> = {\n ...(node.constraints?.[bp] ? constraintDecls(node.constraints[bp]!, parentFree) : {}),\n ...(node.style[bp] ? styleDecls(node.style[bp]!) : {}),\n };\n const inner = serializeRule(selector, decls, \" \");\n if (inner) blocks.push(`@media (min-width: ${px}px) {\\n${inner}\\n}`);\n }\n }\n\n return blocks.join(\"\\n\\n\") + (blocks.length ? \"\\n\" : \"\");\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type DataContext, type ElementRegistry } from \"./registry\";\n\nexport interface CmsPageProps {\n doc: PageDoc;\n registry?: ElementRegistry;\n /** Data context that `{ $bind: \"…\" }` props + repeaters resolve against. */\n data?: DataContext;\n /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */\n includeStyles?: boolean;\n}\n\n/** Render a full page document: the compiled stylesheet + every section. */\nexport function CmsPage({\n doc,\n registry = defaultRegistry,\n data,\n includeStyles = true,\n}: CmsPageProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n {doc.sections.map((id) => (\n <RenderNode key={id} doc={doc} id={id} registry={registry} data={data} />\n ))}\n </Fragment>\n );\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface CmsRegionProps {\n doc: PageDoc;\n /** Root node id of the region to render (a section, or any node). */\n root: string;\n registry?: ElementRegistry;\n includeStyles?: boolean;\n}\n\n/**\n * Render a single subtree — drop into a hand-coded page so the CMS owns just\n * that region (the \"guest, not host\" mounting mode). Phase 0/1 emits the whole\n * document's CSS; scoping it to the subtree is a later optimization.\n */\nexport function CmsRegion({\n doc,\n root,\n registry = defaultRegistry,\n includeStyles = true,\n}: CmsRegionProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n <RenderNode doc={doc} id={root} registry={registry} />\n </Fragment>\n );\n}\n"]}
package/dist/react.d.cts CHANGED
@@ -1,29 +1,37 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { P as PageDoc } from './types-CpfPEq3V.cjs';
3
- import { a as ElementRegistry } from './registry-Bk7GN1rA.cjs';
4
- export { E as ElementContext, b as ElementRenderer, d as defaultRegistry } from './registry-Bk7GN1rA.cjs';
3
+ import { a as ElementRegistry, D as DataContext } from './registry-ni9y0dSw.cjs';
4
+ export { E as ElementContext, b as ElementRenderer, d as defaultRegistry } from './registry-ni9y0dSw.cjs';
5
5
 
6
6
  interface RenderNodeProps {
7
7
  doc: PageDoc;
8
8
  id: string;
9
9
  registry?: ElementRegistry;
10
+ /** Data context bindings resolve against (page data + any repeater scope). */
11
+ data?: DataContext;
10
12
  }
11
13
  /**
12
14
  * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS
13
15
  * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /
14
16
  * 3rd-party **islands** with no registered renderer become an empty, sized
15
17
  * placeholder the host hydrates client-side — mirroring the PHP renderer.
18
+ *
19
+ * `repeater` nodes are special-cased: their first child is a template rendered
20
+ * once per item of the bound `items` array, each in a scope that exposes `item`
21
+ * and `index` to nested `{ $bind: "item.…" }` bindings.
16
22
  */
17
- declare function RenderNode({ doc, id, registry }: RenderNodeProps): ReactElement | null;
23
+ declare function RenderNode({ doc, id, registry, data }: RenderNodeProps): ReactElement | null;
18
24
 
19
25
  interface CmsPageProps {
20
26
  doc: PageDoc;
21
27
  registry?: ElementRegistry;
28
+ /** Data context that `{ $bind: "…" }` props + repeaters resolve against. */
29
+ data?: DataContext;
22
30
  /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */
23
31
  includeStyles?: boolean;
24
32
  }
25
33
  /** Render a full page document: the compiled stylesheet + every section. */
26
- declare function CmsPage({ doc, registry, includeStyles, }: CmsPageProps): ReactElement;
34
+ declare function CmsPage({ doc, registry, data, includeStyles, }: CmsPageProps): ReactElement;
27
35
 
28
36
  interface CmsRegionProps {
29
37
  doc: PageDoc;
package/dist/react.d.ts CHANGED
@@ -1,29 +1,37 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { P as PageDoc } from './types-CpfPEq3V.js';
3
- import { a as ElementRegistry } from './registry-Dht9JBLU.js';
4
- export { E as ElementContext, b as ElementRenderer, d as defaultRegistry } from './registry-Dht9JBLU.js';
3
+ import { a as ElementRegistry, D as DataContext } from './registry-C3PioMnL.js';
4
+ export { E as ElementContext, b as ElementRenderer, d as defaultRegistry } from './registry-C3PioMnL.js';
5
5
 
6
6
  interface RenderNodeProps {
7
7
  doc: PageDoc;
8
8
  id: string;
9
9
  registry?: ElementRegistry;
10
+ /** Data context bindings resolve against (page data + any repeater scope). */
11
+ data?: DataContext;
10
12
  }
11
13
  /**
12
14
  * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS
13
15
  * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /
14
16
  * 3rd-party **islands** with no registered renderer become an empty, sized
15
17
  * placeholder the host hydrates client-side — mirroring the PHP renderer.
18
+ *
19
+ * `repeater` nodes are special-cased: their first child is a template rendered
20
+ * once per item of the bound `items` array, each in a scope that exposes `item`
21
+ * and `index` to nested `{ $bind: "item.…" }` bindings.
16
22
  */
17
- declare function RenderNode({ doc, id, registry }: RenderNodeProps): ReactElement | null;
23
+ declare function RenderNode({ doc, id, registry, data }: RenderNodeProps): ReactElement | null;
18
24
 
19
25
  interface CmsPageProps {
20
26
  doc: PageDoc;
21
27
  registry?: ElementRegistry;
28
+ /** Data context that `{ $bind: "…" }` props + repeaters resolve against. */
29
+ data?: DataContext;
22
30
  /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */
23
31
  includeStyles?: boolean;
24
32
  }
25
33
  /** Render a full page document: the compiled stylesheet + every section. */
26
- declare function CmsPage({ doc, registry, includeStyles, }: CmsPageProps): ReactElement;
34
+ declare function CmsPage({ doc, registry, data, includeStyles, }: CmsPageProps): ReactElement;
27
35
 
28
36
  interface CmsRegionProps {
29
37
  doc: PageDoc;
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
- import { RenderNode, defaultRegistry } from './chunk-E5AAQX7P.js';
2
- export { CmsPage, RenderNode, defaultRegistry } from './chunk-E5AAQX7P.js';
1
+ import { RenderNode, defaultRegistry } from './chunk-IXPYY4S2.js';
2
+ export { CmsPage, RenderNode, defaultRegistry } from './chunk-IXPYY4S2.js';
3
3
  import { emitDocCss } from './chunk-3YZMELJJ.js';
4
4
  import { Fragment } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { N as Node, P as PageDoc, e as Bound, J as Json } from './types-CpfPEq3V.js';
3
+
4
+ /** The page data context bindings resolve against (e.g. server props, repeater `item`). */
5
+ type DataContext = Record<string, unknown>;
6
+ interface ElementContext {
7
+ node: Node;
8
+ doc: PageDoc;
9
+ /** Pre-rendered child nodes, in order. */
10
+ children: ReactNode;
11
+ /** The data context this node renders in (page data + any repeater scope). */
12
+ data?: DataContext;
13
+ /** Resolve a (possibly bound) value to its raw resolved value. */
14
+ resolve: (v: Bound<Json> | undefined) => unknown;
15
+ /** Resolve a (possibly bound) value to a display string. */
16
+ text: (v: Bound<Json> | undefined) => string;
17
+ }
18
+ /**
19
+ * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added
20
+ * by {@link RenderNode}, so a renderer only returns what goes inside.
21
+ */
22
+ type ElementRenderer = (ctx: ElementContext) => ReactNode;
23
+ type ElementRegistry = Record<string, ElementRenderer>;
24
+ /** Built-in native elements. Containers render their children; leaves render content. */
25
+ declare const defaultRegistry: ElementRegistry;
26
+
27
+ export { type DataContext as D, type ElementContext as E, type ElementRegistry as a, type ElementRenderer as b, defaultRegistry as d };
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { N as Node, P as PageDoc, e as Bound, J as Json } from './types-CpfPEq3V.cjs';
3
+
4
+ /** The page data context bindings resolve against (e.g. server props, repeater `item`). */
5
+ type DataContext = Record<string, unknown>;
6
+ interface ElementContext {
7
+ node: Node;
8
+ doc: PageDoc;
9
+ /** Pre-rendered child nodes, in order. */
10
+ children: ReactNode;
11
+ /** The data context this node renders in (page data + any repeater scope). */
12
+ data?: DataContext;
13
+ /** Resolve a (possibly bound) value to its raw resolved value. */
14
+ resolve: (v: Bound<Json> | undefined) => unknown;
15
+ /** Resolve a (possibly bound) value to a display string. */
16
+ text: (v: Bound<Json> | undefined) => string;
17
+ }
18
+ /**
19
+ * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added
20
+ * by {@link RenderNode}, so a renderer only returns what goes inside.
21
+ */
22
+ type ElementRenderer = (ctx: ElementContext) => ReactNode;
23
+ type ElementRegistry = Record<string, ElementRenderer>;
24
+ /** Built-in native elements. Containers render their children; leaves render content. */
25
+ declare const defaultRegistry: ElementRegistry;
26
+
27
+ export { type DataContext as D, type ElementContext as E, type ElementRegistry as a, type ElementRenderer as b, defaultRegistry as d };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-cms-ui",
3
- "version": "0.0.1-dev.13",
3
+ "version": "0.0.1-dev.15",
4
4
  "description": "Editor + isomorphic renderer for fancy-cms — the Stages document model, PageOp op-spine, the JS style→CSS emitter (one half of the dual-emitter pair with the PHP particle-academy/fancy-cms), and the React renderer. Built on the Fancy UI suite.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,86 +0,0 @@
1
- import { childrenOf, emitDocCss, isBinding } from './chunk-3YZMELJJ.js';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { Fragment } from 'react';
4
-
5
- function literal(v) {
6
- if (v === void 0 || v === null) return "";
7
- if (isBinding(v)) return "";
8
- return String(v);
9
- }
10
- var defaultRegistry = {
11
- section: ({ children }) => children,
12
- stack: ({ children }) => children,
13
- grid: ({ children }) => children,
14
- frame: ({ children }) => children,
15
- shape: ({ children }) => children,
16
- card: ({ children }) => children,
17
- text: ({ node }) => literal(node.props.content),
18
- heading: ({ node }) => literal(node.props.content) || "Heading",
19
- link: ({ node }) => /* @__PURE__ */ jsx("a", { href: literal(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: literal(node.props.content) || "link" }),
20
- divider: () => /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
21
- callout: ({ node }) => {
22
- const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[literal(node.props.variant)] ?? "#3b82f6";
23
- return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: literal(node.props.content) || "Callout" }) });
24
- },
25
- code: ({ node }) => /* @__PURE__ */ jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsx("code", { children: literal(node.props.content) }) }),
26
- button: ({ node }) => {
27
- const label = literal(node.props.label) || literal(node.props.content) || "Button";
28
- const variant = literal(node.props.variant) || "primary";
29
- return /* @__PURE__ */ jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label });
30
- },
31
- image: ({ node }) => /* @__PURE__ */ jsx(
32
- "img",
33
- {
34
- src: literal(node.props.src),
35
- alt: literal(node.props.alt),
36
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
37
- }
38
- )
39
- };
40
- function buttonStyle(variant) {
41
- const base = {
42
- display: "inline-flex",
43
- alignItems: "center",
44
- gap: 6,
45
- padding: "9px 16px",
46
- borderRadius: 8,
47
- fontWeight: 600,
48
- fontSize: 14,
49
- lineHeight: 1,
50
- cursor: "pointer",
51
- userSelect: "none"
52
- };
53
- if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
54
- if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
55
- return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
56
- }
57
- function RenderNode({ doc, id, registry = defaultRegistry }) {
58
- const node = doc.nodes[id];
59
- if (!node) return null;
60
- const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(RenderNode, { doc, id: child.id, registry }, child.id));
61
- const renderer = registry[node.type];
62
- const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
63
- return /* @__PURE__ */ jsx(
64
- "div",
65
- {
66
- "data-cms": node.id,
67
- "data-cms-island": node.island ? node.type : void 0,
68
- className: node.className,
69
- children: inner
70
- }
71
- );
72
- }
73
- function CmsPage({
74
- doc,
75
- registry = defaultRegistry,
76
- includeStyles = true
77
- }) {
78
- return /* @__PURE__ */ jsxs(Fragment, { children: [
79
- includeStyles ? /* @__PURE__ */ jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
80
- doc.sections.map((id) => /* @__PURE__ */ jsx(RenderNode, { doc, id, registry }, id))
81
- ] });
82
- }
83
-
84
- export { CmsPage, RenderNode, defaultRegistry };
85
- //# sourceMappingURL=chunk-E5AAQX7P.js.map
86
- //# sourceMappingURL=chunk-E5AAQX7P.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/react/registry.tsx","../src/react/RenderNode.tsx","../src/react/CmsPage.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-E5AAQX7P.js","sourcesContent":["import type { ReactNode } from \"react\";\nimport { isBinding, type Bound, type Json, type Node, type PageDoc } from \"../document/types\";\n\nexport interface ElementContext {\n node: Node;\n doc: PageDoc;\n /** Pre-rendered child nodes, in order. */\n children: ReactNode;\n}\n\n/**\n * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added\n * by {@link RenderNode}, so a renderer only returns what goes inside.\n */\nexport type ElementRenderer = (ctx: ElementContext) => ReactNode;\n\nexport type ElementRegistry = Record<string, ElementRenderer>;\n\n/** Resolve a literal prop to a string (bindings resolve in Phase 3). */\nfunction literal(v: Bound<Json> | undefined): string {\n if (v === undefined || v === null) return \"\";\n if (isBinding(v)) return \"\";\n return String(v);\n}\n\n/** Built-in native elements. Containers render their children; leaves render content. */\nexport const defaultRegistry: ElementRegistry = {\n section: ({ children }) => children,\n stack: ({ children }) => children,\n grid: ({ children }) => children,\n frame: ({ children }) => children,\n shape: ({ children }) => children,\n card: ({ children }) => children,\n text: ({ node }) => literal(node.props.content),\n heading: ({ node }) => literal(node.props.content) || \"Heading\",\n link: ({ node }) => (\n <a href={literal(node.props.href) || \"#\"} style={{ color: \"inherit\", textDecoration: \"underline\" }}>\n {literal(node.props.content) || \"link\"}\n </a>\n ),\n divider: () => <hr style={{ border: \"none\", borderTop: \"1px solid #e2e8f0\", margin: 0 }} />,\n callout: ({ node }) => {\n const tone = ({ info: \"#3b82f6\", success: \"#10b981\", warning: \"#f59e0b\", danger: \"#ef4444\" } as Record<string, string>)[literal(node.props.variant)] ?? \"#3b82f6\";\n return (\n <div style={{ display: \"flex\", gap: 10, padding: \"12px 14px\", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }}>\n <span style={{ fontSize: 13 }}>{literal(node.props.content) || \"Callout\"}</span>\n </div>\n );\n },\n code: ({ node }) => (\n <pre style={{ margin: 0, padding: \"12px 14px\", borderRadius: 10, background: \"#0b1220\", color: \"#e2e8f0\", fontFamily: \"ui-monospace, monospace\", fontSize: 13, overflowX: \"auto\" }}>\n <code>{literal(node.props.content)}</code>\n </pre>\n ),\n button: ({ node }) => {\n const label = literal(node.props.label) || literal(node.props.content) || \"Button\";\n const variant = literal(node.props.variant) || \"primary\";\n return <span data-cms-button={variant} style={buttonStyle(variant)}>{label}</span>;\n },\n image: ({ node }) => (\n <img\n src={literal(node.props.src)}\n alt={literal(node.props.alt)}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }}\n />\n ),\n};\n\n/**\n * A self-contained button look so the `button` primitive renders sensibly with\n * zero host CSS. Hosts that want their own button classes override the `button`\n * renderer in their registry (e.g. the sandbox maps it to `.btn .btn-primary`).\n */\nfunction buttonStyle(variant: string): import(\"react\").CSSProperties {\n const base: import(\"react\").CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"9px 16px\",\n borderRadius: 8,\n fontWeight: 600,\n fontSize: 14,\n lineHeight: 1,\n cursor: \"pointer\",\n userSelect: \"none\",\n };\n if (variant === \"ghost\") return { ...base, background: \"transparent\", color: \"inherit\", border: \"1px solid currentColor\" };\n if (variant === \"outline\") return { ...base, background: \"transparent\", color: \"#7c3aed\", border: \"1px solid #7c3aed\" };\n return { ...base, background: \"#7c3aed\", color: \"#fff\", border: \"1px solid #7c3aed\" };\n}\n","import type { ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { childrenOf } from \"../document/reduce\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface RenderNodeProps {\n doc: PageDoc;\n id: string;\n registry?: ElementRegistry;\n}\n\n/**\n * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS\n * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /\n * 3rd-party **islands** with no registered renderer become an empty, sized\n * placeholder the host hydrates client-side — mirroring the PHP renderer.\n */\nexport function RenderNode({ doc, id, registry = defaultRegistry }: RenderNodeProps): ReactElement | null {\n const node = doc.nodes[id];\n if (!node) return null;\n\n const kids = childrenOf(doc, id).map((child) => (\n <RenderNode key={child.id} doc={doc} id={child.id} registry={registry} />\n ));\n\n const renderer = registry[node.type];\n const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;\n\n return (\n <div\n data-cms={node.id}\n data-cms-island={node.island ? node.type : undefined}\n className={node.className}\n >\n {inner}\n </div>\n );\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type ElementRegistry } from \"./registry\";\n\nexport interface CmsPageProps {\n doc: PageDoc;\n registry?: ElementRegistry;\n /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */\n includeStyles?: boolean;\n}\n\n/** Render a full page document: the compiled stylesheet + every section. */\nexport function CmsPage({\n doc,\n registry = defaultRegistry,\n includeStyles = true,\n}: CmsPageProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n {doc.sections.map((id) => (\n <RenderNode key={id} doc={doc} id={id} registry={registry} />\n ))}\n </Fragment>\n );\n}\n"]}