@particle-academy/fancy-cms-ui 0.0.1-dev.8 → 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-HYHNMW47.js → chunk-GOXVGEKU.js} +103 -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 +475 -116
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +10 -91
- package/dist/editor.d.ts +10 -91
- package/dist/editor.js +364 -420
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +517 -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-5O6U4VIU.js +0 -78
- package/dist/chunk-5O6U4VIU.js.map +0 -1
- package/dist/chunk-7YYLYBLI.js +0 -47
- package/dist/chunk-7YYLYBLI.js.map +0 -1
- package/dist/chunk-HYHNMW47.js.map +0 -1
- package/dist/ops-C_s0FGeV.d.cts +0 -71
- package/dist/ops-nevYkDzK.d.ts +0 -71
- package/dist/registry-Bk7GN1rA.d.cts +0 -19
- package/dist/registry-Dht9JBLU.d.ts +0 -19
package/dist/editor.js
CHANGED
|
@@ -1,386 +1,27 @@
|
|
|
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
|
-
function LayersPanel({ doc, selection, onSelect }) {
|
|
161
|
-
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 }, id)) });
|
|
162
|
-
}
|
|
163
|
-
function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
164
|
-
const node = doc.nodes[id];
|
|
165
|
-
if (!node) return null;
|
|
166
|
-
const selected = selection === id;
|
|
167
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
|
-
/* @__PURE__ */ jsxs(
|
|
169
|
-
"button",
|
|
170
|
-
{
|
|
171
|
-
type: "button",
|
|
172
|
-
onClick: () => onSelect(id),
|
|
173
|
-
style: {
|
|
174
|
-
display: "block",
|
|
175
|
-
width: "100%",
|
|
176
|
-
textAlign: "left",
|
|
177
|
-
border: "none",
|
|
178
|
-
borderRadius: 6,
|
|
179
|
-
cursor: "pointer",
|
|
180
|
-
padding: "4px 8px",
|
|
181
|
-
paddingLeft: 8 + depth * 14,
|
|
182
|
-
background: selected ? "#ede9fe" : "transparent",
|
|
183
|
-
color: selected ? "#5b21b6" : "#334155",
|
|
184
|
-
font: "inherit"
|
|
185
|
-
},
|
|
186
|
-
children: [
|
|
187
|
-
/* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: node.type }),
|
|
188
|
-
" \xB7 ",
|
|
189
|
-
id
|
|
190
|
-
]
|
|
191
|
-
}
|
|
192
|
-
),
|
|
193
|
-
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(
|
|
194
|
-
LayerRow,
|
|
195
|
-
{
|
|
196
|
-
doc,
|
|
197
|
-
id: child.id,
|
|
198
|
-
depth: depth + 1,
|
|
199
|
-
selection,
|
|
200
|
-
onSelect
|
|
201
|
-
},
|
|
202
|
-
child.id
|
|
203
|
-
))
|
|
204
|
-
] });
|
|
205
|
-
}
|
|
206
|
-
var label = {
|
|
207
|
-
display: "block",
|
|
208
|
-
fontSize: 11,
|
|
209
|
-
textTransform: "uppercase",
|
|
210
|
-
letterSpacing: "0.04em",
|
|
211
|
-
color: "#94a3b8",
|
|
212
|
-
margin: "10px 0 4px"
|
|
213
|
-
};
|
|
214
|
-
var input = {
|
|
215
|
-
width: "100%",
|
|
216
|
-
boxSizing: "border-box",
|
|
217
|
-
padding: "6px 8px",
|
|
218
|
-
borderRadius: 6,
|
|
219
|
-
border: "1px solid #e2e8f0",
|
|
220
|
-
font: "inherit",
|
|
221
|
-
fontSize: 13
|
|
222
|
-
};
|
|
223
|
-
function Inspector({ doc, selection, apply }) {
|
|
224
|
-
const node = selection ? doc.nodes[selection] : null;
|
|
225
|
-
if (!node) {
|
|
226
|
-
return /* @__PURE__ */ jsx("div", { style: { padding: 16, color: "#94a3b8", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
|
|
227
|
-
}
|
|
228
|
-
const s = node.style.base;
|
|
229
|
-
const setStyle = (patch) => apply({ t: "set_style", id: node.id, breakpoint: "base", patch });
|
|
230
|
-
const setProp = (key, value) => apply({ t: "set_props", id: node.id, patch: { [key]: value } });
|
|
231
|
-
return /* @__PURE__ */ jsxs("div", { style: { overflow: "auto", padding: 16, fontFamily: "system-ui, sans-serif", height: "100%", boxSizing: "border-box" }, children: [
|
|
232
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#0f172a" }, children: node.type }),
|
|
233
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#94a3b8", marginBottom: 8 }, children: node.id }),
|
|
234
|
-
node.type === "text" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
235
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Content" }),
|
|
236
|
-
/* @__PURE__ */ jsx(
|
|
237
|
-
"textarea",
|
|
238
|
-
{
|
|
239
|
-
style: { ...input, minHeight: 64, resize: "vertical" },
|
|
240
|
-
value: typeof node.props.content === "string" ? node.props.content : "",
|
|
241
|
-
onChange: (e) => setProp("content", e.target.value)
|
|
242
|
-
}
|
|
243
|
-
)
|
|
244
|
-
] }) : null,
|
|
245
|
-
node.type === "image" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
246
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Source" }),
|
|
247
|
-
/* @__PURE__ */ jsx(
|
|
248
|
-
"input",
|
|
249
|
-
{
|
|
250
|
-
style: input,
|
|
251
|
-
value: typeof node.props.src === "string" ? node.props.src : "",
|
|
252
|
-
onChange: (e) => setProp("src", e.target.value)
|
|
253
|
-
}
|
|
254
|
-
)
|
|
255
|
-
] }) : null,
|
|
256
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Background" }),
|
|
257
|
-
/* @__PURE__ */ jsx(
|
|
258
|
-
"input",
|
|
259
|
-
{
|
|
260
|
-
style: input,
|
|
261
|
-
placeholder: "#ffffff | gradient | url(\u2026)",
|
|
262
|
-
value: s.background ?? "",
|
|
263
|
-
onChange: (e) => setStyle({ background: e.target.value })
|
|
264
|
-
}
|
|
265
|
-
),
|
|
266
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Text color" }),
|
|
267
|
-
/* @__PURE__ */ jsx(
|
|
268
|
-
"input",
|
|
269
|
-
{
|
|
270
|
-
style: input,
|
|
271
|
-
placeholder: "#0f172a",
|
|
272
|
-
value: s.color ?? "",
|
|
273
|
-
onChange: (e) => setStyle({ color: e.target.value })
|
|
274
|
-
}
|
|
275
|
-
),
|
|
276
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Font size (px)" }),
|
|
277
|
-
/* @__PURE__ */ jsx(
|
|
278
|
-
"input",
|
|
279
|
-
{
|
|
280
|
-
type: "number",
|
|
281
|
-
style: input,
|
|
282
|
-
value: s.fontSize?.value ?? "",
|
|
283
|
-
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
284
|
-
}
|
|
285
|
-
),
|
|
286
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Padding (px)" }),
|
|
287
|
-
/* @__PURE__ */ jsx(
|
|
288
|
-
"input",
|
|
289
|
-
{
|
|
290
|
-
type: "number",
|
|
291
|
-
style: input,
|
|
292
|
-
value: s.padding?.value ?? "",
|
|
293
|
-
onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
294
|
-
}
|
|
295
|
-
),
|
|
296
|
-
/* @__PURE__ */ jsx("label", { style: label, children: "Opacity" }),
|
|
297
|
-
/* @__PURE__ */ jsx(
|
|
298
|
-
"input",
|
|
299
|
-
{
|
|
300
|
-
type: "number",
|
|
301
|
-
step: "0.1",
|
|
302
|
-
min: "0",
|
|
303
|
-
max: "1",
|
|
304
|
-
style: input,
|
|
305
|
-
value: s.opacity ?? "",
|
|
306
|
-
onChange: (e) => setStyle({ opacity: Number(e.target.value) })
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
] });
|
|
310
|
-
}
|
|
311
|
-
function Editor({ defaultValue, onChange }) {
|
|
312
|
-
const ed = useEditor(defaultValue);
|
|
313
|
-
const first = useRef(true);
|
|
314
|
-
useEffect(() => {
|
|
315
|
-
if (first.current) {
|
|
316
|
-
first.current = false;
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
onChange?.(ed.state.doc);
|
|
320
|
-
}, [ed.state.doc, onChange]);
|
|
321
|
-
const shell = {
|
|
322
|
-
display: "grid",
|
|
323
|
-
gridTemplateColumns: "220px 1fr 300px",
|
|
324
|
-
height: "100%",
|
|
325
|
-
minHeight: 0,
|
|
326
|
-
background: "#fff",
|
|
327
|
-
color: "#0f172a",
|
|
328
|
-
border: "1px solid #e2e8f0",
|
|
329
|
-
borderRadius: 12,
|
|
330
|
-
overflow: "hidden"
|
|
331
|
-
};
|
|
332
|
-
return /* @__PURE__ */ jsxs("div", { style: shell, children: [
|
|
333
|
-
/* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
334
|
-
/* @__PURE__ */ jsx(Toolbar, { ed }),
|
|
335
|
-
/* @__PURE__ */ jsx(LayersPanel, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select })
|
|
336
|
-
] }),
|
|
337
|
-
/* @__PURE__ */ jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
|
|
338
|
-
/* @__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 }) })
|
|
339
|
-
] });
|
|
340
|
-
}
|
|
341
|
-
function Toolbar({ ed }) {
|
|
342
|
-
const btn = {
|
|
343
|
-
font: "inherit",
|
|
344
|
-
fontSize: 12,
|
|
345
|
-
padding: "4px 10px",
|
|
346
|
-
borderRadius: 6,
|
|
347
|
-
border: "1px solid #e2e8f0",
|
|
348
|
-
background: "#fff",
|
|
349
|
-
cursor: "pointer"
|
|
350
|
-
};
|
|
351
|
-
return /* @__PURE__ */ jsxs(
|
|
352
|
-
"div",
|
|
353
|
-
{
|
|
354
|
-
style: {
|
|
355
|
-
display: "flex",
|
|
356
|
-
gap: 6,
|
|
357
|
-
padding: 8,
|
|
358
|
-
borderBottom: "1px solid #e2e8f0",
|
|
359
|
-
fontFamily: "system-ui, sans-serif"
|
|
360
|
-
},
|
|
361
|
-
children: [
|
|
362
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: ed.canUndo ? 1 : 0.4 }, disabled: !ed.canUndo, onClick: ed.undo, children: "Undo" }),
|
|
363
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" })
|
|
364
|
-
]
|
|
365
|
-
}
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
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"]);
|
|
369
9
|
function NodeInspector({
|
|
370
10
|
node,
|
|
371
11
|
transform,
|
|
372
12
|
measured,
|
|
373
13
|
onProps,
|
|
374
14
|
onStyle,
|
|
15
|
+
onLayout,
|
|
375
16
|
onTransform,
|
|
376
17
|
onRemove,
|
|
377
18
|
onClose
|
|
378
19
|
}) {
|
|
379
20
|
const base = node.style.base ?? {};
|
|
380
21
|
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
381
|
-
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
22
|
+
const handled = /* @__PURE__ */ new Set(["content", "label", "html"]);
|
|
382
23
|
const extraKeys = Object.keys(node.props).filter(
|
|
383
|
-
(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]))
|
|
384
25
|
);
|
|
385
26
|
return /* @__PURE__ */ jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
386
27
|
/* @__PURE__ */ jsxs("div", { style: headRow, children: [
|
|
@@ -393,15 +34,12 @@ function NodeInspector({
|
|
|
393
34
|
/* @__PURE__ */ jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
|
|
394
35
|
] }),
|
|
395
36
|
/* @__PURE__ */ jsxs("div", { style: body, children: [
|
|
396
|
-
"content" in node.props ? /* @__PURE__ */
|
|
397
|
-
"
|
|
398
|
-
{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
) }) : null,
|
|
404
|
-
"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,
|
|
405
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,
|
|
406
44
|
/* @__PURE__ */ jsxs(Group, { label: "Position & size", children: [
|
|
407
45
|
/* @__PURE__ */ jsxs(Row, { children: [
|
|
@@ -454,15 +92,50 @@ function NodeInspector({
|
|
|
454
92
|
] }),
|
|
455
93
|
/* @__PURE__ */ jsxs(Row, { children: [
|
|
456
94
|
/* @__PURE__ */ jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
|
|
95
|
+
/* @__PURE__ */ jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
|
|
98
|
+
] }),
|
|
99
|
+
CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Layout", children: [
|
|
100
|
+
/* @__PURE__ */ jsxs(Row, { children: [
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
Sel,
|
|
103
|
+
{
|
|
104
|
+
label: "Mode",
|
|
105
|
+
value: node.layout ?? "free",
|
|
106
|
+
options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
|
|
107
|
+
onChange: (v) => onLayout?.(v)
|
|
108
|
+
}
|
|
109
|
+
),
|
|
457
110
|
/* @__PURE__ */ jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsxs(Row, { children: [
|
|
113
|
+
/* @__PURE__ */ jsx(
|
|
114
|
+
Sel,
|
|
115
|
+
{
|
|
116
|
+
label: "Align",
|
|
117
|
+
value: base.align ?? "",
|
|
118
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
|
|
119
|
+
onChange: (v) => onStyle({ align: v || void 0 })
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ jsx(
|
|
123
|
+
Sel,
|
|
124
|
+
{
|
|
125
|
+
label: "Justify",
|
|
126
|
+
value: base.justify ?? "",
|
|
127
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
|
|
128
|
+
onChange: (v) => onStyle({ justify: v || void 0 })
|
|
129
|
+
}
|
|
130
|
+
)
|
|
458
131
|
] })
|
|
459
|
-
] })
|
|
132
|
+
] }) : null
|
|
460
133
|
] })
|
|
461
134
|
] });
|
|
462
135
|
}
|
|
463
|
-
function Group({ label
|
|
136
|
+
function Group({ label, children }) {
|
|
464
137
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
|
|
465
|
-
/* @__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 }),
|
|
466
139
|
children
|
|
467
140
|
] });
|
|
468
141
|
}
|
|
@@ -470,36 +143,62 @@ function Row({ children }) {
|
|
|
470
143
|
return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
471
144
|
}
|
|
472
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
|
+
}
|
|
473
167
|
if (typeof value === "boolean") {
|
|
474
168
|
return /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
475
169
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
476
|
-
/* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
170
|
+
/* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize", flex: 1 }, children: name }),
|
|
171
|
+
link
|
|
477
172
|
] });
|
|
478
173
|
}
|
|
479
174
|
if (name === "variant") {
|
|
480
|
-
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 });
|
|
481
176
|
}
|
|
482
177
|
const isNum = typeof value === "number";
|
|
483
178
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
484
|
-
/* @__PURE__ */
|
|
179
|
+
/* @__PURE__ */ jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
180
|
+
name,
|
|
181
|
+
" ",
|
|
182
|
+
link
|
|
183
|
+
] }),
|
|
485
184
|
/* @__PURE__ */ jsx(
|
|
486
185
|
"input",
|
|
487
186
|
{
|
|
488
|
-
style:
|
|
187
|
+
style: input,
|
|
489
188
|
type: isNum ? "number" : "text",
|
|
490
|
-
value: String(value),
|
|
189
|
+
value: String(value ?? ""),
|
|
491
190
|
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
492
191
|
}
|
|
493
192
|
)
|
|
494
193
|
] });
|
|
495
194
|
}
|
|
496
|
-
function Num({ label
|
|
195
|
+
function Num({ label, value, placeholder, onChange, step, min, max }) {
|
|
497
196
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
498
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
197
|
+
/* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
|
|
499
198
|
/* @__PURE__ */ jsx(
|
|
500
199
|
"input",
|
|
501
200
|
{
|
|
502
|
-
style:
|
|
201
|
+
style: input,
|
|
503
202
|
type: "number",
|
|
504
203
|
step,
|
|
505
204
|
min,
|
|
@@ -511,16 +210,22 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
|
511
210
|
)
|
|
512
211
|
] });
|
|
513
212
|
}
|
|
514
|
-
function
|
|
213
|
+
function Txt({ label, value, placeholder, onChange }) {
|
|
515
214
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
516
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
517
|
-
/* @__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) })
|
|
518
217
|
] });
|
|
519
218
|
}
|
|
520
|
-
function
|
|
219
|
+
function Color({ label, value, onChange }) {
|
|
521
220
|
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
522
|
-
/* @__PURE__ */ jsx("span", { style: fieldLabel, children:
|
|
523
|
-
/* @__PURE__ */ jsx("
|
|
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) })
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
function Sel({ label, value, options, onChange }) {
|
|
226
|
+
return /* @__PURE__ */ jsxs("label", { style: field, children: [
|
|
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)) })
|
|
524
229
|
] });
|
|
525
230
|
}
|
|
526
231
|
var len = (value) => ({ value, unit: "px" });
|
|
@@ -547,7 +252,7 @@ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12
|
|
|
547
252
|
var body = { padding: 12, overflowY: "auto" };
|
|
548
253
|
var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
|
|
549
254
|
var fieldLabel = { fontSize: 10, opacity: 0.6 };
|
|
550
|
-
var
|
|
255
|
+
var input = {
|
|
551
256
|
width: "100%",
|
|
552
257
|
boxSizing: "border-box",
|
|
553
258
|
background: "#0f172a",
|
|
@@ -665,9 +370,11 @@ function snapshotSubtree(doc, id) {
|
|
|
665
370
|
return { rootId: id, nodes };
|
|
666
371
|
}
|
|
667
372
|
var Z2 = 2147483e3;
|
|
373
|
+
var SNAP = 6;
|
|
668
374
|
function EditablePage({
|
|
669
375
|
doc,
|
|
670
376
|
registry = defaultRegistry,
|
|
377
|
+
data,
|
|
671
378
|
timelineDock,
|
|
672
379
|
transforms,
|
|
673
380
|
onNodeTransform,
|
|
@@ -724,7 +431,7 @@ function EditablePage({
|
|
|
724
431
|
}, [onProgress, frames]);
|
|
725
432
|
const measureBox = useCallback(() => {
|
|
726
433
|
if (!editing || !selection || !pageRef.current) return setBox(null);
|
|
727
|
-
const el = pageRef.current.querySelector(`[data-cms="${
|
|
434
|
+
const el = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
|
|
728
435
|
if (!el) return setBox(null);
|
|
729
436
|
const r = el.getBoundingClientRect();
|
|
730
437
|
setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
|
|
@@ -739,13 +446,17 @@ function EditablePage({
|
|
|
739
446
|
};
|
|
740
447
|
}, [measureBox, ed.state.doc]);
|
|
741
448
|
useEffect(() => {
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
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;
|
|
745
456
|
el.setAttribute("contenteditable", "true");
|
|
746
457
|
el.style.outline = "none";
|
|
747
458
|
el.focus();
|
|
748
|
-
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 } });
|
|
749
460
|
el.addEventListener("blur", commit);
|
|
750
461
|
return () => {
|
|
751
462
|
el.removeEventListener("blur", commit);
|
|
@@ -770,13 +481,87 @@ function EditablePage({
|
|
|
770
481
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
771
482
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
772
483
|
const def = ADD_DEFAULTS[kind];
|
|
773
|
-
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 } } } });
|
|
774
485
|
ed.select(id);
|
|
775
486
|
},
|
|
776
487
|
[ed, selNode]
|
|
777
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
|
+
};
|
|
778
562
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
779
563
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
564
|
+
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
780
565
|
const removeSelected = () => {
|
|
781
566
|
if (!selection) return;
|
|
782
567
|
ed.apply({ t: "remove_node", id: selection });
|
|
@@ -799,8 +584,10 @@ function EditablePage({
|
|
|
799
584
|
const startEditText = (id) => {
|
|
800
585
|
if (!id) return;
|
|
801
586
|
ed.select(id);
|
|
802
|
-
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${
|
|
587
|
+
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape(id)}"]`)?.focus());
|
|
803
588
|
};
|
|
589
|
+
const nudgeRef = useRef({ transforms, onNodeTransform });
|
|
590
|
+
nudgeRef.current = { transforms, onNodeTransform };
|
|
804
591
|
useEffect(() => {
|
|
805
592
|
if (!editing) return;
|
|
806
593
|
const onKey = (e) => {
|
|
@@ -819,6 +606,15 @@ function EditablePage({
|
|
|
819
606
|
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
|
|
820
607
|
e.preventDefault();
|
|
821
608
|
paste(selection);
|
|
609
|
+
} else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
|
|
610
|
+
const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
|
|
611
|
+
if (!commit) return;
|
|
612
|
+
e.preventDefault();
|
|
613
|
+
const step = e.shiftKey ? 10 : 1;
|
|
614
|
+
const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
615
|
+
const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
|
|
616
|
+
const cur = tf?.[selection] ?? {};
|
|
617
|
+
commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
|
|
822
618
|
}
|
|
823
619
|
};
|
|
824
620
|
window.addEventListener("keydown", onKey);
|
|
@@ -830,8 +626,23 @@ function EditablePage({
|
|
|
830
626
|
if (el) ed.select(el.getAttribute("data-cms"));
|
|
831
627
|
else ed.select(null);
|
|
832
628
|
};
|
|
833
|
-
const page = /* @__PURE__ */ jsx(
|
|
834
|
-
|
|
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;
|
|
835
646
|
return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
836
647
|
editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
837
648
|
/* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
|
|
@@ -858,7 +669,7 @@ function EditablePage({
|
|
|
858
669
|
box,
|
|
859
670
|
base: transforms?.[selection] ?? {},
|
|
860
671
|
movable: Boolean(onNodeTransform),
|
|
861
|
-
getEl: () => pageRef.current?.querySelector(`[data-cms="${
|
|
672
|
+
getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape(selection)}"]`) ?? null,
|
|
862
673
|
onTransform: (t) => onNodeTransform?.(selection, t),
|
|
863
674
|
onLive: measureBox
|
|
864
675
|
}
|
|
@@ -872,6 +683,7 @@ function EditablePage({
|
|
|
872
683
|
measured: { w: box.w, h: box.h },
|
|
873
684
|
onProps: setProps,
|
|
874
685
|
onStyle: setStyle,
|
|
686
|
+
onLayout: setLayout,
|
|
875
687
|
onTransform: (t) => selection && onNodeTransform?.(selection, t),
|
|
876
688
|
onRemove: removeSelected,
|
|
877
689
|
onClose: () => ed.select(null)
|
|
@@ -892,16 +704,101 @@ function EditablePage({
|
|
|
892
704
|
onAdd: addNode
|
|
893
705
|
}
|
|
894
706
|
) : null,
|
|
707
|
+
editing && dropHint?.rect ? /* @__PURE__ */ jsx(DropIndicator, { hint: dropHint }) : null,
|
|
708
|
+
editing ? /* @__PURE__ */ jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
|
|
895
709
|
editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
896
710
|
] });
|
|
897
711
|
}
|
|
898
|
-
|
|
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"]);
|
|
899
787
|
var ADD_DEFAULTS = {
|
|
900
788
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
901
789
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
902
790
|
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
791
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
903
792
|
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
904
|
-
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" }
|
|
905
802
|
};
|
|
906
803
|
var GRIPS = [
|
|
907
804
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -922,20 +819,55 @@ function SelectionOverlay({
|
|
|
922
819
|
onLive
|
|
923
820
|
}) {
|
|
924
821
|
const drag = useRef(null);
|
|
822
|
+
const targets = useRef({ vx: [], hy: [] });
|
|
823
|
+
const [guides, setGuides] = useState({ x: null, y: null });
|
|
925
824
|
const apply = (t) => {
|
|
926
825
|
const el = getEl();
|
|
927
826
|
if (el) applyTransform(el, t);
|
|
928
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
|
+
};
|
|
929
838
|
const startMove = (e) => {
|
|
930
839
|
e.stopPropagation();
|
|
931
840
|
e.preventDefault();
|
|
932
841
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
933
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] };
|
|
934
853
|
};
|
|
935
854
|
const moveMove = (e) => {
|
|
936
855
|
const d = drag.current;
|
|
937
856
|
if (!d) return;
|
|
938
|
-
|
|
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 };
|
|
939
871
|
apply(d.live);
|
|
940
872
|
onLive();
|
|
941
873
|
};
|
|
@@ -970,6 +902,7 @@ function SelectionOverlay({
|
|
|
970
902
|
const d = drag.current;
|
|
971
903
|
if (d) onTransform(d.live);
|
|
972
904
|
drag.current = null;
|
|
905
|
+
setGuides({ x: null, y: null });
|
|
973
906
|
};
|
|
974
907
|
const outline = {
|
|
975
908
|
position: "fixed",
|
|
@@ -998,9 +931,11 @@ function SelectionOverlay({
|
|
|
998
931
|
const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
|
|
999
932
|
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
1000
933
|
};
|
|
1001
|
-
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,
|
|
1002
937
|
/* @__PURE__ */ jsx("div", { style: outline }),
|
|
1003
|
-
movable ? /* @__PURE__ */ jsxs(Fragment
|
|
938
|
+
movable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1004
939
|
/* @__PURE__ */ jsx(
|
|
1005
940
|
"div",
|
|
1006
941
|
{
|
|
@@ -1063,11 +998,20 @@ function FloatingToolbar({
|
|
|
1063
998
|
] });
|
|
1064
999
|
}
|
|
1065
1000
|
var ADD_MENU = [
|
|
1066
|
-
{ kind: "text", label: "Text" },
|
|
1067
1001
|
{ kind: "heading", label: "Heading" },
|
|
1002
|
+
{ kind: "text", label: "Text" },
|
|
1068
1003
|
{ kind: "button", label: "Button" },
|
|
1004
|
+
{ kind: "link", label: "Link" },
|
|
1069
1005
|
{ kind: "image", label: "Image" },
|
|
1070
|
-
{ 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" }
|
|
1071
1015
|
];
|
|
1072
1016
|
function EditBar({
|
|
1073
1017
|
editing,
|
|
@@ -1083,9 +1027,9 @@ function EditBar({
|
|
|
1083
1027
|
const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
|
|
1084
1028
|
return /* @__PURE__ */ jsxs("div", { style: bar, children: [
|
|
1085
1029
|
/* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
1086
|
-
editing ? /* @__PURE__ */ jsxs(Fragment
|
|
1030
|
+
editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1087
1031
|
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
|
|
1088
|
-
/* @__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" }),
|
|
1089
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(
|
|
1090
1034
|
"button",
|
|
1091
1035
|
{
|
|
@@ -1120,13 +1064,13 @@ function EditMenuItems({
|
|
|
1120
1064
|
}) {
|
|
1121
1065
|
const editable = node && (node.type === "text" || node.type === "heading");
|
|
1122
1066
|
const nested = Boolean(node && node.parent !== null);
|
|
1123
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
1067
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1124
1068
|
editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1125
1069
|
/* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
|
|
1126
|
-
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1070
|
+
/* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
|
|
1127
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)) })
|
|
1128
1072
|
] }),
|
|
1129
|
-
node ? /* @__PURE__ */ jsxs(Fragment
|
|
1073
|
+
node ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1130
1074
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1131
1075
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
|
|
1132
1076
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
|
|
@@ -1135,7 +1079,7 @@ function EditMenuItems({
|
|
|
1135
1079
|
/* @__PURE__ */ jsx(ContextMenu.Separator, {}),
|
|
1136
1080
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
|
|
1137
1081
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
|
|
1138
|
-
nested ? /* @__PURE__ */ jsxs(Fragment
|
|
1082
|
+
nested ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1139
1083
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
|
|
1140
1084
|
/* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
|
|
1141
1085
|
] }) : null,
|
|
@@ -1160,10 +1104,10 @@ function applyTransform(el, t) {
|
|
|
1160
1104
|
function normalizeColor(v) {
|
|
1161
1105
|
return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
|
|
1162
1106
|
}
|
|
1163
|
-
function
|
|
1107
|
+
function cssEscape(value) {
|
|
1164
1108
|
return value.replace(/["\\]/g, "\\$&");
|
|
1165
1109
|
}
|
|
1166
1110
|
|
|
1167
|
-
export {
|
|
1111
|
+
export { EditablePage, NodeInspector };
|
|
1168
1112
|
//# sourceMappingURL=editor.js.map
|
|
1169
1113
|
//# sourceMappingURL=editor.js.map
|