@particle-academy/fancy-cms-ui 0.0.1-dev.6 → 0.0.1-dev.7

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
@@ -233,8 +233,8 @@ var DEFAULT_BREAKPOINTS = {
233
233
 
234
234
  // src/render/css.ts
235
235
  var selectorFor = (id) => `[data-cms="${id}"]`;
236
- function lengthToCss(len) {
237
- return `${len.value}${len.unit}`;
236
+ function lengthToCss(len2) {
237
+ return `${len2.value}${len2.unit}`;
238
238
  }
239
239
  function sizeToCss(size) {
240
240
  if (size === "fill") return "100%";
@@ -321,9 +321,9 @@ function isParentFree(doc, node) {
321
321
  function serializeRule(selector, decls, indent) {
322
322
  const props = Object.keys(decls).sort();
323
323
  if (props.length === 0) return "";
324
- const body = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
324
+ const body2 = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
325
325
  return `${indent}${selector} {
326
- ${body}
326
+ ${body2}
327
327
  ${indent}}`;
328
328
  }
329
329
  function emitDocCss(doc) {
@@ -368,6 +368,12 @@ var defaultRegistry = {
368
368
  frame: ({ children }) => children,
369
369
  shape: ({ children }) => children,
370
370
  text: ({ node }) => literal(node.props.content),
371
+ heading: ({ node }) => literal(node.props.content) || "Heading",
372
+ button: ({ node }) => {
373
+ const label2 = literal(node.props.label) || literal(node.props.content) || "Button";
374
+ const variant = literal(node.props.variant) || "primary";
375
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
376
+ },
371
377
  image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
372
378
  "img",
373
379
  {
@@ -377,6 +383,23 @@ var defaultRegistry = {
377
383
  }
378
384
  )
379
385
  };
386
+ function buttonStyle(variant) {
387
+ const base = {
388
+ display: "inline-flex",
389
+ alignItems: "center",
390
+ gap: 6,
391
+ padding: "9px 16px",
392
+ borderRadius: 8,
393
+ fontWeight: 600,
394
+ fontSize: 14,
395
+ lineHeight: 1,
396
+ cursor: "pointer",
397
+ userSelect: "none"
398
+ };
399
+ if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
400
+ if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
401
+ return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
402
+ }
380
403
  function RenderNode({ doc, id, registry = defaultRegistry }) {
381
404
  const node = doc.nodes[id];
382
405
  if (!node) return null;
@@ -708,7 +731,202 @@ function keyBetween(a, b) {
708
731
  return a + BASE[MID];
709
732
  }
710
733
  }
734
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
735
+ function NodeInspector({
736
+ node,
737
+ transform,
738
+ measured,
739
+ onProps,
740
+ onStyle,
741
+ onTransform,
742
+ onRemove,
743
+ onClose
744
+ }) {
745
+ const base = node.style.base ?? {};
746
+ const setT = (patch) => onTransform({ ...transform, ...patch });
747
+ const handled = /* @__PURE__ */ new Set(["content", "label"]);
748
+ const extraKeys = Object.keys(node.props).filter(
749
+ (k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
750
+ );
751
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
752
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
753
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
754
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12, textTransform: "capitalize" }, children: node.type }),
755
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
756
+ ] }),
757
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
758
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
759
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
760
+ ] }),
761
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
762
+ "content" in node.props ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Content", children: /* @__PURE__ */ jsxRuntime.jsx(
763
+ "textarea",
764
+ {
765
+ style: { ...input2, minHeight: 64, resize: "vertical", lineHeight: 1.4 },
766
+ value: String(node.props.content ?? ""),
767
+ onChange: (e) => onProps({ content: e.target.value })
768
+ }
769
+ ) }) : null,
770
+ "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,
771
+ 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,
772
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Position & size", children: [
773
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
774
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "X", value: transform.x, placeholder: "0", onChange: (v) => setT({ x: v }) }),
775
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Y", value: transform.y, placeholder: "0", onChange: (v) => setT({ y: v }) })
776
+ ] }),
777
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
778
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "W", value: transform.w, placeholder: `${Math.round(measured.w)}`, onChange: (v) => setT({ w: v }) }),
779
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "H", value: transform.h, placeholder: `${Math.round(measured.h)}`, onChange: (v) => setT({ h: v }) })
780
+ ] }),
781
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
782
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Scale", value: transform.scale, placeholder: "1", step: 0.05, onChange: (v) => setT({ scale: v }) }),
783
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Rotate", value: transform.rotate, placeholder: "0", onChange: (v) => setT({ rotate: v }) })
784
+ ] }),
785
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
786
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Opacity", value: transform.opacity, placeholder: "1", min: 0, max: 1, step: 0.05, onChange: (v) => setT({ opacity: v }) }),
787
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
788
+ ] })
789
+ ] }),
790
+ !CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Text", children: [
791
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
792
+ /* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
793
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
794
+ ] }),
795
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx(
797
+ Sel,
798
+ {
799
+ label: "Weight",
800
+ value: String(base.fontWeight ?? ""),
801
+ options: [["", "\u2014"], ["400", "Regular"], ["500", "Medium"], ["600", "Semibold"], ["700", "Bold"], ["800", "Heavy"]],
802
+ onChange: (v) => onStyle({ fontWeight: v ? Number(v) : void 0 })
803
+ }
804
+ ),
805
+ /* @__PURE__ */ jsxRuntime.jsx(
806
+ Sel,
807
+ {
808
+ label: "Align",
809
+ value: base.textAlign ?? "",
810
+ options: [["", "\u2014"], ["left", "Left"], ["center", "Center"], ["right", "Right"], ["justify", "Justify"]],
811
+ onChange: (v) => onStyle({ textAlign: v || void 0 })
812
+ }
813
+ )
814
+ ] })
815
+ ] }) : null,
816
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Box", children: [
817
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
818
+ /* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Background", value: hex(base.background), onChange: (v) => onStyle({ background: v }) }),
819
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Radius", value: base.radius?.value, placeholder: "0", onChange: (v) => onStyle({ radius: v == null ? void 0 : len(v) }) })
820
+ ] }),
821
+ /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
822
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
823
+ /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
824
+ ] })
825
+ ] })
826
+ ] })
827
+ ] });
828
+ }
829
+ function Group({ label: label2, children }) {
830
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
831
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label2 }),
832
+ children
833
+ ] });
834
+ }
835
+ function Row({ children }) {
836
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
837
+ }
838
+ function PropField({ name, value, onChange }) {
839
+ if (typeof value === "boolean") {
840
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
841
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
842
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
843
+ ] });
844
+ }
845
+ if (name === "variant") {
846
+ return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
847
+ }
848
+ const isNum = typeof value === "number";
849
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
850
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: name }),
851
+ /* @__PURE__ */ jsxRuntime.jsx(
852
+ "input",
853
+ {
854
+ style: input2,
855
+ type: isNum ? "number" : "text",
856
+ value: String(value),
857
+ onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
858
+ }
859
+ )
860
+ ] });
861
+ }
862
+ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
863
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
864
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
865
+ /* @__PURE__ */ jsxRuntime.jsx(
866
+ "input",
867
+ {
868
+ style: input2,
869
+ type: "number",
870
+ step,
871
+ min,
872
+ max,
873
+ value: value ?? "",
874
+ placeholder,
875
+ onChange: (e) => onChange(e.target.value === "" ? void 0 : Number(e.target.value))
876
+ }
877
+ )
878
+ ] });
879
+ }
880
+ function Color({ label: label2, value, onChange }) {
881
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
882
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
883
+ /* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
884
+ ] });
885
+ }
886
+ function Sel({ label: label2, value, options, onChange }) {
887
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
888
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
889
+ /* @__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)) })
890
+ ] });
891
+ }
892
+ var len = (value) => ({ value, unit: "px" });
893
+ var hex = (v) => v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
711
894
  var Z = 2147483e3;
895
+ var panel = {
896
+ position: "fixed",
897
+ top: 60,
898
+ right: 12,
899
+ bottom: 96,
900
+ width: 264,
901
+ display: "flex",
902
+ flexDirection: "column",
903
+ background: "#0b1220",
904
+ color: "#e2e8f0",
905
+ border: "1px solid #1e293b",
906
+ borderRadius: 12,
907
+ boxShadow: "0 18px 48px -16px rgba(0,0,0,0.6)",
908
+ zIndex: Z + 2,
909
+ fontFamily: "system-ui, sans-serif",
910
+ overflow: "hidden"
911
+ };
912
+ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
913
+ var body = { padding: 12, overflowY: "auto" };
914
+ var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
915
+ var fieldLabel = { fontSize: 10, opacity: 0.6 };
916
+ var input2 = {
917
+ width: "100%",
918
+ boxSizing: "border-box",
919
+ background: "#0f172a",
920
+ color: "#e2e8f0",
921
+ border: "1px solid #334155",
922
+ borderRadius: 6,
923
+ padding: "5px 7px",
924
+ font: "inherit",
925
+ fontSize: 12
926
+ };
927
+ var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
928
+ var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
929
+ var Z2 = 2147483e3;
712
930
  function EditablePage({
713
931
  doc,
714
932
  registry = defaultRegistry,
@@ -717,7 +935,8 @@ function EditablePage({
717
935
  onNodeTransform,
718
936
  onSelect,
719
937
  frames = 1,
720
- onProgress
938
+ onProgress,
939
+ pinned = true
721
940
  }) {
722
941
  const ed = useEditor(doc);
723
942
  const [editing, setEditing] = react.useState(false);
@@ -746,22 +965,16 @@ function EditablePage({
746
965
  root.querySelectorAll("[data-cms]").forEach((el) => {
747
966
  const id = el.dataset.cms;
748
967
  const t = id ? transforms?.[id] : void 0;
749
- if (t) {
750
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
751
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
752
- } else {
753
- el.style.transform = "";
754
- el.style.opacity = "";
755
- }
968
+ applyTransform(el, t);
756
969
  });
757
970
  }, [transforms, ed.state.doc]);
758
971
  react.useEffect(() => {
759
972
  const spacer = spacerRef.current;
760
973
  if (!spacer || !onProgress) return;
761
974
  const compute = () => {
762
- const len = spacer.offsetHeight - window.innerHeight;
975
+ const len2 = spacer.offsetHeight - window.innerHeight;
763
976
  const top = spacer.getBoundingClientRect().top;
764
- onProgress(len > 0 ? Math.min(1, Math.max(0, -top / len)) : 0);
977
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
765
978
  };
766
979
  compute();
767
980
  window.addEventListener("scroll", compute, { passive: true });
@@ -786,7 +999,7 @@ function EditablePage({
786
999
  window.removeEventListener("scroll", measureBox);
787
1000
  window.removeEventListener("resize", measureBox);
788
1001
  };
789
- }, [measureBox, ed.state.doc, transforms]);
1002
+ }, [measureBox, ed.state.doc]);
790
1003
  react.useEffect(() => {
791
1004
  if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
792
1005
  const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
@@ -811,17 +1024,29 @@ function EditablePage({
811
1024
  ed.select(el.getAttribute("data-cms"));
812
1025
  }
813
1026
  };
814
- const addText = react.useCallback(() => {
815
- const parent = selNode && selNode.type !== "text" ? selNode.id : ed.state.doc.sections[0] ?? null;
816
- const siblings = childrenOf(ed.state.doc, parent);
817
- const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
818
- const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
819
- ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
820
- ed.select(id);
821
- }, [ed, selNode]);
1027
+ const addNode = react.useCallback(
1028
+ (kind) => {
1029
+ const doc2 = ed.state.doc;
1030
+ const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1031
+ const siblings = childrenOf(doc2, parent);
1032
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
1033
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
1034
+ const def = ADD_DEFAULTS[kind];
1035
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
1036
+ ed.select(id);
1037
+ },
1038
+ [ed, selNode]
1039
+ );
822
1040
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
1041
+ const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
1042
+ const removeSelected = () => {
1043
+ if (!selection) return;
1044
+ ed.apply({ t: "remove_node", id: selection });
1045
+ ed.select(null);
1046
+ };
1047
+ const page = /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) });
823
1048
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
824
- /* @__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: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) }) }) }),
1049
+ 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 }) }) : page,
825
1050
  editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
826
1051
  SelectionOverlay,
827
1052
  {
@@ -833,7 +1058,20 @@ function EditablePage({
833
1058
  onLive: measureBox
834
1059
  }
835
1060
  ) : null,
836
- editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
1061
+ editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text" || selNode.type === "heading", base: selNode.style.base, setStyle }) : null,
1062
+ editing && selNode && box ? /* @__PURE__ */ jsxRuntime.jsx(
1063
+ NodeInspector,
1064
+ {
1065
+ node: selNode,
1066
+ transform: (selection ? transforms?.[selection] : void 0) ?? {},
1067
+ measured: { w: box.w, h: box.h },
1068
+ onProps: setProps,
1069
+ onStyle: setStyle,
1070
+ onTransform: (t) => selection && onNodeTransform?.(selection, t),
1071
+ onRemove: removeSelected,
1072
+ onClose: () => ed.select(null)
1073
+ }
1074
+ ) : null,
837
1075
  revealed || editing ? /* @__PURE__ */ jsxRuntime.jsx(
838
1076
  EditBar,
839
1077
  {
@@ -846,12 +1084,30 @@ function EditablePage({
846
1084
  canRedo: ed.canRedo,
847
1085
  onUndo: ed.undo,
848
1086
  onRedo: ed.redo,
849
- onAddText: addText
1087
+ onAdd: addNode
850
1088
  }
851
1089
  ) : null,
852
- editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z }, children: timelineDock }) : null
1090
+ editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
853
1091
  ] });
854
1092
  }
1093
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
1094
+ var ADD_DEFAULTS = {
1095
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
1096
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
1097
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
1098
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
1099
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
1100
+ };
1101
+ var GRIPS = [
1102
+ { key: "nw", north: true, west: true, cursor: "nwse-resize" },
1103
+ { key: "n", north: true, cursor: "ns-resize" },
1104
+ { key: "ne", north: true, east: true, cursor: "nesw-resize" },
1105
+ { key: "e", east: true, cursor: "ew-resize" },
1106
+ { key: "se", south: true, east: true, cursor: "nwse-resize" },
1107
+ { key: "s", south: true, cursor: "ns-resize" },
1108
+ { key: "sw", south: true, west: true, cursor: "nesw-resize" },
1109
+ { key: "w", west: true, cursor: "ew-resize" }
1110
+ ];
855
1111
  function SelectionOverlay({
856
1112
  box,
857
1113
  base,
@@ -863,29 +1119,48 @@ function SelectionOverlay({
863
1119
  const drag = react.useRef(null);
864
1120
  const apply = (t) => {
865
1121
  const el = getEl();
866
- if (el) {
867
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
868
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
869
- }
1122
+ if (el) applyTransform(el, t);
870
1123
  };
871
- const start = (kind) => (e) => {
1124
+ const startMove = (e) => {
872
1125
  e.stopPropagation();
873
1126
  e.preventDefault();
874
1127
  e.currentTarget.setPointerCapture(e.pointerId);
875
- drag.current = { kind, x: e.clientX, y: e.clientY, w: box.w, live: { ...base } };
1128
+ drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
876
1129
  };
877
- const move = (e) => {
1130
+ const moveMove = (e) => {
878
1131
  const d = drag.current;
879
1132
  if (!d) return;
880
- if (d.kind === "move") {
881
- d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
882
- } else {
883
- const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
884
- d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
885
- }
1133
+ d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
886
1134
  apply(d.live);
887
1135
  onLive();
888
1136
  };
1137
+ const startResize = (g) => (e) => {
1138
+ e.stopPropagation();
1139
+ e.preventDefault();
1140
+ e.currentTarget.setPointerCapture(e.pointerId);
1141
+ drag.current = { x: e.clientX, y: e.clientY, w: base.w ?? box.w, h: base.h ?? box.h, live: { ...base } };
1142
+ e.currentTarget.dataset.grip = g.key;
1143
+ };
1144
+ const resizeMove = (g) => (e) => {
1145
+ const d = drag.current;
1146
+ if (!d) return;
1147
+ const dx = e.clientX - d.x;
1148
+ const dy = e.clientY - d.y;
1149
+ const live = { ...base };
1150
+ if (g.east) live.w = Math.max(16, Math.round(d.w + dx));
1151
+ if (g.west) {
1152
+ live.w = Math.max(16, Math.round(d.w - dx));
1153
+ live.x = (base.x ?? 0) + (d.w - live.w);
1154
+ }
1155
+ if (g.south) live.h = Math.max(16, Math.round(d.h + dy));
1156
+ if (g.north) {
1157
+ live.h = Math.max(16, Math.round(d.h - dy));
1158
+ live.y = (base.y ?? 0) + (d.h - live.h);
1159
+ }
1160
+ d.live = live;
1161
+ apply(live);
1162
+ onLive();
1163
+ };
889
1164
  const end = () => {
890
1165
  const d = drag.current;
891
1166
  if (d) onTransform(d.live);
@@ -900,17 +1175,23 @@ function SelectionOverlay({
900
1175
  outline: "2px solid #8b5cf6",
901
1176
  borderRadius: 4,
902
1177
  pointerEvents: "none",
903
- zIndex: Z
1178
+ zIndex: Z2
904
1179
  };
905
1180
  const handle = {
906
1181
  position: "fixed",
907
- width: 16,
908
- height: 16,
909
- background: "#8b5cf6",
910
- border: "2px solid #fff",
911
- borderRadius: 5,
912
- zIndex: Z + 1,
913
- touchAction: "none"
1182
+ width: 12,
1183
+ height: 12,
1184
+ background: "#fff",
1185
+ border: "2px solid #8b5cf6",
1186
+ borderRadius: 3,
1187
+ zIndex: Z2 + 1,
1188
+ touchAction: "none",
1189
+ boxSizing: "border-box"
1190
+ };
1191
+ const gripPos = (g) => {
1192
+ const cx = g.west ? box.x : g.east ? box.x + box.w : box.x + box.w / 2;
1193
+ const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
1194
+ return { left: cx - 6, top: cy - 6, cursor: g.cursor };
914
1195
  };
915
1196
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
916
1197
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
@@ -919,22 +1200,23 @@ function SelectionOverlay({
919
1200
  "div",
920
1201
  {
921
1202
  title: "Drag to move",
922
- onPointerDown: start("move"),
923
- onPointerMove: move,
1203
+ onPointerDown: startMove,
1204
+ onPointerMove: moveMove,
924
1205
  onPointerUp: end,
925
- style: { ...handle, left: box.x - 9, top: box.y - 9, cursor: "move", borderRadius: 9 }
1206
+ style: { ...handle, width: 18, height: 18, borderRadius: 9, background: "#8b5cf6", border: "2px solid #fff", left: box.x - 22, top: box.y - 22, cursor: "move" }
926
1207
  }
927
1208
  ),
928
- /* @__PURE__ */ jsxRuntime.jsx(
1209
+ GRIPS.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
929
1210
  "div",
930
1211
  {
931
1212
  title: "Drag to resize",
932
- onPointerDown: start("resize"),
933
- onPointerMove: move,
1213
+ onPointerDown: startResize(g),
1214
+ onPointerMove: resizeMove(g),
934
1215
  onPointerUp: end,
935
- style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
936
- }
937
- )
1216
+ style: { ...handle, ...gripPos(g) }
1217
+ },
1218
+ g.key
1219
+ ))
938
1220
  ] }) : null
939
1221
  ] });
940
1222
  }
@@ -957,7 +1239,7 @@ function FloatingToolbar({
957
1239
  border: "1px solid rgba(255,255,255,0.12)",
958
1240
  borderRadius: 999,
959
1241
  boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
960
- zIndex: Z + 2,
1242
+ zIndex: Z2 + 2,
961
1243
  fontFamily: "system-ui, sans-serif",
962
1244
  fontSize: 12
963
1245
  };
@@ -975,6 +1257,13 @@ function FloatingToolbar({
975
1257
  ] })
976
1258
  ] });
977
1259
  }
1260
+ var ADD_MENU = [
1261
+ { kind: "text", label: "Text" },
1262
+ { kind: "heading", label: "Heading" },
1263
+ { kind: "button", label: "Button" },
1264
+ { kind: "image", label: "Image" },
1265
+ { kind: "box", label: "Box" }
1266
+ ];
978
1267
  function EditBar({
979
1268
  editing,
980
1269
  onToggle,
@@ -982,19 +1271,48 @@ function EditBar({
982
1271
  canRedo,
983
1272
  onUndo,
984
1273
  onRedo,
985
- onAddText
1274
+ onAdd
986
1275
  }) {
987
- 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: Z + 3, fontFamily: "system-ui, sans-serif", fontSize: 12 };
1276
+ const [open, setOpen] = react.useState(false);
1277
+ 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 };
988
1278
  const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
989
1279
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
990
1280
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
991
1281
  editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
992
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onClick: onAddText, title: "Add a text element", children: "\uFF0B Text" }),
1282
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1283
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
1284
+ 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(
1285
+ "button",
1286
+ {
1287
+ type: "button",
1288
+ style: { ...btn, textAlign: "left", borderRadius: 6, padding: "6px 10px" },
1289
+ onClick: () => {
1290
+ onAdd(m.kind);
1291
+ setOpen(false);
1292
+ },
1293
+ children: m.label
1294
+ },
1295
+ m.kind
1296
+ )) }) : null
1297
+ ] }),
993
1298
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
994
1299
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
995
1300
  ] }) : null
996
1301
  ] });
997
1302
  }
1303
+ function applyTransform(el, t) {
1304
+ if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
1305
+ el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
1306
+ el.style.opacity = t.opacity != null ? String(t.opacity) : "";
1307
+ el.style.width = t.w != null ? `${t.w}px` : "";
1308
+ el.style.height = t.h != null ? `${t.h}px` : "";
1309
+ } else {
1310
+ el.style.transform = "";
1311
+ el.style.opacity = "";
1312
+ el.style.width = "";
1313
+ el.style.height = "";
1314
+ }
1315
+ }
998
1316
  function normalizeColor(v) {
999
1317
  return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1000
1318
  }
@@ -1007,6 +1325,7 @@ exports.EditablePage = EditablePage;
1007
1325
  exports.Editor = Editor;
1008
1326
  exports.Inspector = Inspector;
1009
1327
  exports.LayersPanel = LayersPanel;
1328
+ exports.NodeInspector = NodeInspector;
1010
1329
  exports.editorReduce = editorReduce;
1011
1330
  exports.initEditor = initEditor;
1012
1331
  exports.useEditor = useEditor;