@particle-academy/fancy-cms-ui 0.0.1-dev.14 → 0.0.1-dev.16

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.
@@ -19,6 +19,9 @@ var defaultRegistry = {
19
19
  card: ({ children }) => children,
20
20
  text: ({ node, text }) => text(node.props.content),
21
21
  heading: ({ node, text }) => text(node.props.content) || "Heading",
22
+ // Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that
23
+ // plain `text` can't express. The author owns the markup (it's their content).
24
+ richtext: ({ node, text }) => /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: text(node.props.html) } }),
22
25
  link: ({ node, text }) => /* @__PURE__ */ jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
23
26
  divider: () => /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
24
27
  callout: ({ node, text }) => {
@@ -89,5 +92,5 @@ function CmsPage({
89
92
  }
90
93
 
91
94
  export { CmsPage, RenderNode, defaultRegistry };
92
- //# sourceMappingURL=chunk-IXPYY4S2.js.map
93
- //# sourceMappingURL=chunk-IXPYY4S2.js.map
95
+ //# sourceMappingURL=chunk-KCJWSR7D.js.map
96
+ //# sourceMappingURL=chunk-KCJWSR7D.js.map
@@ -0,0 +1 @@
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-KCJWSR7D.js","sourcesContent":["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 // Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that\n // plain `text` can't express. The author owns the markup (it's their content).\n richtext: ({ node, text }) => <div dangerouslySetInnerHTML={{ __html: text(node.props.html) }} />,\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","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"]}
package/dist/editor.cjs CHANGED
@@ -378,6 +378,9 @@ var defaultRegistry = {
378
378
  card: ({ children }) => children,
379
379
  text: ({ node, text }) => text(node.props.content),
380
380
  heading: ({ node, text }) => text(node.props.content) || "Heading",
381
+ // Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that
382
+ // plain `text` can't express. The author owns the markup (it's their content).
383
+ richtext: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: text(node.props.html) } }),
381
384
  link: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
382
385
  divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
383
386
  callout: ({ node, text }) => {
@@ -1297,7 +1300,7 @@ function EditablePage({
1297
1300
  },
1298
1301
  [ed, selNode]
1299
1302
  );
1300
- const addNodeAt = react.useCallback(
1303
+ react.useCallback(
1301
1304
  (kind, targetId) => {
1302
1305
  const doc2 = ed.state.doc;
1303
1306
  const target = targetId ? doc2.nodes[targetId] : null;
@@ -1326,12 +1329,48 @@ function EditablePage({
1326
1329
  window.addEventListener("mousemove", onMove);
1327
1330
  return () => window.removeEventListener("mousemove", onMove);
1328
1331
  }, [editing, draggingEl]);
1332
+ const [dropHint, setDropHint] = react.useState(null);
1333
+ const computeDropTarget = (clientX, clientY) => {
1334
+ const el = document.elementFromPoint(clientX, clientY)?.closest("[data-cms]");
1335
+ if (!el) return { id: null, edge: "inside", rect: null };
1336
+ const id = el.getAttribute("data-cms");
1337
+ const node = ed.state.doc.nodes[id];
1338
+ const r = el.getBoundingClientRect();
1339
+ const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
1340
+ if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
1341
+ return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
1342
+ };
1343
+ const onPageDragOver = (e) => {
1344
+ e.preventDefault();
1345
+ e.dataTransfer.dropEffect = "copy";
1346
+ setDropHint(computeDropTarget(e.clientX, e.clientY));
1347
+ };
1348
+ const insertRelative = (kind, hint) => {
1349
+ const doc2 = ed.state.doc;
1350
+ let parent;
1351
+ let order;
1352
+ if (!hint.id || hint.edge === "inside") {
1353
+ parent = hint.id ?? (doc2.sections[doc2.sections.length - 1] ?? null);
1354
+ const sib = childrenOf(doc2, parent);
1355
+ order = keyBetween(sib.length ? sib[sib.length - 1].order : null, null);
1356
+ } else {
1357
+ const target = doc2.nodes[hint.id];
1358
+ parent = target.parent;
1359
+ const sib = childrenOf(doc2, parent);
1360
+ const idx = sib.findIndex((n) => n.id === hint.id);
1361
+ order = hint.edge === "before" ? keyBetween(idx > 0 ? sib[idx - 1].order : null, target.order) : keyBetween(target.order, idx < sib.length - 1 ? sib[idx + 1].order : null);
1362
+ }
1363
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
1364
+ const def = ADD_DEFAULTS[kind];
1365
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
1366
+ ed.select(id);
1367
+ };
1329
1368
  const onPageDrop = (e) => {
1330
1369
  const kind = e.dataTransfer.getData("application/x-cms-element");
1331
1370
  if (!kind) return;
1332
1371
  e.preventDefault();
1333
- const el = document.elementFromPoint(e.clientX, e.clientY)?.closest("[data-cms]");
1334
- addNodeAt(kind, el?.getAttribute("data-cms") ?? null);
1372
+ insertRelative(kind, computeDropTarget(e.clientX, e.clientY));
1373
+ setDropHint(null);
1335
1374
  setDraggingEl(false);
1336
1375
  };
1337
1376
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
@@ -1407,7 +1446,8 @@ function EditablePage({
1407
1446
  ref: pageRef,
1408
1447
  onClickCapture,
1409
1448
  onContextMenu: onContextMenuSelect,
1410
- onDragOver: editing ? (e) => e.preventDefault() : void 0,
1449
+ onDragOver: editing ? onPageDragOver : void 0,
1450
+ onDragLeave: editing ? () => setDropHint(null) : void 0,
1411
1451
  onDrop: editing ? onPageDrop : void 0,
1412
1452
  children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry, data })
1413
1453
  }
@@ -1478,10 +1518,23 @@ function EditablePage({
1478
1518
  onAdd: addNode
1479
1519
  }
1480
1520
  ) : null,
1521
+ editing && dropHint?.rect ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, { hint: dropHint }) : null,
1481
1522
  editing ? /* @__PURE__ */ jsxRuntime.jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
1482
1523
  editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
1483
1524
  ] });
1484
1525
  }
1526
+ function DropIndicator({
1527
+ hint
1528
+ }) {
1529
+ const r = hint.rect;
1530
+ if (!r) return null;
1531
+ const color = "#ec4899";
1532
+ if (hint.edge === "inside") {
1533
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: r.x, top: r.y, width: r.w, height: r.h, outline: `2px dashed ${color}`, outlineOffset: -2, borderRadius: 6, pointerEvents: "none", zIndex: Z2 + 4 } });
1534
+ }
1535
+ const y = hint.edge === "before" ? r.y : r.y + r.h;
1536
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: r.x, top: y - 1.5, width: r.w, height: 3, background: color, borderRadius: 2, boxShadow: "0 0 0 1px #fff", pointerEvents: "none", zIndex: Z2 + 4 } });
1537
+ }
1485
1538
  function ElementPalette({ open, onDragChange }) {
1486
1539
  return /* @__PURE__ */ jsxRuntime.jsxs(
1487
1540
  "div",
@@ -1558,6 +1611,7 @@ var ADD_DEFAULTS = {
1558
1611
  callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
1559
1612
  divider: { type: "divider", props: {}, style: {} },
1560
1613
  code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
1614
+ richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
1561
1615
  repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
1562
1616
  };
1563
1617
  var GRIPS = [
@@ -1770,6 +1824,7 @@ var ADD_MENU = [
1770
1824
  { kind: "box", label: "Box" },
1771
1825
  { kind: "divider", label: "Divider" },
1772
1826
  { kind: "code", label: "Code" },
1827
+ { kind: "richtext", label: "Rich text" },
1773
1828
  { kind: "repeater", label: "Repeater" }
1774
1829
  ];
1775
1830
  function EditBar({