@particle-academy/fancy-cms-ui 0.0.1-dev.6 → 0.0.1-dev.8
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/{chunk-5PWZGIYZ.js → chunk-5O6U4VIU.js} +25 -2
- package/dist/chunk-5O6U4VIU.js.map +1 -0
- package/dist/editor.cjs +625 -62
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +42 -3
- package/dist/editor.d.ts +42 -3
- package/dist/editor.js +565 -59
- package/dist/editor.js.map +1 -1
- package/dist/react.cjs +23 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +2 -2
- package/package.json +5 -4
- package/dist/chunk-5PWZGIYZ.js.map +0 -1
package/dist/editor.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var reactFancy = require('@particle-academy/react-fancy');
|
|
5
6
|
|
|
6
7
|
// src/editor/Editor.tsx
|
|
7
8
|
|
|
@@ -233,8 +234,8 @@ var DEFAULT_BREAKPOINTS = {
|
|
|
233
234
|
|
|
234
235
|
// src/render/css.ts
|
|
235
236
|
var selectorFor = (id) => `[data-cms="${id}"]`;
|
|
236
|
-
function lengthToCss(
|
|
237
|
-
return `${
|
|
237
|
+
function lengthToCss(len2) {
|
|
238
|
+
return `${len2.value}${len2.unit}`;
|
|
238
239
|
}
|
|
239
240
|
function sizeToCss(size) {
|
|
240
241
|
if (size === "fill") return "100%";
|
|
@@ -321,9 +322,9 @@ function isParentFree(doc, node) {
|
|
|
321
322
|
function serializeRule(selector, decls, indent) {
|
|
322
323
|
const props = Object.keys(decls).sort();
|
|
323
324
|
if (props.length === 0) return "";
|
|
324
|
-
const
|
|
325
|
+
const body2 = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
|
|
325
326
|
return `${indent}${selector} {
|
|
326
|
-
${
|
|
327
|
+
${body2}
|
|
327
328
|
${indent}}`;
|
|
328
329
|
}
|
|
329
330
|
function emitDocCss(doc) {
|
|
@@ -368,6 +369,12 @@ var defaultRegistry = {
|
|
|
368
369
|
frame: ({ children }) => children,
|
|
369
370
|
shape: ({ children }) => children,
|
|
370
371
|
text: ({ node }) => literal(node.props.content),
|
|
372
|
+
heading: ({ node }) => literal(node.props.content) || "Heading",
|
|
373
|
+
button: ({ node }) => {
|
|
374
|
+
const label2 = literal(node.props.label) || literal(node.props.content) || "Button";
|
|
375
|
+
const variant = literal(node.props.variant) || "primary";
|
|
376
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
|
|
377
|
+
},
|
|
371
378
|
image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
372
379
|
"img",
|
|
373
380
|
{
|
|
@@ -377,6 +384,23 @@ var defaultRegistry = {
|
|
|
377
384
|
}
|
|
378
385
|
)
|
|
379
386
|
};
|
|
387
|
+
function buttonStyle(variant) {
|
|
388
|
+
const base = {
|
|
389
|
+
display: "inline-flex",
|
|
390
|
+
alignItems: "center",
|
|
391
|
+
gap: 6,
|
|
392
|
+
padding: "9px 16px",
|
|
393
|
+
borderRadius: 8,
|
|
394
|
+
fontWeight: 600,
|
|
395
|
+
fontSize: 14,
|
|
396
|
+
lineHeight: 1,
|
|
397
|
+
cursor: "pointer",
|
|
398
|
+
userSelect: "none"
|
|
399
|
+
};
|
|
400
|
+
if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
|
|
401
|
+
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
402
|
+
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
403
|
+
}
|
|
380
404
|
function RenderNode({ doc, id, registry = defaultRegistry }) {
|
|
381
405
|
const node = doc.nodes[id];
|
|
382
406
|
if (!node) return null;
|
|
@@ -702,13 +726,345 @@ function Toolbar({ ed }) {
|
|
|
702
726
|
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
703
727
|
var RADIX = BASE.length;
|
|
704
728
|
var MID = Math.floor(RADIX / 2);
|
|
729
|
+
function val(c) {
|
|
730
|
+
const i = BASE.indexOf(c);
|
|
731
|
+
return i < 0 ? 0 : i;
|
|
732
|
+
}
|
|
705
733
|
function keyBetween(a, b) {
|
|
706
734
|
if (a === null) a = "";
|
|
707
|
-
{
|
|
735
|
+
if (b === null) {
|
|
708
736
|
return a + BASE[MID];
|
|
709
737
|
}
|
|
738
|
+
if (a >= b) {
|
|
739
|
+
throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
|
|
740
|
+
}
|
|
741
|
+
let result = "";
|
|
742
|
+
let i = 0;
|
|
743
|
+
for (; ; ) {
|
|
744
|
+
const ca = i < a.length ? val(a[i]) : 0;
|
|
745
|
+
const cb = i < b.length ? val(b[i]) : RADIX;
|
|
746
|
+
if (cb - ca > 1) {
|
|
747
|
+
result += BASE[ca + Math.floor((cb - ca) / 2)];
|
|
748
|
+
return result;
|
|
749
|
+
}
|
|
750
|
+
if (ca === cb) {
|
|
751
|
+
result += BASE[ca];
|
|
752
|
+
i++;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
result += BASE[ca];
|
|
756
|
+
i++;
|
|
757
|
+
for (; ; ) {
|
|
758
|
+
const da = i < a.length ? val(a[i]) : 0;
|
|
759
|
+
if (RADIX - da > 1) {
|
|
760
|
+
result += BASE[da + Math.floor((RADIX - da) / 2)];
|
|
761
|
+
return result;
|
|
762
|
+
}
|
|
763
|
+
result += BASE[da];
|
|
764
|
+
i++;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
710
767
|
}
|
|
768
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
|
|
769
|
+
function NodeInspector({
|
|
770
|
+
node,
|
|
771
|
+
transform,
|
|
772
|
+
measured,
|
|
773
|
+
onProps,
|
|
774
|
+
onStyle,
|
|
775
|
+
onTransform,
|
|
776
|
+
onRemove,
|
|
777
|
+
onClose
|
|
778
|
+
}) {
|
|
779
|
+
const base = node.style.base ?? {};
|
|
780
|
+
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
781
|
+
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
782
|
+
const extraKeys = Object.keys(node.props).filter(
|
|
783
|
+
(k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
|
|
784
|
+
);
|
|
785
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
786
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
|
|
788
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12, textTransform: "capitalize" }, children: node.type }),
|
|
789
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
|
|
790
|
+
] }),
|
|
791
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
|
|
792
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
|
|
793
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
|
|
794
|
+
] }),
|
|
795
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
|
|
796
|
+
"content" in node.props ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Content", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
797
|
+
"textarea",
|
|
798
|
+
{
|
|
799
|
+
style: { ...input2, minHeight: 64, resize: "vertical", lineHeight: 1.4 },
|
|
800
|
+
value: String(node.props.content ?? ""),
|
|
801
|
+
onChange: (e) => onProps({ content: e.target.value })
|
|
802
|
+
}
|
|
803
|
+
) }) : null,
|
|
804
|
+
"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,
|
|
805
|
+
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,
|
|
806
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Position & size", children: [
|
|
807
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
808
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "X", value: transform.x, placeholder: "0", onChange: (v) => setT({ x: v }) }),
|
|
809
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Y", value: transform.y, placeholder: "0", onChange: (v) => setT({ y: v }) })
|
|
810
|
+
] }),
|
|
811
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
812
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "W", value: transform.w, placeholder: `${Math.round(measured.w)}`, onChange: (v) => setT({ w: v }) }),
|
|
813
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "H", value: transform.h, placeholder: `${Math.round(measured.h)}`, onChange: (v) => setT({ h: v }) })
|
|
814
|
+
] }),
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
816
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Scale", value: transform.scale, placeholder: "1", step: 0.05, onChange: (v) => setT({ scale: v }) }),
|
|
817
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Rotate", value: transform.rotate, placeholder: "0", onChange: (v) => setT({ rotate: v }) })
|
|
818
|
+
] }),
|
|
819
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
820
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Opacity", value: transform.opacity, placeholder: "1", min: 0, max: 1, step: 0.05, onChange: (v) => setT({ opacity: v }) }),
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
|
|
822
|
+
] })
|
|
823
|
+
] }),
|
|
824
|
+
!CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Text", children: [
|
|
825
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
826
|
+
/* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
|
|
827
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
|
|
828
|
+
] }),
|
|
829
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
830
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
831
|
+
Sel,
|
|
832
|
+
{
|
|
833
|
+
label: "Weight",
|
|
834
|
+
value: String(base.fontWeight ?? ""),
|
|
835
|
+
options: [["", "\u2014"], ["400", "Regular"], ["500", "Medium"], ["600", "Semibold"], ["700", "Bold"], ["800", "Heavy"]],
|
|
836
|
+
onChange: (v) => onStyle({ fontWeight: v ? Number(v) : void 0 })
|
|
837
|
+
}
|
|
838
|
+
),
|
|
839
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
840
|
+
Sel,
|
|
841
|
+
{
|
|
842
|
+
label: "Align",
|
|
843
|
+
value: base.textAlign ?? "",
|
|
844
|
+
options: [["", "\u2014"], ["left", "Left"], ["center", "Center"], ["right", "Right"], ["justify", "Justify"]],
|
|
845
|
+
onChange: (v) => onStyle({ textAlign: v || void 0 })
|
|
846
|
+
}
|
|
847
|
+
)
|
|
848
|
+
] })
|
|
849
|
+
] }) : null,
|
|
850
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Box", children: [
|
|
851
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
852
|
+
/* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Background", value: hex(base.background), onChange: (v) => onStyle({ background: v }) }),
|
|
853
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Radius", value: base.radius?.value, placeholder: "0", onChange: (v) => onStyle({ radius: v == null ? void 0 : len(v) }) })
|
|
854
|
+
] }),
|
|
855
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
856
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
|
|
857
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
|
|
858
|
+
] })
|
|
859
|
+
] })
|
|
860
|
+
] })
|
|
861
|
+
] });
|
|
862
|
+
}
|
|
863
|
+
function Group({ label: label2, children }) {
|
|
864
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
|
|
865
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label2 }),
|
|
866
|
+
children
|
|
867
|
+
] });
|
|
868
|
+
}
|
|
869
|
+
function Row({ children }) {
|
|
870
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
871
|
+
}
|
|
872
|
+
function PropField({ name, value, onChange }) {
|
|
873
|
+
if (typeof value === "boolean") {
|
|
874
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
875
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
876
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
877
|
+
] });
|
|
878
|
+
}
|
|
879
|
+
if (name === "variant") {
|
|
880
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
881
|
+
}
|
|
882
|
+
const isNum = typeof value === "number";
|
|
883
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
884
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: name }),
|
|
885
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
886
|
+
"input",
|
|
887
|
+
{
|
|
888
|
+
style: input2,
|
|
889
|
+
type: isNum ? "number" : "text",
|
|
890
|
+
value: String(value),
|
|
891
|
+
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
897
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
900
|
+
"input",
|
|
901
|
+
{
|
|
902
|
+
style: input2,
|
|
903
|
+
type: "number",
|
|
904
|
+
step,
|
|
905
|
+
min,
|
|
906
|
+
max,
|
|
907
|
+
value: value ?? "",
|
|
908
|
+
placeholder,
|
|
909
|
+
onChange: (e) => onChange(e.target.value === "" ? void 0 : Number(e.target.value))
|
|
910
|
+
}
|
|
911
|
+
)
|
|
912
|
+
] });
|
|
913
|
+
}
|
|
914
|
+
function Color({ label: label2, value, onChange }) {
|
|
915
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
916
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
917
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
|
|
918
|
+
] });
|
|
919
|
+
}
|
|
920
|
+
function Sel({ label: label2, value, options, onChange }) {
|
|
921
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
923
|
+
/* @__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)) })
|
|
924
|
+
] });
|
|
925
|
+
}
|
|
926
|
+
var len = (value) => ({ value, unit: "px" });
|
|
927
|
+
var hex = (v) => v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
711
928
|
var Z = 2147483e3;
|
|
929
|
+
var panel = {
|
|
930
|
+
position: "fixed",
|
|
931
|
+
top: 60,
|
|
932
|
+
right: 12,
|
|
933
|
+
bottom: 96,
|
|
934
|
+
width: 264,
|
|
935
|
+
display: "flex",
|
|
936
|
+
flexDirection: "column",
|
|
937
|
+
background: "#0b1220",
|
|
938
|
+
color: "#e2e8f0",
|
|
939
|
+
border: "1px solid #1e293b",
|
|
940
|
+
borderRadius: 12,
|
|
941
|
+
boxShadow: "0 18px 48px -16px rgba(0,0,0,0.6)",
|
|
942
|
+
zIndex: Z + 2,
|
|
943
|
+
fontFamily: "system-ui, sans-serif",
|
|
944
|
+
overflow: "hidden"
|
|
945
|
+
};
|
|
946
|
+
var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
|
|
947
|
+
var body = { padding: 12, overflowY: "auto" };
|
|
948
|
+
var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
|
|
949
|
+
var fieldLabel = { fontSize: 10, opacity: 0.6 };
|
|
950
|
+
var input2 = {
|
|
951
|
+
width: "100%",
|
|
952
|
+
boxSizing: "border-box",
|
|
953
|
+
background: "#0f172a",
|
|
954
|
+
color: "#e2e8f0",
|
|
955
|
+
border: "1px solid #334155",
|
|
956
|
+
borderRadius: 6,
|
|
957
|
+
padding: "5px 7px",
|
|
958
|
+
font: "inherit",
|
|
959
|
+
fontSize: 12
|
|
960
|
+
};
|
|
961
|
+
var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
|
|
962
|
+
var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
|
|
963
|
+
|
|
964
|
+
// src/editor/editorOps.ts
|
|
965
|
+
function childrenOfMap(nodes, parent) {
|
|
966
|
+
return Object.values(nodes).filter((n) => n.parent === parent).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
|
|
967
|
+
}
|
|
968
|
+
function subtreeBfs(nodes, root) {
|
|
969
|
+
const out = [];
|
|
970
|
+
const queue = [root];
|
|
971
|
+
while (queue.length) {
|
|
972
|
+
const cur = queue.shift();
|
|
973
|
+
out.push(cur);
|
|
974
|
+
for (const child of childrenOfMap(nodes, cur)) queue.push(child.id);
|
|
975
|
+
}
|
|
976
|
+
return out;
|
|
977
|
+
}
|
|
978
|
+
function cloneFrom(nodes, rootId, newParent, newOrder, seqBase) {
|
|
979
|
+
if (!nodes[rootId]) return { ops: [], newRootId: null };
|
|
980
|
+
const order = subtreeBfs(nodes, rootId);
|
|
981
|
+
const idMap = {};
|
|
982
|
+
order.forEach((old, i) => idMap[old] = `${old}_c${seqBase}_${i}`);
|
|
983
|
+
const ops = order.map((old) => {
|
|
984
|
+
const src = nodes[old];
|
|
985
|
+
const cloned = JSON.parse(JSON.stringify(src));
|
|
986
|
+
cloned.id = idMap[old];
|
|
987
|
+
cloned.parent = old === rootId ? newParent : idMap[src.parent];
|
|
988
|
+
if (old === rootId) cloned.order = newOrder;
|
|
989
|
+
return { t: "insert_node", node: cloned };
|
|
990
|
+
});
|
|
991
|
+
return { ops, newRootId: idMap[rootId] };
|
|
992
|
+
}
|
|
993
|
+
function duplicateOps(doc, id) {
|
|
994
|
+
const node = doc.nodes[id];
|
|
995
|
+
if (!node) return { ops: [], newRootId: null };
|
|
996
|
+
const sibs = childrenOf(doc, node.parent);
|
|
997
|
+
const idx = sibs.findIndex((s) => s.id === id);
|
|
998
|
+
const newOrder = keyBetween(node.order, sibs[idx + 1]?.order ?? null);
|
|
999
|
+
const { ops, newRootId } = cloneFrom(doc.nodes, id, node.parent, newOrder, doc.seq);
|
|
1000
|
+
if (node.parent === null && newRootId) {
|
|
1001
|
+
const base = doc.sections;
|
|
1002
|
+
const at = base.indexOf(id);
|
|
1003
|
+
ops.push({ t: "reorder_sections", order: [...base.slice(0, at + 1), newRootId, ...base.slice(at + 1)] });
|
|
1004
|
+
}
|
|
1005
|
+
return { ops, newRootId };
|
|
1006
|
+
}
|
|
1007
|
+
function pasteOps(doc, clip, target, containerTypes) {
|
|
1008
|
+
const sel = target ? doc.nodes[target] : null;
|
|
1009
|
+
const parent = sel ? containerTypes.has(sel.type) ? sel.id : sel.parent : doc.sections[doc.sections.length - 1] ?? null;
|
|
1010
|
+
const sibs = childrenOf(doc, parent);
|
|
1011
|
+
const newOrder = keyBetween(sibs.length ? sibs[sibs.length - 1].order : null, null);
|
|
1012
|
+
const { ops, newRootId } = cloneFrom(clip.nodes, clip.rootId, parent, newOrder, doc.seq);
|
|
1013
|
+
return { ops, newRootId };
|
|
1014
|
+
}
|
|
1015
|
+
function reorderOps(doc, id, dir) {
|
|
1016
|
+
const node = doc.nodes[id];
|
|
1017
|
+
if (!node) return [];
|
|
1018
|
+
if (node.parent === null) {
|
|
1019
|
+
const s = [...doc.sections];
|
|
1020
|
+
const i = s.indexOf(id);
|
|
1021
|
+
if (i < 0) return [];
|
|
1022
|
+
s.splice(i, 1);
|
|
1023
|
+
if (dir === "front") s.unshift(id);
|
|
1024
|
+
else if (dir === "back") s.push(id);
|
|
1025
|
+
else s.splice(Math.max(0, Math.min(s.length, dir === "up" ? i - 1 : i + 1)), 0, id);
|
|
1026
|
+
return [{ t: "reorder_sections", order: s }];
|
|
1027
|
+
}
|
|
1028
|
+
const sibs = childrenOf(doc, node.parent);
|
|
1029
|
+
const idx = sibs.findIndex((sb) => sb.id === id);
|
|
1030
|
+
if (idx < 0) return [];
|
|
1031
|
+
const last = sibs.length - 1;
|
|
1032
|
+
let newOrder;
|
|
1033
|
+
if (dir === "up") {
|
|
1034
|
+
if (idx <= 0) return [];
|
|
1035
|
+
newOrder = keyBetween(sibs[idx - 2]?.order ?? null, sibs[idx - 1].order);
|
|
1036
|
+
} else if (dir === "down") {
|
|
1037
|
+
if (idx >= last) return [];
|
|
1038
|
+
newOrder = keyBetween(sibs[idx + 1].order, sibs[idx + 2]?.order ?? null);
|
|
1039
|
+
} else if (dir === "front") {
|
|
1040
|
+
if (idx >= last) return [];
|
|
1041
|
+
newOrder = keyBetween(sibs[last].order, null);
|
|
1042
|
+
} else {
|
|
1043
|
+
if (idx <= 0) return [];
|
|
1044
|
+
newOrder = keyBetween(null, sibs[0].order);
|
|
1045
|
+
}
|
|
1046
|
+
return [{ t: "move_node", id, parent: node.parent, order: newOrder }];
|
|
1047
|
+
}
|
|
1048
|
+
function wrapInBoxOps(doc, id) {
|
|
1049
|
+
const node = doc.nodes[id];
|
|
1050
|
+
if (!node || node.parent === null) return { ops: [], newRootId: null };
|
|
1051
|
+
const frameId = `box_${doc.seq}`;
|
|
1052
|
+
return {
|
|
1053
|
+
ops: [
|
|
1054
|
+
{ t: "insert_node", node: { id: frameId, type: "frame", parent: node.parent, order: node.order, props: {}, style: { base: { padding: { value: 16, unit: "px" } } } } },
|
|
1055
|
+
{ t: "move_node", id, parent: frameId, order: "a" }
|
|
1056
|
+
],
|
|
1057
|
+
newRootId: frameId
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function snapshotSubtree(doc, id) {
|
|
1061
|
+
if (!doc.nodes[id]) return null;
|
|
1062
|
+
const ids = subtreeBfs(doc.nodes, id);
|
|
1063
|
+
const nodes = {};
|
|
1064
|
+
for (const nid of ids) nodes[nid] = JSON.parse(JSON.stringify(doc.nodes[nid]));
|
|
1065
|
+
return { rootId: id, nodes };
|
|
1066
|
+
}
|
|
1067
|
+
var Z2 = 2147483e3;
|
|
712
1068
|
function EditablePage({
|
|
713
1069
|
doc,
|
|
714
1070
|
registry = defaultRegistry,
|
|
@@ -717,7 +1073,8 @@ function EditablePage({
|
|
|
717
1073
|
onNodeTransform,
|
|
718
1074
|
onSelect,
|
|
719
1075
|
frames = 1,
|
|
720
|
-
onProgress
|
|
1076
|
+
onProgress,
|
|
1077
|
+
pinned = true
|
|
721
1078
|
}) {
|
|
722
1079
|
const ed = useEditor(doc);
|
|
723
1080
|
const [editing, setEditing] = react.useState(false);
|
|
@@ -746,22 +1103,16 @@ function EditablePage({
|
|
|
746
1103
|
root.querySelectorAll("[data-cms]").forEach((el) => {
|
|
747
1104
|
const id = el.dataset.cms;
|
|
748
1105
|
const t = id ? transforms?.[id] : void 0;
|
|
749
|
-
|
|
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
|
-
}
|
|
1106
|
+
applyTransform(el, t);
|
|
756
1107
|
});
|
|
757
1108
|
}, [transforms, ed.state.doc]);
|
|
758
1109
|
react.useEffect(() => {
|
|
759
1110
|
const spacer = spacerRef.current;
|
|
760
1111
|
if (!spacer || !onProgress) return;
|
|
761
1112
|
const compute = () => {
|
|
762
|
-
const
|
|
1113
|
+
const len2 = spacer.offsetHeight - window.innerHeight;
|
|
763
1114
|
const top = spacer.getBoundingClientRect().top;
|
|
764
|
-
onProgress(
|
|
1115
|
+
onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
|
|
765
1116
|
};
|
|
766
1117
|
compute();
|
|
767
1118
|
window.addEventListener("scroll", compute, { passive: true });
|
|
@@ -786,7 +1137,7 @@ function EditablePage({
|
|
|
786
1137
|
window.removeEventListener("scroll", measureBox);
|
|
787
1138
|
window.removeEventListener("resize", measureBox);
|
|
788
1139
|
};
|
|
789
|
-
}, [measureBox, ed.state.doc
|
|
1140
|
+
}, [measureBox, ed.state.doc]);
|
|
790
1141
|
react.useEffect(() => {
|
|
791
1142
|
if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
|
|
792
1143
|
const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
@@ -811,17 +1162,96 @@ function EditablePage({
|
|
|
811
1162
|
ed.select(el.getAttribute("data-cms"));
|
|
812
1163
|
}
|
|
813
1164
|
};
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1165
|
+
const addNode = react.useCallback(
|
|
1166
|
+
(kind) => {
|
|
1167
|
+
const doc2 = ed.state.doc;
|
|
1168
|
+
const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
|
|
1169
|
+
const siblings = childrenOf(doc2, parent);
|
|
1170
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
1171
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1172
|
+
const def = ADD_DEFAULTS[kind];
|
|
1173
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
1174
|
+
ed.select(id);
|
|
1175
|
+
},
|
|
1176
|
+
[ed, selNode]
|
|
1177
|
+
);
|
|
822
1178
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
1179
|
+
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
1180
|
+
const removeSelected = () => {
|
|
1181
|
+
if (!selection) return;
|
|
1182
|
+
ed.apply({ t: "remove_node", id: selection });
|
|
1183
|
+
ed.select(null);
|
|
1184
|
+
};
|
|
1185
|
+
const clip = react.useRef(null);
|
|
1186
|
+
const runOps = (result) => {
|
|
1187
|
+
result.ops.forEach((op) => ed.apply(op));
|
|
1188
|
+
if (result.newRootId) ed.select(result.newRootId);
|
|
1189
|
+
};
|
|
1190
|
+
const duplicate = (id) => id && runOps(duplicateOps(ed.state.doc, id));
|
|
1191
|
+
const reorder = (id, dir) => id && reorderOps(ed.state.doc, id, dir).forEach((op) => ed.apply(op));
|
|
1192
|
+
const wrapInBox = (id) => id && runOps(wrapInBoxOps(ed.state.doc, id));
|
|
1193
|
+
const copy = (id) => {
|
|
1194
|
+
if (id) clip.current = snapshotSubtree(ed.state.doc, id);
|
|
1195
|
+
};
|
|
1196
|
+
const paste = (target) => {
|
|
1197
|
+
if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
|
|
1198
|
+
};
|
|
1199
|
+
const startEditText = (id) => {
|
|
1200
|
+
if (!id) return;
|
|
1201
|
+
ed.select(id);
|
|
1202
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
|
|
1203
|
+
};
|
|
1204
|
+
react.useEffect(() => {
|
|
1205
|
+
if (!editing) return;
|
|
1206
|
+
const onKey = (e) => {
|
|
1207
|
+
const t = e.target;
|
|
1208
|
+
const typing = t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
|
|
1209
|
+
if (e.key === "Escape") return void ed.select(null);
|
|
1210
|
+
if (typing) return;
|
|
1211
|
+
if ((e.key === "Delete" || e.key === "Backspace") && selection) {
|
|
1212
|
+
e.preventDefault();
|
|
1213
|
+
removeSelected();
|
|
1214
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "d" && selection) {
|
|
1215
|
+
e.preventDefault();
|
|
1216
|
+
duplicate(selection);
|
|
1217
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selection) {
|
|
1218
|
+
copy(selection);
|
|
1219
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
|
|
1220
|
+
e.preventDefault();
|
|
1221
|
+
paste(selection);
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
window.addEventListener("keydown", onKey);
|
|
1225
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1226
|
+
}, [editing, selection]);
|
|
1227
|
+
const onContextMenuSelect = (e) => {
|
|
1228
|
+
if (!editing) return;
|
|
1229
|
+
const el = e.target.closest("[data-cms]");
|
|
1230
|
+
if (el) ed.select(el.getAttribute("data-cms"));
|
|
1231
|
+
else ed.select(null);
|
|
1232
|
+
};
|
|
1233
|
+
const page = /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) });
|
|
1234
|
+
const pageShell = 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;
|
|
823
1235
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
824
|
-
|
|
1236
|
+
editing ? /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
|
|
1237
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { children: pageShell }),
|
|
1238
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Content, { className: "!z-[2147483646]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1239
|
+
EditMenuItems,
|
|
1240
|
+
{
|
|
1241
|
+
node: selNode ?? null,
|
|
1242
|
+
hasClipboard: Boolean(clip.current),
|
|
1243
|
+
isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
|
|
1244
|
+
onEditText: () => startEditText(selection),
|
|
1245
|
+
onAdd: addNode,
|
|
1246
|
+
onDuplicate: () => duplicate(selection),
|
|
1247
|
+
onCopy: () => copy(selection),
|
|
1248
|
+
onPaste: () => paste(selection),
|
|
1249
|
+
onWrap: () => wrapInBox(selection),
|
|
1250
|
+
onReorder: (d) => reorder(selection, d),
|
|
1251
|
+
onDelete: removeSelected
|
|
1252
|
+
}
|
|
1253
|
+
) })
|
|
1254
|
+
] }) : pageShell,
|
|
825
1255
|
editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
826
1256
|
SelectionOverlay,
|
|
827
1257
|
{
|
|
@@ -833,7 +1263,20 @@ function EditablePage({
|
|
|
833
1263
|
onLive: measureBox
|
|
834
1264
|
}
|
|
835
1265
|
) : null,
|
|
836
|
-
editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
|
|
1266
|
+
editing && box && selNode ? /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbar, { box, isText: selNode.type === "text" || selNode.type === "heading", base: selNode.style.base, setStyle }) : null,
|
|
1267
|
+
editing && selNode && box ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1268
|
+
NodeInspector,
|
|
1269
|
+
{
|
|
1270
|
+
node: selNode,
|
|
1271
|
+
transform: (selection ? transforms?.[selection] : void 0) ?? {},
|
|
1272
|
+
measured: { w: box.w, h: box.h },
|
|
1273
|
+
onProps: setProps,
|
|
1274
|
+
onStyle: setStyle,
|
|
1275
|
+
onTransform: (t) => selection && onNodeTransform?.(selection, t),
|
|
1276
|
+
onRemove: removeSelected,
|
|
1277
|
+
onClose: () => ed.select(null)
|
|
1278
|
+
}
|
|
1279
|
+
) : null,
|
|
837
1280
|
revealed || editing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
838
1281
|
EditBar,
|
|
839
1282
|
{
|
|
@@ -846,12 +1289,30 @@ function EditablePage({
|
|
|
846
1289
|
canRedo: ed.canRedo,
|
|
847
1290
|
onUndo: ed.undo,
|
|
848
1291
|
onRedo: ed.redo,
|
|
849
|
-
|
|
1292
|
+
onAdd: addNode
|
|
850
1293
|
}
|
|
851
1294
|
) : null,
|
|
852
|
-
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex:
|
|
1295
|
+
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
853
1296
|
] });
|
|
854
1297
|
}
|
|
1298
|
+
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
|
|
1299
|
+
var ADD_DEFAULTS = {
|
|
1300
|
+
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
1301
|
+
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
1302
|
+
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
1303
|
+
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
1304
|
+
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
|
|
1305
|
+
};
|
|
1306
|
+
var GRIPS = [
|
|
1307
|
+
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
1308
|
+
{ key: "n", north: true, cursor: "ns-resize" },
|
|
1309
|
+
{ key: "ne", north: true, east: true, cursor: "nesw-resize" },
|
|
1310
|
+
{ key: "e", east: true, cursor: "ew-resize" },
|
|
1311
|
+
{ key: "se", south: true, east: true, cursor: "nwse-resize" },
|
|
1312
|
+
{ key: "s", south: true, cursor: "ns-resize" },
|
|
1313
|
+
{ key: "sw", south: true, west: true, cursor: "nesw-resize" },
|
|
1314
|
+
{ key: "w", west: true, cursor: "ew-resize" }
|
|
1315
|
+
];
|
|
855
1316
|
function SelectionOverlay({
|
|
856
1317
|
box,
|
|
857
1318
|
base,
|
|
@@ -863,29 +1324,48 @@ function SelectionOverlay({
|
|
|
863
1324
|
const drag = react.useRef(null);
|
|
864
1325
|
const apply = (t) => {
|
|
865
1326
|
const el = getEl();
|
|
866
|
-
if (el)
|
|
867
|
-
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
|
|
868
|
-
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
869
|
-
}
|
|
1327
|
+
if (el) applyTransform(el, t);
|
|
870
1328
|
};
|
|
871
|
-
const
|
|
1329
|
+
const startMove = (e) => {
|
|
872
1330
|
e.stopPropagation();
|
|
873
1331
|
e.preventDefault();
|
|
874
1332
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
875
|
-
drag.current = {
|
|
1333
|
+
drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
|
|
876
1334
|
};
|
|
877
|
-
const
|
|
1335
|
+
const moveMove = (e) => {
|
|
878
1336
|
const d = drag.current;
|
|
879
1337
|
if (!d) return;
|
|
880
|
-
|
|
881
|
-
d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
|
|
882
|
-
} else {
|
|
883
|
-
const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
|
|
884
|
-
d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
|
|
885
|
-
}
|
|
1338
|
+
d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
|
|
886
1339
|
apply(d.live);
|
|
887
1340
|
onLive();
|
|
888
1341
|
};
|
|
1342
|
+
const startResize = (g) => (e) => {
|
|
1343
|
+
e.stopPropagation();
|
|
1344
|
+
e.preventDefault();
|
|
1345
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1346
|
+
drag.current = { x: e.clientX, y: e.clientY, w: base.w ?? box.w, h: base.h ?? box.h, live: { ...base } };
|
|
1347
|
+
e.currentTarget.dataset.grip = g.key;
|
|
1348
|
+
};
|
|
1349
|
+
const resizeMove = (g) => (e) => {
|
|
1350
|
+
const d = drag.current;
|
|
1351
|
+
if (!d) return;
|
|
1352
|
+
const dx = e.clientX - d.x;
|
|
1353
|
+
const dy = e.clientY - d.y;
|
|
1354
|
+
const live = { ...base };
|
|
1355
|
+
if (g.east) live.w = Math.max(16, Math.round(d.w + dx));
|
|
1356
|
+
if (g.west) {
|
|
1357
|
+
live.w = Math.max(16, Math.round(d.w - dx));
|
|
1358
|
+
live.x = (base.x ?? 0) + (d.w - live.w);
|
|
1359
|
+
}
|
|
1360
|
+
if (g.south) live.h = Math.max(16, Math.round(d.h + dy));
|
|
1361
|
+
if (g.north) {
|
|
1362
|
+
live.h = Math.max(16, Math.round(d.h - dy));
|
|
1363
|
+
live.y = (base.y ?? 0) + (d.h - live.h);
|
|
1364
|
+
}
|
|
1365
|
+
d.live = live;
|
|
1366
|
+
apply(live);
|
|
1367
|
+
onLive();
|
|
1368
|
+
};
|
|
889
1369
|
const end = () => {
|
|
890
1370
|
const d = drag.current;
|
|
891
1371
|
if (d) onTransform(d.live);
|
|
@@ -900,17 +1380,23 @@ function SelectionOverlay({
|
|
|
900
1380
|
outline: "2px solid #8b5cf6",
|
|
901
1381
|
borderRadius: 4,
|
|
902
1382
|
pointerEvents: "none",
|
|
903
|
-
zIndex:
|
|
1383
|
+
zIndex: Z2
|
|
904
1384
|
};
|
|
905
1385
|
const handle = {
|
|
906
1386
|
position: "fixed",
|
|
907
|
-
width:
|
|
908
|
-
height:
|
|
909
|
-
background: "#
|
|
910
|
-
border: "2px solid #
|
|
911
|
-
borderRadius:
|
|
912
|
-
zIndex:
|
|
913
|
-
touchAction: "none"
|
|
1387
|
+
width: 12,
|
|
1388
|
+
height: 12,
|
|
1389
|
+
background: "#fff",
|
|
1390
|
+
border: "2px solid #8b5cf6",
|
|
1391
|
+
borderRadius: 3,
|
|
1392
|
+
zIndex: Z2 + 1,
|
|
1393
|
+
touchAction: "none",
|
|
1394
|
+
boxSizing: "border-box"
|
|
1395
|
+
};
|
|
1396
|
+
const gripPos = (g) => {
|
|
1397
|
+
const cx = g.west ? box.x : g.east ? box.x + box.w : box.x + box.w / 2;
|
|
1398
|
+
const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
|
|
1399
|
+
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
914
1400
|
};
|
|
915
1401
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
916
1402
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
@@ -919,22 +1405,23 @@ function SelectionOverlay({
|
|
|
919
1405
|
"div",
|
|
920
1406
|
{
|
|
921
1407
|
title: "Drag to move",
|
|
922
|
-
onPointerDown:
|
|
923
|
-
onPointerMove:
|
|
1408
|
+
onPointerDown: startMove,
|
|
1409
|
+
onPointerMove: moveMove,
|
|
924
1410
|
onPointerUp: end,
|
|
925
|
-
style: { ...handle, left: box.x -
|
|
1411
|
+
style: { ...handle, width: 18, height: 18, borderRadius: 9, background: "#8b5cf6", border: "2px solid #fff", left: box.x - 22, top: box.y - 22, cursor: "move" }
|
|
926
1412
|
}
|
|
927
1413
|
),
|
|
928
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1414
|
+
GRIPS.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
929
1415
|
"div",
|
|
930
1416
|
{
|
|
931
1417
|
title: "Drag to resize",
|
|
932
|
-
onPointerDown:
|
|
933
|
-
onPointerMove:
|
|
1418
|
+
onPointerDown: startResize(g),
|
|
1419
|
+
onPointerMove: resizeMove(g),
|
|
934
1420
|
onPointerUp: end,
|
|
935
|
-
style: { ...handle,
|
|
936
|
-
}
|
|
937
|
-
|
|
1421
|
+
style: { ...handle, ...gripPos(g) }
|
|
1422
|
+
},
|
|
1423
|
+
g.key
|
|
1424
|
+
))
|
|
938
1425
|
] }) : null
|
|
939
1426
|
] });
|
|
940
1427
|
}
|
|
@@ -957,7 +1444,7 @@ function FloatingToolbar({
|
|
|
957
1444
|
border: "1px solid rgba(255,255,255,0.12)",
|
|
958
1445
|
borderRadius: 999,
|
|
959
1446
|
boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
|
|
960
|
-
zIndex:
|
|
1447
|
+
zIndex: Z2 + 2,
|
|
961
1448
|
fontFamily: "system-ui, sans-serif",
|
|
962
1449
|
fontSize: 12
|
|
963
1450
|
};
|
|
@@ -975,6 +1462,13 @@ function FloatingToolbar({
|
|
|
975
1462
|
] })
|
|
976
1463
|
] });
|
|
977
1464
|
}
|
|
1465
|
+
var ADD_MENU = [
|
|
1466
|
+
{ kind: "text", label: "Text" },
|
|
1467
|
+
{ kind: "heading", label: "Heading" },
|
|
1468
|
+
{ kind: "button", label: "Button" },
|
|
1469
|
+
{ kind: "image", label: "Image" },
|
|
1470
|
+
{ kind: "box", label: "Box" }
|
|
1471
|
+
];
|
|
978
1472
|
function EditBar({
|
|
979
1473
|
editing,
|
|
980
1474
|
onToggle,
|
|
@@ -982,19 +1476,87 @@ function EditBar({
|
|
|
982
1476
|
canRedo,
|
|
983
1477
|
onUndo,
|
|
984
1478
|
onRedo,
|
|
985
|
-
|
|
1479
|
+
onAdd
|
|
986
1480
|
}) {
|
|
987
|
-
const
|
|
1481
|
+
const [open, setOpen] = react.useState(false);
|
|
1482
|
+
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 };
|
|
988
1483
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
989
1484
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bar, children: [
|
|
990
1485
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
991
1486
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
992
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1487
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1488
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
|
|
1489
|
+
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(
|
|
1490
|
+
"button",
|
|
1491
|
+
{
|
|
1492
|
+
type: "button",
|
|
1493
|
+
style: { ...btn, textAlign: "left", borderRadius: 6, padding: "6px 10px" },
|
|
1494
|
+
onClick: () => {
|
|
1495
|
+
onAdd(m.kind);
|
|
1496
|
+
setOpen(false);
|
|
1497
|
+
},
|
|
1498
|
+
children: m.label
|
|
1499
|
+
},
|
|
1500
|
+
m.kind
|
|
1501
|
+
)) }) : null
|
|
1502
|
+
] }),
|
|
993
1503
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
|
|
994
1504
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
|
|
995
1505
|
] }) : null
|
|
996
1506
|
] });
|
|
997
1507
|
}
|
|
1508
|
+
function EditMenuItems({
|
|
1509
|
+
node,
|
|
1510
|
+
hasClipboard,
|
|
1511
|
+
isContainer,
|
|
1512
|
+
onEditText,
|
|
1513
|
+
onAdd,
|
|
1514
|
+
onDuplicate,
|
|
1515
|
+
onCopy,
|
|
1516
|
+
onPaste,
|
|
1517
|
+
onWrap,
|
|
1518
|
+
onReorder,
|
|
1519
|
+
onDelete
|
|
1520
|
+
}) {
|
|
1521
|
+
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1522
|
+
const nested = Boolean(node && node.parent !== null);
|
|
1523
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1524
|
+
editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1525
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
|
|
1526
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1527
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubContent, { className: "!z-[2147483647]", children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onAdd(m.kind), children: m.label }, m.kind)) })
|
|
1528
|
+
] }),
|
|
1529
|
+
node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1530
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1531
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1532
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
1533
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" }),
|
|
1534
|
+
nested ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onWrap, children: "Wrap in Box" }) : null,
|
|
1535
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1536
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1537
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1538
|
+
nested ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1539
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1540
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1541
|
+
] }) : null,
|
|
1542
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1543
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { danger: true, onClick: onDelete, children: "Delete" })
|
|
1544
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" })
|
|
1545
|
+
] });
|
|
1546
|
+
}
|
|
1547
|
+
function applyTransform(el, t) {
|
|
1548
|
+
if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
|
|
1549
|
+
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
|
|
1550
|
+
el.style.opacity = t.opacity != null ? String(t.opacity) : "";
|
|
1551
|
+
el.style.width = t.w != null ? `${t.w}px` : "";
|
|
1552
|
+
el.style.height = t.h != null ? `${t.h}px` : "";
|
|
1553
|
+
} else {
|
|
1554
|
+
el.style.transform = "";
|
|
1555
|
+
el.style.opacity = "";
|
|
1556
|
+
el.style.width = "";
|
|
1557
|
+
el.style.height = "";
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
998
1560
|
function normalizeColor(v) {
|
|
999
1561
|
return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
1000
1562
|
}
|
|
@@ -1007,6 +1569,7 @@ exports.EditablePage = EditablePage;
|
|
|
1007
1569
|
exports.Editor = Editor;
|
|
1008
1570
|
exports.Inspector = Inspector;
|
|
1009
1571
|
exports.LayersPanel = LayersPanel;
|
|
1572
|
+
exports.NodeInspector = NodeInspector;
|
|
1010
1573
|
exports.editorReduce = editorReduce;
|
|
1011
1574
|
exports.initEditor = initEditor;
|
|
1012
1575
|
exports.useEditor = useEditor;
|