@particle-academy/fancy-cms-ui 0.2.0 → 0.3.1
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/README.md +10 -1
- package/dist/{chunk-XQ3KHMBJ.js → chunk-6Q7DKNDZ.js} +181 -16
- package/dist/chunk-6Q7DKNDZ.js.map +1 -0
- package/dist/editor.cjs +209 -66
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.js +24 -57
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +181 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -5
- package/dist/index.d.ts +50 -5
- package/dist/index.js +1 -1
- package/dist/{useEditor-CVT8sTum.d.cts → useEditor-BQtAHLHl.d.cts} +30 -5
- package/dist/{useEditor-CV5jFAgo.d.ts → useEditor-CpX-LbfH.d.ts} +30 -5
- package/package.json +1 -1
- package/dist/chunk-XQ3KHMBJ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -753,6 +753,8 @@ var input = {
|
|
|
753
753
|
fontSize: 13
|
|
754
754
|
};
|
|
755
755
|
function Inspector({ doc, selection, apply }) {
|
|
756
|
+
const uid = react.useId();
|
|
757
|
+
const fieldId = (key) => `${uid}-${key}`;
|
|
756
758
|
const node = selection ? doc.nodes[selection] : null;
|
|
757
759
|
if (!node) {
|
|
758
760
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "var(--fcms-muted)", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
|
|
@@ -764,10 +766,12 @@ function Inspector({ doc, selection, apply }) {
|
|
|
764
766
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--fcms-fg)" }, children: node.type }),
|
|
765
767
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--fcms-muted)", marginBottom: 8 }, children: node.id }),
|
|
766
768
|
node.type === "text" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
767
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Content" }),
|
|
769
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("content"), children: "Content" }),
|
|
768
770
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
771
|
"textarea",
|
|
770
772
|
{
|
|
773
|
+
id: fieldId("content"),
|
|
774
|
+
"data-cms-field": "content",
|
|
771
775
|
style: { ...input, minHeight: 64, resize: "vertical" },
|
|
772
776
|
value: typeof node.props.content === "string" ? node.props.content : "",
|
|
773
777
|
onChange: (e) => setProp("content", e.target.value)
|
|
@@ -775,60 +779,72 @@ function Inspector({ doc, selection, apply }) {
|
|
|
775
779
|
)
|
|
776
780
|
] }) : null,
|
|
777
781
|
node.type === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
778
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Source" }),
|
|
782
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("src"), children: "Source" }),
|
|
779
783
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
780
784
|
"input",
|
|
781
785
|
{
|
|
786
|
+
id: fieldId("src"),
|
|
787
|
+
"data-cms-field": "src",
|
|
782
788
|
style: input,
|
|
783
789
|
value: typeof node.props.src === "string" ? node.props.src : "",
|
|
784
790
|
onChange: (e) => setProp("src", e.target.value)
|
|
785
791
|
}
|
|
786
792
|
)
|
|
787
793
|
] }) : null,
|
|
788
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Background" }),
|
|
794
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("background"), children: "Background" }),
|
|
789
795
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
790
796
|
"input",
|
|
791
797
|
{
|
|
798
|
+
id: fieldId("background"),
|
|
799
|
+
"data-cms-field": "background",
|
|
792
800
|
style: input,
|
|
793
801
|
placeholder: "#ffffff | gradient | url(\u2026)",
|
|
794
802
|
value: s.background ?? "",
|
|
795
803
|
onChange: (e) => setStyle({ background: e.target.value })
|
|
796
804
|
}
|
|
797
805
|
),
|
|
798
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Text color" }),
|
|
806
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("color"), children: "Text color" }),
|
|
799
807
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
800
808
|
"input",
|
|
801
809
|
{
|
|
810
|
+
id: fieldId("color"),
|
|
811
|
+
"data-cms-field": "color",
|
|
802
812
|
style: input,
|
|
803
813
|
placeholder: "#0f172a",
|
|
804
814
|
value: s.color ?? "",
|
|
805
815
|
onChange: (e) => setStyle({ color: e.target.value })
|
|
806
816
|
}
|
|
807
817
|
),
|
|
808
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Font size (px)" }),
|
|
818
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("fontSize"), children: "Font size (px)" }),
|
|
809
819
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
810
820
|
"input",
|
|
811
821
|
{
|
|
822
|
+
id: fieldId("fontSize"),
|
|
823
|
+
"data-cms-field": "fontSize",
|
|
812
824
|
type: "number",
|
|
813
825
|
style: input,
|
|
814
826
|
value: s.fontSize?.value ?? "",
|
|
815
827
|
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
816
828
|
}
|
|
817
829
|
),
|
|
818
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Padding (px)" }),
|
|
830
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("padding"), children: "Padding (px)" }),
|
|
819
831
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
820
832
|
"input",
|
|
821
833
|
{
|
|
834
|
+
id: fieldId("padding"),
|
|
835
|
+
"data-cms-field": "padding",
|
|
822
836
|
type: "number",
|
|
823
837
|
style: input,
|
|
824
838
|
value: s.padding?.value ?? "",
|
|
825
839
|
onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
826
840
|
}
|
|
827
841
|
),
|
|
828
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Opacity" }),
|
|
842
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("opacity"), children: "Opacity" }),
|
|
829
843
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
830
844
|
"input",
|
|
831
845
|
{
|
|
846
|
+
id: fieldId("opacity"),
|
|
847
|
+
"data-cms-field": "opacity",
|
|
832
848
|
type: "number",
|
|
833
849
|
step: "0.1",
|
|
834
850
|
min: "0",
|
|
@@ -840,8 +856,82 @@ function Inspector({ doc, selection, apply }) {
|
|
|
840
856
|
)
|
|
841
857
|
] });
|
|
842
858
|
}
|
|
859
|
+
|
|
860
|
+
// src/editor/insert.ts
|
|
861
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
862
|
+
"section",
|
|
863
|
+
"frame",
|
|
864
|
+
"stack",
|
|
865
|
+
"grid",
|
|
866
|
+
"shape",
|
|
867
|
+
"card",
|
|
868
|
+
"device",
|
|
869
|
+
"repeater"
|
|
870
|
+
]);
|
|
871
|
+
var ADD_DEFAULTS = {
|
|
872
|
+
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
873
|
+
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
874
|
+
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
875
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
876
|
+
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
877
|
+
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
|
|
878
|
+
stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
|
|
879
|
+
grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
|
|
880
|
+
card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
|
|
881
|
+
callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
|
|
882
|
+
divider: { type: "divider", props: {}, style: {} },
|
|
883
|
+
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
|
|
884
|
+
richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
|
|
885
|
+
repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
|
|
886
|
+
};
|
|
887
|
+
var ADD_MENU = [
|
|
888
|
+
{ kind: "heading", label: "Heading" },
|
|
889
|
+
{ kind: "text", label: "Text" },
|
|
890
|
+
{ kind: "button", label: "Button" },
|
|
891
|
+
{ kind: "link", label: "Link" },
|
|
892
|
+
{ kind: "image", label: "Image" },
|
|
893
|
+
{ kind: "card", label: "Card" },
|
|
894
|
+
{ kind: "callout", label: "Callout" },
|
|
895
|
+
{ kind: "stack", label: "Stack" },
|
|
896
|
+
{ kind: "grid", label: "Grid" },
|
|
897
|
+
{ kind: "box", label: "Box" },
|
|
898
|
+
{ kind: "divider", label: "Divider" },
|
|
899
|
+
{ kind: "code", label: "Code" },
|
|
900
|
+
{ kind: "richtext", label: "Rich text" },
|
|
901
|
+
{ kind: "repeater", label: "Repeater" }
|
|
902
|
+
];
|
|
903
|
+
function resolveInsertParent(doc, selectedId) {
|
|
904
|
+
const selected = selectedId ? doc.nodes[selectedId] : null;
|
|
905
|
+
if (selected) {
|
|
906
|
+
return CONTAINER_TYPES.has(selected.type) ? selected.id : selected.parent;
|
|
907
|
+
}
|
|
908
|
+
return doc.sections[doc.sections.length - 1] ?? null;
|
|
909
|
+
}
|
|
910
|
+
function buildInsertOp(doc, kind, selectedId, targetId) {
|
|
911
|
+
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);
|
|
912
|
+
const siblings = childrenOf(doc, parent);
|
|
913
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
914
|
+
const id = `n${doc.seq + 1}-${Math.floor(performance.now())}`;
|
|
915
|
+
const def = ADD_DEFAULTS[kind];
|
|
916
|
+
return {
|
|
917
|
+
id,
|
|
918
|
+
op: {
|
|
919
|
+
t: "insert_node",
|
|
920
|
+
node: {
|
|
921
|
+
id,
|
|
922
|
+
type: def.type,
|
|
923
|
+
parent,
|
|
924
|
+
order,
|
|
925
|
+
layout: def.layout,
|
|
926
|
+
props: { ...def.props },
|
|
927
|
+
style: { base: { ...def.style } }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
843
932
|
function Editor({ defaultValue, onChange, registry, data }) {
|
|
844
|
-
const
|
|
933
|
+
const initial = react.useMemo(() => defaultValue ?? emptyDoc("untitled"), [defaultValue]);
|
|
934
|
+
const ed = useEditor(initial);
|
|
845
935
|
const first = react.useRef(true);
|
|
846
936
|
const onChangeRef = useLatestRef(onChange);
|
|
847
937
|
react.useEffect(() => {
|
|
@@ -903,15 +993,93 @@ function Toolbar({ ed }) {
|
|
|
903
993
|
},
|
|
904
994
|
children: [
|
|
905
995
|
/* @__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" })
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" }),
|
|
997
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddElementMenu, { ed, btn })
|
|
907
998
|
]
|
|
908
999
|
}
|
|
909
1000
|
);
|
|
910
1001
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1002
|
+
function AddElementMenu({ ed, btn }) {
|
|
1003
|
+
const [open, setOpen] = react.useState(false);
|
|
1004
|
+
react.useEffect(() => {
|
|
1005
|
+
if (!open) return;
|
|
1006
|
+
const close = () => setOpen(false);
|
|
1007
|
+
const onKey = (e) => e.key === "Escape" && setOpen(false);
|
|
1008
|
+
window.addEventListener("click", close);
|
|
1009
|
+
window.addEventListener("keydown", onKey);
|
|
1010
|
+
return () => {
|
|
1011
|
+
window.removeEventListener("click", close);
|
|
1012
|
+
window.removeEventListener("keydown", onKey);
|
|
1013
|
+
};
|
|
1014
|
+
}, [open]);
|
|
1015
|
+
const add = (kind) => {
|
|
1016
|
+
const { op, id } = buildInsertOp(ed.state.doc, kind, ed.state.selection);
|
|
1017
|
+
ed.apply(op);
|
|
1018
|
+
ed.select(id);
|
|
1019
|
+
setOpen(false);
|
|
1020
|
+
};
|
|
1021
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", marginLeft: "auto" }, onClick: (e) => e.stopPropagation(), children: [
|
|
1022
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1023
|
+
"button",
|
|
1024
|
+
{
|
|
1025
|
+
type: "button",
|
|
1026
|
+
style: { ...btn, borderColor: "var(--fcms-accent, #7c3aed)" },
|
|
1027
|
+
"aria-haspopup": "menu",
|
|
1028
|
+
"aria-expanded": open,
|
|
1029
|
+
"data-cms-add-trigger": "",
|
|
1030
|
+
onClick: () => setOpen((v) => !v),
|
|
1031
|
+
children: "+ Add"
|
|
1032
|
+
}
|
|
1033
|
+
),
|
|
1034
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
+
"div",
|
|
1036
|
+
{
|
|
1037
|
+
role: "menu",
|
|
1038
|
+
"data-cms-add-menu": "",
|
|
1039
|
+
style: {
|
|
1040
|
+
position: "absolute",
|
|
1041
|
+
top: "calc(100% + 4px)",
|
|
1042
|
+
left: 0,
|
|
1043
|
+
zIndex: 20,
|
|
1044
|
+
minWidth: 150,
|
|
1045
|
+
maxHeight: 280,
|
|
1046
|
+
overflowY: "auto",
|
|
1047
|
+
padding: 4,
|
|
1048
|
+
borderRadius: 8,
|
|
1049
|
+
border: "1px solid var(--fcms-border)",
|
|
1050
|
+
background: "var(--fcms-bg)",
|
|
1051
|
+
boxShadow: "0 12px 32px rgba(0,0,0,.28)"
|
|
1052
|
+
},
|
|
1053
|
+
children: ADD_MENU.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1054
|
+
"button",
|
|
1055
|
+
{
|
|
1056
|
+
type: "button",
|
|
1057
|
+
role: "menuitem",
|
|
1058
|
+
"data-cms-add": item.kind,
|
|
1059
|
+
style: {
|
|
1060
|
+
...btn,
|
|
1061
|
+
display: "block",
|
|
1062
|
+
width: "100%",
|
|
1063
|
+
textAlign: "left",
|
|
1064
|
+
border: 0,
|
|
1065
|
+
background: "transparent"
|
|
1066
|
+
},
|
|
1067
|
+
onClick: () => add(item.kind),
|
|
1068
|
+
children: item.label
|
|
1069
|
+
},
|
|
1070
|
+
item.kind
|
|
1071
|
+
))
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1074
|
+
] });
|
|
1075
|
+
}
|
|
1076
|
+
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;--fcms-drop:#ec4899;--fcms-danger:#fca5a5;";
|
|
1077
|
+
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;--fcms-drop:#db2777;--fcms-danger:#dc2626;";
|
|
913
1078
|
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
1079
|
|
|
1080
|
+
exports.ADD_DEFAULTS = ADD_DEFAULTS;
|
|
1081
|
+
exports.ADD_MENU = ADD_MENU;
|
|
1082
|
+
exports.CONTAINER_TYPES = CONTAINER_TYPES;
|
|
915
1083
|
exports.Canvas = Canvas;
|
|
916
1084
|
exports.CmsPage = CmsPage;
|
|
917
1085
|
exports.CmsRegion = CmsRegion;
|
|
@@ -920,6 +1088,7 @@ exports.Editor = Editor;
|
|
|
920
1088
|
exports.Inspector = Inspector;
|
|
921
1089
|
exports.LayersPanel = LayersPanel;
|
|
922
1090
|
exports.RenderNode = RenderNode;
|
|
1091
|
+
exports.buildInsertOp = buildInsertOp;
|
|
923
1092
|
exports.childrenOf = childrenOf;
|
|
924
1093
|
exports.defaultRegistry = defaultRegistry;
|
|
925
1094
|
exports.descendantIds = descendantIds;
|
|
@@ -933,6 +1102,7 @@ exports.isBinding = isBinding;
|
|
|
933
1102
|
exports.keyBetween = keyBetween;
|
|
934
1103
|
exports.reduce = reduce;
|
|
935
1104
|
exports.reduceAll = reduceAll;
|
|
1105
|
+
exports.resolveInsertParent = resolveInsertParent;
|
|
936
1106
|
exports.resolveValue = resolveValue;
|
|
937
1107
|
exports.useEditor = useEditor;
|
|
938
1108
|
//# sourceMappingURL=index.cjs.map
|