@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.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react';
|
|
2
|
-
import { P as PageDoc, N as Node, m as StyleProps } from './types-CpfPEq3V.cjs';
|
|
2
|
+
import { P as PageDoc, i as NodeId, N as Node, m as StyleProps, L as LayoutMode } from './types-CpfPEq3V.cjs';
|
|
3
3
|
import { a as ElementRegistry } from './registry-Bk7GN1rA.cjs';
|
|
4
|
-
import { P as PageOp } from './ops-
|
|
4
|
+
import { P as PageOp } from './ops-CdBZarAT.cjs';
|
|
5
5
|
|
|
6
6
|
interface EditorProps {
|
|
7
7
|
/** Initial document; edits are surfaced via {@link EditorProps.onChange}. */
|
|
@@ -73,9 +73,11 @@ interface LayersPanelProps {
|
|
|
73
73
|
doc: PageDoc;
|
|
74
74
|
selection: string | null;
|
|
75
75
|
onSelect: (id: string) => void;
|
|
76
|
+
/** Reparent/reorder a node (drag-to-reorder). Omit to disable dragging. */
|
|
77
|
+
onMove?: (id: NodeId, parent: NodeId | null, order: string) => void;
|
|
76
78
|
}
|
|
77
|
-
/** The section/layer tree. Click a row to select. */
|
|
78
|
-
declare function LayersPanel({ doc, selection, onSelect }: LayersPanelProps): ReactElement;
|
|
79
|
+
/** The section/layer tree. Click a row to select; drag a row onto another to reorder. */
|
|
80
|
+
declare function LayersPanel({ doc, selection, onSelect, onMove }: LayersPanelProps): ReactElement;
|
|
79
81
|
|
|
80
82
|
interface InspectorProps {
|
|
81
83
|
doc: PageDoc;
|
|
@@ -98,6 +100,8 @@ interface NodeInspectorProps {
|
|
|
98
100
|
onProps: (patch: Record<string, unknown>) => void;
|
|
99
101
|
/** Patch the node's base style. */
|
|
100
102
|
onStyle: (patch: Partial<StyleProps>) => void;
|
|
103
|
+
/** Set how a container arranges its children (free / stack / grid). */
|
|
104
|
+
onLayout?: (layout: LayoutMode | undefined) => void;
|
|
101
105
|
/** Commit a transform (position / size / opacity / scale / rotate) into the active keyframe. */
|
|
102
106
|
onTransform: (t: NodeTransform) => void;
|
|
103
107
|
onRemove: () => void;
|
|
@@ -111,7 +115,7 @@ interface NodeInspectorProps {
|
|
|
111
115
|
* box. Every control writes through the op-spine or the active keyframe, so edits
|
|
112
116
|
* are undoable and animatable.
|
|
113
117
|
*/
|
|
114
|
-
declare function NodeInspector({ node, transform, measured, onProps, onStyle, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
|
|
118
|
+
declare function NodeInspector({ node, transform, measured, onProps, onStyle, onLayout, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
|
|
115
119
|
|
|
116
120
|
/**
|
|
117
121
|
* Editor state engine — pure and framework-agnostic so it's unit-testable.
|
package/dist/editor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react';
|
|
2
|
-
import { P as PageDoc, N as Node, m as StyleProps } from './types-CpfPEq3V.js';
|
|
2
|
+
import { P as PageDoc, i as NodeId, N as Node, m as StyleProps, L as LayoutMode } from './types-CpfPEq3V.js';
|
|
3
3
|
import { a as ElementRegistry } from './registry-Dht9JBLU.js';
|
|
4
|
-
import { P as PageOp } from './ops-
|
|
4
|
+
import { P as PageOp } from './ops-BsM6kZxY.js';
|
|
5
5
|
|
|
6
6
|
interface EditorProps {
|
|
7
7
|
/** Initial document; edits are surfaced via {@link EditorProps.onChange}. */
|
|
@@ -73,9 +73,11 @@ interface LayersPanelProps {
|
|
|
73
73
|
doc: PageDoc;
|
|
74
74
|
selection: string | null;
|
|
75
75
|
onSelect: (id: string) => void;
|
|
76
|
+
/** Reparent/reorder a node (drag-to-reorder). Omit to disable dragging. */
|
|
77
|
+
onMove?: (id: NodeId, parent: NodeId | null, order: string) => void;
|
|
76
78
|
}
|
|
77
|
-
/** The section/layer tree. Click a row to select. */
|
|
78
|
-
declare function LayersPanel({ doc, selection, onSelect }: LayersPanelProps): ReactElement;
|
|
79
|
+
/** The section/layer tree. Click a row to select; drag a row onto another to reorder. */
|
|
80
|
+
declare function LayersPanel({ doc, selection, onSelect, onMove }: LayersPanelProps): ReactElement;
|
|
79
81
|
|
|
80
82
|
interface InspectorProps {
|
|
81
83
|
doc: PageDoc;
|
|
@@ -98,6 +100,8 @@ interface NodeInspectorProps {
|
|
|
98
100
|
onProps: (patch: Record<string, unknown>) => void;
|
|
99
101
|
/** Patch the node's base style. */
|
|
100
102
|
onStyle: (patch: Partial<StyleProps>) => void;
|
|
103
|
+
/** Set how a container arranges its children (free / stack / grid). */
|
|
104
|
+
onLayout?: (layout: LayoutMode | undefined) => void;
|
|
101
105
|
/** Commit a transform (position / size / opacity / scale / rotate) into the active keyframe. */
|
|
102
106
|
onTransform: (t: NodeTransform) => void;
|
|
103
107
|
onRemove: () => void;
|
|
@@ -111,7 +115,7 @@ interface NodeInspectorProps {
|
|
|
111
115
|
* box. Every control writes through the op-spine or the active keyframe, so edits
|
|
112
116
|
* are undoable and animatable.
|
|
113
117
|
*/
|
|
114
|
-
declare function NodeInspector({ node, transform, measured, onProps, onStyle, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
|
|
118
|
+
declare function NodeInspector({ node, transform, measured, onProps, onStyle, onLayout, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
|
|
115
119
|
|
|
116
120
|
/**
|
|
117
121
|
* Editor state engine — pure and framework-agnostic so it's unit-testable.
|
package/dist/editor.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { keyBetween } from './chunk-7YYLYBLI.js';
|
|
2
|
-
import { CmsPage, defaultRegistry } from './chunk-
|
|
3
|
-
import { reduce, childrenOf } from './chunk-
|
|
2
|
+
import { CmsPage, defaultRegistry } from './chunk-ZV7CV54P.js';
|
|
3
|
+
import { reduce, childrenOf } from './chunk-3YZMELJJ.js';
|
|
4
4
|
import { useReducer, useCallback, useMemo, useRef, useState, useLayoutEffect, Fragment, useEffect } from 'react';
|
|
5
5
|
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
6
|
+
import { ContextMenu } from '@particle-academy/react-fancy';
|
|
6
7
|
|
|
7
8
|
function initEditor(doc) {
|
|
8
9
|
return { doc, past: [], future: [], selection: null };
|
|
@@ -156,31 +157,66 @@ function Canvas({ doc, selection, onSelect, apply }) {
|
|
|
156
157
|
function cssEscape(value) {
|
|
157
158
|
return value.replace(/["\\]/g, "\\$&");
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
var MIME = "application/x-cms-node";
|
|
161
|
+
function isAncestorOrSelf(doc, id, maybeAncestor) {
|
|
162
|
+
for (let cur = id; cur; cur = doc.nodes[cur]?.parent ?? null) {
|
|
163
|
+
if (cur === maybeAncestor) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
function LayersPanel({ doc, selection, onSelect, onMove }) {
|
|
168
|
+
const [overId, setOverId] = useState(null);
|
|
169
|
+
return /* @__PURE__ */ jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsx(LayerRow, { doc, id, depth: 0, selection, onSelect, onMove, overId, setOverId }, id)) });
|
|
161
170
|
}
|
|
162
|
-
function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
171
|
+
function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
|
|
163
172
|
const node = doc.nodes[id];
|
|
164
173
|
if (!node) return null;
|
|
165
174
|
const selected = selection === id;
|
|
175
|
+
const draggable = Boolean(onMove);
|
|
176
|
+
const drop = (e) => {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
setOverId(null);
|
|
180
|
+
if (!onMove) return;
|
|
181
|
+
const dragId = e.dataTransfer.getData(MIME);
|
|
182
|
+
if (!dragId || dragId === id || isAncestorOrSelf(doc, id, dragId)) return;
|
|
183
|
+
const siblings = childrenOf(doc, node.parent);
|
|
184
|
+
const idx = siblings.findIndex((n) => n.id === id);
|
|
185
|
+
const next = siblings[idx + 1];
|
|
186
|
+
onMove(dragId, node.parent, keyBetween(node.order, next ? next.order : null));
|
|
187
|
+
};
|
|
166
188
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
167
189
|
/* @__PURE__ */ jsxs(
|
|
168
190
|
"button",
|
|
169
191
|
{
|
|
170
192
|
type: "button",
|
|
193
|
+
draggable,
|
|
171
194
|
onClick: () => onSelect(id),
|
|
195
|
+
onDragStart: (e) => {
|
|
196
|
+
e.dataTransfer.setData(MIME, id);
|
|
197
|
+
e.dataTransfer.effectAllowed = "move";
|
|
198
|
+
},
|
|
199
|
+
onDragOver: (e) => {
|
|
200
|
+
if (!onMove) return;
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
e.dataTransfer.dropEffect = "move";
|
|
203
|
+
if (overId !== id) setOverId(id);
|
|
204
|
+
},
|
|
205
|
+
onDragLeave: () => overId === id && setOverId(null),
|
|
206
|
+
onDrop: drop,
|
|
172
207
|
style: {
|
|
173
208
|
display: "block",
|
|
174
209
|
width: "100%",
|
|
175
210
|
textAlign: "left",
|
|
176
211
|
border: "none",
|
|
177
212
|
borderRadius: 6,
|
|
178
|
-
cursor: "pointer",
|
|
213
|
+
cursor: draggable ? "grab" : "pointer",
|
|
179
214
|
padding: "4px 8px",
|
|
180
215
|
paddingLeft: 8 + depth * 14,
|
|
181
216
|
background: selected ? "#ede9fe" : "transparent",
|
|
182
217
|
color: selected ? "#5b21b6" : "#334155",
|
|
183
|
-
font: "inherit"
|
|
218
|
+
font: "inherit",
|
|
219
|
+
boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
|
|
184
220
|
},
|
|
185
221
|
children: [
|
|
186
222
|
/* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: node.type }),
|
|
@@ -189,17 +225,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
|
189
225
|
]
|
|
190
226
|
}
|
|
191
227
|
),
|
|
192
|
-
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(
|
|
193
|
-
LayerRow,
|
|
194
|
-
{
|
|
195
|
-
doc,
|
|
196
|
-
id: child.id,
|
|
197
|
-
depth: depth + 1,
|
|
198
|
-
selection,
|
|
199
|
-
onSelect
|
|
200
|
-
},
|
|
201
|
-
child.id
|
|
202
|
-
))
|
|
228
|
+
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
|
|
203
229
|
] });
|
|
204
230
|
}
|
|
205
231
|
var label = {
|
|
@@ -331,7 +357,15 @@ function Editor({ defaultValue, onChange }) {
|
|
|
331
357
|
return /* @__PURE__ */ jsxs("div", { style: shell, children: [
|
|
332
358
|
/* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
333
359
|
/* @__PURE__ */ jsx(Toolbar, { ed }),
|
|
334
|
-
/* @__PURE__ */ jsx(
|
|
360
|
+
/* @__PURE__ */ jsx(
|
|
361
|
+
LayersPanel,
|
|
362
|
+
{
|
|
363
|
+
doc: ed.state.doc,
|
|
364
|
+
selection: ed.state.selection,
|
|
365
|
+
onSelect: ed.select,
|
|
366
|
+
onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
|
|
367
|
+
}
|
|
368
|
+
)
|
|
335
369
|
] }),
|
|
336
370
|
/* @__PURE__ */ jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
|
|
337
371
|
/* @__PURE__ */ jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
|
|
@@ -371,6 +405,7 @@ function NodeInspector({
|
|
|
371
405
|
measured,
|
|
372
406
|
onProps,
|
|
373
407
|
onStyle,
|
|
408
|
+
onLayout,
|
|
374
409
|
onTransform,
|
|
375
410
|
onRemove,
|
|
376
411
|
onClose
|
|
@@ -453,9 +488,44 @@ function NodeInspector({
|
|
|
453
488
|
] }),
|
|
454
489
|
/* @__PURE__ */ jsxs(Row, { children: [
|
|
455
490
|
/* @__PURE__ */ jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
|
|
491
|
+
/* @__PURE__ */ jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
|
|
492
|
+
] }),
|
|
493
|
+
/* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
|
|
494
|
+
] }),
|
|
495
|
+
CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Layout", children: [
|
|
496
|
+
/* @__PURE__ */ jsxs(Row, { children: [
|
|
497
|
+
/* @__PURE__ */ jsx(
|
|
498
|
+
Sel,
|
|
499
|
+
{
|
|
500
|
+
label: "Mode",
|
|
501
|
+
value: node.layout ?? "free",
|
|
502
|
+
options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
|
|
503
|
+
onChange: (v) => onLayout?.(v)
|
|
504
|
+
}
|
|
505
|
+
),
|
|
456
506
|
/* @__PURE__ */ jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ jsxs(Row, { children: [
|
|
509
|
+
/* @__PURE__ */ jsx(
|
|
510
|
+
Sel,
|
|
511
|
+
{
|
|
512
|
+
label: "Align",
|
|
513
|
+
value: base.align ?? "",
|
|
514
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
|
|
515
|
+
onChange: (v) => onStyle({ align: v || void 0 })
|
|
516
|
+
}
|
|
517
|
+
),
|
|
518
|
+
/* @__PURE__ */ jsx(
|
|
519
|
+
Sel,
|
|
520
|
+
{
|
|
521
|
+
label: "Justify",
|
|
522
|
+
value: base.justify ?? "",
|
|
523
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
|
|
524
|
+
onChange: (v) => onStyle({ justify: v || void 0 })
|
|
525
|
+
}
|
|
526
|
+
)
|
|
457
527
|
] })
|
|
458
|
-
] })
|
|
528
|
+
] }) : null
|
|
459
529
|
] })
|
|
460
530
|
] });
|
|
461
531
|
}
|
|
@@ -510,6 +580,12 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
|
510
580
|
)
|
|
511
581
|
] });
|
|
512
582
|
}
|
|
583
|
+
function Txt({ label: label2, value, placeholder, onChange }) {
|
|
584
|
+
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
585
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
|
|
586
|
+
/* @__PURE__ */ jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
|
|
587
|
+
] });
|
|
588
|
+
}
|
|
513
589
|
function Color({ label: label2, value, onChange }) {
|
|
514
590
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
515
591
|
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
|
|
@@ -559,6 +635,110 @@ var input2 = {
|
|
|
559
635
|
};
|
|
560
636
|
var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
|
|
561
637
|
var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
|
|
638
|
+
|
|
639
|
+
// src/editor/editorOps.ts
|
|
640
|
+
function childrenOfMap(nodes, parent) {
|
|
641
|
+
return Object.values(nodes).filter((n) => n.parent === parent).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
|
|
642
|
+
}
|
|
643
|
+
function subtreeBfs(nodes, root) {
|
|
644
|
+
const out = [];
|
|
645
|
+
const queue = [root];
|
|
646
|
+
while (queue.length) {
|
|
647
|
+
const cur = queue.shift();
|
|
648
|
+
out.push(cur);
|
|
649
|
+
for (const child of childrenOfMap(nodes, cur)) queue.push(child.id);
|
|
650
|
+
}
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
function cloneFrom(nodes, rootId, newParent, newOrder, seqBase) {
|
|
654
|
+
if (!nodes[rootId]) return { ops: [], newRootId: null };
|
|
655
|
+
const order = subtreeBfs(nodes, rootId);
|
|
656
|
+
const idMap = {};
|
|
657
|
+
order.forEach((old, i) => idMap[old] = `${old}_c${seqBase}_${i}`);
|
|
658
|
+
const ops = order.map((old) => {
|
|
659
|
+
const src = nodes[old];
|
|
660
|
+
const cloned = JSON.parse(JSON.stringify(src));
|
|
661
|
+
cloned.id = idMap[old];
|
|
662
|
+
cloned.parent = old === rootId ? newParent : idMap[src.parent];
|
|
663
|
+
if (old === rootId) cloned.order = newOrder;
|
|
664
|
+
return { t: "insert_node", node: cloned };
|
|
665
|
+
});
|
|
666
|
+
return { ops, newRootId: idMap[rootId] };
|
|
667
|
+
}
|
|
668
|
+
function duplicateOps(doc, id) {
|
|
669
|
+
const node = doc.nodes[id];
|
|
670
|
+
if (!node) return { ops: [], newRootId: null };
|
|
671
|
+
const sibs = childrenOf(doc, node.parent);
|
|
672
|
+
const idx = sibs.findIndex((s) => s.id === id);
|
|
673
|
+
const newOrder = keyBetween(node.order, sibs[idx + 1]?.order ?? null);
|
|
674
|
+
const { ops, newRootId } = cloneFrom(doc.nodes, id, node.parent, newOrder, doc.seq);
|
|
675
|
+
if (node.parent === null && newRootId) {
|
|
676
|
+
const base = doc.sections;
|
|
677
|
+
const at = base.indexOf(id);
|
|
678
|
+
ops.push({ t: "reorder_sections", order: [...base.slice(0, at + 1), newRootId, ...base.slice(at + 1)] });
|
|
679
|
+
}
|
|
680
|
+
return { ops, newRootId };
|
|
681
|
+
}
|
|
682
|
+
function pasteOps(doc, clip, target, containerTypes) {
|
|
683
|
+
const sel = target ? doc.nodes[target] : null;
|
|
684
|
+
const parent = sel ? containerTypes.has(sel.type) ? sel.id : sel.parent : doc.sections[doc.sections.length - 1] ?? null;
|
|
685
|
+
const sibs = childrenOf(doc, parent);
|
|
686
|
+
const newOrder = keyBetween(sibs.length ? sibs[sibs.length - 1].order : null, null);
|
|
687
|
+
const { ops, newRootId } = cloneFrom(clip.nodes, clip.rootId, parent, newOrder, doc.seq);
|
|
688
|
+
return { ops, newRootId };
|
|
689
|
+
}
|
|
690
|
+
function reorderOps(doc, id, dir) {
|
|
691
|
+
const node = doc.nodes[id];
|
|
692
|
+
if (!node) return [];
|
|
693
|
+
if (node.parent === null) {
|
|
694
|
+
const s = [...doc.sections];
|
|
695
|
+
const i = s.indexOf(id);
|
|
696
|
+
if (i < 0) return [];
|
|
697
|
+
s.splice(i, 1);
|
|
698
|
+
if (dir === "front") s.unshift(id);
|
|
699
|
+
else if (dir === "back") s.push(id);
|
|
700
|
+
else s.splice(Math.max(0, Math.min(s.length, dir === "up" ? i - 1 : i + 1)), 0, id);
|
|
701
|
+
return [{ t: "reorder_sections", order: s }];
|
|
702
|
+
}
|
|
703
|
+
const sibs = childrenOf(doc, node.parent);
|
|
704
|
+
const idx = sibs.findIndex((sb) => sb.id === id);
|
|
705
|
+
if (idx < 0) return [];
|
|
706
|
+
const last = sibs.length - 1;
|
|
707
|
+
let newOrder;
|
|
708
|
+
if (dir === "up") {
|
|
709
|
+
if (idx <= 0) return [];
|
|
710
|
+
newOrder = keyBetween(sibs[idx - 2]?.order ?? null, sibs[idx - 1].order);
|
|
711
|
+
} else if (dir === "down") {
|
|
712
|
+
if (idx >= last) return [];
|
|
713
|
+
newOrder = keyBetween(sibs[idx + 1].order, sibs[idx + 2]?.order ?? null);
|
|
714
|
+
} else if (dir === "front") {
|
|
715
|
+
if (idx >= last) return [];
|
|
716
|
+
newOrder = keyBetween(sibs[last].order, null);
|
|
717
|
+
} else {
|
|
718
|
+
if (idx <= 0) return [];
|
|
719
|
+
newOrder = keyBetween(null, sibs[0].order);
|
|
720
|
+
}
|
|
721
|
+
return [{ t: "move_node", id, parent: node.parent, order: newOrder }];
|
|
722
|
+
}
|
|
723
|
+
function wrapInBoxOps(doc, id) {
|
|
724
|
+
const node = doc.nodes[id];
|
|
725
|
+
if (!node || node.parent === null) return { ops: [], newRootId: null };
|
|
726
|
+
const frameId = `box_${doc.seq}`;
|
|
727
|
+
return {
|
|
728
|
+
ops: [
|
|
729
|
+
{ t: "insert_node", node: { id: frameId, type: "frame", parent: node.parent, order: node.order, props: {}, style: { base: { padding: { value: 16, unit: "px" } } } } },
|
|
730
|
+
{ t: "move_node", id, parent: frameId, order: "a" }
|
|
731
|
+
],
|
|
732
|
+
newRootId: frameId
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function snapshotSubtree(doc, id) {
|
|
736
|
+
if (!doc.nodes[id]) return null;
|
|
737
|
+
const ids = subtreeBfs(doc.nodes, id);
|
|
738
|
+
const nodes = {};
|
|
739
|
+
for (const nid of ids) nodes[nid] = JSON.parse(JSON.stringify(doc.nodes[nid]));
|
|
740
|
+
return { rootId: id, nodes };
|
|
741
|
+
}
|
|
562
742
|
var Z2 = 2147483e3;
|
|
563
743
|
function EditablePage({
|
|
564
744
|
doc,
|
|
@@ -672,14 +852,93 @@ function EditablePage({
|
|
|
672
852
|
);
|
|
673
853
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
674
854
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
855
|
+
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
675
856
|
const removeSelected = () => {
|
|
676
857
|
if (!selection) return;
|
|
677
858
|
ed.apply({ t: "remove_node", id: selection });
|
|
678
859
|
ed.select(null);
|
|
679
860
|
};
|
|
680
|
-
const
|
|
861
|
+
const clip = useRef(null);
|
|
862
|
+
const runOps = (result) => {
|
|
863
|
+
result.ops.forEach((op) => ed.apply(op));
|
|
864
|
+
if (result.newRootId) ed.select(result.newRootId);
|
|
865
|
+
};
|
|
866
|
+
const duplicate = (id) => id && runOps(duplicateOps(ed.state.doc, id));
|
|
867
|
+
const reorder = (id, dir) => id && reorderOps(ed.state.doc, id, dir).forEach((op) => ed.apply(op));
|
|
868
|
+
const wrapInBox = (id) => id && runOps(wrapInBoxOps(ed.state.doc, id));
|
|
869
|
+
const copy = (id) => {
|
|
870
|
+
if (id) clip.current = snapshotSubtree(ed.state.doc, id);
|
|
871
|
+
};
|
|
872
|
+
const paste = (target) => {
|
|
873
|
+
if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
|
|
874
|
+
};
|
|
875
|
+
const startEditText = (id) => {
|
|
876
|
+
if (!id) return;
|
|
877
|
+
ed.select(id);
|
|
878
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
|
|
879
|
+
};
|
|
880
|
+
const nudgeRef = useRef({ transforms, onNodeTransform });
|
|
881
|
+
nudgeRef.current = { transforms, onNodeTransform };
|
|
882
|
+
useEffect(() => {
|
|
883
|
+
if (!editing) return;
|
|
884
|
+
const onKey = (e) => {
|
|
885
|
+
const t = e.target;
|
|
886
|
+
const typing = t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
|
|
887
|
+
if (e.key === "Escape") return void ed.select(null);
|
|
888
|
+
if (typing) return;
|
|
889
|
+
if ((e.key === "Delete" || e.key === "Backspace") && selection) {
|
|
890
|
+
e.preventDefault();
|
|
891
|
+
removeSelected();
|
|
892
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "d" && selection) {
|
|
893
|
+
e.preventDefault();
|
|
894
|
+
duplicate(selection);
|
|
895
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selection) {
|
|
896
|
+
copy(selection);
|
|
897
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
|
|
898
|
+
e.preventDefault();
|
|
899
|
+
paste(selection);
|
|
900
|
+
} else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
|
|
901
|
+
const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
|
|
902
|
+
if (!commit) return;
|
|
903
|
+
e.preventDefault();
|
|
904
|
+
const step = e.shiftKey ? 10 : 1;
|
|
905
|
+
const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
906
|
+
const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
|
|
907
|
+
const cur = tf?.[selection] ?? {};
|
|
908
|
+
commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
window.addEventListener("keydown", onKey);
|
|
912
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
913
|
+
}, [editing, selection]);
|
|
914
|
+
const onContextMenuSelect = (e) => {
|
|
915
|
+
if (!editing) return;
|
|
916
|
+
const el = e.target.closest("[data-cms]");
|
|
917
|
+
if (el) ed.select(el.getAttribute("data-cms"));
|
|
918
|
+
else ed.select(null);
|
|
919
|
+
};
|
|
920
|
+
const page = /* @__PURE__ */ jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry }) });
|
|
921
|
+
const pageShell = pinned ? /* @__PURE__ */ jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : page;
|
|
681
922
|
return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
682
|
-
|
|
923
|
+
editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
924
|
+
/* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
|
|
925
|
+
/* @__PURE__ */ jsx(ContextMenu.Content, { className: "!z-[2147483646]", children: /* @__PURE__ */ jsx(
|
|
926
|
+
EditMenuItems,
|
|
927
|
+
{
|
|
928
|
+
node: selNode ?? null,
|
|
929
|
+
hasClipboard: Boolean(clip.current),
|
|
930
|
+
isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
|
|
931
|
+
onEditText: () => startEditText(selection),
|
|
932
|
+
onAdd: addNode,
|
|
933
|
+
onDuplicate: () => duplicate(selection),
|
|
934
|
+
onCopy: () => copy(selection),
|
|
935
|
+
onPaste: () => paste(selection),
|
|
936
|
+
onWrap: () => wrapInBox(selection),
|
|
937
|
+
onReorder: (d) => reorder(selection, d),
|
|
938
|
+
onDelete: removeSelected
|
|
939
|
+
}
|
|
940
|
+
) })
|
|
941
|
+
] }) : pageShell,
|
|
683
942
|
editing && box && selection ? /* @__PURE__ */ jsx(
|
|
684
943
|
SelectionOverlay,
|
|
685
944
|
{
|
|
@@ -700,6 +959,7 @@ function EditablePage({
|
|
|
700
959
|
measured: { w: box.w, h: box.h },
|
|
701
960
|
onProps: setProps,
|
|
702
961
|
onStyle: setStyle,
|
|
962
|
+
onLayout: setLayout,
|
|
703
963
|
onTransform: (t) => selection && onNodeTransform?.(selection, t),
|
|
704
964
|
onRemove: removeSelected,
|
|
705
965
|
onClose: () => ed.select(null)
|
|
@@ -933,6 +1193,45 @@ function EditBar({
|
|
|
933
1193
|
] }) : null
|
|
934
1194
|
] });
|
|
935
1195
|
}
|
|
1196
|
+
function EditMenuItems({
|
|
1197
|
+
node,
|
|
1198
|
+
hasClipboard,
|
|
1199
|
+
isContainer,
|
|
1200
|
+
onEditText,
|
|
1201
|
+
onAdd,
|
|
1202
|
+
onDuplicate,
|
|
1203
|
+
onCopy,
|
|
1204
|
+
onPaste,
|
|
1205
|
+
onWrap,
|
|
1206
|
+
onReorder,
|
|
1207
|
+
onDelete
|
|
1208
|
+
}) {
|
|
1209
|
+
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1210
|
+
const nested = Boolean(node && node.parent !== null);
|
|
1211
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1212
|
+
editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1213
|
+
/* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
|
|
1214
|
+
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1215
|
+
/* @__PURE__ */ jsx(ContextMenu.SubContent, { className: "!z-[2147483647]", children: ADD_MENU.map((m) => /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onAdd(m.kind), children: m.label }, m.kind)) })
|
|
1216
|
+
] }),
|
|
1217
|
+
node ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1218
|
+
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1219
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1220
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
1221
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" }),
|
|
1222
|
+
nested ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onWrap, children: "Wrap in Box" }) : null,
|
|
1223
|
+
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1224
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1225
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1226
|
+
nested ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1227
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1228
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1229
|
+
] }) : null,
|
|
1230
|
+
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1231
|
+
/* @__PURE__ */ jsx(ContextMenu.Item, { danger: true, onClick: onDelete, children: "Delete" })
|
|
1232
|
+
] }) : /* @__PURE__ */ jsx(ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" })
|
|
1233
|
+
] });
|
|
1234
|
+
}
|
|
936
1235
|
function applyTransform(el, t) {
|
|
937
1236
|
if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
|
|
938
1237
|
el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
|