@particle-academy/fancy-cms-ui 0.0.1-dev.3 → 0.0.1-dev.4
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 +133 -68
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +18 -7
- package/dist/editor.d.ts +18 -7
- package/dist/editor.js +133 -68
- package/dist/editor.js.map +1 -1
- package/package.json +1 -1
package/dist/editor.cjs
CHANGED
|
@@ -712,7 +712,10 @@ var Z = 2147483e3;
|
|
|
712
712
|
function EditablePage({
|
|
713
713
|
doc,
|
|
714
714
|
registry = defaultRegistry,
|
|
715
|
-
timelineDock
|
|
715
|
+
timelineDock,
|
|
716
|
+
transforms,
|
|
717
|
+
onNodeTransform,
|
|
718
|
+
onSelect
|
|
716
719
|
}) {
|
|
717
720
|
const ed = useEditor(doc);
|
|
718
721
|
const [editing, setEditing] = react.useState(false);
|
|
@@ -721,6 +724,7 @@ function EditablePage({
|
|
|
721
724
|
const [box, setBox] = react.useState(null);
|
|
722
725
|
const { selection } = ed.state;
|
|
723
726
|
const selNode = selection ? ed.state.doc.nodes[selection] : null;
|
|
727
|
+
react.useEffect(() => onSelect?.(selection), [selection, onSelect]);
|
|
724
728
|
react.useEffect(() => {
|
|
725
729
|
const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
|
|
726
730
|
const onBlur = () => setRevealed(false);
|
|
@@ -733,36 +737,45 @@ function EditablePage({
|
|
|
733
737
|
window.removeEventListener("blur", onBlur);
|
|
734
738
|
};
|
|
735
739
|
}, []);
|
|
740
|
+
react.useEffect(() => {
|
|
741
|
+
const root = pageRef.current;
|
|
742
|
+
if (!root) return;
|
|
743
|
+
root.querySelectorAll("[data-cms]").forEach((el) => {
|
|
744
|
+
const id = el.dataset.cms;
|
|
745
|
+
const t = id ? transforms?.[id] : void 0;
|
|
746
|
+
if (t) {
|
|
747
|
+
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
|
|
748
|
+
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
749
|
+
} else {
|
|
750
|
+
el.style.transform = "";
|
|
751
|
+
el.style.opacity = "";
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}, [transforms, ed.state.doc]);
|
|
755
|
+
const measureBox = react.useCallback(() => {
|
|
756
|
+
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
757
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
758
|
+
if (!el) return setBox(null);
|
|
759
|
+
const r = el.getBoundingClientRect();
|
|
760
|
+
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
761
|
+
}, [editing, selection]);
|
|
736
762
|
react.useLayoutEffect(() => {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
const update = () => {
|
|
742
|
-
const el = pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
743
|
-
if (!el) return setBox(null);
|
|
744
|
-
const r = el.getBoundingClientRect();
|
|
745
|
-
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
746
|
-
};
|
|
747
|
-
update();
|
|
748
|
-
window.addEventListener("scroll", update, { passive: true });
|
|
749
|
-
window.addEventListener("resize", update);
|
|
763
|
+
measureBox();
|
|
764
|
+
window.addEventListener("scroll", measureBox, { passive: true });
|
|
765
|
+
window.addEventListener("resize", measureBox);
|
|
750
766
|
return () => {
|
|
751
|
-
window.removeEventListener("scroll",
|
|
752
|
-
window.removeEventListener("resize",
|
|
767
|
+
window.removeEventListener("scroll", measureBox);
|
|
768
|
+
window.removeEventListener("resize", measureBox);
|
|
753
769
|
};
|
|
754
|
-
}, [
|
|
770
|
+
}, [measureBox, ed.state.doc, transforms]);
|
|
755
771
|
react.useEffect(() => {
|
|
756
|
-
if (!editing || !selection || !pageRef.current) return;
|
|
757
|
-
if (selNode?.type !== "text") return;
|
|
772
|
+
if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
|
|
758
773
|
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
759
774
|
if (!el) return;
|
|
760
775
|
el.setAttribute("contenteditable", "true");
|
|
761
776
|
el.style.outline = "none";
|
|
762
777
|
el.focus();
|
|
763
|
-
const commit = () => {
|
|
764
|
-
ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
765
|
-
};
|
|
778
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
766
779
|
el.addEventListener("blur", commit);
|
|
767
780
|
return () => {
|
|
768
781
|
el.removeEventListener("blur", commit);
|
|
@@ -784,29 +797,21 @@ function EditablePage({
|
|
|
784
797
|
const siblings = childrenOf(ed.state.doc, parent);
|
|
785
798
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
|
|
786
799
|
const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
|
|
787
|
-
ed.apply({
|
|
788
|
-
t: "insert_node",
|
|
789
|
-
node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } }
|
|
790
|
-
});
|
|
800
|
+
ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
|
|
791
801
|
ed.select(id);
|
|
792
802
|
}, [ed, selNode]);
|
|
793
803
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
794
804
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
795
805
|
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) }),
|
|
796
|
-
editing && box ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
797
|
-
|
|
806
|
+
editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
807
|
+
SelectionOverlay,
|
|
798
808
|
{
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
outline: "2px solid #8b5cf6",
|
|
806
|
-
borderRadius: 4,
|
|
807
|
-
pointerEvents: "none",
|
|
808
|
-
zIndex: Z
|
|
809
|
-
}
|
|
809
|
+
box,
|
|
810
|
+
base: transforms?.[selection] ?? {},
|
|
811
|
+
movable: Boolean(onNodeTransform),
|
|
812
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
|
|
813
|
+
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
814
|
+
onLive: measureBox
|
|
810
815
|
}
|
|
811
816
|
) : null,
|
|
812
817
|
editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
|
|
@@ -828,17 +833,102 @@ function EditablePage({
|
|
|
828
833
|
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z }, children: timelineDock }) : null
|
|
829
834
|
] });
|
|
830
835
|
}
|
|
836
|
+
function SelectionOverlay({
|
|
837
|
+
box,
|
|
838
|
+
base,
|
|
839
|
+
movable,
|
|
840
|
+
getEl,
|
|
841
|
+
onTransform,
|
|
842
|
+
onLive
|
|
843
|
+
}) {
|
|
844
|
+
const drag = react.useRef(null);
|
|
845
|
+
const apply = (t) => {
|
|
846
|
+
const el = getEl();
|
|
847
|
+
if (el) {
|
|
848
|
+
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
|
|
849
|
+
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
const start = (kind) => (e) => {
|
|
853
|
+
e.stopPropagation();
|
|
854
|
+
e.preventDefault();
|
|
855
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
856
|
+
drag.current = { kind, x: e.clientX, y: e.clientY, w: box.w, live: { ...base } };
|
|
857
|
+
};
|
|
858
|
+
const move = (e) => {
|
|
859
|
+
const d = drag.current;
|
|
860
|
+
if (!d) return;
|
|
861
|
+
if (d.kind === "move") {
|
|
862
|
+
d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
|
|
863
|
+
} else {
|
|
864
|
+
const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
|
|
865
|
+
d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
|
|
866
|
+
}
|
|
867
|
+
apply(d.live);
|
|
868
|
+
onLive();
|
|
869
|
+
};
|
|
870
|
+
const end = () => {
|
|
871
|
+
const d = drag.current;
|
|
872
|
+
if (d) onTransform(d.live);
|
|
873
|
+
drag.current = null;
|
|
874
|
+
};
|
|
875
|
+
const outline = {
|
|
876
|
+
position: "fixed",
|
|
877
|
+
left: box.x - 1,
|
|
878
|
+
top: box.y - 1,
|
|
879
|
+
width: box.w + 2,
|
|
880
|
+
height: box.h + 2,
|
|
881
|
+
outline: "2px solid #8b5cf6",
|
|
882
|
+
borderRadius: 4,
|
|
883
|
+
pointerEvents: "none",
|
|
884
|
+
zIndex: Z
|
|
885
|
+
};
|
|
886
|
+
const handle = {
|
|
887
|
+
position: "fixed",
|
|
888
|
+
width: 16,
|
|
889
|
+
height: 16,
|
|
890
|
+
background: "#8b5cf6",
|
|
891
|
+
border: "2px solid #fff",
|
|
892
|
+
borderRadius: 5,
|
|
893
|
+
zIndex: Z + 1,
|
|
894
|
+
touchAction: "none"
|
|
895
|
+
};
|
|
896
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
898
|
+
movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
900
|
+
"div",
|
|
901
|
+
{
|
|
902
|
+
title: "Drag to move",
|
|
903
|
+
onPointerDown: start("move"),
|
|
904
|
+
onPointerMove: move,
|
|
905
|
+
onPointerUp: end,
|
|
906
|
+
style: { ...handle, left: box.x - 9, top: box.y - 9, cursor: "move", borderRadius: 9 }
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
910
|
+
"div",
|
|
911
|
+
{
|
|
912
|
+
title: "Drag to resize",
|
|
913
|
+
onPointerDown: start("resize"),
|
|
914
|
+
onPointerMove: move,
|
|
915
|
+
onPointerUp: end,
|
|
916
|
+
style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
] }) : null
|
|
920
|
+
] });
|
|
921
|
+
}
|
|
831
922
|
function FloatingToolbar({
|
|
832
923
|
box,
|
|
833
924
|
isText,
|
|
834
925
|
base,
|
|
835
926
|
setStyle
|
|
836
927
|
}) {
|
|
837
|
-
const top = Math.max(8, box.y - 46);
|
|
838
928
|
const wrap = {
|
|
839
929
|
position: "fixed",
|
|
840
930
|
left: box.x,
|
|
841
|
-
top,
|
|
931
|
+
top: Math.max(8, box.y - 46),
|
|
842
932
|
display: "flex",
|
|
843
933
|
alignItems: "center",
|
|
844
934
|
gap: 8,
|
|
@@ -859,17 +949,7 @@ function FloatingToolbar({
|
|
|
859
949
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "A" }),
|
|
860
950
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: base.color ?? "#0f172a", onChange: (e) => setStyle({ color: e.target.value }) })
|
|
861
951
|
] }) : null,
|
|
862
|
-
isText ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
863
|
-
"input",
|
|
864
|
-
{
|
|
865
|
-
type: "number",
|
|
866
|
-
title: "Font size (px)",
|
|
867
|
-
style: num,
|
|
868
|
-
placeholder: "size",
|
|
869
|
-
value: base.fontSize?.value ?? "",
|
|
870
|
-
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
871
|
-
}
|
|
872
|
-
) : null,
|
|
952
|
+
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,
|
|
873
953
|
/* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Background", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
874
954
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "BG" }),
|
|
875
955
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: normalizeColor(base.background), onChange: (e) => setStyle({ background: e.target.value }) })
|
|
@@ -885,22 +965,7 @@ function EditBar({
|
|
|
885
965
|
onRedo,
|
|
886
966
|
onAddText
|
|
887
967
|
}) {
|
|
888
|
-
const bar = {
|
|
889
|
-
position: "fixed",
|
|
890
|
-
top: 12,
|
|
891
|
-
right: 12,
|
|
892
|
-
display: "flex",
|
|
893
|
-
gap: 6,
|
|
894
|
-
alignItems: "center",
|
|
895
|
-
padding: 6,
|
|
896
|
-
background: "#0f172a",
|
|
897
|
-
color: "#fff",
|
|
898
|
-
borderRadius: 999,
|
|
899
|
-
boxShadow: "0 8px 24px -8px rgba(0,0,0,0.4)",
|
|
900
|
-
zIndex: Z + 3,
|
|
901
|
-
fontFamily: "system-ui, sans-serif",
|
|
902
|
-
fontSize: 12
|
|
903
|
-
};
|
|
968
|
+
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 };
|
|
904
969
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
905
970
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
|
|
906
971
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|