@particle-academy/fancy-cms-ui 0.0.1-dev.7 → 0.0.1-dev.9
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-HYHNMW47.js → chunk-3YZMELJJ.js} +11 -2
- package/dist/chunk-3YZMELJJ.js.map +1 -0
- package/dist/{chunk-5O6U4VIU.js → chunk-ZV7CV54P.js} +3 -3
- package/dist/{chunk-5O6U4VIU.js.map → chunk-ZV7CV54P.js.map} +1 -1
- package/dist/editor.cjs +368 -32
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +9 -5
- package/dist/editor.d.ts +9 -5
- package/dist/editor.js +321 -22
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{ops-C_s0FGeV.d.cts → ops-BsM6kZxY.d.ts} +5 -1
- package/dist/{ops-nevYkDzK.d.ts → ops-CdBZarAT.d.cts} +5 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +3 -3
- package/package.json +2 -1
- package/dist/chunk-HYHNMW47.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
|
|
|
@@ -97,6 +98,10 @@ function reduce(doc, op, opts = {}) {
|
|
|
97
98
|
props: { ...doc.nodes[op.id].props, ...op.patch }
|
|
98
99
|
});
|
|
99
100
|
}
|
|
101
|
+
case "set_layout": {
|
|
102
|
+
if (!doc.nodes[op.id]) return invalid("node not found");
|
|
103
|
+
return patchNode(doc, op.id, { layout: op.layout });
|
|
104
|
+
}
|
|
100
105
|
case "set_style": {
|
|
101
106
|
const node = doc.nodes[op.id];
|
|
102
107
|
if (!node) return invalid("node not found");
|
|
@@ -512,31 +517,110 @@ function Canvas({ doc, selection, onSelect, apply }) {
|
|
|
512
517
|
function cssEscape(value) {
|
|
513
518
|
return value.replace(/["\\]/g, "\\$&");
|
|
514
519
|
}
|
|
515
|
-
|
|
516
|
-
|
|
520
|
+
|
|
521
|
+
// src/document/fractional.ts
|
|
522
|
+
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
523
|
+
var RADIX = BASE.length;
|
|
524
|
+
var MID = Math.floor(RADIX / 2);
|
|
525
|
+
function val(c) {
|
|
526
|
+
const i = BASE.indexOf(c);
|
|
527
|
+
return i < 0 ? 0 : i;
|
|
528
|
+
}
|
|
529
|
+
function keyBetween(a, b) {
|
|
530
|
+
if (a === null) a = "";
|
|
531
|
+
if (b === null) {
|
|
532
|
+
return a + BASE[MID];
|
|
533
|
+
}
|
|
534
|
+
if (a >= b) {
|
|
535
|
+
throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
|
|
536
|
+
}
|
|
537
|
+
let result = "";
|
|
538
|
+
let i = 0;
|
|
539
|
+
for (; ; ) {
|
|
540
|
+
const ca = i < a.length ? val(a[i]) : 0;
|
|
541
|
+
const cb = i < b.length ? val(b[i]) : RADIX;
|
|
542
|
+
if (cb - ca > 1) {
|
|
543
|
+
result += BASE[ca + Math.floor((cb - ca) / 2)];
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
546
|
+
if (ca === cb) {
|
|
547
|
+
result += BASE[ca];
|
|
548
|
+
i++;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
result += BASE[ca];
|
|
552
|
+
i++;
|
|
553
|
+
for (; ; ) {
|
|
554
|
+
const da = i < a.length ? val(a[i]) : 0;
|
|
555
|
+
if (RADIX - da > 1) {
|
|
556
|
+
result += BASE[da + Math.floor((RADIX - da) / 2)];
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
result += BASE[da];
|
|
560
|
+
i++;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
var MIME = "application/x-cms-node";
|
|
565
|
+
function isAncestorOrSelf(doc, id, maybeAncestor) {
|
|
566
|
+
for (let cur = id; cur; cur = doc.nodes[cur]?.parent ?? null) {
|
|
567
|
+
if (cur === maybeAncestor) return true;
|
|
568
|
+
}
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
function LayersPanel({ doc, selection, onSelect, onMove }) {
|
|
572
|
+
const [overId, setOverId] = react.useState(null);
|
|
573
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id, depth: 0, selection, onSelect, onMove, overId, setOverId }, id)) });
|
|
517
574
|
}
|
|
518
|
-
function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
575
|
+
function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
|
|
519
576
|
const node = doc.nodes[id];
|
|
520
577
|
if (!node) return null;
|
|
521
578
|
const selected = selection === id;
|
|
579
|
+
const draggable = Boolean(onMove);
|
|
580
|
+
const drop = (e) => {
|
|
581
|
+
e.preventDefault();
|
|
582
|
+
e.stopPropagation();
|
|
583
|
+
setOverId(null);
|
|
584
|
+
if (!onMove) return;
|
|
585
|
+
const dragId = e.dataTransfer.getData(MIME);
|
|
586
|
+
if (!dragId || dragId === id || isAncestorOrSelf(doc, id, dragId)) return;
|
|
587
|
+
const siblings = childrenOf(doc, node.parent);
|
|
588
|
+
const idx = siblings.findIndex((n) => n.id === id);
|
|
589
|
+
const next = siblings[idx + 1];
|
|
590
|
+
onMove(dragId, node.parent, keyBetween(node.order, next ? next.order : null));
|
|
591
|
+
};
|
|
522
592
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
523
593
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
524
594
|
"button",
|
|
525
595
|
{
|
|
526
596
|
type: "button",
|
|
597
|
+
draggable,
|
|
527
598
|
onClick: () => onSelect(id),
|
|
599
|
+
onDragStart: (e) => {
|
|
600
|
+
e.dataTransfer.setData(MIME, id);
|
|
601
|
+
e.dataTransfer.effectAllowed = "move";
|
|
602
|
+
},
|
|
603
|
+
onDragOver: (e) => {
|
|
604
|
+
if (!onMove) return;
|
|
605
|
+
e.preventDefault();
|
|
606
|
+
e.dataTransfer.dropEffect = "move";
|
|
607
|
+
if (overId !== id) setOverId(id);
|
|
608
|
+
},
|
|
609
|
+
onDragLeave: () => overId === id && setOverId(null),
|
|
610
|
+
onDrop: drop,
|
|
528
611
|
style: {
|
|
529
612
|
display: "block",
|
|
530
613
|
width: "100%",
|
|
531
614
|
textAlign: "left",
|
|
532
615
|
border: "none",
|
|
533
616
|
borderRadius: 6,
|
|
534
|
-
cursor: "pointer",
|
|
617
|
+
cursor: draggable ? "grab" : "pointer",
|
|
535
618
|
padding: "4px 8px",
|
|
536
619
|
paddingLeft: 8 + depth * 14,
|
|
537
620
|
background: selected ? "#ede9fe" : "transparent",
|
|
538
621
|
color: selected ? "#5b21b6" : "#334155",
|
|
539
|
-
font: "inherit"
|
|
622
|
+
font: "inherit",
|
|
623
|
+
boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
|
|
540
624
|
},
|
|
541
625
|
children: [
|
|
542
626
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: node.type }),
|
|
@@ -545,17 +629,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
|
545
629
|
]
|
|
546
630
|
}
|
|
547
631
|
),
|
|
548
|
-
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
549
|
-
LayerRow,
|
|
550
|
-
{
|
|
551
|
-
doc,
|
|
552
|
-
id: child.id,
|
|
553
|
-
depth: depth + 1,
|
|
554
|
-
selection,
|
|
555
|
-
onSelect
|
|
556
|
-
},
|
|
557
|
-
child.id
|
|
558
|
-
))
|
|
632
|
+
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
|
|
559
633
|
] });
|
|
560
634
|
}
|
|
561
635
|
var label = {
|
|
@@ -687,7 +761,15 @@ function Editor({ defaultValue, onChange }) {
|
|
|
687
761
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shell, children: [
|
|
688
762
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
689
763
|
/* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
|
|
690
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
765
|
+
LayersPanel,
|
|
766
|
+
{
|
|
767
|
+
doc: ed.state.doc,
|
|
768
|
+
selection: ed.state.selection,
|
|
769
|
+
onSelect: ed.select,
|
|
770
|
+
onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
|
|
771
|
+
}
|
|
772
|
+
)
|
|
691
773
|
] }),
|
|
692
774
|
/* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
|
|
693
775
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
|
|
@@ -720,17 +802,6 @@ function Toolbar({ ed }) {
|
|
|
720
802
|
}
|
|
721
803
|
);
|
|
722
804
|
}
|
|
723
|
-
|
|
724
|
-
// src/document/fractional.ts
|
|
725
|
-
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
726
|
-
var RADIX = BASE.length;
|
|
727
|
-
var MID = Math.floor(RADIX / 2);
|
|
728
|
-
function keyBetween(a, b) {
|
|
729
|
-
if (a === null) a = "";
|
|
730
|
-
{
|
|
731
|
-
return a + BASE[MID];
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
805
|
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
|
|
735
806
|
function NodeInspector({
|
|
736
807
|
node,
|
|
@@ -738,6 +809,7 @@ function NodeInspector({
|
|
|
738
809
|
measured,
|
|
739
810
|
onProps,
|
|
740
811
|
onStyle,
|
|
812
|
+
onLayout,
|
|
741
813
|
onTransform,
|
|
742
814
|
onRemove,
|
|
743
815
|
onClose
|
|
@@ -820,9 +892,44 @@ function NodeInspector({
|
|
|
820
892
|
] }),
|
|
821
893
|
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
822
894
|
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
|
|
895
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
|
|
896
|
+
] }),
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
|
|
898
|
+
] }),
|
|
899
|
+
CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Layout", children: [
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
901
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
902
|
+
Sel,
|
|
903
|
+
{
|
|
904
|
+
label: "Mode",
|
|
905
|
+
value: node.layout ?? "free",
|
|
906
|
+
options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
|
|
907
|
+
onChange: (v) => onLayout?.(v)
|
|
908
|
+
}
|
|
909
|
+
),
|
|
823
910
|
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
|
|
911
|
+
] }),
|
|
912
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
913
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
914
|
+
Sel,
|
|
915
|
+
{
|
|
916
|
+
label: "Align",
|
|
917
|
+
value: base.align ?? "",
|
|
918
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
|
|
919
|
+
onChange: (v) => onStyle({ align: v || void 0 })
|
|
920
|
+
}
|
|
921
|
+
),
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
923
|
+
Sel,
|
|
924
|
+
{
|
|
925
|
+
label: "Justify",
|
|
926
|
+
value: base.justify ?? "",
|
|
927
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
|
|
928
|
+
onChange: (v) => onStyle({ justify: v || void 0 })
|
|
929
|
+
}
|
|
930
|
+
)
|
|
824
931
|
] })
|
|
825
|
-
] })
|
|
932
|
+
] }) : null
|
|
826
933
|
] })
|
|
827
934
|
] });
|
|
828
935
|
}
|
|
@@ -877,6 +984,12 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
|
877
984
|
)
|
|
878
985
|
] });
|
|
879
986
|
}
|
|
987
|
+
function Txt({ label: label2, value, placeholder, onChange }) {
|
|
988
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
989
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
|
|
991
|
+
] });
|
|
992
|
+
}
|
|
880
993
|
function Color({ label: label2, value, onChange }) {
|
|
881
994
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
882
995
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
@@ -926,6 +1039,110 @@ var input2 = {
|
|
|
926
1039
|
};
|
|
927
1040
|
var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
|
|
928
1041
|
var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
|
|
1042
|
+
|
|
1043
|
+
// src/editor/editorOps.ts
|
|
1044
|
+
function childrenOfMap(nodes, parent) {
|
|
1045
|
+
return Object.values(nodes).filter((n) => n.parent === parent).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
|
|
1046
|
+
}
|
|
1047
|
+
function subtreeBfs(nodes, root) {
|
|
1048
|
+
const out = [];
|
|
1049
|
+
const queue = [root];
|
|
1050
|
+
while (queue.length) {
|
|
1051
|
+
const cur = queue.shift();
|
|
1052
|
+
out.push(cur);
|
|
1053
|
+
for (const child of childrenOfMap(nodes, cur)) queue.push(child.id);
|
|
1054
|
+
}
|
|
1055
|
+
return out;
|
|
1056
|
+
}
|
|
1057
|
+
function cloneFrom(nodes, rootId, newParent, newOrder, seqBase) {
|
|
1058
|
+
if (!nodes[rootId]) return { ops: [], newRootId: null };
|
|
1059
|
+
const order = subtreeBfs(nodes, rootId);
|
|
1060
|
+
const idMap = {};
|
|
1061
|
+
order.forEach((old, i) => idMap[old] = `${old}_c${seqBase}_${i}`);
|
|
1062
|
+
const ops = order.map((old) => {
|
|
1063
|
+
const src = nodes[old];
|
|
1064
|
+
const cloned = JSON.parse(JSON.stringify(src));
|
|
1065
|
+
cloned.id = idMap[old];
|
|
1066
|
+
cloned.parent = old === rootId ? newParent : idMap[src.parent];
|
|
1067
|
+
if (old === rootId) cloned.order = newOrder;
|
|
1068
|
+
return { t: "insert_node", node: cloned };
|
|
1069
|
+
});
|
|
1070
|
+
return { ops, newRootId: idMap[rootId] };
|
|
1071
|
+
}
|
|
1072
|
+
function duplicateOps(doc, id) {
|
|
1073
|
+
const node = doc.nodes[id];
|
|
1074
|
+
if (!node) return { ops: [], newRootId: null };
|
|
1075
|
+
const sibs = childrenOf(doc, node.parent);
|
|
1076
|
+
const idx = sibs.findIndex((s) => s.id === id);
|
|
1077
|
+
const newOrder = keyBetween(node.order, sibs[idx + 1]?.order ?? null);
|
|
1078
|
+
const { ops, newRootId } = cloneFrom(doc.nodes, id, node.parent, newOrder, doc.seq);
|
|
1079
|
+
if (node.parent === null && newRootId) {
|
|
1080
|
+
const base = doc.sections;
|
|
1081
|
+
const at = base.indexOf(id);
|
|
1082
|
+
ops.push({ t: "reorder_sections", order: [...base.slice(0, at + 1), newRootId, ...base.slice(at + 1)] });
|
|
1083
|
+
}
|
|
1084
|
+
return { ops, newRootId };
|
|
1085
|
+
}
|
|
1086
|
+
function pasteOps(doc, clip, target, containerTypes) {
|
|
1087
|
+
const sel = target ? doc.nodes[target] : null;
|
|
1088
|
+
const parent = sel ? containerTypes.has(sel.type) ? sel.id : sel.parent : doc.sections[doc.sections.length - 1] ?? null;
|
|
1089
|
+
const sibs = childrenOf(doc, parent);
|
|
1090
|
+
const newOrder = keyBetween(sibs.length ? sibs[sibs.length - 1].order : null, null);
|
|
1091
|
+
const { ops, newRootId } = cloneFrom(clip.nodes, clip.rootId, parent, newOrder, doc.seq);
|
|
1092
|
+
return { ops, newRootId };
|
|
1093
|
+
}
|
|
1094
|
+
function reorderOps(doc, id, dir) {
|
|
1095
|
+
const node = doc.nodes[id];
|
|
1096
|
+
if (!node) return [];
|
|
1097
|
+
if (node.parent === null) {
|
|
1098
|
+
const s = [...doc.sections];
|
|
1099
|
+
const i = s.indexOf(id);
|
|
1100
|
+
if (i < 0) return [];
|
|
1101
|
+
s.splice(i, 1);
|
|
1102
|
+
if (dir === "front") s.unshift(id);
|
|
1103
|
+
else if (dir === "back") s.push(id);
|
|
1104
|
+
else s.splice(Math.max(0, Math.min(s.length, dir === "up" ? i - 1 : i + 1)), 0, id);
|
|
1105
|
+
return [{ t: "reorder_sections", order: s }];
|
|
1106
|
+
}
|
|
1107
|
+
const sibs = childrenOf(doc, node.parent);
|
|
1108
|
+
const idx = sibs.findIndex((sb) => sb.id === id);
|
|
1109
|
+
if (idx < 0) return [];
|
|
1110
|
+
const last = sibs.length - 1;
|
|
1111
|
+
let newOrder;
|
|
1112
|
+
if (dir === "up") {
|
|
1113
|
+
if (idx <= 0) return [];
|
|
1114
|
+
newOrder = keyBetween(sibs[idx - 2]?.order ?? null, sibs[idx - 1].order);
|
|
1115
|
+
} else if (dir === "down") {
|
|
1116
|
+
if (idx >= last) return [];
|
|
1117
|
+
newOrder = keyBetween(sibs[idx + 1].order, sibs[idx + 2]?.order ?? null);
|
|
1118
|
+
} else if (dir === "front") {
|
|
1119
|
+
if (idx >= last) return [];
|
|
1120
|
+
newOrder = keyBetween(sibs[last].order, null);
|
|
1121
|
+
} else {
|
|
1122
|
+
if (idx <= 0) return [];
|
|
1123
|
+
newOrder = keyBetween(null, sibs[0].order);
|
|
1124
|
+
}
|
|
1125
|
+
return [{ t: "move_node", id, parent: node.parent, order: newOrder }];
|
|
1126
|
+
}
|
|
1127
|
+
function wrapInBoxOps(doc, id) {
|
|
1128
|
+
const node = doc.nodes[id];
|
|
1129
|
+
if (!node || node.parent === null) return { ops: [], newRootId: null };
|
|
1130
|
+
const frameId = `box_${doc.seq}`;
|
|
1131
|
+
return {
|
|
1132
|
+
ops: [
|
|
1133
|
+
{ t: "insert_node", node: { id: frameId, type: "frame", parent: node.parent, order: node.order, props: {}, style: { base: { padding: { value: 16, unit: "px" } } } } },
|
|
1134
|
+
{ t: "move_node", id, parent: frameId, order: "a" }
|
|
1135
|
+
],
|
|
1136
|
+
newRootId: frameId
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function snapshotSubtree(doc, id) {
|
|
1140
|
+
if (!doc.nodes[id]) return null;
|
|
1141
|
+
const ids = subtreeBfs(doc.nodes, id);
|
|
1142
|
+
const nodes = {};
|
|
1143
|
+
for (const nid of ids) nodes[nid] = JSON.parse(JSON.stringify(doc.nodes[nid]));
|
|
1144
|
+
return { rootId: id, nodes };
|
|
1145
|
+
}
|
|
929
1146
|
var Z2 = 2147483e3;
|
|
930
1147
|
function EditablePage({
|
|
931
1148
|
doc,
|
|
@@ -1029,7 +1246,7 @@ function EditablePage({
|
|
|
1029
1246
|
const doc2 = ed.state.doc;
|
|
1030
1247
|
const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
|
|
1031
1248
|
const siblings = childrenOf(doc2, parent);
|
|
1032
|
-
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null);
|
|
1249
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
1033
1250
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1034
1251
|
const def = ADD_DEFAULTS[kind];
|
|
1035
1252
|
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
@@ -1039,14 +1256,93 @@ function EditablePage({
|
|
|
1039
1256
|
);
|
|
1040
1257
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
1041
1258
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
1259
|
+
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
1042
1260
|
const removeSelected = () => {
|
|
1043
1261
|
if (!selection) return;
|
|
1044
1262
|
ed.apply({ t: "remove_node", id: selection });
|
|
1045
1263
|
ed.select(null);
|
|
1046
1264
|
};
|
|
1047
|
-
const
|
|
1265
|
+
const clip = react.useRef(null);
|
|
1266
|
+
const runOps = (result) => {
|
|
1267
|
+
result.ops.forEach((op) => ed.apply(op));
|
|
1268
|
+
if (result.newRootId) ed.select(result.newRootId);
|
|
1269
|
+
};
|
|
1270
|
+
const duplicate = (id) => id && runOps(duplicateOps(ed.state.doc, id));
|
|
1271
|
+
const reorder = (id, dir) => id && reorderOps(ed.state.doc, id, dir).forEach((op) => ed.apply(op));
|
|
1272
|
+
const wrapInBox = (id) => id && runOps(wrapInBoxOps(ed.state.doc, id));
|
|
1273
|
+
const copy = (id) => {
|
|
1274
|
+
if (id) clip.current = snapshotSubtree(ed.state.doc, id);
|
|
1275
|
+
};
|
|
1276
|
+
const paste = (target) => {
|
|
1277
|
+
if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
|
|
1278
|
+
};
|
|
1279
|
+
const startEditText = (id) => {
|
|
1280
|
+
if (!id) return;
|
|
1281
|
+
ed.select(id);
|
|
1282
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
|
|
1283
|
+
};
|
|
1284
|
+
const nudgeRef = react.useRef({ transforms, onNodeTransform });
|
|
1285
|
+
nudgeRef.current = { transforms, onNodeTransform };
|
|
1286
|
+
react.useEffect(() => {
|
|
1287
|
+
if (!editing) return;
|
|
1288
|
+
const onKey = (e) => {
|
|
1289
|
+
const t = e.target;
|
|
1290
|
+
const typing = t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
|
|
1291
|
+
if (e.key === "Escape") return void ed.select(null);
|
|
1292
|
+
if (typing) return;
|
|
1293
|
+
if ((e.key === "Delete" || e.key === "Backspace") && selection) {
|
|
1294
|
+
e.preventDefault();
|
|
1295
|
+
removeSelected();
|
|
1296
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "d" && selection) {
|
|
1297
|
+
e.preventDefault();
|
|
1298
|
+
duplicate(selection);
|
|
1299
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selection) {
|
|
1300
|
+
copy(selection);
|
|
1301
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
|
|
1302
|
+
e.preventDefault();
|
|
1303
|
+
paste(selection);
|
|
1304
|
+
} else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
|
|
1305
|
+
const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
|
|
1306
|
+
if (!commit) return;
|
|
1307
|
+
e.preventDefault();
|
|
1308
|
+
const step = e.shiftKey ? 10 : 1;
|
|
1309
|
+
const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
1310
|
+
const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
|
|
1311
|
+
const cur = tf?.[selection] ?? {};
|
|
1312
|
+
commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
window.addEventListener("keydown", onKey);
|
|
1316
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1317
|
+
}, [editing, selection]);
|
|
1318
|
+
const onContextMenuSelect = (e) => {
|
|
1319
|
+
if (!editing) return;
|
|
1320
|
+
const el = e.target.closest("[data-cms]");
|
|
1321
|
+
if (el) ed.select(el.getAttribute("data-cms"));
|
|
1322
|
+
else ed.select(null);
|
|
1323
|
+
};
|
|
1324
|
+
const page = /* @__PURE__ */ jsxRuntime.jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry }) });
|
|
1325
|
+
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;
|
|
1048
1326
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1049
|
-
|
|
1327
|
+
editing ? /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
|
|
1328
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { children: pageShell }),
|
|
1329
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Content, { className: "!z-[2147483646]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1330
|
+
EditMenuItems,
|
|
1331
|
+
{
|
|
1332
|
+
node: selNode ?? null,
|
|
1333
|
+
hasClipboard: Boolean(clip.current),
|
|
1334
|
+
isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
|
|
1335
|
+
onEditText: () => startEditText(selection),
|
|
1336
|
+
onAdd: addNode,
|
|
1337
|
+
onDuplicate: () => duplicate(selection),
|
|
1338
|
+
onCopy: () => copy(selection),
|
|
1339
|
+
onPaste: () => paste(selection),
|
|
1340
|
+
onWrap: () => wrapInBox(selection),
|
|
1341
|
+
onReorder: (d) => reorder(selection, d),
|
|
1342
|
+
onDelete: removeSelected
|
|
1343
|
+
}
|
|
1344
|
+
) })
|
|
1345
|
+
] }) : pageShell,
|
|
1050
1346
|
editing && box && selection ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1051
1347
|
SelectionOverlay,
|
|
1052
1348
|
{
|
|
@@ -1067,6 +1363,7 @@ function EditablePage({
|
|
|
1067
1363
|
measured: { w: box.w, h: box.h },
|
|
1068
1364
|
onProps: setProps,
|
|
1069
1365
|
onStyle: setStyle,
|
|
1366
|
+
onLayout: setLayout,
|
|
1070
1367
|
onTransform: (t) => selection && onNodeTransform?.(selection, t),
|
|
1071
1368
|
onRemove: removeSelected,
|
|
1072
1369
|
onClose: () => ed.select(null)
|
|
@@ -1300,6 +1597,45 @@ function EditBar({
|
|
|
1300
1597
|
] }) : null
|
|
1301
1598
|
] });
|
|
1302
1599
|
}
|
|
1600
|
+
function EditMenuItems({
|
|
1601
|
+
node,
|
|
1602
|
+
hasClipboard,
|
|
1603
|
+
isContainer,
|
|
1604
|
+
onEditText,
|
|
1605
|
+
onAdd,
|
|
1606
|
+
onDuplicate,
|
|
1607
|
+
onCopy,
|
|
1608
|
+
onPaste,
|
|
1609
|
+
onWrap,
|
|
1610
|
+
onReorder,
|
|
1611
|
+
onDelete
|
|
1612
|
+
}) {
|
|
1613
|
+
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1614
|
+
const nested = Boolean(node && node.parent !== null);
|
|
1615
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1616
|
+
editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1617
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
|
|
1618
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1619
|
+
/* @__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)) })
|
|
1620
|
+
] }),
|
|
1621
|
+
node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1622
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1623
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1624
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
1625
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" }),
|
|
1626
|
+
nested ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onWrap, children: "Wrap in Box" }) : null,
|
|
1627
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1628
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1629
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1630
|
+
nested ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1631
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1632
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1633
|
+
] }) : null,
|
|
1634
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Separator, {}),
|
|
1635
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { danger: true, onClick: onDelete, children: "Delete" })
|
|
1636
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" })
|
|
1637
|
+
] });
|
|
1638
|
+
}
|
|
1303
1639
|
function applyTransform(el, t) {
|
|
1304
1640
|
if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
|
|
1305
1641
|
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
|