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

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.
@@ -13,8 +13,16 @@ var defaultRegistry = {
13
13
  grid: ({ children }) => children,
14
14
  frame: ({ children }) => children,
15
15
  shape: ({ children }) => children,
16
+ card: ({ children }) => children,
16
17
  text: ({ node }) => literal(node.props.content),
17
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) }) }),
18
26
  button: ({ node }) => {
19
27
  const label = literal(node.props.label) || literal(node.props.content) || "Button";
20
28
  const variant = literal(node.props.variant) || "primary";
@@ -74,5 +82,5 @@ function CmsPage({
74
82
  }
75
83
 
76
84
  export { CmsPage, RenderNode, defaultRegistry };
77
- //# sourceMappingURL=chunk-ZV7CV54P.js.map
78
- //# sourceMappingURL=chunk-ZV7CV54P.js.map
85
+ //# sourceMappingURL=chunk-E5AAQX7P.js.map
86
+ //# sourceMappingURL=chunk-E5AAQX7P.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-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"]}
package/dist/editor.cjs CHANGED
@@ -372,8 +372,16 @@ var defaultRegistry = {
372
372
  grid: ({ children }) => children,
373
373
  frame: ({ children }) => children,
374
374
  shape: ({ children }) => children,
375
+ card: ({ children }) => children,
375
376
  text: ({ node }) => literal(node.props.content),
376
377
  heading: ({ node }) => literal(node.props.content) || "Heading",
378
+ link: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: literal(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: literal(node.props.content) || "link" }),
379
+ divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
380
+ callout: ({ node }) => {
381
+ const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[literal(node.props.variant)] ?? "#3b82f6";
382
+ 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" }) });
383
+ },
384
+ 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) }) }),
377
385
  button: ({ node }) => {
378
386
  const label2 = literal(node.props.label) || literal(node.props.content) || "Button";
379
387
  const variant = literal(node.props.variant) || "primary";
@@ -802,7 +810,7 @@ function Toolbar({ ed }) {
802
810
  }
803
811
  );
804
812
  }
805
- var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
813
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
806
814
  function NodeInspector({
807
815
  node,
808
816
  transform,
@@ -1250,11 +1258,48 @@ function EditablePage({
1250
1258
  const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
1251
1259
  const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
1252
1260
  const def = ADD_DEFAULTS[kind];
1253
- ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
1261
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
1254
1262
  ed.select(id);
1255
1263
  },
1256
1264
  [ed, selNode]
1257
1265
  );
1266
+ const addNodeAt = react.useCallback(
1267
+ (kind, targetId) => {
1268
+ const doc2 = ed.state.doc;
1269
+ const target = targetId ? doc2.nodes[targetId] : null;
1270
+ const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1271
+ const siblings = childrenOf(doc2, parent);
1272
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
1273
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
1274
+ const def = ADD_DEFAULTS[kind];
1275
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
1276
+ ed.select(id);
1277
+ },
1278
+ [ed]
1279
+ );
1280
+ const [paletteOpen, setPaletteOpen] = react.useState(false);
1281
+ const [draggingEl, setDraggingEl] = react.useState(false);
1282
+ react.useEffect(() => {
1283
+ if (!editing) {
1284
+ setPaletteOpen(false);
1285
+ return;
1286
+ }
1287
+ const onMove = (e) => {
1288
+ if (draggingEl) return;
1289
+ if (e.clientX >= window.innerWidth - 26) setPaletteOpen(true);
1290
+ else if (e.clientX < window.innerWidth - 236) setPaletteOpen(false);
1291
+ };
1292
+ window.addEventListener("mousemove", onMove);
1293
+ return () => window.removeEventListener("mousemove", onMove);
1294
+ }, [editing, draggingEl]);
1295
+ const onPageDrop = (e) => {
1296
+ const kind = e.dataTransfer.getData("application/x-cms-element");
1297
+ if (!kind) return;
1298
+ e.preventDefault();
1299
+ const el = document.elementFromPoint(e.clientX, e.clientY)?.closest("[data-cms]");
1300
+ addNodeAt(kind, el?.getAttribute("data-cms") ?? null);
1301
+ setDraggingEl(false);
1302
+ };
1258
1303
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
1259
1304
  const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
1260
1305
  const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
@@ -1322,7 +1367,17 @@ function EditablePage({
1322
1367
  if (el) ed.select(el.getAttribute("data-cms"));
1323
1368
  else ed.select(null);
1324
1369
  };
1325
- const page = /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) });
1370
+ const page = /* @__PURE__ */ jsxRuntime.jsx(
1371
+ "div",
1372
+ {
1373
+ ref: pageRef,
1374
+ onClickCapture,
1375
+ onContextMenu: onContextMenuSelect,
1376
+ onDragOver: editing ? (e) => e.preventDefault() : void 0,
1377
+ onDrop: editing ? onPageDrop : void 0,
1378
+ children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry })
1379
+ }
1380
+ );
1326
1381
  const pageShell = pinned ? /* @__PURE__ */ jsxRuntime.jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : frames > 1 ? (
1327
1382
  // Normal flow, but "expanded" — extra scroll length below the content so a
1328
1383
  // page can be made longer than its content (the + Frame control).
@@ -1389,16 +1444,86 @@ function EditablePage({
1389
1444
  onAdd: addNode
1390
1445
  }
1391
1446
  ) : null,
1447
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
1392
1448
  editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
1393
1449
  ] });
1394
1450
  }
1395
- var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
1451
+ function ElementPalette({ open, onDragChange }) {
1452
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1453
+ "div",
1454
+ {
1455
+ style: {
1456
+ position: "fixed",
1457
+ top: 64,
1458
+ bottom: 96,
1459
+ right: 0,
1460
+ width: 216,
1461
+ transform: open ? "translateX(0)" : "translateX(100%)",
1462
+ transition: "transform 0.18s ease",
1463
+ background: "#0b1220",
1464
+ color: "#e2e8f0",
1465
+ borderLeft: "1px solid #1e293b",
1466
+ borderTopLeftRadius: 12,
1467
+ borderBottomLeftRadius: 12,
1468
+ boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
1469
+ zIndex: Z2 + 2,
1470
+ fontFamily: "system-ui, sans-serif",
1471
+ padding: 12,
1472
+ overflowY: "auto"
1473
+ },
1474
+ children: [
1475
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5, marginBottom: 10 }, children: "Elements" }),
1476
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
1477
+ "div",
1478
+ {
1479
+ draggable: true,
1480
+ onDragStart: (e) => {
1481
+ e.dataTransfer.setData("application/x-cms-element", m.kind);
1482
+ e.dataTransfer.effectAllowed = "copy";
1483
+ onDragChange(true);
1484
+ },
1485
+ onDragEnd: () => onDragChange(false),
1486
+ title: `Drag "${m.label}" onto the page`,
1487
+ style: {
1488
+ display: "flex",
1489
+ flexDirection: "column",
1490
+ alignItems: "center",
1491
+ justifyContent: "center",
1492
+ gap: 4,
1493
+ height: 56,
1494
+ borderRadius: 8,
1495
+ background: "#0f172a",
1496
+ border: "1px solid #1e293b",
1497
+ cursor: "grab",
1498
+ fontSize: 12,
1499
+ userSelect: "none"
1500
+ },
1501
+ children: [
1502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 16, opacity: 0.7 }, children: "\uFF0B" }),
1503
+ m.label
1504
+ ]
1505
+ },
1506
+ m.kind
1507
+ )) }),
1508
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 10.5, opacity: 0.45, marginTop: 12, lineHeight: 1.4 }, children: "Drag an Element onto the page. It drops into the container you release over." })
1509
+ ]
1510
+ }
1511
+ );
1512
+ }
1513
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
1396
1514
  var ADD_DEFAULTS = {
1397
1515
  text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
1398
1516
  heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
1399
1517
  button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
1518
+ link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
1400
1519
  image: { type: "image", props: { src: "", alt: "" }, style: {} },
1401
- box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
1520
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
1521
+ stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
1522
+ grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
1523
+ card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
1524
+ callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
1525
+ divider: { type: "divider", props: {}, style: {} },
1526
+ code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} }
1402
1527
  };
1403
1528
  var GRIPS = [
1404
1529
  { key: "nw", north: true, west: true, cursor: "nwse-resize" },
@@ -1598,11 +1723,18 @@ function FloatingToolbar({
1598
1723
  ] });
1599
1724
  }
1600
1725
  var ADD_MENU = [
1601
- { kind: "text", label: "Text" },
1602
1726
  { kind: "heading", label: "Heading" },
1727
+ { kind: "text", label: "Text" },
1603
1728
  { kind: "button", label: "Button" },
1729
+ { kind: "link", label: "Link" },
1604
1730
  { kind: "image", label: "Image" },
1605
- { kind: "box", label: "Box" }
1731
+ { kind: "card", label: "Card" },
1732
+ { kind: "callout", label: "Callout" },
1733
+ { kind: "stack", label: "Stack" },
1734
+ { kind: "grid", label: "Grid" },
1735
+ { kind: "box", label: "Box" },
1736
+ { kind: "divider", label: "Divider" },
1737
+ { kind: "code", label: "Code" }
1606
1738
  ];
1607
1739
  function EditBar({
1608
1740
  editing,
@@ -1620,7 +1752,7 @@ function EditBar({
1620
1752
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
1621
1753
  editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1622
1754
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1623
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
1755
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an Element (or drag from the right-edge palette)", children: "\uFF0B Element \u25BE" }),
1624
1756
  open ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", top: "calc(100% + 6px)", right: 0, background: "#0f172a", border: "1px solid #1e293b", borderRadius: 10, padding: 4, display: "flex", flexDirection: "column", minWidth: 132, boxShadow: "0 12px 32px -10px rgba(0,0,0,0.6)" }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
1625
1757
  "button",
1626
1758
  {
@@ -1658,7 +1790,7 @@ function EditMenuItems({
1658
1790
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1659
1791
  editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
1660
1792
  /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
1661
- /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
1793
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
1662
1794
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubContent, { className: "!z-[2147483647]", children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onAdd(m.kind), children: m.label }, m.kind)) })
1663
1795
  ] }),
1664
1796
  node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [