@particle-academy/fancy-cms-ui 0.0.1-dev.5 → 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,35 +965,23 @@ 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
- let raf = 0;
762
974
  const compute = () => {
763
- raf = 0;
764
- const len = spacer.offsetHeight - window.innerHeight;
975
+ const len2 = spacer.offsetHeight - window.innerHeight;
765
976
  const top = spacer.getBoundingClientRect().top;
766
- onProgress(len > 0 ? Math.min(1, Math.max(0, -top / len)) : 0);
767
- };
768
- const onScroll = () => {
769
- if (!raf) raf = requestAnimationFrame(compute);
977
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
770
978
  };
771
979
  compute();
772
- window.addEventListener("scroll", onScroll, { passive: true });
773
- window.addEventListener("resize", onScroll);
980
+ window.addEventListener("scroll", compute, { passive: true });
981
+ window.addEventListener("resize", compute);
774
982
  return () => {
775
- window.removeEventListener("scroll", onScroll);
776
- window.removeEventListener("resize", onScroll);
777
- if (raf) cancelAnimationFrame(raf);
983
+ window.removeEventListener("scroll", compute);
984
+ window.removeEventListener("resize", compute);
778
985
  };
779
986
  }, [onProgress, frames]);
780
987
  const measureBox = react.useCallback(() => {
@@ -792,7 +999,7 @@ function EditablePage({
792
999
  window.removeEventListener("scroll", measureBox);
793
1000
  window.removeEventListener("resize", measureBox);
794
1001
  };
795
- }, [measureBox, ed.state.doc, transforms]);
1002
+ }, [measureBox, ed.state.doc]);
796
1003
  react.useEffect(() => {
797
1004
  if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
798
1005
  const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
@@ -817,17 +1024,29 @@ function EditablePage({
817
1024
  ed.select(el.getAttribute("data-cms"));
818
1025
  }
819
1026
  };
820
- const addText = react.useCallback(() => {
821
- const parent = selNode && selNode.type !== "text" ? selNode.id : ed.state.doc.sections[0] ?? null;
822
- const siblings = childrenOf(ed.state.doc, parent);
823
- const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
824
- const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
825
- ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
826
- ed.select(id);
827
- }, [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
+ );
828
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 }) });
829
1048
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
830
- /* @__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,
831
1050
  editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
832
1051
  SelectionOverlay,
833
1052
  {
@@ -839,7 +1058,20 @@ function EditablePage({
839
1058
  onLive: measureBox
840
1059
  }
841
1060
  ) : null,
842
- 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,
843
1075
  revealed || editing ? /* @__PURE__ */ jsxRuntime.jsx(
844
1076
  EditBar,
845
1077
  {
@@ -852,12 +1084,30 @@ function EditablePage({
852
1084
  canRedo: ed.canRedo,
853
1085
  onUndo: ed.undo,
854
1086
  onRedo: ed.redo,
855
- onAddText: addText
1087
+ onAdd: addNode
856
1088
  }
857
1089
  ) : null,
858
- 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
859
1091
  ] });
860
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
+ ];
861
1111
  function SelectionOverlay({
862
1112
  box,
863
1113
  base,
@@ -869,29 +1119,48 @@ function SelectionOverlay({
869
1119
  const drag = react.useRef(null);
870
1120
  const apply = (t) => {
871
1121
  const el = getEl();
872
- if (el) {
873
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
874
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
875
- }
1122
+ if (el) applyTransform(el, t);
876
1123
  };
877
- const start = (kind) => (e) => {
1124
+ const startMove = (e) => {
878
1125
  e.stopPropagation();
879
1126
  e.preventDefault();
880
1127
  e.currentTarget.setPointerCapture(e.pointerId);
881
- 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 } };
882
1129
  };
883
- const move = (e) => {
1130
+ const moveMove = (e) => {
884
1131
  const d = drag.current;
885
1132
  if (!d) return;
886
- if (d.kind === "move") {
887
- d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
888
- } else {
889
- const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
890
- d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
891
- }
1133
+ d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
892
1134
  apply(d.live);
893
1135
  onLive();
894
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
+ };
895
1164
  const end = () => {
896
1165
  const d = drag.current;
897
1166
  if (d) onTransform(d.live);
@@ -906,17 +1175,23 @@ function SelectionOverlay({
906
1175
  outline: "2px solid #8b5cf6",
907
1176
  borderRadius: 4,
908
1177
  pointerEvents: "none",
909
- zIndex: Z
1178
+ zIndex: Z2
910
1179
  };
911
1180
  const handle = {
912
1181
  position: "fixed",
913
- width: 16,
914
- height: 16,
915
- background: "#8b5cf6",
916
- border: "2px solid #fff",
917
- borderRadius: 5,
918
- zIndex: Z + 1,
919
- 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 };
920
1195
  };
921
1196
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
922
1197
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
@@ -925,22 +1200,23 @@ function SelectionOverlay({
925
1200
  "div",
926
1201
  {
927
1202
  title: "Drag to move",
928
- onPointerDown: start("move"),
929
- onPointerMove: move,
1203
+ onPointerDown: startMove,
1204
+ onPointerMove: moveMove,
930
1205
  onPointerUp: end,
931
- 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" }
932
1207
  }
933
1208
  ),
934
- /* @__PURE__ */ jsxRuntime.jsx(
1209
+ GRIPS.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
935
1210
  "div",
936
1211
  {
937
1212
  title: "Drag to resize",
938
- onPointerDown: start("resize"),
939
- onPointerMove: move,
1213
+ onPointerDown: startResize(g),
1214
+ onPointerMove: resizeMove(g),
940
1215
  onPointerUp: end,
941
- style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
942
- }
943
- )
1216
+ style: { ...handle, ...gripPos(g) }
1217
+ },
1218
+ g.key
1219
+ ))
944
1220
  ] }) : null
945
1221
  ] });
946
1222
  }
@@ -963,7 +1239,7 @@ function FloatingToolbar({
963
1239
  border: "1px solid rgba(255,255,255,0.12)",
964
1240
  borderRadius: 999,
965
1241
  boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
966
- zIndex: Z + 2,
1242
+ zIndex: Z2 + 2,
967
1243
  fontFamily: "system-ui, sans-serif",
968
1244
  fontSize: 12
969
1245
  };
@@ -981,6 +1257,13 @@ function FloatingToolbar({
981
1257
  ] })
982
1258
  ] });
983
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
+ ];
984
1267
  function EditBar({
985
1268
  editing,
986
1269
  onToggle,
@@ -988,19 +1271,48 @@ function EditBar({
988
1271
  canRedo,
989
1272
  onUndo,
990
1273
  onRedo,
991
- onAddText
1274
+ onAdd
992
1275
  }) {
993
- 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 };
994
1278
  const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
995
1279
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
996
1280
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
997
1281
  editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
998
- /* @__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
+ ] }),
999
1298
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
1000
1299
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
1001
1300
  ] }) : null
1002
1301
  ] });
1003
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
+ }
1004
1316
  function normalizeColor(v) {
1005
1317
  return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1006
1318
  }
@@ -1013,6 +1325,7 @@ exports.EditablePage = EditablePage;
1013
1325
  exports.Editor = Editor;
1014
1326
  exports.Inspector = Inspector;
1015
1327
  exports.LayersPanel = LayersPanel;
1328
+ exports.NodeInspector = NodeInspector;
1016
1329
  exports.editorReduce = editorReduce;
1017
1330
  exports.initEditor = initEditor;
1018
1331
  exports.useEditor = useEditor;