@particle-academy/fancy-cms-ui 0.0.1-dev.9 → 0.1.1
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-524NYOHK.js +450 -0
- package/dist/chunk-524NYOHK.js.map +1 -0
- package/dist/{chunk-3YZMELJJ.js → chunk-GOXVGEKU.js} +94 -3
- package/dist/chunk-GOXVGEKU.js.map +1 -0
- package/dist/chunk-QYNWSECG.js +19 -0
- package/dist/chunk-QYNWSECG.js.map +1 -0
- package/dist/editor.cjs +338 -60
- 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 +318 -459
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +516 -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 +7 -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, useLatestRef, keyBetween } from './chunk-524NYOHK.js';
|
|
2
|
+
export { Canvas, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor } from './chunk-524NYOHK.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,
|
|
@@ -759,7 +391,8 @@ function EditablePage({
|
|
|
759
391
|
const [box, setBox] = useState(null);
|
|
760
392
|
const { selection } = ed.state;
|
|
761
393
|
const selNode = selection ? ed.state.doc.nodes[selection] : null;
|
|
762
|
-
|
|
394
|
+
const onSelectRef = useLatestRef(onSelect);
|
|
395
|
+
useEffect(() => onSelectRef.current?.(selection), [selection, onSelectRef]);
|
|
763
396
|
useEffect(() => {
|
|
764
397
|
const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
|
|
765
398
|
const onBlur = () => setRevealed(false);
|
|
@@ -781,13 +414,15 @@ function EditablePage({
|
|
|
781
414
|
applyTransform(el, t);
|
|
782
415
|
});
|
|
783
416
|
}, [transforms, ed.state.doc]);
|
|
417
|
+
const onProgressRef = useLatestRef(onProgress);
|
|
418
|
+
const hasProgress = !!onProgress;
|
|
784
419
|
useEffect(() => {
|
|
785
420
|
const spacer = spacerRef.current;
|
|
786
|
-
if (!spacer || !
|
|
421
|
+
if (!spacer || !hasProgress) return;
|
|
787
422
|
const compute = () => {
|
|
788
423
|
const len2 = spacer.offsetHeight - window.innerHeight;
|
|
789
424
|
const top = spacer.getBoundingClientRect().top;
|
|
790
|
-
|
|
425
|
+
onProgressRef.current?.(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
|
|
791
426
|
};
|
|
792
427
|
compute();
|
|
793
428
|
window.addEventListener("scroll", compute, { passive: true });
|
|
@@ -796,10 +431,10 @@ function EditablePage({
|
|
|
796
431
|
window.removeEventListener("scroll", compute);
|
|
797
432
|
window.removeEventListener("resize", compute);
|
|
798
433
|
};
|
|
799
|
-
}, [
|
|
434
|
+
}, [hasProgress, onProgressRef, frames]);
|
|
800
435
|
const measureBox = useCallback(() => {
|
|
801
436
|
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
802
|
-
const el = pageRef.current.querySelector(`[data-cms="${
|
|
437
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
803
438
|
if (!el) return setBox(null);
|
|
804
439
|
const r = el.getBoundingClientRect();
|
|
805
440
|
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
@@ -814,13 +449,17 @@ function EditablePage({
|
|
|
814
449
|
};
|
|
815
450
|
}, [measureBox, ed.state.doc]);
|
|
816
451
|
useEffect(() => {
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
if (!
|
|
452
|
+
const t = selNode?.type;
|
|
453
|
+
const editable = t === "text" || t === "heading" || t === "richtext";
|
|
454
|
+
if (!editing || !selection || !pageRef.current || !editable) return;
|
|
455
|
+
const wrapper = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
456
|
+
if (!wrapper) return;
|
|
457
|
+
const isRich = t === "richtext";
|
|
458
|
+
const el = (isRich ? wrapper.firstElementChild : wrapper) ?? wrapper;
|
|
820
459
|
el.setAttribute("contenteditable", "true");
|
|
821
460
|
el.style.outline = "none";
|
|
822
461
|
el.focus();
|
|
823
|
-
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
462
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: isRich ? { html: el.innerHTML } : { content: el.innerText } });
|
|
824
463
|
el.addEventListener("blur", commit);
|
|
825
464
|
return () => {
|
|
826
465
|
el.removeEventListener("blur", commit);
|
|
@@ -845,11 +484,84 @@ function EditablePage({
|
|
|
845
484
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
846
485
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
847
486
|
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 } } } });
|
|
487
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
849
488
|
ed.select(id);
|
|
850
489
|
},
|
|
851
490
|
[ed, selNode]
|
|
852
491
|
);
|
|
492
|
+
useCallback(
|
|
493
|
+
(kind, targetId) => {
|
|
494
|
+
const doc2 = ed.state.doc;
|
|
495
|
+
const target = targetId ? doc2.nodes[targetId] : null;
|
|
496
|
+
const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
|
|
497
|
+
const siblings = childrenOf(doc2, parent);
|
|
498
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
499
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
500
|
+
const def = ADD_DEFAULTS[kind];
|
|
501
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
502
|
+
ed.select(id);
|
|
503
|
+
},
|
|
504
|
+
[ed]
|
|
505
|
+
);
|
|
506
|
+
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
507
|
+
const [draggingEl, setDraggingEl] = useState(false);
|
|
508
|
+
useEffect(() => {
|
|
509
|
+
if (!editing) {
|
|
510
|
+
setPaletteOpen(false);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const onMove = (e) => {
|
|
514
|
+
if (draggingEl) return;
|
|
515
|
+
if (e.clientX >= window.innerWidth - 26) setPaletteOpen(true);
|
|
516
|
+
else if (e.clientX < window.innerWidth - 236) setPaletteOpen(false);
|
|
517
|
+
};
|
|
518
|
+
window.addEventListener("mousemove", onMove);
|
|
519
|
+
return () => window.removeEventListener("mousemove", onMove);
|
|
520
|
+
}, [editing, draggingEl]);
|
|
521
|
+
const [dropHint, setDropHint] = useState(null);
|
|
522
|
+
const computeDropTarget = (clientX, clientY) => {
|
|
523
|
+
const el = document.elementFromPoint(clientX, clientY)?.closest("[data-cms]");
|
|
524
|
+
if (!el) return { id: null, edge: "inside", rect: null };
|
|
525
|
+
const id = el.getAttribute("data-cms");
|
|
526
|
+
const node = ed.state.doc.nodes[id];
|
|
527
|
+
const r = el.getBoundingClientRect();
|
|
528
|
+
const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
|
|
529
|
+
if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
|
|
530
|
+
return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
|
|
531
|
+
};
|
|
532
|
+
const onPageDragOver = (e) => {
|
|
533
|
+
e.preventDefault();
|
|
534
|
+
e.dataTransfer.dropEffect = "copy";
|
|
535
|
+
setDropHint(computeDropTarget(e.clientX, e.clientY));
|
|
536
|
+
};
|
|
537
|
+
const insertRelative = (kind, hint) => {
|
|
538
|
+
const doc2 = ed.state.doc;
|
|
539
|
+
let parent;
|
|
540
|
+
let order;
|
|
541
|
+
if (!hint.id || hint.edge === "inside") {
|
|
542
|
+
parent = hint.id ?? (doc2.sections[doc2.sections.length - 1] ?? null);
|
|
543
|
+
const sib = childrenOf(doc2, parent);
|
|
544
|
+
order = keyBetween(sib.length ? sib[sib.length - 1].order : null, null);
|
|
545
|
+
} else {
|
|
546
|
+
const target = doc2.nodes[hint.id];
|
|
547
|
+
parent = target.parent;
|
|
548
|
+
const sib = childrenOf(doc2, parent);
|
|
549
|
+
const idx = sib.findIndex((n) => n.id === hint.id);
|
|
550
|
+
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);
|
|
551
|
+
}
|
|
552
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
553
|
+
const def = ADD_DEFAULTS[kind];
|
|
554
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
555
|
+
ed.select(id);
|
|
556
|
+
};
|
|
557
|
+
const onPageDrop = (e) => {
|
|
558
|
+
const kind = e.dataTransfer.getData("application/x-cms-element");
|
|
559
|
+
if (!kind) return;
|
|
560
|
+
e.preventDefault();
|
|
561
|
+
insertRelative(kind, computeDropTarget(e.clientX, e.clientY));
|
|
562
|
+
setDropHint(null);
|
|
563
|
+
setDraggingEl(false);
|
|
564
|
+
};
|
|
853
565
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
854
566
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
855
567
|
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
@@ -875,7 +587,7 @@ function EditablePage({
|
|
|
875
587
|
const startEditText = (id) => {
|
|
876
588
|
if (!id) return;
|
|
877
589
|
ed.select(id);
|
|
878
|
-
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${
|
|
590
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape(id)}"]`)?.focus());
|
|
879
591
|
};
|
|
880
592
|
const nudgeRef = useRef({ transforms, onNodeTransform });
|
|
881
593
|
nudgeRef.current = { transforms, onNodeTransform };
|
|
@@ -917,8 +629,23 @@ function EditablePage({
|
|
|
917
629
|
if (el) ed.select(el.getAttribute("data-cms"));
|
|
918
630
|
else ed.select(null);
|
|
919
631
|
};
|
|
920
|
-
const page = /* @__PURE__ */ jsx(
|
|
921
|
-
|
|
632
|
+
const page = /* @__PURE__ */ jsx(
|
|
633
|
+
"div",
|
|
634
|
+
{
|
|
635
|
+
ref: pageRef,
|
|
636
|
+
onClickCapture,
|
|
637
|
+
onContextMenu: onContextMenuSelect,
|
|
638
|
+
onDragOver: editing ? onPageDragOver : void 0,
|
|
639
|
+
onDragLeave: editing ? () => setDropHint(null) : void 0,
|
|
640
|
+
onDrop: editing ? onPageDrop : void 0,
|
|
641
|
+
children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry, data })
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
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 ? (
|
|
645
|
+
// Normal flow, but "expanded" — extra scroll length below the content so a
|
|
646
|
+
// page can be made longer than its content (the + Frame control).
|
|
647
|
+
/* @__PURE__ */ jsx("div", { style: { minHeight: `${frames * 100}vh` }, children: page })
|
|
648
|
+
) : page;
|
|
922
649
|
return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
923
650
|
editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
924
651
|
/* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
|
|
@@ -945,7 +672,7 @@ function EditablePage({
|
|
|
945
672
|
box,
|
|
946
673
|
base: transforms?.[selection] ?? {},
|
|
947
674
|
movable: Boolean(onNodeTransform),
|
|
948
|
-
getEl: () => pageRef.current?.querySelector(`[data-cms="${
|
|
675
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape(selection)}"]`) ?? null,
|
|
949
676
|
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
950
677
|
onLive: measureBox
|
|
951
678
|
}
|
|
@@ -980,16 +707,101 @@ function EditablePage({
|
|
|
980
707
|
onAdd: addNode
|
|
981
708
|
}
|
|
982
709
|
) : null,
|
|
710
|
+
editing && dropHint?.rect ? /* @__PURE__ */ jsx(DropIndicator, { hint: dropHint }) : null,
|
|
711
|
+
editing ? /* @__PURE__ */ jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
|
|
983
712
|
editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
984
713
|
] });
|
|
985
714
|
}
|
|
986
|
-
|
|
715
|
+
function DropIndicator({
|
|
716
|
+
hint
|
|
717
|
+
}) {
|
|
718
|
+
const r = hint.rect;
|
|
719
|
+
if (!r) return null;
|
|
720
|
+
const color = "#ec4899";
|
|
721
|
+
if (hint.edge === "inside") {
|
|
722
|
+
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 } });
|
|
723
|
+
}
|
|
724
|
+
const y = hint.edge === "before" ? r.y : r.y + r.h;
|
|
725
|
+
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 } });
|
|
726
|
+
}
|
|
727
|
+
function ElementPalette({ open, onDragChange }) {
|
|
728
|
+
return /* @__PURE__ */ jsxs(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
style: {
|
|
732
|
+
position: "fixed",
|
|
733
|
+
top: 64,
|
|
734
|
+
bottom: 96,
|
|
735
|
+
right: 0,
|
|
736
|
+
width: 216,
|
|
737
|
+
transform: open ? "translateX(0)" : "translateX(100%)",
|
|
738
|
+
transition: "transform 0.18s ease",
|
|
739
|
+
background: "#0b1220",
|
|
740
|
+
color: "#e2e8f0",
|
|
741
|
+
borderLeft: "1px solid #1e293b",
|
|
742
|
+
borderTopLeftRadius: 12,
|
|
743
|
+
borderBottomLeftRadius: 12,
|
|
744
|
+
boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
|
|
745
|
+
zIndex: Z2 + 2,
|
|
746
|
+
fontFamily: "system-ui, sans-serif",
|
|
747
|
+
padding: 12,
|
|
748
|
+
overflowY: "auto"
|
|
749
|
+
},
|
|
750
|
+
children: [
|
|
751
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5, marginBottom: 10 }, children: "Elements" }),
|
|
752
|
+
/* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxs(
|
|
753
|
+
"div",
|
|
754
|
+
{
|
|
755
|
+
draggable: true,
|
|
756
|
+
onDragStart: (e) => {
|
|
757
|
+
e.dataTransfer.setData("application/x-cms-element", m.kind);
|
|
758
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
759
|
+
onDragChange(true);
|
|
760
|
+
},
|
|
761
|
+
onDragEnd: () => onDragChange(false),
|
|
762
|
+
title: `Drag "${m.label}" onto the page`,
|
|
763
|
+
style: {
|
|
764
|
+
display: "flex",
|
|
765
|
+
flexDirection: "column",
|
|
766
|
+
alignItems: "center",
|
|
767
|
+
justifyContent: "center",
|
|
768
|
+
gap: 4,
|
|
769
|
+
height: 56,
|
|
770
|
+
borderRadius: 8,
|
|
771
|
+
background: "#0f172a",
|
|
772
|
+
border: "1px solid #1e293b",
|
|
773
|
+
cursor: "grab",
|
|
774
|
+
fontSize: 12,
|
|
775
|
+
userSelect: "none"
|
|
776
|
+
},
|
|
777
|
+
children: [
|
|
778
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 16, opacity: 0.7 }, children: "\uFF0B" }),
|
|
779
|
+
m.label
|
|
780
|
+
]
|
|
781
|
+
},
|
|
782
|
+
m.kind
|
|
783
|
+
)) }),
|
|
784
|
+
/* @__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." })
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
|
|
987
790
|
var ADD_DEFAULTS = {
|
|
988
791
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
989
792
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
990
793
|
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
794
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
991
795
|
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
992
|
-
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
|
|
796
|
+
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
|
|
797
|
+
stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
|
|
798
|
+
grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
|
|
799
|
+
card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
|
|
800
|
+
callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
|
|
801
|
+
divider: { type: "divider", props: {}, style: {} },
|
|
802
|
+
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
|
|
803
|
+
richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
|
|
804
|
+
repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
|
|
993
805
|
};
|
|
994
806
|
var GRIPS = [
|
|
995
807
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -1010,20 +822,55 @@ function SelectionOverlay({
|
|
|
1010
822
|
onLive
|
|
1011
823
|
}) {
|
|
1012
824
|
const drag = useRef(null);
|
|
825
|
+
const targets = useRef({ vx: [], hy: [] });
|
|
826
|
+
const [guides, setGuides] = useState({ x: null, y: null });
|
|
1013
827
|
const apply = (t) => {
|
|
1014
828
|
const el = getEl();
|
|
1015
829
|
if (el) applyTransform(el, t);
|
|
1016
830
|
};
|
|
831
|
+
const snap = (cands, lines) => {
|
|
832
|
+
let best = { delta: 0, at: null, dist: SNAP + 1 };
|
|
833
|
+
for (const line of lines) {
|
|
834
|
+
for (const c of cands) {
|
|
835
|
+
const diff = line - c;
|
|
836
|
+
if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
|
|
840
|
+
};
|
|
1017
841
|
const startMove = (e) => {
|
|
1018
842
|
e.stopPropagation();
|
|
1019
843
|
e.preventDefault();
|
|
1020
844
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1021
845
|
drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
|
|
846
|
+
const sel = getEl();
|
|
847
|
+
const vx = /* @__PURE__ */ new Set();
|
|
848
|
+
const hy = /* @__PURE__ */ new Set();
|
|
849
|
+
document.querySelectorAll("[data-cms]").forEach((el) => {
|
|
850
|
+
if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
|
|
851
|
+
const r = el.getBoundingClientRect();
|
|
852
|
+
vx.add(r.left).add(r.left + r.width / 2).add(r.right);
|
|
853
|
+
hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
|
|
854
|
+
});
|
|
855
|
+
targets.current = { vx: [...vx], hy: [...hy] };
|
|
1022
856
|
};
|
|
1023
857
|
const moveMove = (e) => {
|
|
1024
858
|
const d = drag.current;
|
|
1025
859
|
if (!d) return;
|
|
1026
|
-
|
|
860
|
+
let dx = e.clientX - d.x;
|
|
861
|
+
let dy = e.clientY - d.y;
|
|
862
|
+
if (!e.altKey) {
|
|
863
|
+
const l = box.x + dx;
|
|
864
|
+
const t = box.y + dy;
|
|
865
|
+
const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
|
|
866
|
+
const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
|
|
867
|
+
dx += sx.delta;
|
|
868
|
+
dy += sy.delta;
|
|
869
|
+
setGuides({ x: sx.at, y: sy.at });
|
|
870
|
+
} else if (guides.x !== null || guides.y !== null) {
|
|
871
|
+
setGuides({ x: null, y: null });
|
|
872
|
+
}
|
|
873
|
+
d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
|
|
1027
874
|
apply(d.live);
|
|
1028
875
|
onLive();
|
|
1029
876
|
};
|
|
@@ -1058,6 +905,7 @@ function SelectionOverlay({
|
|
|
1058
905
|
const d = drag.current;
|
|
1059
906
|
if (d) onTransform(d.live);
|
|
1060
907
|
drag.current = null;
|
|
908
|
+
setGuides({ x: null, y: null });
|
|
1061
909
|
};
|
|
1062
910
|
const outline = {
|
|
1063
911
|
position: "fixed",
|
|
@@ -1086,9 +934,11 @@ function SelectionOverlay({
|
|
|
1086
934
|
const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
|
|
1087
935
|
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
1088
936
|
};
|
|
1089
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
937
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
938
|
+
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,
|
|
939
|
+
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
940
|
/* @__PURE__ */ jsx("div", { style: outline }),
|
|
1091
|
-
movable ? /* @__PURE__ */ jsxs(Fragment
|
|
941
|
+
movable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1092
942
|
/* @__PURE__ */ jsx(
|
|
1093
943
|
"div",
|
|
1094
944
|
{
|
|
@@ -1151,11 +1001,20 @@ function FloatingToolbar({
|
|
|
1151
1001
|
] });
|
|
1152
1002
|
}
|
|
1153
1003
|
var ADD_MENU = [
|
|
1154
|
-
{ kind: "text", label: "Text" },
|
|
1155
1004
|
{ kind: "heading", label: "Heading" },
|
|
1005
|
+
{ kind: "text", label: "Text" },
|
|
1156
1006
|
{ kind: "button", label: "Button" },
|
|
1007
|
+
{ kind: "link", label: "Link" },
|
|
1157
1008
|
{ kind: "image", label: "Image" },
|
|
1158
|
-
{ kind: "
|
|
1009
|
+
{ kind: "card", label: "Card" },
|
|
1010
|
+
{ kind: "callout", label: "Callout" },
|
|
1011
|
+
{ kind: "stack", label: "Stack" },
|
|
1012
|
+
{ kind: "grid", label: "Grid" },
|
|
1013
|
+
{ kind: "box", label: "Box" },
|
|
1014
|
+
{ kind: "divider", label: "Divider" },
|
|
1015
|
+
{ kind: "code", label: "Code" },
|
|
1016
|
+
{ kind: "richtext", label: "Rich text" },
|
|
1017
|
+
{ kind: "repeater", label: "Repeater" }
|
|
1159
1018
|
];
|
|
1160
1019
|
function EditBar({
|
|
1161
1020
|
editing,
|
|
@@ -1171,9 +1030,9 @@ function EditBar({
|
|
|
1171
1030
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
1172
1031
|
return /* @__PURE__ */ jsxs("div", { style: bar, children: [
|
|
1173
1032
|
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
1174
|
-
editing ? /* @__PURE__ */ jsxs(Fragment
|
|
1033
|
+
editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1175
1034
|
/* @__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
|
|
1035
|
+
/* @__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
1036
|
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
1037
|
"button",
|
|
1179
1038
|
{
|
|
@@ -1208,13 +1067,13 @@ function EditMenuItems({
|
|
|
1208
1067
|
}) {
|
|
1209
1068
|
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1210
1069
|
const nested = Boolean(node && node.parent !== null);
|
|
1211
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
1070
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1212
1071
|
editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1213
1072
|
/* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
|
|
1214
|
-
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1073
|
+
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
|
|
1215
1074
|
/* @__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
1075
|
] }),
|
|
1217
|
-
node ? /* @__PURE__ */ jsxs(Fragment
|
|
1076
|
+
node ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1218
1077
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1219
1078
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1220
1079
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
@@ -1223,7 +1082,7 @@ function EditMenuItems({
|
|
|
1223
1082
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1224
1083
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1225
1084
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1226
|
-
nested ? /* @__PURE__ */ jsxs(Fragment
|
|
1085
|
+
nested ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1227
1086
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1228
1087
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1229
1088
|
] }) : null,
|
|
@@ -1248,10 +1107,10 @@ function applyTransform(el, t) {
|
|
|
1248
1107
|
function normalizeColor(v) {
|
|
1249
1108
|
return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
1250
1109
|
}
|
|
1251
|
-
function
|
|
1110
|
+
function cssEscape(value) {
|
|
1252
1111
|
return value.replace(/["\\]/g, "\\$&");
|
|
1253
1112
|
}
|
|
1254
1113
|
|
|
1255
|
-
export {
|
|
1114
|
+
export { EditablePage, NodeInspector };
|
|
1256
1115
|
//# sourceMappingURL=editor.js.map
|
|
1257
1116
|
//# sourceMappingURL=editor.js.map
|