@particle-academy/fancy-cms-ui 0.2.0 → 0.3.0

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/index.cjs CHANGED
@@ -840,8 +840,82 @@ function Inspector({ doc, selection, apply }) {
840
840
  )
841
841
  ] });
842
842
  }
843
+
844
+ // src/editor/insert.ts
845
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set([
846
+ "section",
847
+ "frame",
848
+ "stack",
849
+ "grid",
850
+ "shape",
851
+ "card",
852
+ "device",
853
+ "repeater"
854
+ ]);
855
+ var ADD_DEFAULTS = {
856
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
857
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
858
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
859
+ link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
860
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
861
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
862
+ stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
863
+ grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
864
+ card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
865
+ callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
866
+ divider: { type: "divider", props: {}, style: {} },
867
+ code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
868
+ richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
869
+ repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
870
+ };
871
+ var ADD_MENU = [
872
+ { kind: "heading", label: "Heading" },
873
+ { kind: "text", label: "Text" },
874
+ { kind: "button", label: "Button" },
875
+ { kind: "link", label: "Link" },
876
+ { kind: "image", label: "Image" },
877
+ { kind: "card", label: "Card" },
878
+ { kind: "callout", label: "Callout" },
879
+ { kind: "stack", label: "Stack" },
880
+ { kind: "grid", label: "Grid" },
881
+ { kind: "box", label: "Box" },
882
+ { kind: "divider", label: "Divider" },
883
+ { kind: "code", label: "Code" },
884
+ { kind: "richtext", label: "Rich text" },
885
+ { kind: "repeater", label: "Repeater" }
886
+ ];
887
+ function resolveInsertParent(doc, selectedId) {
888
+ const selected = selectedId ? doc.nodes[selectedId] : null;
889
+ if (selected) {
890
+ return CONTAINER_TYPES.has(selected.type) ? selected.id : selected.parent;
891
+ }
892
+ return doc.sections[doc.sections.length - 1] ?? null;
893
+ }
894
+ function buildInsertOp(doc, kind, selectedId, targetId) {
895
+ const parent = targetId !== void 0 ? targetId && doc.nodes[targetId] ? CONTAINER_TYPES.has(doc.nodes[targetId].type) ? targetId : doc.nodes[targetId].parent : null : resolveInsertParent(doc, selectedId);
896
+ const siblings = childrenOf(doc, parent);
897
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
898
+ const id = `n${doc.seq + 1}-${Math.floor(performance.now())}`;
899
+ const def = ADD_DEFAULTS[kind];
900
+ return {
901
+ id,
902
+ op: {
903
+ t: "insert_node",
904
+ node: {
905
+ id,
906
+ type: def.type,
907
+ parent,
908
+ order,
909
+ layout: def.layout,
910
+ props: { ...def.props },
911
+ style: { base: { ...def.style } }
912
+ }
913
+ }
914
+ };
915
+ }
843
916
  function Editor({ defaultValue, onChange, registry, data }) {
844
- const ed = useEditor(defaultValue);
917
+ const initial = react.useMemo(() => defaultValue ?? emptyDoc("untitled"), [defaultValue]);
918
+ const ed = useEditor(initial);
845
919
  const first = react.useRef(true);
846
920
  const onChangeRef = useLatestRef(onChange);
847
921
  react.useEffect(() => {
@@ -903,15 +977,93 @@ function Toolbar({ ed }) {
903
977
  },
904
978
  children: [
905
979
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canUndo ? 1 : 0.4 }, disabled: !ed.canUndo, onClick: ed.undo, children: "Undo" }),
906
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" })
980
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" }),
981
+ /* @__PURE__ */ jsxRuntime.jsx(AddElementMenu, { ed, btn })
907
982
  ]
908
983
  }
909
984
  );
910
985
  }
986
+ function AddElementMenu({ ed, btn }) {
987
+ const [open, setOpen] = react.useState(false);
988
+ react.useEffect(() => {
989
+ if (!open) return;
990
+ const close = () => setOpen(false);
991
+ const onKey = (e) => e.key === "Escape" && setOpen(false);
992
+ window.addEventListener("click", close);
993
+ window.addEventListener("keydown", onKey);
994
+ return () => {
995
+ window.removeEventListener("click", close);
996
+ window.removeEventListener("keydown", onKey);
997
+ };
998
+ }, [open]);
999
+ const add = (kind) => {
1000
+ const { op, id } = buildInsertOp(ed.state.doc, kind, ed.state.selection);
1001
+ ed.apply(op);
1002
+ ed.select(id);
1003
+ setOpen(false);
1004
+ };
1005
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", marginLeft: "auto" }, onClick: (e) => e.stopPropagation(), children: [
1006
+ /* @__PURE__ */ jsxRuntime.jsx(
1007
+ "button",
1008
+ {
1009
+ type: "button",
1010
+ style: { ...btn, borderColor: "var(--fcms-accent, #7c3aed)" },
1011
+ "aria-haspopup": "menu",
1012
+ "aria-expanded": open,
1013
+ "data-cms-add-trigger": "",
1014
+ onClick: () => setOpen((v) => !v),
1015
+ children: "+ Add"
1016
+ }
1017
+ ),
1018
+ open && /* @__PURE__ */ jsxRuntime.jsx(
1019
+ "div",
1020
+ {
1021
+ role: "menu",
1022
+ "data-cms-add-menu": "",
1023
+ style: {
1024
+ position: "absolute",
1025
+ top: "calc(100% + 4px)",
1026
+ left: 0,
1027
+ zIndex: 20,
1028
+ minWidth: 150,
1029
+ maxHeight: 280,
1030
+ overflowY: "auto",
1031
+ padding: 4,
1032
+ borderRadius: 8,
1033
+ border: "1px solid var(--fcms-border)",
1034
+ background: "var(--fcms-bg)",
1035
+ boxShadow: "0 12px 32px rgba(0,0,0,.28)"
1036
+ },
1037
+ children: ADD_MENU.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1038
+ "button",
1039
+ {
1040
+ type: "button",
1041
+ role: "menuitem",
1042
+ "data-cms-add": item.kind,
1043
+ style: {
1044
+ ...btn,
1045
+ display: "block",
1046
+ width: "100%",
1047
+ textAlign: "left",
1048
+ border: 0,
1049
+ background: "transparent"
1050
+ },
1051
+ onClick: () => add(item.kind),
1052
+ children: item.label
1053
+ },
1054
+ item.kind
1055
+ ))
1056
+ }
1057
+ )
1058
+ ] });
1059
+ }
911
1060
  var DARK_VARS = "color-scheme:dark;--fcms-bg:#0b0f19;--fcms-fg:#e5e7eb;--fcms-muted:#94a3b8;--fcms-border:#27272a;--fcms-canvas:#0f141f;--fcms-input-bg:#111827;--fcms-row-fg:#cbd5e1;--fcms-sel-bg:color-mix(in oklab, #8b5cf6 24%, transparent);--fcms-sel-fg:#ddd6fe;";
912
1061
  var LIGHT_VARS = "color-scheme:light;--fcms-bg:#ffffff;--fcms-fg:#0f172a;--fcms-muted:#64748b;--fcms-border:#e2e8f0;--fcms-canvas:#f8fafc;--fcms-input-bg:#ffffff;--fcms-row-fg:#334155;--fcms-sel-bg:#ede9fe;--fcms-sel-fg:#5b21b6;";
913
1062
  var CHROME_CSS = `.fancy-cms-editor{--fcms-accent:#8b5cf6;${LIGHT_VARS}}@media (prefers-color-scheme:dark){.fancy-cms-editor{${DARK_VARS}}}:where(.light,[data-theme="light"]) .fancy-cms-editor{${LIGHT_VARS}}:where(.dark,[data-theme="dark"]) .fancy-cms-editor{${DARK_VARS}}`;
914
1063
 
1064
+ exports.ADD_DEFAULTS = ADD_DEFAULTS;
1065
+ exports.ADD_MENU = ADD_MENU;
1066
+ exports.CONTAINER_TYPES = CONTAINER_TYPES;
915
1067
  exports.Canvas = Canvas;
916
1068
  exports.CmsPage = CmsPage;
917
1069
  exports.CmsRegion = CmsRegion;
@@ -920,6 +1072,7 @@ exports.Editor = Editor;
920
1072
  exports.Inspector = Inspector;
921
1073
  exports.LayersPanel = LayersPanel;
922
1074
  exports.RenderNode = RenderNode;
1075
+ exports.buildInsertOp = buildInsertOp;
923
1076
  exports.childrenOf = childrenOf;
924
1077
  exports.defaultRegistry = defaultRegistry;
925
1078
  exports.descendantIds = descendantIds;
@@ -933,6 +1086,7 @@ exports.isBinding = isBinding;
933
1086
  exports.keyBetween = keyBetween;
934
1087
  exports.reduce = reduce;
935
1088
  exports.reduceAll = reduceAll;
1089
+ exports.resolveInsertParent = resolveInsertParent;
936
1090
  exports.resolveValue = resolveValue;
937
1091
  exports.useEditor = useEditor;
938
1092
  //# sourceMappingURL=index.cjs.map