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

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/editor.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var reactFancy = require('@particle-academy/react-fancy');
5
6
 
6
7
  // src/editor/Editor.tsx
7
8
 
@@ -97,6 +98,10 @@ function reduce(doc, op, opts = {}) {
97
98
  props: { ...doc.nodes[op.id].props, ...op.patch }
98
99
  });
99
100
  }
101
+ case "set_layout": {
102
+ if (!doc.nodes[op.id]) return invalid("node not found");
103
+ return patchNode(doc, op.id, { layout: op.layout });
104
+ }
100
105
  case "set_style": {
101
106
  const node = doc.nodes[op.id];
102
107
  if (!node) return invalid("node not found");
@@ -233,8 +238,8 @@ var DEFAULT_BREAKPOINTS = {
233
238
 
234
239
  // src/render/css.ts
235
240
  var selectorFor = (id) => `[data-cms="${id}"]`;
236
- function lengthToCss(len) {
237
- return `${len.value}${len.unit}`;
241
+ function lengthToCss(len2) {
242
+ return `${len2.value}${len2.unit}`;
238
243
  }
239
244
  function sizeToCss(size) {
240
245
  if (size === "fill") return "100%";
@@ -321,9 +326,9 @@ function isParentFree(doc, node) {
321
326
  function serializeRule(selector, decls, indent) {
322
327
  const props = Object.keys(decls).sort();
323
328
  if (props.length === 0) return "";
324
- const body = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
329
+ const body2 = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
325
330
  return `${indent}${selector} {
326
- ${body}
331
+ ${body2}
327
332
  ${indent}}`;
328
333
  }
329
334
  function emitDocCss(doc) {
@@ -368,6 +373,12 @@ var defaultRegistry = {
368
373
  frame: ({ children }) => children,
369
374
  shape: ({ children }) => children,
370
375
  text: ({ node }) => literal(node.props.content),
376
+ heading: ({ node }) => literal(node.props.content) || "Heading",
377
+ button: ({ node }) => {
378
+ const label2 = literal(node.props.label) || literal(node.props.content) || "Button";
379
+ const variant = literal(node.props.variant) || "primary";
380
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
381
+ },
371
382
  image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
372
383
  "img",
373
384
  {
@@ -377,13 +388,38 @@ var defaultRegistry = {
377
388
  }
378
389
  )
379
390
  };
391
+ function buttonStyle(variant) {
392
+ const base = {
393
+ display: "inline-flex",
394
+ alignItems: "center",
395
+ gap: 6,
396
+ padding: "9px 16px",
397
+ borderRadius: 8,
398
+ fontWeight: 600,
399
+ fontSize: 14,
400
+ lineHeight: 1,
401
+ cursor: "pointer",
402
+ userSelect: "none"
403
+ };
404
+ if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
405
+ if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
406
+ return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
407
+ }
380
408
  function RenderNode({ doc, id, registry = defaultRegistry }) {
381
409
  const node = doc.nodes[id];
382
410
  if (!node) return null;
383
411
  const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry }, child.id));
384
412
  const renderer = registry[node.type];
385
413
  const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
386
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, children: inner });
414
+ return /* @__PURE__ */ jsxRuntime.jsx(
415
+ "div",
416
+ {
417
+ "data-cms": node.id,
418
+ "data-cms-island": node.island ? node.type : void 0,
419
+ className: node.className,
420
+ children: inner
421
+ }
422
+ );
387
423
  }
388
424
  function CmsPage({
389
425
  doc,
@@ -481,31 +517,110 @@ function Canvas({ doc, selection, onSelect, apply }) {
481
517
  function cssEscape(value) {
482
518
  return value.replace(/["\\]/g, "\\$&");
483
519
  }
484
- function LayersPanel({ doc, selection, onSelect }) {
485
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id, depth: 0, selection, onSelect }, id)) });
520
+
521
+ // src/document/fractional.ts
522
+ var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
523
+ var RADIX = BASE.length;
524
+ var MID = Math.floor(RADIX / 2);
525
+ function val(c) {
526
+ const i = BASE.indexOf(c);
527
+ return i < 0 ? 0 : i;
486
528
  }
487
- function LayerRow({ doc, id, depth, selection, onSelect }) {
529
+ function keyBetween(a, b) {
530
+ if (a === null) a = "";
531
+ if (b === null) {
532
+ return a + BASE[MID];
533
+ }
534
+ if (a >= b) {
535
+ throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
536
+ }
537
+ let result = "";
538
+ let i = 0;
539
+ for (; ; ) {
540
+ const ca = i < a.length ? val(a[i]) : 0;
541
+ const cb = i < b.length ? val(b[i]) : RADIX;
542
+ if (cb - ca > 1) {
543
+ result += BASE[ca + Math.floor((cb - ca) / 2)];
544
+ return result;
545
+ }
546
+ if (ca === cb) {
547
+ result += BASE[ca];
548
+ i++;
549
+ continue;
550
+ }
551
+ result += BASE[ca];
552
+ i++;
553
+ for (; ; ) {
554
+ const da = i < a.length ? val(a[i]) : 0;
555
+ if (RADIX - da > 1) {
556
+ result += BASE[da + Math.floor((RADIX - da) / 2)];
557
+ return result;
558
+ }
559
+ result += BASE[da];
560
+ i++;
561
+ }
562
+ }
563
+ }
564
+ var MIME = "application/x-cms-node";
565
+ function isAncestorOrSelf(doc, id, maybeAncestor) {
566
+ for (let cur = id; cur; cur = doc.nodes[cur]?.parent ?? null) {
567
+ if (cur === maybeAncestor) return true;
568
+ }
569
+ return false;
570
+ }
571
+ function LayersPanel({ doc, selection, onSelect, onMove }) {
572
+ const [overId, setOverId] = react.useState(null);
573
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id, depth: 0, selection, onSelect, onMove, overId, setOverId }, id)) });
574
+ }
575
+ function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
488
576
  const node = doc.nodes[id];
489
577
  if (!node) return null;
490
578
  const selected = selection === id;
579
+ const draggable = Boolean(onMove);
580
+ const drop = (e) => {
581
+ e.preventDefault();
582
+ e.stopPropagation();
583
+ setOverId(null);
584
+ if (!onMove) return;
585
+ const dragId = e.dataTransfer.getData(MIME);
586
+ if (!dragId || dragId === id || isAncestorOrSelf(doc, id, dragId)) return;
587
+ const siblings = childrenOf(doc, node.parent);
588
+ const idx = siblings.findIndex((n) => n.id === id);
589
+ const next = siblings[idx + 1];
590
+ onMove(dragId, node.parent, keyBetween(node.order, next ? next.order : null));
591
+ };
491
592
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
492
593
  /* @__PURE__ */ jsxRuntime.jsxs(
493
594
  "button",
494
595
  {
495
596
  type: "button",
597
+ draggable,
496
598
  onClick: () => onSelect(id),
599
+ onDragStart: (e) => {
600
+ e.dataTransfer.setData(MIME, id);
601
+ e.dataTransfer.effectAllowed = "move";
602
+ },
603
+ onDragOver: (e) => {
604
+ if (!onMove) return;
605
+ e.preventDefault();
606
+ e.dataTransfer.dropEffect = "move";
607
+ if (overId !== id) setOverId(id);
608
+ },
609
+ onDragLeave: () => overId === id && setOverId(null),
610
+ onDrop: drop,
497
611
  style: {
498
612
  display: "block",
499
613
  width: "100%",
500
614
  textAlign: "left",
501
615
  border: "none",
502
616
  borderRadius: 6,
503
- cursor: "pointer",
617
+ cursor: draggable ? "grab" : "pointer",
504
618
  padding: "4px 8px",
505
619
  paddingLeft: 8 + depth * 14,
506
620
  background: selected ? "#ede9fe" : "transparent",
507
621
  color: selected ? "#5b21b6" : "#334155",
508
- font: "inherit"
622
+ font: "inherit",
623
+ boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
509
624
  },
510
625
  children: [
511
626
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: node.type }),
@@ -514,17 +629,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
514
629
  ]
515
630
  }
516
631
  ),
517
- childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(
518
- LayerRow,
519
- {
520
- doc,
521
- id: child.id,
522
- depth: depth + 1,
523
- selection,
524
- onSelect
525
- },
526
- child.id
527
- ))
632
+ childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
528
633
  ] });
529
634
  }
530
635
  var label = {
@@ -656,7 +761,15 @@ function Editor({ defaultValue, onChange }) {
656
761
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shell, children: [
657
762
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
658
763
  /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
659
- /* @__PURE__ */ jsxRuntime.jsx(LayersPanel, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select })
764
+ /* @__PURE__ */ jsxRuntime.jsx(
765
+ LayersPanel,
766
+ {
767
+ doc: ed.state.doc,
768
+ selection: ed.state.selection,
769
+ onSelect: ed.select,
770
+ onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
771
+ }
772
+ )
660
773
  ] }),
661
774
  /* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
662
775
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
@@ -689,11 +802,909 @@ function Toolbar({ ed }) {
689
802
  }
690
803
  );
691
804
  }
805
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
806
+ function NodeInspector({
807
+ node,
808
+ transform,
809
+ measured,
810
+ onProps,
811
+ onStyle,
812
+ onLayout,
813
+ onTransform,
814
+ onRemove,
815
+ onClose
816
+ }) {
817
+ const base = node.style.base ?? {};
818
+ const setT = (patch) => onTransform({ ...transform, ...patch });
819
+ const handled = /* @__PURE__ */ new Set(["content", "label"]);
820
+ const extraKeys = Object.keys(node.props).filter(
821
+ (k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
822
+ );
823
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
824
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
825
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
826
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12, textTransform: "capitalize" }, children: node.type }),
827
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
828
+ ] }),
829
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
830
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
831
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
832
+ ] }),
833
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
834
+ "content" in node.props ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Content", children: /* @__PURE__ */ jsxRuntime.jsx(
835
+ "textarea",
836
+ {
837
+ style: { ...input2, minHeight: 64, resize: "vertical", lineHeight: 1.4 },
838
+ value: String(node.props.content ?? ""),
839
+ onChange: (e) => onProps({ content: e.target.value })
840
+ }
841
+ ) }) : null,
842
+ "label" in node.props ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Label", children: /* @__PURE__ */ jsxRuntime.jsx("input", { style: input2, value: String(node.props.label ?? ""), onChange: (e) => onProps({ label: e.target.value }) }) }) : null,
843
+ extraKeys.length ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Properties", children: extraKeys.map((k) => /* @__PURE__ */ jsxRuntime.jsx(PropField, { name: k, value: node.props[k], onChange: (v) => onProps({ [k]: v }) }, k)) }) : null,
844
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Position & size", children: [
845
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
846
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "X", value: transform.x, placeholder: "0", onChange: (v) => setT({ x: v }) }),
847
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Y", value: transform.y, placeholder: "0", onChange: (v) => setT({ y: v }) })
848
+ ] }),
849
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
850
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "W", value: transform.w, placeholder: `${Math.round(measured.w)}`, onChange: (v) => setT({ w: v }) }),
851
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "H", value: transform.h, placeholder: `${Math.round(measured.h)}`, onChange: (v) => setT({ h: v }) })
852
+ ] }),
853
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
854
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Scale", value: transform.scale, placeholder: "1", step: 0.05, onChange: (v) => setT({ scale: v }) }),
855
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Rotate", value: transform.rotate, placeholder: "0", onChange: (v) => setT({ rotate: v }) })
856
+ ] }),
857
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
858
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Opacity", value: transform.opacity, placeholder: "1", min: 0, max: 1, step: 0.05, onChange: (v) => setT({ opacity: v }) }),
859
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
860
+ ] })
861
+ ] }),
862
+ !CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Text", children: [
863
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
864
+ /* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
865
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
866
+ ] }),
867
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
868
+ /* @__PURE__ */ jsxRuntime.jsx(
869
+ Sel,
870
+ {
871
+ label: "Weight",
872
+ value: String(base.fontWeight ?? ""),
873
+ options: [["", "\u2014"], ["400", "Regular"], ["500", "Medium"], ["600", "Semibold"], ["700", "Bold"], ["800", "Heavy"]],
874
+ onChange: (v) => onStyle({ fontWeight: v ? Number(v) : void 0 })
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsxRuntime.jsx(
878
+ Sel,
879
+ {
880
+ label: "Align",
881
+ value: base.textAlign ?? "",
882
+ options: [["", "\u2014"], ["left", "Left"], ["center", "Center"], ["right", "Right"], ["justify", "Justify"]],
883
+ onChange: (v) => onStyle({ textAlign: v || void 0 })
884
+ }
885
+ )
886
+ ] })
887
+ ] }) : null,
888
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Box", children: [
889
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
890
+ /* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Background", value: hex(base.background), onChange: (v) => onStyle({ background: v }) }),
891
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Radius", value: base.radius?.value, placeholder: "0", onChange: (v) => onStyle({ radius: v == null ? void 0 : len(v) }) })
892
+ ] }),
893
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
894
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
895
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
896
+ ] }),
897
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
898
+ ] }),
899
+ CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Layout", children: [
900
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
901
+ /* @__PURE__ */ jsxRuntime.jsx(
902
+ Sel,
903
+ {
904
+ label: "Mode",
905
+ value: node.layout ?? "free",
906
+ options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
907
+ onChange: (v) => onLayout?.(v)
908
+ }
909
+ ),
910
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
911
+ ] }),
912
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
913
+ /* @__PURE__ */ jsxRuntime.jsx(
914
+ Sel,
915
+ {
916
+ label: "Align",
917
+ value: base.align ?? "",
918
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
919
+ onChange: (v) => onStyle({ align: v || void 0 })
920
+ }
921
+ ),
922
+ /* @__PURE__ */ jsxRuntime.jsx(
923
+ Sel,
924
+ {
925
+ label: "Justify",
926
+ value: base.justify ?? "",
927
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
928
+ onChange: (v) => onStyle({ justify: v || void 0 })
929
+ }
930
+ )
931
+ ] })
932
+ ] }) : null
933
+ ] })
934
+ ] });
935
+ }
936
+ function Group({ label: label2, children }) {
937
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
938
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label2 }),
939
+ children
940
+ ] });
941
+ }
942
+ function Row({ children }) {
943
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
944
+ }
945
+ function PropField({ name, value, onChange }) {
946
+ if (typeof value === "boolean") {
947
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
948
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
949
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
950
+ ] });
951
+ }
952
+ if (name === "variant") {
953
+ return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
954
+ }
955
+ const isNum = typeof value === "number";
956
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
957
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: name }),
958
+ /* @__PURE__ */ jsxRuntime.jsx(
959
+ "input",
960
+ {
961
+ style: input2,
962
+ type: isNum ? "number" : "text",
963
+ value: String(value),
964
+ onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
965
+ }
966
+ )
967
+ ] });
968
+ }
969
+ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
970
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
972
+ /* @__PURE__ */ jsxRuntime.jsx(
973
+ "input",
974
+ {
975
+ style: input2,
976
+ type: "number",
977
+ step,
978
+ min,
979
+ max,
980
+ value: value ?? "",
981
+ placeholder,
982
+ onChange: (e) => onChange(e.target.value === "" ? void 0 : Number(e.target.value))
983
+ }
984
+ )
985
+ ] });
986
+ }
987
+ function Txt({ label: label2, value, placeholder, onChange }) {
988
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
989
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
990
+ /* @__PURE__ */ jsxRuntime.jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
991
+ ] });
992
+ }
993
+ function Color({ label: label2, value, onChange }) {
994
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
995
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
996
+ /* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
997
+ ] });
998
+ }
999
+ function Sel({ label: label2, value, options, onChange }) {
1000
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
1001
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
1002
+ /* @__PURE__ */ jsxRuntime.jsx("select", { style: input2, value, onChange: (e) => onChange(e.target.value), children: options.map(([v, l]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: v, children: l }, v)) })
1003
+ ] });
1004
+ }
1005
+ var len = (value) => ({ value, unit: "px" });
1006
+ var hex = (v) => v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1007
+ var Z = 2147483e3;
1008
+ var panel = {
1009
+ position: "fixed",
1010
+ top: 60,
1011
+ right: 12,
1012
+ bottom: 96,
1013
+ width: 264,
1014
+ display: "flex",
1015
+ flexDirection: "column",
1016
+ background: "#0b1220",
1017
+ color: "#e2e8f0",
1018
+ border: "1px solid #1e293b",
1019
+ borderRadius: 12,
1020
+ boxShadow: "0 18px 48px -16px rgba(0,0,0,0.6)",
1021
+ zIndex: Z + 2,
1022
+ fontFamily: "system-ui, sans-serif",
1023
+ overflow: "hidden"
1024
+ };
1025
+ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
1026
+ var body = { padding: 12, overflowY: "auto" };
1027
+ var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
1028
+ var fieldLabel = { fontSize: 10, opacity: 0.6 };
1029
+ var input2 = {
1030
+ width: "100%",
1031
+ boxSizing: "border-box",
1032
+ background: "#0f172a",
1033
+ color: "#e2e8f0",
1034
+ border: "1px solid #334155",
1035
+ borderRadius: 6,
1036
+ padding: "5px 7px",
1037
+ font: "inherit",
1038
+ fontSize: 12
1039
+ };
1040
+ var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
1041
+ var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
1042
+
1043
+ // src/editor/editorOps.ts
1044
+ function childrenOfMap(nodes, parent) {
1045
+ return Object.values(nodes).filter((n) => n.parent === parent).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
1046
+ }
1047
+ function subtreeBfs(nodes, root) {
1048
+ const out = [];
1049
+ const queue = [root];
1050
+ while (queue.length) {
1051
+ const cur = queue.shift();
1052
+ out.push(cur);
1053
+ for (const child of childrenOfMap(nodes, cur)) queue.push(child.id);
1054
+ }
1055
+ return out;
1056
+ }
1057
+ function cloneFrom(nodes, rootId, newParent, newOrder, seqBase) {
1058
+ if (!nodes[rootId]) return { ops: [], newRootId: null };
1059
+ const order = subtreeBfs(nodes, rootId);
1060
+ const idMap = {};
1061
+ order.forEach((old, i) => idMap[old] = `${old}_c${seqBase}_${i}`);
1062
+ const ops = order.map((old) => {
1063
+ const src = nodes[old];
1064
+ const cloned = JSON.parse(JSON.stringify(src));
1065
+ cloned.id = idMap[old];
1066
+ cloned.parent = old === rootId ? newParent : idMap[src.parent];
1067
+ if (old === rootId) cloned.order = newOrder;
1068
+ return { t: "insert_node", node: cloned };
1069
+ });
1070
+ return { ops, newRootId: idMap[rootId] };
1071
+ }
1072
+ function duplicateOps(doc, id) {
1073
+ const node = doc.nodes[id];
1074
+ if (!node) return { ops: [], newRootId: null };
1075
+ const sibs = childrenOf(doc, node.parent);
1076
+ const idx = sibs.findIndex((s) => s.id === id);
1077
+ const newOrder = keyBetween(node.order, sibs[idx + 1]?.order ?? null);
1078
+ const { ops, newRootId } = cloneFrom(doc.nodes, id, node.parent, newOrder, doc.seq);
1079
+ if (node.parent === null && newRootId) {
1080
+ const base = doc.sections;
1081
+ const at = base.indexOf(id);
1082
+ ops.push({ t: "reorder_sections", order: [...base.slice(0, at + 1), newRootId, ...base.slice(at + 1)] });
1083
+ }
1084
+ return { ops, newRootId };
1085
+ }
1086
+ function pasteOps(doc, clip, target, containerTypes) {
1087
+ const sel = target ? doc.nodes[target] : null;
1088
+ const parent = sel ? containerTypes.has(sel.type) ? sel.id : sel.parent : doc.sections[doc.sections.length - 1] ?? null;
1089
+ const sibs = childrenOf(doc, parent);
1090
+ const newOrder = keyBetween(sibs.length ? sibs[sibs.length - 1].order : null, null);
1091
+ const { ops, newRootId } = cloneFrom(clip.nodes, clip.rootId, parent, newOrder, doc.seq);
1092
+ return { ops, newRootId };
1093
+ }
1094
+ function reorderOps(doc, id, dir) {
1095
+ const node = doc.nodes[id];
1096
+ if (!node) return [];
1097
+ if (node.parent === null) {
1098
+ const s = [...doc.sections];
1099
+ const i = s.indexOf(id);
1100
+ if (i < 0) return [];
1101
+ s.splice(i, 1);
1102
+ if (dir === "front") s.unshift(id);
1103
+ else if (dir === "back") s.push(id);
1104
+ else s.splice(Math.max(0, Math.min(s.length, dir === "up" ? i - 1 : i + 1)), 0, id);
1105
+ return [{ t: "reorder_sections", order: s }];
1106
+ }
1107
+ const sibs = childrenOf(doc, node.parent);
1108
+ const idx = sibs.findIndex((sb) => sb.id === id);
1109
+ if (idx < 0) return [];
1110
+ const last = sibs.length - 1;
1111
+ let newOrder;
1112
+ if (dir === "up") {
1113
+ if (idx <= 0) return [];
1114
+ newOrder = keyBetween(sibs[idx - 2]?.order ?? null, sibs[idx - 1].order);
1115
+ } else if (dir === "down") {
1116
+ if (idx >= last) return [];
1117
+ newOrder = keyBetween(sibs[idx + 1].order, sibs[idx + 2]?.order ?? null);
1118
+ } else if (dir === "front") {
1119
+ if (idx >= last) return [];
1120
+ newOrder = keyBetween(sibs[last].order, null);
1121
+ } else {
1122
+ if (idx <= 0) return [];
1123
+ newOrder = keyBetween(null, sibs[0].order);
1124
+ }
1125
+ return [{ t: "move_node", id, parent: node.parent, order: newOrder }];
1126
+ }
1127
+ function wrapInBoxOps(doc, id) {
1128
+ const node = doc.nodes[id];
1129
+ if (!node || node.parent === null) return { ops: [], newRootId: null };
1130
+ const frameId = `box_${doc.seq}`;
1131
+ return {
1132
+ ops: [
1133
+ { t: "insert_node", node: { id: frameId, type: "frame", parent: node.parent, order: node.order, props: {}, style: { base: { padding: { value: 16, unit: "px" } } } } },
1134
+ { t: "move_node", id, parent: frameId, order: "a" }
1135
+ ],
1136
+ newRootId: frameId
1137
+ };
1138
+ }
1139
+ function snapshotSubtree(doc, id) {
1140
+ if (!doc.nodes[id]) return null;
1141
+ const ids = subtreeBfs(doc.nodes, id);
1142
+ const nodes = {};
1143
+ for (const nid of ids) nodes[nid] = JSON.parse(JSON.stringify(doc.nodes[nid]));
1144
+ return { rootId: id, nodes };
1145
+ }
1146
+ var Z2 = 2147483e3;
1147
+ var SNAP = 6;
1148
+ function EditablePage({
1149
+ doc,
1150
+ registry = defaultRegistry,
1151
+ timelineDock,
1152
+ transforms,
1153
+ onNodeTransform,
1154
+ onSelect,
1155
+ frames = 1,
1156
+ onProgress,
1157
+ pinned = true
1158
+ }) {
1159
+ const ed = useEditor(doc);
1160
+ const [editing, setEditing] = react.useState(false);
1161
+ const [revealed, setRevealed] = react.useState(false);
1162
+ const pageRef = react.useRef(null);
1163
+ const spacerRef = react.useRef(null);
1164
+ const [box, setBox] = react.useState(null);
1165
+ const { selection } = ed.state;
1166
+ const selNode = selection ? ed.state.doc.nodes[selection] : null;
1167
+ react.useEffect(() => onSelect?.(selection), [selection, onSelect]);
1168
+ react.useEffect(() => {
1169
+ const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
1170
+ const onBlur = () => setRevealed(false);
1171
+ window.addEventListener("keydown", onKey);
1172
+ window.addEventListener("keyup", onKey);
1173
+ window.addEventListener("blur", onBlur);
1174
+ return () => {
1175
+ window.removeEventListener("keydown", onKey);
1176
+ window.removeEventListener("keyup", onKey);
1177
+ window.removeEventListener("blur", onBlur);
1178
+ };
1179
+ }, []);
1180
+ react.useEffect(() => {
1181
+ const root = pageRef.current;
1182
+ if (!root) return;
1183
+ root.querySelectorAll("[data-cms]").forEach((el) => {
1184
+ const id = el.dataset.cms;
1185
+ const t = id ? transforms?.[id] : void 0;
1186
+ applyTransform(el, t);
1187
+ });
1188
+ }, [transforms, ed.state.doc]);
1189
+ react.useEffect(() => {
1190
+ const spacer = spacerRef.current;
1191
+ if (!spacer || !onProgress) return;
1192
+ const compute = () => {
1193
+ const len2 = spacer.offsetHeight - window.innerHeight;
1194
+ const top = spacer.getBoundingClientRect().top;
1195
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
1196
+ };
1197
+ compute();
1198
+ window.addEventListener("scroll", compute, { passive: true });
1199
+ window.addEventListener("resize", compute);
1200
+ return () => {
1201
+ window.removeEventListener("scroll", compute);
1202
+ window.removeEventListener("resize", compute);
1203
+ };
1204
+ }, [onProgress, frames]);
1205
+ const measureBox = react.useCallback(() => {
1206
+ if (!editing || !selection || !pageRef.current) return setBox(null);
1207
+ const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
1208
+ if (!el) return setBox(null);
1209
+ const r = el.getBoundingClientRect();
1210
+ setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
1211
+ }, [editing, selection]);
1212
+ react.useLayoutEffect(() => {
1213
+ measureBox();
1214
+ window.addEventListener("scroll", measureBox, { passive: true });
1215
+ window.addEventListener("resize", measureBox);
1216
+ return () => {
1217
+ window.removeEventListener("scroll", measureBox);
1218
+ window.removeEventListener("resize", measureBox);
1219
+ };
1220
+ }, [measureBox, ed.state.doc]);
1221
+ react.useEffect(() => {
1222
+ if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
1223
+ const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
1224
+ if (!el) return;
1225
+ el.setAttribute("contenteditable", "true");
1226
+ el.style.outline = "none";
1227
+ el.focus();
1228
+ const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
1229
+ el.addEventListener("blur", commit);
1230
+ return () => {
1231
+ el.removeEventListener("blur", commit);
1232
+ el.removeAttribute("contenteditable");
1233
+ };
1234
+ }, [editing, selection, selNode?.type, ed]);
1235
+ const onClickCapture = (e) => {
1236
+ if (!editing) return;
1237
+ const el = e.target.closest("[data-cms]");
1238
+ if (el) {
1239
+ if (el.getAttribute("contenteditable") === "true") return;
1240
+ e.preventDefault();
1241
+ e.stopPropagation();
1242
+ ed.select(el.getAttribute("data-cms"));
1243
+ }
1244
+ };
1245
+ const addNode = react.useCallback(
1246
+ (kind) => {
1247
+ const doc2 = ed.state.doc;
1248
+ const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1249
+ const siblings = childrenOf(doc2, parent);
1250
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
1251
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
1252
+ 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 } } } });
1254
+ ed.select(id);
1255
+ },
1256
+ [ed, selNode]
1257
+ );
1258
+ const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
1259
+ const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
1260
+ const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
1261
+ const removeSelected = () => {
1262
+ if (!selection) return;
1263
+ ed.apply({ t: "remove_node", id: selection });
1264
+ ed.select(null);
1265
+ };
1266
+ const clip = react.useRef(null);
1267
+ const runOps = (result) => {
1268
+ result.ops.forEach((op) => ed.apply(op));
1269
+ if (result.newRootId) ed.select(result.newRootId);
1270
+ };
1271
+ const duplicate = (id) => id && runOps(duplicateOps(ed.state.doc, id));
1272
+ const reorder = (id, dir) => id && reorderOps(ed.state.doc, id, dir).forEach((op) => ed.apply(op));
1273
+ const wrapInBox = (id) => id && runOps(wrapInBoxOps(ed.state.doc, id));
1274
+ const copy = (id) => {
1275
+ if (id) clip.current = snapshotSubtree(ed.state.doc, id);
1276
+ };
1277
+ const paste = (target) => {
1278
+ if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
1279
+ };
1280
+ const startEditText = (id) => {
1281
+ if (!id) return;
1282
+ ed.select(id);
1283
+ requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
1284
+ };
1285
+ const nudgeRef = react.useRef({ transforms, onNodeTransform });
1286
+ nudgeRef.current = { transforms, onNodeTransform };
1287
+ react.useEffect(() => {
1288
+ if (!editing) return;
1289
+ const onKey = (e) => {
1290
+ const t = e.target;
1291
+ const typing = t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
1292
+ if (e.key === "Escape") return void ed.select(null);
1293
+ if (typing) return;
1294
+ if ((e.key === "Delete" || e.key === "Backspace") && selection) {
1295
+ e.preventDefault();
1296
+ removeSelected();
1297
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "d" && selection) {
1298
+ e.preventDefault();
1299
+ duplicate(selection);
1300
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selection) {
1301
+ copy(selection);
1302
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
1303
+ e.preventDefault();
1304
+ paste(selection);
1305
+ } else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
1306
+ const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
1307
+ if (!commit) return;
1308
+ e.preventDefault();
1309
+ const step = e.shiftKey ? 10 : 1;
1310
+ const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
1311
+ const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
1312
+ const cur = tf?.[selection] ?? {};
1313
+ commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
1314
+ }
1315
+ };
1316
+ window.addEventListener("keydown", onKey);
1317
+ return () => window.removeEventListener("keydown", onKey);
1318
+ }, [editing, selection]);
1319
+ const onContextMenuSelect = (e) => {
1320
+ if (!editing) return;
1321
+ const el = e.target.closest("[data-cms]");
1322
+ if (el) ed.select(el.getAttribute("data-cms"));
1323
+ else ed.select(null);
1324
+ };
1325
+ const page = /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) });
1326
+ 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
+ // Normal flow, but "expanded" — extra scroll length below the content so a
1328
+ // page can be made longer than its content (the + Frame control).
1329
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minHeight: `${frames * 100}vh` }, children: page })
1330
+ ) : page;
1331
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1332
+ editing ? /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
1333
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { children: pageShell }),
1334
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Content, { className: "!z-[2147483646]", children: /* @__PURE__ */ jsxRuntime.jsx(
1335
+ EditMenuItems,
1336
+ {
1337
+ node: selNode ?? null,
1338
+ hasClipboard: Boolean(clip.current),
1339
+ isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
1340
+ onEditText: () => startEditText(selection),
1341
+ onAdd: addNode,
1342
+ onDuplicate: () => duplicate(selection),
1343
+ onCopy: () => copy(selection),
1344
+ onPaste: () => paste(selection),
1345
+ onWrap: () => wrapInBox(selection),
1346
+ onReorder: (d) => reorder(selection, d),
1347
+ onDelete: removeSelected
1348
+ }
1349
+ ) })
1350
+ ] }) : pageShell,
1351
+ editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
1352
+ SelectionOverlay,
1353
+ {
1354
+ box,
1355
+ base: transforms?.[selection] ?? {},
1356
+ movable: Boolean(onNodeTransform),
1357
+ getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
1358
+ onTransform: (t) => onNodeTransform?.(selection, t),
1359
+ onLive: measureBox
1360
+ }
1361
+ ) : null,
1362
+ editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text" || selNode.type === "heading", base: selNode.style.base, setStyle }) : null,
1363
+ editing && selNode && box ? /* @__PURE__ */ jsxRuntime.jsx(
1364
+ NodeInspector,
1365
+ {
1366
+ node: selNode,
1367
+ transform: (selection ? transforms?.[selection] : void 0) ?? {},
1368
+ measured: { w: box.w, h: box.h },
1369
+ onProps: setProps,
1370
+ onStyle: setStyle,
1371
+ onLayout: setLayout,
1372
+ onTransform: (t) => selection && onNodeTransform?.(selection, t),
1373
+ onRemove: removeSelected,
1374
+ onClose: () => ed.select(null)
1375
+ }
1376
+ ) : null,
1377
+ revealed || editing ? /* @__PURE__ */ jsxRuntime.jsx(
1378
+ EditBar,
1379
+ {
1380
+ editing,
1381
+ onToggle: () => {
1382
+ setEditing((v) => !v);
1383
+ ed.select(null);
1384
+ },
1385
+ canUndo: ed.canUndo,
1386
+ canRedo: ed.canRedo,
1387
+ onUndo: ed.undo,
1388
+ onRedo: ed.redo,
1389
+ onAdd: addNode
1390
+ }
1391
+ ) : null,
1392
+ editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
1393
+ ] });
1394
+ }
1395
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
1396
+ var ADD_DEFAULTS = {
1397
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
1398
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
1399
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
1400
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
1401
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
1402
+ };
1403
+ var GRIPS = [
1404
+ { key: "nw", north: true, west: true, cursor: "nwse-resize" },
1405
+ { key: "n", north: true, cursor: "ns-resize" },
1406
+ { key: "ne", north: true, east: true, cursor: "nesw-resize" },
1407
+ { key: "e", east: true, cursor: "ew-resize" },
1408
+ { key: "se", south: true, east: true, cursor: "nwse-resize" },
1409
+ { key: "s", south: true, cursor: "ns-resize" },
1410
+ { key: "sw", south: true, west: true, cursor: "nesw-resize" },
1411
+ { key: "w", west: true, cursor: "ew-resize" }
1412
+ ];
1413
+ function SelectionOverlay({
1414
+ box,
1415
+ base,
1416
+ movable,
1417
+ getEl,
1418
+ onTransform,
1419
+ onLive
1420
+ }) {
1421
+ const drag = react.useRef(null);
1422
+ const targets = react.useRef({ vx: [], hy: [] });
1423
+ const [guides, setGuides] = react.useState({ x: null, y: null });
1424
+ const apply = (t) => {
1425
+ const el = getEl();
1426
+ if (el) applyTransform(el, t);
1427
+ };
1428
+ const snap = (cands, lines) => {
1429
+ let best = { delta: 0, at: null, dist: SNAP + 1 };
1430
+ for (const line of lines) {
1431
+ for (const c of cands) {
1432
+ const diff = line - c;
1433
+ if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
1434
+ }
1435
+ }
1436
+ return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
1437
+ };
1438
+ const startMove = (e) => {
1439
+ e.stopPropagation();
1440
+ e.preventDefault();
1441
+ e.currentTarget.setPointerCapture(e.pointerId);
1442
+ drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
1443
+ const sel = getEl();
1444
+ const vx = /* @__PURE__ */ new Set();
1445
+ const hy = /* @__PURE__ */ new Set();
1446
+ document.querySelectorAll("[data-cms]").forEach((el) => {
1447
+ if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
1448
+ const r = el.getBoundingClientRect();
1449
+ vx.add(r.left).add(r.left + r.width / 2).add(r.right);
1450
+ hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
1451
+ });
1452
+ targets.current = { vx: [...vx], hy: [...hy] };
1453
+ };
1454
+ const moveMove = (e) => {
1455
+ const d = drag.current;
1456
+ if (!d) return;
1457
+ let dx = e.clientX - d.x;
1458
+ let dy = e.clientY - d.y;
1459
+ if (!e.altKey) {
1460
+ const l = box.x + dx;
1461
+ const t = box.y + dy;
1462
+ const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
1463
+ const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
1464
+ dx += sx.delta;
1465
+ dy += sy.delta;
1466
+ setGuides({ x: sx.at, y: sy.at });
1467
+ } else if (guides.x !== null || guides.y !== null) {
1468
+ setGuides({ x: null, y: null });
1469
+ }
1470
+ d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
1471
+ apply(d.live);
1472
+ onLive();
1473
+ };
1474
+ const startResize = (g) => (e) => {
1475
+ e.stopPropagation();
1476
+ e.preventDefault();
1477
+ e.currentTarget.setPointerCapture(e.pointerId);
1478
+ drag.current = { x: e.clientX, y: e.clientY, w: base.w ?? box.w, h: base.h ?? box.h, live: { ...base } };
1479
+ e.currentTarget.dataset.grip = g.key;
1480
+ };
1481
+ const resizeMove = (g) => (e) => {
1482
+ const d = drag.current;
1483
+ if (!d) return;
1484
+ const dx = e.clientX - d.x;
1485
+ const dy = e.clientY - d.y;
1486
+ const live = { ...base };
1487
+ if (g.east) live.w = Math.max(16, Math.round(d.w + dx));
1488
+ if (g.west) {
1489
+ live.w = Math.max(16, Math.round(d.w - dx));
1490
+ live.x = (base.x ?? 0) + (d.w - live.w);
1491
+ }
1492
+ if (g.south) live.h = Math.max(16, Math.round(d.h + dy));
1493
+ if (g.north) {
1494
+ live.h = Math.max(16, Math.round(d.h - dy));
1495
+ live.y = (base.y ?? 0) + (d.h - live.h);
1496
+ }
1497
+ d.live = live;
1498
+ apply(live);
1499
+ onLive();
1500
+ };
1501
+ const end = () => {
1502
+ const d = drag.current;
1503
+ if (d) onTransform(d.live);
1504
+ drag.current = null;
1505
+ setGuides({ x: null, y: null });
1506
+ };
1507
+ const outline = {
1508
+ position: "fixed",
1509
+ left: box.x - 1,
1510
+ top: box.y - 1,
1511
+ width: box.w + 2,
1512
+ height: box.h + 2,
1513
+ outline: "2px solid #8b5cf6",
1514
+ borderRadius: 4,
1515
+ pointerEvents: "none",
1516
+ zIndex: Z2
1517
+ };
1518
+ const handle = {
1519
+ position: "fixed",
1520
+ width: 12,
1521
+ height: 12,
1522
+ background: "#fff",
1523
+ border: "2px solid #8b5cf6",
1524
+ borderRadius: 3,
1525
+ zIndex: Z2 + 1,
1526
+ touchAction: "none",
1527
+ boxSizing: "border-box"
1528
+ };
1529
+ const gripPos = (g) => {
1530
+ const cx = g.west ? box.x : g.east ? box.x + box.w : box.x + box.w / 2;
1531
+ const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
1532
+ return { left: cx - 6, top: cy - 6, cursor: g.cursor };
1533
+ };
1534
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1535
+ guides.x !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: guides.x, top: 0, bottom: 0, width: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
1536
+ guides.y !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", top: guides.y, left: 0, right: 0, height: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
1537
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
1538
+ movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1539
+ /* @__PURE__ */ jsxRuntime.jsx(
1540
+ "div",
1541
+ {
1542
+ title: "Drag to move",
1543
+ onPointerDown: startMove,
1544
+ onPointerMove: moveMove,
1545
+ onPointerUp: end,
1546
+ style: { ...handle, width: 18, height: 18, borderRadius: 9, background: "#8b5cf6", border: "2px solid #fff", left: box.x - 22, top: box.y - 22, cursor: "move" }
1547
+ }
1548
+ ),
1549
+ GRIPS.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
1550
+ "div",
1551
+ {
1552
+ title: "Drag to resize",
1553
+ onPointerDown: startResize(g),
1554
+ onPointerMove: resizeMove(g),
1555
+ onPointerUp: end,
1556
+ style: { ...handle, ...gripPos(g) }
1557
+ },
1558
+ g.key
1559
+ ))
1560
+ ] }) : null
1561
+ ] });
1562
+ }
1563
+ function FloatingToolbar({
1564
+ box,
1565
+ isText,
1566
+ base,
1567
+ setStyle
1568
+ }) {
1569
+ const wrap = {
1570
+ position: "fixed",
1571
+ left: box.x,
1572
+ top: Math.max(8, box.y - 46),
1573
+ display: "flex",
1574
+ alignItems: "center",
1575
+ gap: 8,
1576
+ padding: "6px 10px",
1577
+ background: "#0f172a",
1578
+ color: "#fff",
1579
+ border: "1px solid rgba(255,255,255,0.12)",
1580
+ borderRadius: 999,
1581
+ boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
1582
+ zIndex: Z2 + 2,
1583
+ fontFamily: "system-ui, sans-serif",
1584
+ fontSize: 12
1585
+ };
1586
+ const swatch = { width: 22, height: 22, border: "none", background: "transparent", padding: 0, cursor: "pointer" };
1587
+ const num = { width: 52, background: "#1e293b", color: "#fff", border: "1px solid #334155", borderRadius: 6, padding: "3px 6px", font: "inherit" };
1588
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: wrap, onMouseDown: (e) => e.preventDefault(), children: [
1589
+ isText ? /* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Text color", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "A" }),
1591
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: base.color ?? "#0f172a", onChange: (e) => setStyle({ color: e.target.value }) })
1592
+ ] }) : null,
1593
+ isText ? /* @__PURE__ */ jsxRuntime.jsx("input", { type: "number", title: "Font size (px)", style: num, placeholder: "size", value: base.fontSize?.value ?? "", onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } }) }) : null,
1594
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Background", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1595
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "BG" }),
1596
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: normalizeColor(base.background), onChange: (e) => setStyle({ background: e.target.value }) })
1597
+ ] })
1598
+ ] });
1599
+ }
1600
+ var ADD_MENU = [
1601
+ { kind: "text", label: "Text" },
1602
+ { kind: "heading", label: "Heading" },
1603
+ { kind: "button", label: "Button" },
1604
+ { kind: "image", label: "Image" },
1605
+ { kind: "box", label: "Box" }
1606
+ ];
1607
+ function EditBar({
1608
+ editing,
1609
+ onToggle,
1610
+ canUndo,
1611
+ canRedo,
1612
+ onUndo,
1613
+ onRedo,
1614
+ onAdd
1615
+ }) {
1616
+ const [open, setOpen] = react.useState(false);
1617
+ const bar = { position: "fixed", top: 12, right: 12, display: "flex", gap: 6, alignItems: "center", padding: 6, background: "#0f172a", color: "#fff", borderRadius: 999, boxShadow: "0 8px 24px -8px rgba(0,0,0,0.4)", zIndex: Z2 + 3, fontFamily: "system-ui, sans-serif", fontSize: 12 };
1618
+ const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
1619
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
1620
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
1621
+ editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1622
+ /* @__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" }),
1624
+ 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
+ "button",
1626
+ {
1627
+ type: "button",
1628
+ style: { ...btn, textAlign: "left", borderRadius: 6, padding: "6px 10px" },
1629
+ onClick: () => {
1630
+ onAdd(m.kind);
1631
+ setOpen(false);
1632
+ },
1633
+ children: m.label
1634
+ },
1635
+ m.kind
1636
+ )) }) : null
1637
+ ] }),
1638
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
1639
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
1640
+ ] }) : null
1641
+ ] });
1642
+ }
1643
+ function EditMenuItems({
1644
+ node,
1645
+ hasClipboard,
1646
+ isContainer,
1647
+ onEditText,
1648
+ onAdd,
1649
+ onDuplicate,
1650
+ onCopy,
1651
+ onPaste,
1652
+ onWrap,
1653
+ onReorder,
1654
+ onDelete
1655
+ }) {
1656
+ const editable = node && (node.type === "text" || node.type === "heading");
1657
+ const nested = Boolean(node && node.parent !== null);
1658
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1659
+ editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
1660
+ /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
1661
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
1662
+ /* @__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
+ ] }),
1664
+ node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1665
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
1666
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
1667
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
1668
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" }),
1669
+ nested ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onWrap, children: "Wrap in Box" }) : null,
1670
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
1671
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
1672
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
1673
+ nested ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1674
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
1675
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
1676
+ ] }) : null,
1677
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
1678
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { danger: true, onClick: onDelete, children: "Delete" })
1679
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" })
1680
+ ] });
1681
+ }
1682
+ function applyTransform(el, t) {
1683
+ if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
1684
+ el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
1685
+ el.style.opacity = t.opacity != null ? String(t.opacity) : "";
1686
+ el.style.width = t.w != null ? `${t.w}px` : "";
1687
+ el.style.height = t.h != null ? `${t.h}px` : "";
1688
+ } else {
1689
+ el.style.transform = "";
1690
+ el.style.opacity = "";
1691
+ el.style.width = "";
1692
+ el.style.height = "";
1693
+ }
1694
+ }
1695
+ function normalizeColor(v) {
1696
+ return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1697
+ }
1698
+ function cssEscape2(value) {
1699
+ return value.replace(/["\\]/g, "\\$&");
1700
+ }
692
1701
 
693
1702
  exports.Canvas = Canvas;
1703
+ exports.EditablePage = EditablePage;
694
1704
  exports.Editor = Editor;
695
1705
  exports.Inspector = Inspector;
696
1706
  exports.LayersPanel = LayersPanel;
1707
+ exports.NodeInspector = NodeInspector;
697
1708
  exports.editorReduce = editorReduce;
698
1709
  exports.initEditor = initEditor;
699
1710
  exports.useEditor = useEditor;