@particle-academy/fancy-cms-ui 0.0.1-dev.9 → 0.1.0
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/README.md +72 -0
- package/dist/{chunk-3YZMELJJ.js → chunk-GOXVGEKU.js} +94 -3
- package/dist/chunk-GOXVGEKU.js.map +1 -0
- package/dist/chunk-N22BQAY3.js +442 -0
- package/dist/chunk-N22BQAY3.js.map +1 -0
- package/dist/chunk-QYNWSECG.js +19 -0
- package/dist/chunk-QYNWSECG.js.map +1 -0
- package/dist/editor.cjs +321 -54
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +7 -92
- package/dist/editor.d.ts +7 -92
- package/dist/editor.js +311 -455
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +508 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/react.cjs +45 -25
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +13 -6
- package/dist/react.d.ts +13 -6
- package/dist/react.js +2 -19
- package/dist/react.js.map +1 -1
- package/dist/{types-CpfPEq3V.d.cts → registry-D5y5IgCR.d.cts} +30 -1
- package/dist/{types-CpfPEq3V.d.ts → registry-D5y5IgCR.d.ts} +30 -1
- package/dist/useEditor-DLH7hU6G.d.cts +162 -0
- package/dist/useEditor-qmF9n2e6.d.ts +162 -0
- package/package.json +6 -6
- package/dist/chunk-3YZMELJJ.js.map +0 -1
- package/dist/chunk-7YYLYBLI.js +0 -47
- package/dist/chunk-7YYLYBLI.js.map +0 -1
- package/dist/chunk-ZV7CV54P.js +0 -78
- package/dist/chunk-ZV7CV54P.js.map +0 -1
- package/dist/ops-BsM6kZxY.d.ts +0 -75
- package/dist/ops-CdBZarAT.d.cts +0 -75
- package/dist/registry-Bk7GN1rA.d.cts +0 -19
- package/dist/registry-Dht9JBLU.d.ts +0 -19
package/dist/editor.js
CHANGED
|
@@ -1,404 +1,11 @@
|
|
|
1
|
-
import { keyBetween } from './chunk-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
1
|
+
import { useEditor, keyBetween } from './chunk-N22BQAY3.js';
|
|
2
|
+
export { Canvas, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor } from './chunk-N22BQAY3.js';
|
|
3
|
+
import { isBinding, childrenOf, CmsPage, defaultRegistry } from './chunk-GOXVGEKU.js';
|
|
4
|
+
import { useState, useRef, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
6
5
|
import { ContextMenu } from '@particle-academy/react-fancy';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
return { doc, past: [], future: [], selection: null };
|
|
10
|
-
}
|
|
11
|
-
function editorReduce(state, action) {
|
|
12
|
-
switch (action.type) {
|
|
13
|
-
case "apply": {
|
|
14
|
-
const next = reduce(state.doc, action.op);
|
|
15
|
-
if (next === state.doc) return state;
|
|
16
|
-
return {
|
|
17
|
-
...state,
|
|
18
|
-
doc: next,
|
|
19
|
-
past: [...state.past, state.doc].slice(-100),
|
|
20
|
-
future: []
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
case "undo": {
|
|
24
|
-
if (state.past.length === 0) return state;
|
|
25
|
-
const prev = state.past[state.past.length - 1];
|
|
26
|
-
return {
|
|
27
|
-
...state,
|
|
28
|
-
doc: prev,
|
|
29
|
-
past: state.past.slice(0, -1),
|
|
30
|
-
future: [state.doc, ...state.future]
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
case "redo": {
|
|
34
|
-
if (state.future.length === 0) return state;
|
|
35
|
-
const next = state.future[0];
|
|
36
|
-
return {
|
|
37
|
-
...state,
|
|
38
|
-
doc: next,
|
|
39
|
-
past: [...state.past, state.doc],
|
|
40
|
-
future: state.future.slice(1)
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
case "select":
|
|
44
|
-
return state.selection === action.id ? state : { ...state, selection: action.id };
|
|
45
|
-
case "replace":
|
|
46
|
-
return state.doc === action.doc ? state : { ...state, doc: action.doc, future: [] };
|
|
47
|
-
default:
|
|
48
|
-
return state;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/editor/useEditor.ts
|
|
53
|
-
function useEditor(initialDoc) {
|
|
54
|
-
const [state, dispatch] = useReducer(editorReduce, initialDoc, initEditor);
|
|
55
|
-
const apply = useCallback((op) => dispatch({ type: "apply", op }), []);
|
|
56
|
-
const undo = useCallback(() => dispatch({ type: "undo" }), []);
|
|
57
|
-
const redo = useCallback(() => dispatch({ type: "redo" }), []);
|
|
58
|
-
const select = useCallback((id) => dispatch({ type: "select", id }), []);
|
|
59
|
-
const replace = useCallback((doc) => dispatch({ type: "replace", doc }), []);
|
|
60
|
-
return useMemo(
|
|
61
|
-
() => ({
|
|
62
|
-
state,
|
|
63
|
-
apply,
|
|
64
|
-
undo,
|
|
65
|
-
redo,
|
|
66
|
-
select,
|
|
67
|
-
replace,
|
|
68
|
-
canUndo: state.past.length > 0,
|
|
69
|
-
canRedo: state.future.length > 0
|
|
70
|
-
}),
|
|
71
|
-
[state, apply, undo, redo, select, replace]
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
function Canvas({ doc, selection, onSelect, apply }) {
|
|
75
|
-
const ref = useRef(null);
|
|
76
|
-
const [box, setBox] = useState(null);
|
|
77
|
-
const drag = useRef(null);
|
|
78
|
-
useLayoutEffect(() => {
|
|
79
|
-
const root = ref.current;
|
|
80
|
-
if (!root || !selection) {
|
|
81
|
-
setBox(null);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const el = root.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
85
|
-
if (!el) {
|
|
86
|
-
setBox(null);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const r = el.getBoundingClientRect();
|
|
90
|
-
const rootRect = root.getBoundingClientRect();
|
|
91
|
-
setBox({
|
|
92
|
-
x: r.left - rootRect.left + root.scrollLeft,
|
|
93
|
-
y: r.top - rootRect.top + root.scrollTop,
|
|
94
|
-
w: r.width,
|
|
95
|
-
h: r.height
|
|
96
|
-
});
|
|
97
|
-
}, [selection, doc]);
|
|
98
|
-
const handleClick = (e) => {
|
|
99
|
-
const target = e.target.closest("[data-cms]");
|
|
100
|
-
onSelect(target ? target.getAttribute("data-cms") : null);
|
|
101
|
-
};
|
|
102
|
-
const handlePointerDown = (e) => {
|
|
103
|
-
if (!selection) return;
|
|
104
|
-
const c = doc.nodes[selection]?.constraints?.base;
|
|
105
|
-
const left = c && c.left && typeof c.left === "object" ? c.left.value : 0;
|
|
106
|
-
const top = c && c.top && typeof c.top === "object" ? c.top.value : 0;
|
|
107
|
-
drag.current = { x: e.clientX, y: e.clientY, left, top };
|
|
108
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
109
|
-
e.stopPropagation();
|
|
110
|
-
};
|
|
111
|
-
const handlePointerMove = (e) => {
|
|
112
|
-
const d = drag.current;
|
|
113
|
-
if (!d || !selection) return;
|
|
114
|
-
apply({
|
|
115
|
-
t: "set_constraints",
|
|
116
|
-
id: selection,
|
|
117
|
-
breakpoint: "base",
|
|
118
|
-
patch: {
|
|
119
|
-
left: { value: Math.round(d.left + (e.clientX - d.x)), unit: "px" },
|
|
120
|
-
top: { value: Math.round(d.top + (e.clientY - d.y)), unit: "px" }
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
const handlePointerUp = () => {
|
|
125
|
-
drag.current = null;
|
|
126
|
-
};
|
|
127
|
-
const surface = {
|
|
128
|
-
position: "relative",
|
|
129
|
-
overflow: "auto",
|
|
130
|
-
height: "100%",
|
|
131
|
-
background: "#f8fafc"
|
|
132
|
-
};
|
|
133
|
-
const overlay = box ? {
|
|
134
|
-
position: "absolute",
|
|
135
|
-
left: box.x,
|
|
136
|
-
top: box.y,
|
|
137
|
-
width: box.w,
|
|
138
|
-
height: box.h,
|
|
139
|
-
outline: "2px solid #8b5cf6",
|
|
140
|
-
outlineOffset: -1,
|
|
141
|
-
cursor: "move",
|
|
142
|
-
touchAction: "none"
|
|
143
|
-
} : { display: "none" };
|
|
144
|
-
return /* @__PURE__ */ jsxs("div", { ref, onClick: handleClick, style: surface, children: [
|
|
145
|
-
/* @__PURE__ */ jsx(CmsPage, { doc }),
|
|
146
|
-
/* @__PURE__ */ jsx(
|
|
147
|
-
"div",
|
|
148
|
-
{
|
|
149
|
-
onPointerDown: handlePointerDown,
|
|
150
|
-
onPointerMove: handlePointerMove,
|
|
151
|
-
onPointerUp: handlePointerUp,
|
|
152
|
-
style: overlay
|
|
153
|
-
}
|
|
154
|
-
)
|
|
155
|
-
] });
|
|
156
|
-
}
|
|
157
|
-
function cssEscape(value) {
|
|
158
|
-
return value.replace(/["\\]/g, "\\$&");
|
|
159
|
-
}
|
|
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)) });
|
|
170
|
-
}
|
|
171
|
-
function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
|
|
172
|
-
const node = doc.nodes[id];
|
|
173
|
-
if (!node) return null;
|
|
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
|
-
};
|
|
188
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
189
|
-
/* @__PURE__ */ jsxs(
|
|
190
|
-
"button",
|
|
191
|
-
{
|
|
192
|
-
type: "button",
|
|
193
|
-
draggable,
|
|
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,
|
|
207
|
-
style: {
|
|
208
|
-
display: "block",
|
|
209
|
-
width: "100%",
|
|
210
|
-
textAlign: "left",
|
|
211
|
-
border: "none",
|
|
212
|
-
borderRadius: 6,
|
|
213
|
-
cursor: draggable ? "grab" : "pointer",
|
|
214
|
-
padding: "4px 8px",
|
|
215
|
-
paddingLeft: 8 + depth * 14,
|
|
216
|
-
background: selected ? "#ede9fe" : "transparent",
|
|
217
|
-
color: selected ? "#5b21b6" : "#334155",
|
|
218
|
-
font: "inherit",
|
|
219
|
-
boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
|
|
220
|
-
},
|
|
221
|
-
children: [
|
|
222
|
-
/* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: node.type }),
|
|
223
|
-
" \xB7 ",
|
|
224
|
-
id
|
|
225
|
-
]
|
|
226
|
-
}
|
|
227
|
-
),
|
|
228
|
-
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
|
|
229
|
-
] });
|
|
230
|
-
}
|
|
231
|
-
var label = {
|
|
232
|
-
display: "block",
|
|
233
|
-
fontSize: 11,
|
|
234
|
-
textTransform: "uppercase",
|
|
235
|
-
letterSpacing: "0.04em",
|
|
236
|
-
color: "#94a3b8",
|
|
237
|
-
margin: "10px 0 4px"
|
|
238
|
-
};
|
|
239
|
-
var input = {
|
|
240
|
-
width: "100%",
|
|
241
|
-
boxSizing: "border-box",
|
|
242
|
-
padding: "6px 8px",
|
|
243
|
-
borderRadius: 6,
|
|
244
|
-
border: "1px solid #e2e8f0",
|
|
245
|
-
font: "inherit",
|
|
246
|
-
fontSize: 13
|
|
247
|
-
};
|
|
248
|
-
function Inspector({ doc, selection, apply }) {
|
|
249
|
-
const node = selection ? doc.nodes[selection] : null;
|
|
250
|
-
if (!node) {
|
|
251
|
-
return /* @__PURE__ */ jsx("div", { style: { padding: 16, color: "#94a3b8", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
|
|
252
|
-
}
|
|
253
|
-
const s = node.style.base;
|
|
254
|
-
const setStyle = (patch) => apply({ t: "set_style", id: node.id, breakpoint: "base", patch });
|
|
255
|
-
const setProp = (key, value) => apply({ t: "set_props", id: node.id, patch: { [key]: value } });
|
|
256
|
-
return /* @__PURE__ */ jsxs("div", { style: { overflow: "auto", padding: 16, fontFamily: "system-ui, sans-serif", height: "100%", boxSizing: "border-box" }, children: [
|
|
257
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#0f172a" }, children: node.type }),
|
|
258
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#94a3b8", marginBottom: 8 }, children: node.id }),
|
|
259
|
-
node.type === "text" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
260
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Content" }),
|
|
261
|
-
/* @__PURE__ */ jsx(
|
|
262
|
-
"textarea",
|
|
263
|
-
{
|
|
264
|
-
style: { ...input, minHeight: 64, resize: "vertical" },
|
|
265
|
-
value: typeof node.props.content === "string" ? node.props.content : "",
|
|
266
|
-
onChange: (e) => setProp("content", e.target.value)
|
|
267
|
-
}
|
|
268
|
-
)
|
|
269
|
-
] }) : null,
|
|
270
|
-
node.type === "image" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
271
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Source" }),
|
|
272
|
-
/* @__PURE__ */ jsx(
|
|
273
|
-
"input",
|
|
274
|
-
{
|
|
275
|
-
style: input,
|
|
276
|
-
value: typeof node.props.src === "string" ? node.props.src : "",
|
|
277
|
-
onChange: (e) => setProp("src", e.target.value)
|
|
278
|
-
}
|
|
279
|
-
)
|
|
280
|
-
] }) : null,
|
|
281
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Background" }),
|
|
282
|
-
/* @__PURE__ */ jsx(
|
|
283
|
-
"input",
|
|
284
|
-
{
|
|
285
|
-
style: input,
|
|
286
|
-
placeholder: "#ffffff | gradient | url(\u2026)",
|
|
287
|
-
value: s.background ?? "",
|
|
288
|
-
onChange: (e) => setStyle({ background: e.target.value })
|
|
289
|
-
}
|
|
290
|
-
),
|
|
291
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Text color" }),
|
|
292
|
-
/* @__PURE__ */ jsx(
|
|
293
|
-
"input",
|
|
294
|
-
{
|
|
295
|
-
style: input,
|
|
296
|
-
placeholder: "#0f172a",
|
|
297
|
-
value: s.color ?? "",
|
|
298
|
-
onChange: (e) => setStyle({ color: e.target.value })
|
|
299
|
-
}
|
|
300
|
-
),
|
|
301
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Font size (px)" }),
|
|
302
|
-
/* @__PURE__ */ jsx(
|
|
303
|
-
"input",
|
|
304
|
-
{
|
|
305
|
-
type: "number",
|
|
306
|
-
style: input,
|
|
307
|
-
value: s.fontSize?.value ?? "",
|
|
308
|
-
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
309
|
-
}
|
|
310
|
-
),
|
|
311
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Padding (px)" }),
|
|
312
|
-
/* @__PURE__ */ jsx(
|
|
313
|
-
"input",
|
|
314
|
-
{
|
|
315
|
-
type: "number",
|
|
316
|
-
style: input,
|
|
317
|
-
value: s.padding?.value ?? "",
|
|
318
|
-
onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
319
|
-
}
|
|
320
|
-
),
|
|
321
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Opacity" }),
|
|
322
|
-
/* @__PURE__ */ jsx(
|
|
323
|
-
"input",
|
|
324
|
-
{
|
|
325
|
-
type: "number",
|
|
326
|
-
step: "0.1",
|
|
327
|
-
min: "0",
|
|
328
|
-
max: "1",
|
|
329
|
-
style: input,
|
|
330
|
-
value: s.opacity ?? "",
|
|
331
|
-
onChange: (e) => setStyle({ opacity: Number(e.target.value) })
|
|
332
|
-
}
|
|
333
|
-
)
|
|
334
|
-
] });
|
|
335
|
-
}
|
|
336
|
-
function Editor({ defaultValue, onChange }) {
|
|
337
|
-
const ed = useEditor(defaultValue);
|
|
338
|
-
const first = useRef(true);
|
|
339
|
-
useEffect(() => {
|
|
340
|
-
if (first.current) {
|
|
341
|
-
first.current = false;
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
onChange?.(ed.state.doc);
|
|
345
|
-
}, [ed.state.doc, onChange]);
|
|
346
|
-
const shell = {
|
|
347
|
-
display: "grid",
|
|
348
|
-
gridTemplateColumns: "220px 1fr 300px",
|
|
349
|
-
height: "100%",
|
|
350
|
-
minHeight: 0,
|
|
351
|
-
background: "#fff",
|
|
352
|
-
color: "#0f172a",
|
|
353
|
-
border: "1px solid #e2e8f0",
|
|
354
|
-
borderRadius: 12,
|
|
355
|
-
overflow: "hidden"
|
|
356
|
-
};
|
|
357
|
-
return /* @__PURE__ */ jsxs("div", { style: shell, children: [
|
|
358
|
-
/* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
359
|
-
/* @__PURE__ */ jsx(Toolbar, { ed }),
|
|
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
|
-
)
|
|
369
|
-
] }),
|
|
370
|
-
/* @__PURE__ */ jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
|
|
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 }) })
|
|
372
|
-
] });
|
|
373
|
-
}
|
|
374
|
-
function Toolbar({ ed }) {
|
|
375
|
-
const btn = {
|
|
376
|
-
font: "inherit",
|
|
377
|
-
fontSize: 12,
|
|
378
|
-
padding: "4px 10px",
|
|
379
|
-
borderRadius: 6,
|
|
380
|
-
border: "1px solid #e2e8f0",
|
|
381
|
-
background: "#fff",
|
|
382
|
-
cursor: "pointer"
|
|
383
|
-
};
|
|
384
|
-
return /* @__PURE__ */ jsxs(
|
|
385
|
-
"div",
|
|
386
|
-
{
|
|
387
|
-
style: {
|
|
388
|
-
display: "flex",
|
|
389
|
-
gap: 6,
|
|
390
|
-
padding: 8,
|
|
391
|
-
borderBottom: "1px solid #e2e8f0",
|
|
392
|
-
fontFamily: "system-ui, sans-serif"
|
|
393
|
-
},
|
|
394
|
-
children: [
|
|
395
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: ed.canUndo ? 1 : 0.4 }, disabled: !ed.canUndo, onClick: ed.undo, children: "Undo" }),
|
|
396
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" })
|
|
397
|
-
]
|
|
398
|
-
}
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
|
|
8
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
|
|
402
9
|
function NodeInspector({
|
|
403
10
|
node,
|
|
404
11
|
transform,
|
|
@@ -412,9 +19,9 @@ function NodeInspector({
|
|
|
412
19
|
}) {
|
|
413
20
|
const base = node.style.base ?? {};
|
|
414
21
|
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
415
|
-
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
22
|
+
const handled = /* @__PURE__ */ new Set(["content", "label", "html"]);
|
|
416
23
|
const extraKeys = Object.keys(node.props).filter(
|
|
417
|
-
(k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
|
|
24
|
+
(k) => !handled.has(k) && (["string", "number", "boolean"].includes(typeof node.props[k]) || isBinding(node.props[k]))
|
|
418
25
|
);
|
|
419
26
|
return /* @__PURE__ */ jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
420
27
|
/* @__PURE__ */ jsxs("div", { style: headRow, children: [
|
|
@@ -427,15 +34,12 @@ function NodeInspector({
|
|
|
427
34
|
/* @__PURE__ */ jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
|
|
428
35
|
] }),
|
|
429
36
|
/* @__PURE__ */ jsxs("div", { style: body, children: [
|
|
430
|
-
"content" in node.props ? /* @__PURE__ */
|
|
431
|
-
"
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
) }) : null,
|
|
438
|
-
"label" in node.props ? /* @__PURE__ */ jsx(Group, { label: "Label", children: /* @__PURE__ */ jsx("input", { style: input2, value: String(node.props.label ?? ""), onChange: (e) => onProps({ label: e.target.value }) }) }) : null,
|
|
37
|
+
"content" in node.props || node.type === "richtext" ? /* @__PURE__ */ jsxs("p", { style: { fontSize: 11, color: "var(--fg-3, #94a3b8)", margin: "0 0 12px", lineHeight: 1.45, paddingBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
|
|
38
|
+
"\u270E Edit the text ",
|
|
39
|
+
/* @__PURE__ */ jsx("strong", { children: "directly on the page" }),
|
|
40
|
+
" \u2014 click in and type. This panel is for layout, style, and bindings."
|
|
41
|
+
] }) : null,
|
|
42
|
+
"label" in node.props ? /* @__PURE__ */ jsx(Group, { label: "Label", children: /* @__PURE__ */ jsx("input", { style: input, value: String(node.props.label ?? ""), onChange: (e) => onProps({ label: e.target.value }) }) }) : null,
|
|
439
43
|
extraKeys.length ? /* @__PURE__ */ jsx(Group, { label: "Properties", children: extraKeys.map((k) => /* @__PURE__ */ jsx(PropField, { name: k, value: node.props[k], onChange: (v) => onProps({ [k]: v }) }, k)) }) : null,
|
|
440
44
|
/* @__PURE__ */ jsxs(Group, { label: "Position & size", children: [
|
|
441
45
|
/* @__PURE__ */ jsxs(Row, { children: [
|
|
@@ -529,9 +133,9 @@ function NodeInspector({
|
|
|
529
133
|
] })
|
|
530
134
|
] });
|
|
531
135
|
}
|
|
532
|
-
function Group({ label
|
|
136
|
+
function Group({ label, children }) {
|
|
533
137
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
|
|
534
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children:
|
|
138
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label }),
|
|
535
139
|
children
|
|
536
140
|
] });
|
|
537
141
|
}
|
|
@@ -539,36 +143,62 @@ function Row({ children }) {
|
|
|
539
143
|
return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
540
144
|
}
|
|
541
145
|
function PropField({ name, value, onChange }) {
|
|
146
|
+
const bound = isBinding(value);
|
|
147
|
+
const link = /* @__PURE__ */ jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
type: "button",
|
|
151
|
+
title: bound ? "Unbind (use a literal value)" : "Bind to data (e.g. item.name)",
|
|
152
|
+
onClick: () => onChange(bound ? "" : { $bind: "" }),
|
|
153
|
+
style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 11, color: bound ? "#a78bfa" : "#64748b" },
|
|
154
|
+
children: "\u{1F517}"
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
if (bound) {
|
|
158
|
+
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
159
|
+
/* @__PURE__ */ jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
160
|
+
name,
|
|
161
|
+
" ",
|
|
162
|
+
link
|
|
163
|
+
] }),
|
|
164
|
+
/* @__PURE__ */ jsx("input", { style: { ...input, borderColor: "#7c3aed" }, placeholder: "data path \xB7 e.g. item.name", value: value.$bind, onChange: (e) => onChange({ $bind: e.target.value }) })
|
|
165
|
+
] });
|
|
166
|
+
}
|
|
542
167
|
if (typeof value === "boolean") {
|
|
543
168
|
return /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
544
169
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
545
|
-
/* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
170
|
+
/* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize", flex: 1 }, children: name }),
|
|
171
|
+
link
|
|
546
172
|
] });
|
|
547
173
|
}
|
|
548
174
|
if (name === "variant") {
|
|
549
|
-
return /* @__PURE__ */ jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
175
|
+
return /* @__PURE__ */ jsx(Sel, { label: name, value: String(value ?? ""), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
550
176
|
}
|
|
551
177
|
const isNum = typeof value === "number";
|
|
552
178
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
553
|
-
/* @__PURE__ */
|
|
179
|
+
/* @__PURE__ */ jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
180
|
+
name,
|
|
181
|
+
" ",
|
|
182
|
+
link
|
|
183
|
+
] }),
|
|
554
184
|
/* @__PURE__ */ jsx(
|
|
555
185
|
"input",
|
|
556
186
|
{
|
|
557
|
-
style:
|
|
187
|
+
style: input,
|
|
558
188
|
type: isNum ? "number" : "text",
|
|
559
|
-
value: String(value),
|
|
189
|
+
value: String(value ?? ""),
|
|
560
190
|
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
561
191
|
}
|
|
562
192
|
)
|
|
563
193
|
] });
|
|
564
194
|
}
|
|
565
|
-
function Num({ label
|
|
195
|
+
function Num({ label, value, placeholder, onChange, step, min, max }) {
|
|
566
196
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
567
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
197
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
|
|
568
198
|
/* @__PURE__ */ jsx(
|
|
569
199
|
"input",
|
|
570
200
|
{
|
|
571
|
-
style:
|
|
201
|
+
style: input,
|
|
572
202
|
type: "number",
|
|
573
203
|
step,
|
|
574
204
|
min,
|
|
@@ -580,22 +210,22 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
|
580
210
|
)
|
|
581
211
|
] });
|
|
582
212
|
}
|
|
583
|
-
function Txt({ label
|
|
213
|
+
function Txt({ label, value, placeholder, onChange }) {
|
|
584
214
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
585
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
586
|
-
/* @__PURE__ */ jsx("input", { style:
|
|
215
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
|
|
216
|
+
/* @__PURE__ */ jsx("input", { style: input, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
|
|
587
217
|
] });
|
|
588
218
|
}
|
|
589
|
-
function Color({ label
|
|
219
|
+
function Color({ label, value, onChange }) {
|
|
590
220
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
591
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
592
|
-
/* @__PURE__ */ jsx("input", { style: { ...
|
|
221
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
|
|
222
|
+
/* @__PURE__ */ jsx("input", { style: { ...input, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
|
|
593
223
|
] });
|
|
594
224
|
}
|
|
595
|
-
function Sel({ label
|
|
225
|
+
function Sel({ label, value, options, onChange }) {
|
|
596
226
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
597
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
598
|
-
/* @__PURE__ */ jsx("select", { style:
|
|
227
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
|
|
228
|
+
/* @__PURE__ */ jsx("select", { style: input, value, onChange: (e) => onChange(e.target.value), children: options.map(([v, l]) => /* @__PURE__ */ jsx("option", { value: v, children: l }, v)) })
|
|
599
229
|
] });
|
|
600
230
|
}
|
|
601
231
|
var len = (value) => ({ value, unit: "px" });
|
|
@@ -622,7 +252,7 @@ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12
|
|
|
622
252
|
var body = { padding: 12, overflowY: "auto" };
|
|
623
253
|
var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
|
|
624
254
|
var fieldLabel = { fontSize: 10, opacity: 0.6 };
|
|
625
|
-
var
|
|
255
|
+
var input = {
|
|
626
256
|
width: "100%",
|
|
627
257
|
boxSizing: "border-box",
|
|
628
258
|
background: "#0f172a",
|
|
@@ -740,9 +370,11 @@ function snapshotSubtree(doc, id) {
|
|
|
740
370
|
return { rootId: id, nodes };
|
|
741
371
|
}
|
|
742
372
|
var Z2 = 2147483e3;
|
|
373
|
+
var SNAP = 6;
|
|
743
374
|
function EditablePage({
|
|
744
375
|
doc,
|
|
745
376
|
registry = defaultRegistry,
|
|
377
|
+
data,
|
|
746
378
|
timelineDock,
|
|
747
379
|
transforms,
|
|
748
380
|
onNodeTransform,
|
|
@@ -799,7 +431,7 @@ function EditablePage({
|
|
|
799
431
|
}, [onProgress, frames]);
|
|
800
432
|
const measureBox = useCallback(() => {
|
|
801
433
|
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
802
|
-
const el = pageRef.current.querySelector(`[data-cms="${
|
|
434
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
803
435
|
if (!el) return setBox(null);
|
|
804
436
|
const r = el.getBoundingClientRect();
|
|
805
437
|
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
@@ -814,13 +446,17 @@ function EditablePage({
|
|
|
814
446
|
};
|
|
815
447
|
}, [measureBox, ed.state.doc]);
|
|
816
448
|
useEffect(() => {
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
if (!
|
|
449
|
+
const t = selNode?.type;
|
|
450
|
+
const editable = t === "text" || t === "heading" || t === "richtext";
|
|
451
|
+
if (!editing || !selection || !pageRef.current || !editable) return;
|
|
452
|
+
const wrapper = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
453
|
+
if (!wrapper) return;
|
|
454
|
+
const isRich = t === "richtext";
|
|
455
|
+
const el = (isRich ? wrapper.firstElementChild : wrapper) ?? wrapper;
|
|
820
456
|
el.setAttribute("contenteditable", "true");
|
|
821
457
|
el.style.outline = "none";
|
|
822
458
|
el.focus();
|
|
823
|
-
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
459
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: isRich ? { html: el.innerHTML } : { content: el.innerText } });
|
|
824
460
|
el.addEventListener("blur", commit);
|
|
825
461
|
return () => {
|
|
826
462
|
el.removeEventListener("blur", commit);
|
|
@@ -845,11 +481,84 @@ function EditablePage({
|
|
|
845
481
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
846
482
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
847
483
|
const def = ADD_DEFAULTS[kind];
|
|
848
|
-
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
484
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
849
485
|
ed.select(id);
|
|
850
486
|
},
|
|
851
487
|
[ed, selNode]
|
|
852
488
|
);
|
|
489
|
+
useCallback(
|
|
490
|
+
(kind, targetId) => {
|
|
491
|
+
const doc2 = ed.state.doc;
|
|
492
|
+
const target = targetId ? doc2.nodes[targetId] : null;
|
|
493
|
+
const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
|
|
494
|
+
const siblings = childrenOf(doc2, parent);
|
|
495
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
496
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
497
|
+
const def = ADD_DEFAULTS[kind];
|
|
498
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
499
|
+
ed.select(id);
|
|
500
|
+
},
|
|
501
|
+
[ed]
|
|
502
|
+
);
|
|
503
|
+
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
504
|
+
const [draggingEl, setDraggingEl] = useState(false);
|
|
505
|
+
useEffect(() => {
|
|
506
|
+
if (!editing) {
|
|
507
|
+
setPaletteOpen(false);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const onMove = (e) => {
|
|
511
|
+
if (draggingEl) return;
|
|
512
|
+
if (e.clientX >= window.innerWidth - 26) setPaletteOpen(true);
|
|
513
|
+
else if (e.clientX < window.innerWidth - 236) setPaletteOpen(false);
|
|
514
|
+
};
|
|
515
|
+
window.addEventListener("mousemove", onMove);
|
|
516
|
+
return () => window.removeEventListener("mousemove", onMove);
|
|
517
|
+
}, [editing, draggingEl]);
|
|
518
|
+
const [dropHint, setDropHint] = useState(null);
|
|
519
|
+
const computeDropTarget = (clientX, clientY) => {
|
|
520
|
+
const el = document.elementFromPoint(clientX, clientY)?.closest("[data-cms]");
|
|
521
|
+
if (!el) return { id: null, edge: "inside", rect: null };
|
|
522
|
+
const id = el.getAttribute("data-cms");
|
|
523
|
+
const node = ed.state.doc.nodes[id];
|
|
524
|
+
const r = el.getBoundingClientRect();
|
|
525
|
+
const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
|
|
526
|
+
if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
|
|
527
|
+
return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
|
|
528
|
+
};
|
|
529
|
+
const onPageDragOver = (e) => {
|
|
530
|
+
e.preventDefault();
|
|
531
|
+
e.dataTransfer.dropEffect = "copy";
|
|
532
|
+
setDropHint(computeDropTarget(e.clientX, e.clientY));
|
|
533
|
+
};
|
|
534
|
+
const insertRelative = (kind, hint) => {
|
|
535
|
+
const doc2 = ed.state.doc;
|
|
536
|
+
let parent;
|
|
537
|
+
let order;
|
|
538
|
+
if (!hint.id || hint.edge === "inside") {
|
|
539
|
+
parent = hint.id ?? (doc2.sections[doc2.sections.length - 1] ?? null);
|
|
540
|
+
const sib = childrenOf(doc2, parent);
|
|
541
|
+
order = keyBetween(sib.length ? sib[sib.length - 1].order : null, null);
|
|
542
|
+
} else {
|
|
543
|
+
const target = doc2.nodes[hint.id];
|
|
544
|
+
parent = target.parent;
|
|
545
|
+
const sib = childrenOf(doc2, parent);
|
|
546
|
+
const idx = sib.findIndex((n) => n.id === hint.id);
|
|
547
|
+
order = hint.edge === "before" ? keyBetween(idx > 0 ? sib[idx - 1].order : null, target.order) : keyBetween(target.order, idx < sib.length - 1 ? sib[idx + 1].order : null);
|
|
548
|
+
}
|
|
549
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
550
|
+
const def = ADD_DEFAULTS[kind];
|
|
551
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
552
|
+
ed.select(id);
|
|
553
|
+
};
|
|
554
|
+
const onPageDrop = (e) => {
|
|
555
|
+
const kind = e.dataTransfer.getData("application/x-cms-element");
|
|
556
|
+
if (!kind) return;
|
|
557
|
+
e.preventDefault();
|
|
558
|
+
insertRelative(kind, computeDropTarget(e.clientX, e.clientY));
|
|
559
|
+
setDropHint(null);
|
|
560
|
+
setDraggingEl(false);
|
|
561
|
+
};
|
|
853
562
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
854
563
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
855
564
|
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
@@ -875,7 +584,7 @@ function EditablePage({
|
|
|
875
584
|
const startEditText = (id) => {
|
|
876
585
|
if (!id) return;
|
|
877
586
|
ed.select(id);
|
|
878
|
-
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${
|
|
587
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape(id)}"]`)?.focus());
|
|
879
588
|
};
|
|
880
589
|
const nudgeRef = useRef({ transforms, onNodeTransform });
|
|
881
590
|
nudgeRef.current = { transforms, onNodeTransform };
|
|
@@ -917,8 +626,23 @@ function EditablePage({
|
|
|
917
626
|
if (el) ed.select(el.getAttribute("data-cms"));
|
|
918
627
|
else ed.select(null);
|
|
919
628
|
};
|
|
920
|
-
const page = /* @__PURE__ */ jsx(
|
|
921
|
-
|
|
629
|
+
const page = /* @__PURE__ */ jsx(
|
|
630
|
+
"div",
|
|
631
|
+
{
|
|
632
|
+
ref: pageRef,
|
|
633
|
+
onClickCapture,
|
|
634
|
+
onContextMenu: onContextMenuSelect,
|
|
635
|
+
onDragOver: editing ? onPageDragOver : void 0,
|
|
636
|
+
onDragLeave: editing ? () => setDropHint(null) : void 0,
|
|
637
|
+
onDrop: editing ? onPageDrop : void 0,
|
|
638
|
+
children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry, data })
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
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 }) }) : frames > 1 ? (
|
|
642
|
+
// Normal flow, but "expanded" — extra scroll length below the content so a
|
|
643
|
+
// page can be made longer than its content (the + Frame control).
|
|
644
|
+
/* @__PURE__ */ jsx("div", { style: { minHeight: `${frames * 100}vh` }, children: page })
|
|
645
|
+
) : page;
|
|
922
646
|
return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
923
647
|
editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
924
648
|
/* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
|
|
@@ -945,7 +669,7 @@ function EditablePage({
|
|
|
945
669
|
box,
|
|
946
670
|
base: transforms?.[selection] ?? {},
|
|
947
671
|
movable: Boolean(onNodeTransform),
|
|
948
|
-
getEl: () => pageRef.current?.querySelector(`[data-cms="${
|
|
672
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape(selection)}"]`) ?? null,
|
|
949
673
|
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
950
674
|
onLive: measureBox
|
|
951
675
|
}
|
|
@@ -980,16 +704,101 @@ function EditablePage({
|
|
|
980
704
|
onAdd: addNode
|
|
981
705
|
}
|
|
982
706
|
) : null,
|
|
707
|
+
editing && dropHint?.rect ? /* @__PURE__ */ jsx(DropIndicator, { hint: dropHint }) : null,
|
|
708
|
+
editing ? /* @__PURE__ */ jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
|
|
983
709
|
editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
984
710
|
] });
|
|
985
711
|
}
|
|
986
|
-
|
|
712
|
+
function DropIndicator({
|
|
713
|
+
hint
|
|
714
|
+
}) {
|
|
715
|
+
const r = hint.rect;
|
|
716
|
+
if (!r) return null;
|
|
717
|
+
const color = "#ec4899";
|
|
718
|
+
if (hint.edge === "inside") {
|
|
719
|
+
return /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: r.x, top: r.y, width: r.w, height: r.h, outline: `2px dashed ${color}`, outlineOffset: -2, borderRadius: 6, pointerEvents: "none", zIndex: Z2 + 4 } });
|
|
720
|
+
}
|
|
721
|
+
const y = hint.edge === "before" ? r.y : r.y + r.h;
|
|
722
|
+
return /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: r.x, top: y - 1.5, width: r.w, height: 3, background: color, borderRadius: 2, boxShadow: "0 0 0 1px #fff", pointerEvents: "none", zIndex: Z2 + 4 } });
|
|
723
|
+
}
|
|
724
|
+
function ElementPalette({ open, onDragChange }) {
|
|
725
|
+
return /* @__PURE__ */ jsxs(
|
|
726
|
+
"div",
|
|
727
|
+
{
|
|
728
|
+
style: {
|
|
729
|
+
position: "fixed",
|
|
730
|
+
top: 64,
|
|
731
|
+
bottom: 96,
|
|
732
|
+
right: 0,
|
|
733
|
+
width: 216,
|
|
734
|
+
transform: open ? "translateX(0)" : "translateX(100%)",
|
|
735
|
+
transition: "transform 0.18s ease",
|
|
736
|
+
background: "#0b1220",
|
|
737
|
+
color: "#e2e8f0",
|
|
738
|
+
borderLeft: "1px solid #1e293b",
|
|
739
|
+
borderTopLeftRadius: 12,
|
|
740
|
+
borderBottomLeftRadius: 12,
|
|
741
|
+
boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
|
|
742
|
+
zIndex: Z2 + 2,
|
|
743
|
+
fontFamily: "system-ui, sans-serif",
|
|
744
|
+
padding: 12,
|
|
745
|
+
overflowY: "auto"
|
|
746
|
+
},
|
|
747
|
+
children: [
|
|
748
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5, marginBottom: 10 }, children: "Elements" }),
|
|
749
|
+
/* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxs(
|
|
750
|
+
"div",
|
|
751
|
+
{
|
|
752
|
+
draggable: true,
|
|
753
|
+
onDragStart: (e) => {
|
|
754
|
+
e.dataTransfer.setData("application/x-cms-element", m.kind);
|
|
755
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
756
|
+
onDragChange(true);
|
|
757
|
+
},
|
|
758
|
+
onDragEnd: () => onDragChange(false),
|
|
759
|
+
title: `Drag "${m.label}" onto the page`,
|
|
760
|
+
style: {
|
|
761
|
+
display: "flex",
|
|
762
|
+
flexDirection: "column",
|
|
763
|
+
alignItems: "center",
|
|
764
|
+
justifyContent: "center",
|
|
765
|
+
gap: 4,
|
|
766
|
+
height: 56,
|
|
767
|
+
borderRadius: 8,
|
|
768
|
+
background: "#0f172a",
|
|
769
|
+
border: "1px solid #1e293b",
|
|
770
|
+
cursor: "grab",
|
|
771
|
+
fontSize: 12,
|
|
772
|
+
userSelect: "none"
|
|
773
|
+
},
|
|
774
|
+
children: [
|
|
775
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 16, opacity: 0.7 }, children: "\uFF0B" }),
|
|
776
|
+
m.label
|
|
777
|
+
]
|
|
778
|
+
},
|
|
779
|
+
m.kind
|
|
780
|
+
)) }),
|
|
781
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: 10.5, opacity: 0.45, marginTop: 12, lineHeight: 1.4 }, children: "Drag an Element onto the page. It drops into the container you release over." })
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
|
|
987
787
|
var ADD_DEFAULTS = {
|
|
988
788
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
989
789
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
990
790
|
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
791
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
991
792
|
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
992
|
-
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
|
|
793
|
+
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
|
|
794
|
+
stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
|
|
795
|
+
grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
|
|
796
|
+
card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
|
|
797
|
+
callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
|
|
798
|
+
divider: { type: "divider", props: {}, style: {} },
|
|
799
|
+
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
|
|
800
|
+
richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
|
|
801
|
+
repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
|
|
993
802
|
};
|
|
994
803
|
var GRIPS = [
|
|
995
804
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -1010,20 +819,55 @@ function SelectionOverlay({
|
|
|
1010
819
|
onLive
|
|
1011
820
|
}) {
|
|
1012
821
|
const drag = useRef(null);
|
|
822
|
+
const targets = useRef({ vx: [], hy: [] });
|
|
823
|
+
const [guides, setGuides] = useState({ x: null, y: null });
|
|
1013
824
|
const apply = (t) => {
|
|
1014
825
|
const el = getEl();
|
|
1015
826
|
if (el) applyTransform(el, t);
|
|
1016
827
|
};
|
|
828
|
+
const snap = (cands, lines) => {
|
|
829
|
+
let best = { delta: 0, at: null, dist: SNAP + 1 };
|
|
830
|
+
for (const line of lines) {
|
|
831
|
+
for (const c of cands) {
|
|
832
|
+
const diff = line - c;
|
|
833
|
+
if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
|
|
837
|
+
};
|
|
1017
838
|
const startMove = (e) => {
|
|
1018
839
|
e.stopPropagation();
|
|
1019
840
|
e.preventDefault();
|
|
1020
841
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1021
842
|
drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
|
|
843
|
+
const sel = getEl();
|
|
844
|
+
const vx = /* @__PURE__ */ new Set();
|
|
845
|
+
const hy = /* @__PURE__ */ new Set();
|
|
846
|
+
document.querySelectorAll("[data-cms]").forEach((el) => {
|
|
847
|
+
if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
|
|
848
|
+
const r = el.getBoundingClientRect();
|
|
849
|
+
vx.add(r.left).add(r.left + r.width / 2).add(r.right);
|
|
850
|
+
hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
|
|
851
|
+
});
|
|
852
|
+
targets.current = { vx: [...vx], hy: [...hy] };
|
|
1022
853
|
};
|
|
1023
854
|
const moveMove = (e) => {
|
|
1024
855
|
const d = drag.current;
|
|
1025
856
|
if (!d) return;
|
|
1026
|
-
|
|
857
|
+
let dx = e.clientX - d.x;
|
|
858
|
+
let dy = e.clientY - d.y;
|
|
859
|
+
if (!e.altKey) {
|
|
860
|
+
const l = box.x + dx;
|
|
861
|
+
const t = box.y + dy;
|
|
862
|
+
const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
|
|
863
|
+
const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
|
|
864
|
+
dx += sx.delta;
|
|
865
|
+
dy += sy.delta;
|
|
866
|
+
setGuides({ x: sx.at, y: sy.at });
|
|
867
|
+
} else if (guides.x !== null || guides.y !== null) {
|
|
868
|
+
setGuides({ x: null, y: null });
|
|
869
|
+
}
|
|
870
|
+
d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
|
|
1027
871
|
apply(d.live);
|
|
1028
872
|
onLive();
|
|
1029
873
|
};
|
|
@@ -1058,6 +902,7 @@ function SelectionOverlay({
|
|
|
1058
902
|
const d = drag.current;
|
|
1059
903
|
if (d) onTransform(d.live);
|
|
1060
904
|
drag.current = null;
|
|
905
|
+
setGuides({ x: null, y: null });
|
|
1061
906
|
};
|
|
1062
907
|
const outline = {
|
|
1063
908
|
position: "fixed",
|
|
@@ -1086,9 +931,11 @@ function SelectionOverlay({
|
|
|
1086
931
|
const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
|
|
1087
932
|
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
1088
933
|
};
|
|
1089
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
934
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
935
|
+
guides.x !== null ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: guides.x, top: 0, bottom: 0, width: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
|
|
936
|
+
guides.y !== null ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", top: guides.y, left: 0, right: 0, height: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
|
|
1090
937
|
/* @__PURE__ */ jsx("div", { style: outline }),
|
|
1091
|
-
movable ? /* @__PURE__ */ jsxs(Fragment
|
|
938
|
+
movable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1092
939
|
/* @__PURE__ */ jsx(
|
|
1093
940
|
"div",
|
|
1094
941
|
{
|
|
@@ -1151,11 +998,20 @@ function FloatingToolbar({
|
|
|
1151
998
|
] });
|
|
1152
999
|
}
|
|
1153
1000
|
var ADD_MENU = [
|
|
1154
|
-
{ kind: "text", label: "Text" },
|
|
1155
1001
|
{ kind: "heading", label: "Heading" },
|
|
1002
|
+
{ kind: "text", label: "Text" },
|
|
1156
1003
|
{ kind: "button", label: "Button" },
|
|
1004
|
+
{ kind: "link", label: "Link" },
|
|
1157
1005
|
{ kind: "image", label: "Image" },
|
|
1158
|
-
{ kind: "
|
|
1006
|
+
{ kind: "card", label: "Card" },
|
|
1007
|
+
{ kind: "callout", label: "Callout" },
|
|
1008
|
+
{ kind: "stack", label: "Stack" },
|
|
1009
|
+
{ kind: "grid", label: "Grid" },
|
|
1010
|
+
{ kind: "box", label: "Box" },
|
|
1011
|
+
{ kind: "divider", label: "Divider" },
|
|
1012
|
+
{ kind: "code", label: "Code" },
|
|
1013
|
+
{ kind: "richtext", label: "Rich text" },
|
|
1014
|
+
{ kind: "repeater", label: "Repeater" }
|
|
1159
1015
|
];
|
|
1160
1016
|
function EditBar({
|
|
1161
1017
|
editing,
|
|
@@ -1171,9 +1027,9 @@ function EditBar({
|
|
|
1171
1027
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
1172
1028
|
return /* @__PURE__ */ jsxs("div", { style: bar, children: [
|
|
1173
1029
|
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
1174
|
-
editing ? /* @__PURE__ */ jsxs(Fragment
|
|
1030
|
+
editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1175
1031
|
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
1176
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an
|
|
1032
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an Element (or drag from the right-edge palette)", children: "\uFF0B Element \u25BE" }),
|
|
1177
1033
|
open ? /* @__PURE__ */ 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__ */ jsx(
|
|
1178
1034
|
"button",
|
|
1179
1035
|
{
|
|
@@ -1208,13 +1064,13 @@ function EditMenuItems({
|
|
|
1208
1064
|
}) {
|
|
1209
1065
|
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1210
1066
|
const nested = Boolean(node && node.parent !== null);
|
|
1211
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
1067
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1212
1068
|
editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1213
1069
|
/* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
|
|
1214
|
-
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1070
|
+
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
|
|
1215
1071
|
/* @__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
1072
|
] }),
|
|
1217
|
-
node ? /* @__PURE__ */ jsxs(Fragment
|
|
1073
|
+
node ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1218
1074
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1219
1075
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1220
1076
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
@@ -1223,7 +1079,7 @@ function EditMenuItems({
|
|
|
1223
1079
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1224
1080
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1225
1081
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1226
|
-
nested ? /* @__PURE__ */ jsxs(Fragment
|
|
1082
|
+
nested ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1227
1083
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1228
1084
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1229
1085
|
] }) : null,
|
|
@@ -1248,10 +1104,10 @@ function applyTransform(el, t) {
|
|
|
1248
1104
|
function normalizeColor(v) {
|
|
1249
1105
|
return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
1250
1106
|
}
|
|
1251
|
-
function
|
|
1107
|
+
function cssEscape(value) {
|
|
1252
1108
|
return value.replace(/["\\]/g, "\\$&");
|
|
1253
1109
|
}
|
|
1254
1110
|
|
|
1255
|
-
export {
|
|
1111
|
+
export { EditablePage, NodeInspector };
|
|
1256
1112
|
//# sourceMappingURL=editor.js.map
|
|
1257
1113
|
//# sourceMappingURL=editor.js.map
|