@particle-academy/fancy-cms-ui 0.0.1-dev.3 → 0.0.1-dev.5
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 +159 -69
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +22 -7
- package/dist/editor.d.ts +22 -7
- package/dist/editor.js +159 -69
- package/dist/editor.js.map +1 -1
- package/package.json +1 -1
package/dist/editor.cjs
CHANGED
|
@@ -712,15 +712,22 @@ var Z = 2147483e3;
|
|
|
712
712
|
function EditablePage({
|
|
713
713
|
doc,
|
|
714
714
|
registry = defaultRegistry,
|
|
715
|
-
timelineDock
|
|
715
|
+
timelineDock,
|
|
716
|
+
transforms,
|
|
717
|
+
onNodeTransform,
|
|
718
|
+
onSelect,
|
|
719
|
+
frames = 1,
|
|
720
|
+
onProgress
|
|
716
721
|
}) {
|
|
717
722
|
const ed = useEditor(doc);
|
|
718
723
|
const [editing, setEditing] = react.useState(false);
|
|
719
724
|
const [revealed, setRevealed] = react.useState(false);
|
|
720
725
|
const pageRef = react.useRef(null);
|
|
726
|
+
const spacerRef = react.useRef(null);
|
|
721
727
|
const [box, setBox] = react.useState(null);
|
|
722
728
|
const { selection } = ed.state;
|
|
723
729
|
const selNode = selection ? ed.state.doc.nodes[selection] : null;
|
|
730
|
+
react.useEffect(() => onSelect?.(selection), [selection, onSelect]);
|
|
724
731
|
react.useEffect(() => {
|
|
725
732
|
const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
|
|
726
733
|
const onBlur = () => setRevealed(false);
|
|
@@ -733,36 +740,67 @@ function EditablePage({
|
|
|
733
740
|
window.removeEventListener("blur", onBlur);
|
|
734
741
|
};
|
|
735
742
|
}, []);
|
|
736
|
-
react.
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
743
|
+
react.useEffect(() => {
|
|
744
|
+
const root = pageRef.current;
|
|
745
|
+
if (!root) return;
|
|
746
|
+
root.querySelectorAll("[data-cms]").forEach((el) => {
|
|
747
|
+
const id = el.dataset.cms;
|
|
748
|
+
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
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}, [transforms, ed.state.doc]);
|
|
758
|
+
react.useEffect(() => {
|
|
759
|
+
const spacer = spacerRef.current;
|
|
760
|
+
if (!spacer || !onProgress) return;
|
|
761
|
+
let raf = 0;
|
|
762
|
+
const compute = () => {
|
|
763
|
+
raf = 0;
|
|
764
|
+
const len = spacer.offsetHeight - window.innerHeight;
|
|
765
|
+
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);
|
|
746
770
|
};
|
|
747
|
-
|
|
748
|
-
window.addEventListener("scroll",
|
|
749
|
-
window.addEventListener("resize",
|
|
771
|
+
compute();
|
|
772
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
773
|
+
window.addEventListener("resize", onScroll);
|
|
750
774
|
return () => {
|
|
751
|
-
window.removeEventListener("scroll",
|
|
752
|
-
window.removeEventListener("resize",
|
|
775
|
+
window.removeEventListener("scroll", onScroll);
|
|
776
|
+
window.removeEventListener("resize", onScroll);
|
|
777
|
+
if (raf) cancelAnimationFrame(raf);
|
|
753
778
|
};
|
|
754
|
-
}, [
|
|
779
|
+
}, [onProgress, frames]);
|
|
780
|
+
const measureBox = react.useCallback(() => {
|
|
781
|
+
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
782
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
783
|
+
if (!el) return setBox(null);
|
|
784
|
+
const r = el.getBoundingClientRect();
|
|
785
|
+
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
786
|
+
}, [editing, selection]);
|
|
787
|
+
react.useLayoutEffect(() => {
|
|
788
|
+
measureBox();
|
|
789
|
+
window.addEventListener("scroll", measureBox, { passive: true });
|
|
790
|
+
window.addEventListener("resize", measureBox);
|
|
791
|
+
return () => {
|
|
792
|
+
window.removeEventListener("scroll", measureBox);
|
|
793
|
+
window.removeEventListener("resize", measureBox);
|
|
794
|
+
};
|
|
795
|
+
}, [measureBox, ed.state.doc, transforms]);
|
|
755
796
|
react.useEffect(() => {
|
|
756
|
-
if (!editing || !selection || !pageRef.current) return;
|
|
757
|
-
if (selNode?.type !== "text") return;
|
|
797
|
+
if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
|
|
758
798
|
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
759
799
|
if (!el) return;
|
|
760
800
|
el.setAttribute("contenteditable", "true");
|
|
761
801
|
el.style.outline = "none";
|
|
762
802
|
el.focus();
|
|
763
|
-
const commit = () => {
|
|
764
|
-
ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
765
|
-
};
|
|
803
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
766
804
|
el.addEventListener("blur", commit);
|
|
767
805
|
return () => {
|
|
768
806
|
el.removeEventListener("blur", commit);
|
|
@@ -784,29 +822,21 @@ function EditablePage({
|
|
|
784
822
|
const siblings = childrenOf(ed.state.doc, parent);
|
|
785
823
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
|
|
786
824
|
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
|
-
});
|
|
825
|
+
ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
|
|
791
826
|
ed.select(id);
|
|
792
827
|
}, [ed, selNode]);
|
|
793
828
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
794
829
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
795
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) }),
|
|
796
|
-
editing && box ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
797
|
-
|
|
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 }) }) }) }),
|
|
831
|
+
editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
832
|
+
SelectionOverlay,
|
|
798
833
|
{
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
outline: "2px solid #8b5cf6",
|
|
806
|
-
borderRadius: 4,
|
|
807
|
-
pointerEvents: "none",
|
|
808
|
-
zIndex: Z
|
|
809
|
-
}
|
|
834
|
+
box,
|
|
835
|
+
base: transforms?.[selection] ?? {},
|
|
836
|
+
movable: Boolean(onNodeTransform),
|
|
837
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
|
|
838
|
+
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
839
|
+
onLive: measureBox
|
|
810
840
|
}
|
|
811
841
|
) : null,
|
|
812
842
|
editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
|
|
@@ -828,17 +858,102 @@ function EditablePage({
|
|
|
828
858
|
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z }, children: timelineDock }) : null
|
|
829
859
|
] });
|
|
830
860
|
}
|
|
861
|
+
function SelectionOverlay({
|
|
862
|
+
box,
|
|
863
|
+
base,
|
|
864
|
+
movable,
|
|
865
|
+
getEl,
|
|
866
|
+
onTransform,
|
|
867
|
+
onLive
|
|
868
|
+
}) {
|
|
869
|
+
const drag = react.useRef(null);
|
|
870
|
+
const apply = (t) => {
|
|
871
|
+
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
|
+
}
|
|
876
|
+
};
|
|
877
|
+
const start = (kind) => (e) => {
|
|
878
|
+
e.stopPropagation();
|
|
879
|
+
e.preventDefault();
|
|
880
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
881
|
+
drag.current = { kind, x: e.clientX, y: e.clientY, w: box.w, live: { ...base } };
|
|
882
|
+
};
|
|
883
|
+
const move = (e) => {
|
|
884
|
+
const d = drag.current;
|
|
885
|
+
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
|
+
}
|
|
892
|
+
apply(d.live);
|
|
893
|
+
onLive();
|
|
894
|
+
};
|
|
895
|
+
const end = () => {
|
|
896
|
+
const d = drag.current;
|
|
897
|
+
if (d) onTransform(d.live);
|
|
898
|
+
drag.current = null;
|
|
899
|
+
};
|
|
900
|
+
const outline = {
|
|
901
|
+
position: "fixed",
|
|
902
|
+
left: box.x - 1,
|
|
903
|
+
top: box.y - 1,
|
|
904
|
+
width: box.w + 2,
|
|
905
|
+
height: box.h + 2,
|
|
906
|
+
outline: "2px solid #8b5cf6",
|
|
907
|
+
borderRadius: 4,
|
|
908
|
+
pointerEvents: "none",
|
|
909
|
+
zIndex: Z
|
|
910
|
+
};
|
|
911
|
+
const handle = {
|
|
912
|
+
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"
|
|
920
|
+
};
|
|
921
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
923
|
+
movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
924
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
925
|
+
"div",
|
|
926
|
+
{
|
|
927
|
+
title: "Drag to move",
|
|
928
|
+
onPointerDown: start("move"),
|
|
929
|
+
onPointerMove: move,
|
|
930
|
+
onPointerUp: end,
|
|
931
|
+
style: { ...handle, left: box.x - 9, top: box.y - 9, cursor: "move", borderRadius: 9 }
|
|
932
|
+
}
|
|
933
|
+
),
|
|
934
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
935
|
+
"div",
|
|
936
|
+
{
|
|
937
|
+
title: "Drag to resize",
|
|
938
|
+
onPointerDown: start("resize"),
|
|
939
|
+
onPointerMove: move,
|
|
940
|
+
onPointerUp: end,
|
|
941
|
+
style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
|
|
942
|
+
}
|
|
943
|
+
)
|
|
944
|
+
] }) : null
|
|
945
|
+
] });
|
|
946
|
+
}
|
|
831
947
|
function FloatingToolbar({
|
|
832
948
|
box,
|
|
833
949
|
isText,
|
|
834
950
|
base,
|
|
835
951
|
setStyle
|
|
836
952
|
}) {
|
|
837
|
-
const top = Math.max(8, box.y - 46);
|
|
838
953
|
const wrap = {
|
|
839
954
|
position: "fixed",
|
|
840
955
|
left: box.x,
|
|
841
|
-
top,
|
|
956
|
+
top: Math.max(8, box.y - 46),
|
|
842
957
|
display: "flex",
|
|
843
958
|
alignItems: "center",
|
|
844
959
|
gap: 8,
|
|
@@ -859,17 +974,7 @@ function FloatingToolbar({
|
|
|
859
974
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "A" }),
|
|
860
975
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: base.color ?? "#0f172a", onChange: (e) => setStyle({ color: e.target.value }) })
|
|
861
976
|
] }) : 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,
|
|
977
|
+
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
978
|
/* @__PURE__ */ jsxRuntime.jsxs("label", { title: "Background", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
874
979
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.7 }, children: "BG" }),
|
|
875
980
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", style: swatch, value: normalizeColor(base.background), onChange: (e) => setStyle({ background: e.target.value }) })
|
|
@@ -885,22 +990,7 @@ function EditBar({
|
|
|
885
990
|
onRedo,
|
|
886
991
|
onAddText
|
|
887
992
|
}) {
|
|
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
|
-
};
|
|
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 };
|
|
904
994
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
905
995
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
|
|
906
996
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|