@particle-academy/fancy-cms-ui 0.0.1-dev.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor.js CHANGED
@@ -1,404 +1,11 @@
1
- import { keyBetween } from './chunk-7YYLYBLI.js';
2
- import { CmsPage, defaultRegistry } from './chunk-ZV7CV54P.js';
3
- import { reduce, childrenOf } from './chunk-3YZMELJJ.js';
4
- import { useReducer, useCallback, useMemo, useRef, useState, useLayoutEffect, Fragment, useEffect } from 'react';
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
- function initEditor(doc) {
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__ */ jsx(Group, { label: "Content", children: /* @__PURE__ */ jsx(
431
- "textarea",
432
- {
433
- style: { ...input2, minHeight: 64, resize: "vertical", lineHeight: 1.4 },
434
- value: String(node.props.content ?? ""),
435
- onChange: (e) => onProps({ content: e.target.value })
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: label2, children }) {
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: label2 }),
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__ */ jsx("span", { style: fieldLabel, children: name }),
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: input2,
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: label2, value, placeholder, onChange, step, min, max }) {
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: label2 }),
197
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label }),
568
198
  /* @__PURE__ */ jsx(
569
199
  "input",
570
200
  {
571
- style: input2,
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: label2, value, placeholder, onChange }) {
213
+ function Txt({ label, value, placeholder, onChange }) {
584
214
  return /* @__PURE__ */ jsxs("label", { style: field, children: [
585
- /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
586
- /* @__PURE__ */ jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
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: label2, value, onChange }) {
219
+ function Color({ label, value, onChange }) {
590
220
  return /* @__PURE__ */ jsxs("label", { style: field, children: [
591
- /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
592
- /* @__PURE__ */ jsx("input", { style: { ...input2, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
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: label2, value, options, onChange }) {
225
+ function Sel({ label, value, options, onChange }) {
596
226
  return /* @__PURE__ */ jsxs("label", { style: field, children: [
597
- /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
598
- /* @__PURE__ */ jsx("select", { style: input2, value, onChange: (e) => onChange(e.target.value), children: options.map(([v, l]) => /* @__PURE__ */ jsx("option", { value: v, children: l }, v)) })
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 input2 = {
255
+ var input = {
626
256
  width: "100%",
627
257
  boxSizing: "border-box",
628
258
  background: "#0f172a",
@@ -740,9 +370,11 @@ function snapshotSubtree(doc, id) {
740
370
  return { rootId: id, nodes };
741
371
  }
742
372
  var Z2 = 2147483e3;
373
+ var SNAP = 6;
743
374
  function EditablePage({
744
375
  doc,
745
376
  registry = defaultRegistry,
377
+ data,
746
378
  timelineDock,
747
379
  transforms,
748
380
  onNodeTransform,
@@ -799,7 +431,7 @@ function EditablePage({
799
431
  }, [onProgress, frames]);
800
432
  const measureBox = useCallback(() => {
801
433
  if (!editing || !selection || !pageRef.current) return setBox(null);
802
- const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
434
+ const el = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
803
435
  if (!el) return setBox(null);
804
436
  const r = el.getBoundingClientRect();
805
437
  setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
@@ -814,13 +446,17 @@ function EditablePage({
814
446
  };
815
447
  }, [measureBox, ed.state.doc]);
816
448
  useEffect(() => {
817
- if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
818
- const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
819
- if (!el) return;
449
+ const t = selNode?.type;
450
+ const editable = t === "text" || t === "heading" || t === "richtext";
451
+ if (!editing || !selection || !pageRef.current || !editable) return;
452
+ const wrapper = pageRef.current.querySelector(`[data-cms="${cssEscape(selection)}"]`);
453
+ if (!wrapper) return;
454
+ const isRich = t === "richtext";
455
+ const el = (isRich ? wrapper.firstElementChild : wrapper) ?? wrapper;
820
456
  el.setAttribute("contenteditable", "true");
821
457
  el.style.outline = "none";
822
458
  el.focus();
823
- const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
459
+ const commit = () => ed.apply({ t: "set_props", id: selection, patch: isRich ? { html: el.innerHTML } : { content: el.innerText } });
824
460
  el.addEventListener("blur", commit);
825
461
  return () => {
826
462
  el.removeEventListener("blur", commit);
@@ -845,11 +481,84 @@ function EditablePage({
845
481
  const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
846
482
  const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
847
483
  const def = ADD_DEFAULTS[kind];
848
- ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
484
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
849
485
  ed.select(id);
850
486
  },
851
487
  [ed, selNode]
852
488
  );
489
+ useCallback(
490
+ (kind, targetId) => {
491
+ const doc2 = ed.state.doc;
492
+ const target = targetId ? doc2.nodes[targetId] : null;
493
+ const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
494
+ const siblings = childrenOf(doc2, parent);
495
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
496
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
497
+ const def = ADD_DEFAULTS[kind];
498
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
499
+ ed.select(id);
500
+ },
501
+ [ed]
502
+ );
503
+ const [paletteOpen, setPaletteOpen] = useState(false);
504
+ const [draggingEl, setDraggingEl] = useState(false);
505
+ useEffect(() => {
506
+ if (!editing) {
507
+ setPaletteOpen(false);
508
+ return;
509
+ }
510
+ const onMove = (e) => {
511
+ if (draggingEl) return;
512
+ if (e.clientX >= window.innerWidth - 26) setPaletteOpen(true);
513
+ else if (e.clientX < window.innerWidth - 236) setPaletteOpen(false);
514
+ };
515
+ window.addEventListener("mousemove", onMove);
516
+ return () => window.removeEventListener("mousemove", onMove);
517
+ }, [editing, draggingEl]);
518
+ const [dropHint, setDropHint] = useState(null);
519
+ const computeDropTarget = (clientX, clientY) => {
520
+ const el = document.elementFromPoint(clientX, clientY)?.closest("[data-cms]");
521
+ if (!el) return { id: null, edge: "inside", rect: null };
522
+ const id = el.getAttribute("data-cms");
523
+ const node = ed.state.doc.nodes[id];
524
+ const r = el.getBoundingClientRect();
525
+ const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
526
+ if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
527
+ return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
528
+ };
529
+ const onPageDragOver = (e) => {
530
+ e.preventDefault();
531
+ e.dataTransfer.dropEffect = "copy";
532
+ setDropHint(computeDropTarget(e.clientX, e.clientY));
533
+ };
534
+ const insertRelative = (kind, hint) => {
535
+ const doc2 = ed.state.doc;
536
+ let parent;
537
+ let order;
538
+ if (!hint.id || hint.edge === "inside") {
539
+ parent = hint.id ?? (doc2.sections[doc2.sections.length - 1] ?? null);
540
+ const sib = childrenOf(doc2, parent);
541
+ order = keyBetween(sib.length ? sib[sib.length - 1].order : null, null);
542
+ } else {
543
+ const target = doc2.nodes[hint.id];
544
+ parent = target.parent;
545
+ const sib = childrenOf(doc2, parent);
546
+ const idx = sib.findIndex((n) => n.id === hint.id);
547
+ order = hint.edge === "before" ? keyBetween(idx > 0 ? sib[idx - 1].order : null, target.order) : keyBetween(target.order, idx < sib.length - 1 ? sib[idx + 1].order : null);
548
+ }
549
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
550
+ const def = ADD_DEFAULTS[kind];
551
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
552
+ ed.select(id);
553
+ };
554
+ const onPageDrop = (e) => {
555
+ const kind = e.dataTransfer.getData("application/x-cms-element");
556
+ if (!kind) return;
557
+ e.preventDefault();
558
+ insertRelative(kind, computeDropTarget(e.clientX, e.clientY));
559
+ setDropHint(null);
560
+ setDraggingEl(false);
561
+ };
853
562
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
854
563
  const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
855
564
  const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
@@ -875,7 +584,7 @@ function EditablePage({
875
584
  const startEditText = (id) => {
876
585
  if (!id) return;
877
586
  ed.select(id);
878
- requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
587
+ requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape(id)}"]`)?.focus());
879
588
  };
880
589
  const nudgeRef = useRef({ transforms, onNodeTransform });
881
590
  nudgeRef.current = { transforms, onNodeTransform };
@@ -917,8 +626,23 @@ function EditablePage({
917
626
  if (el) ed.select(el.getAttribute("data-cms"));
918
627
  else ed.select(null);
919
628
  };
920
- const page = /* @__PURE__ */ jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry }) });
921
- const pageShell = pinned ? /* @__PURE__ */ jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : page;
629
+ const page = /* @__PURE__ */ jsx(
630
+ "div",
631
+ {
632
+ ref: pageRef,
633
+ onClickCapture,
634
+ onContextMenu: onContextMenuSelect,
635
+ onDragOver: editing ? onPageDragOver : void 0,
636
+ onDragLeave: editing ? () => setDropHint(null) : void 0,
637
+ onDrop: editing ? onPageDrop : void 0,
638
+ children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry, data })
639
+ }
640
+ );
641
+ const pageShell = pinned ? /* @__PURE__ */ jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : frames > 1 ? (
642
+ // Normal flow, but "expanded" — extra scroll length below the content so a
643
+ // page can be made longer than its content (the + Frame control).
644
+ /* @__PURE__ */ jsx("div", { style: { minHeight: `${frames * 100}vh` }, children: page })
645
+ ) : page;
922
646
  return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
923
647
  editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
924
648
  /* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
@@ -945,7 +669,7 @@ function EditablePage({
945
669
  box,
946
670
  base: transforms?.[selection] ?? {},
947
671
  movable: Boolean(onNodeTransform),
948
- getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
672
+ getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape(selection)}"]`) ?? null,
949
673
  onTransform: (t) => onNodeTransform?.(selection, t),
950
674
  onLive: measureBox
951
675
  }
@@ -980,16 +704,101 @@ function EditablePage({
980
704
  onAdd: addNode
981
705
  }
982
706
  ) : null,
707
+ editing && dropHint?.rect ? /* @__PURE__ */ jsx(DropIndicator, { hint: dropHint }) : null,
708
+ editing ? /* @__PURE__ */ jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
983
709
  editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
984
710
  ] });
985
711
  }
986
- var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
712
+ function DropIndicator({
713
+ hint
714
+ }) {
715
+ const r = hint.rect;
716
+ if (!r) return null;
717
+ const color = "#ec4899";
718
+ if (hint.edge === "inside") {
719
+ return /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: r.x, top: r.y, width: r.w, height: r.h, outline: `2px dashed ${color}`, outlineOffset: -2, borderRadius: 6, pointerEvents: "none", zIndex: Z2 + 4 } });
720
+ }
721
+ const y = hint.edge === "before" ? r.y : r.y + r.h;
722
+ return /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: r.x, top: y - 1.5, width: r.w, height: 3, background: color, borderRadius: 2, boxShadow: "0 0 0 1px #fff", pointerEvents: "none", zIndex: Z2 + 4 } });
723
+ }
724
+ function ElementPalette({ open, onDragChange }) {
725
+ return /* @__PURE__ */ jsxs(
726
+ "div",
727
+ {
728
+ style: {
729
+ position: "fixed",
730
+ top: 64,
731
+ bottom: 96,
732
+ right: 0,
733
+ width: 216,
734
+ transform: open ? "translateX(0)" : "translateX(100%)",
735
+ transition: "transform 0.18s ease",
736
+ background: "#0b1220",
737
+ color: "#e2e8f0",
738
+ borderLeft: "1px solid #1e293b",
739
+ borderTopLeftRadius: 12,
740
+ borderBottomLeftRadius: 12,
741
+ boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
742
+ zIndex: Z2 + 2,
743
+ fontFamily: "system-ui, sans-serif",
744
+ padding: 12,
745
+ overflowY: "auto"
746
+ },
747
+ children: [
748
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5, marginBottom: 10 }, children: "Elements" }),
749
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxs(
750
+ "div",
751
+ {
752
+ draggable: true,
753
+ onDragStart: (e) => {
754
+ e.dataTransfer.setData("application/x-cms-element", m.kind);
755
+ e.dataTransfer.effectAllowed = "copy";
756
+ onDragChange(true);
757
+ },
758
+ onDragEnd: () => onDragChange(false),
759
+ title: `Drag "${m.label}" onto the page`,
760
+ style: {
761
+ display: "flex",
762
+ flexDirection: "column",
763
+ alignItems: "center",
764
+ justifyContent: "center",
765
+ gap: 4,
766
+ height: 56,
767
+ borderRadius: 8,
768
+ background: "#0f172a",
769
+ border: "1px solid #1e293b",
770
+ cursor: "grab",
771
+ fontSize: 12,
772
+ userSelect: "none"
773
+ },
774
+ children: [
775
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 16, opacity: 0.7 }, children: "\uFF0B" }),
776
+ m.label
777
+ ]
778
+ },
779
+ m.kind
780
+ )) }),
781
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 10.5, opacity: 0.45, marginTop: 12, lineHeight: 1.4 }, children: "Drag an Element onto the page. It drops into the container you release over." })
782
+ ]
783
+ }
784
+ );
785
+ }
786
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
987
787
  var ADD_DEFAULTS = {
988
788
  text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
989
789
  heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
990
790
  button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
791
+ link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
991
792
  image: { type: "image", props: { src: "", alt: "" }, style: {} },
992
- box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
793
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
794
+ stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
795
+ grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
796
+ card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
797
+ callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
798
+ divider: { type: "divider", props: {}, style: {} },
799
+ code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
800
+ richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
801
+ repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
993
802
  };
994
803
  var GRIPS = [
995
804
  { key: "nw", north: true, west: true, cursor: "nwse-resize" },
@@ -1010,20 +819,55 @@ function SelectionOverlay({
1010
819
  onLive
1011
820
  }) {
1012
821
  const drag = useRef(null);
822
+ const targets = useRef({ vx: [], hy: [] });
823
+ const [guides, setGuides] = useState({ x: null, y: null });
1013
824
  const apply = (t) => {
1014
825
  const el = getEl();
1015
826
  if (el) applyTransform(el, t);
1016
827
  };
828
+ const snap = (cands, lines) => {
829
+ let best = { delta: 0, at: null, dist: SNAP + 1 };
830
+ for (const line of lines) {
831
+ for (const c of cands) {
832
+ const diff = line - c;
833
+ if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
834
+ }
835
+ }
836
+ return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
837
+ };
1017
838
  const startMove = (e) => {
1018
839
  e.stopPropagation();
1019
840
  e.preventDefault();
1020
841
  e.currentTarget.setPointerCapture(e.pointerId);
1021
842
  drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
843
+ const sel = getEl();
844
+ const vx = /* @__PURE__ */ new Set();
845
+ const hy = /* @__PURE__ */ new Set();
846
+ document.querySelectorAll("[data-cms]").forEach((el) => {
847
+ if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
848
+ const r = el.getBoundingClientRect();
849
+ vx.add(r.left).add(r.left + r.width / 2).add(r.right);
850
+ hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
851
+ });
852
+ targets.current = { vx: [...vx], hy: [...hy] };
1022
853
  };
1023
854
  const moveMove = (e) => {
1024
855
  const d = drag.current;
1025
856
  if (!d) return;
1026
- d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
857
+ let dx = e.clientX - d.x;
858
+ let dy = e.clientY - d.y;
859
+ if (!e.altKey) {
860
+ const l = box.x + dx;
861
+ const t = box.y + dy;
862
+ const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
863
+ const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
864
+ dx += sx.delta;
865
+ dy += sy.delta;
866
+ setGuides({ x: sx.at, y: sy.at });
867
+ } else if (guides.x !== null || guides.y !== null) {
868
+ setGuides({ x: null, y: null });
869
+ }
870
+ d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
1027
871
  apply(d.live);
1028
872
  onLive();
1029
873
  };
@@ -1058,6 +902,7 @@ function SelectionOverlay({
1058
902
  const d = drag.current;
1059
903
  if (d) onTransform(d.live);
1060
904
  drag.current = null;
905
+ setGuides({ x: null, y: null });
1061
906
  };
1062
907
  const outline = {
1063
908
  position: "fixed",
@@ -1086,9 +931,11 @@ function SelectionOverlay({
1086
931
  const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
1087
932
  return { left: cx - 6, top: cy - 6, cursor: g.cursor };
1088
933
  };
1089
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
934
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
935
+ guides.x !== null ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: guides.x, top: 0, bottom: 0, width: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
936
+ guides.y !== null ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", top: guides.y, left: 0, right: 0, height: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
1090
937
  /* @__PURE__ */ jsx("div", { style: outline }),
1091
- movable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
938
+ movable ? /* @__PURE__ */ jsxs(Fragment, { children: [
1092
939
  /* @__PURE__ */ jsx(
1093
940
  "div",
1094
941
  {
@@ -1151,11 +998,20 @@ function FloatingToolbar({
1151
998
  ] });
1152
999
  }
1153
1000
  var ADD_MENU = [
1154
- { kind: "text", label: "Text" },
1155
1001
  { kind: "heading", label: "Heading" },
1002
+ { kind: "text", label: "Text" },
1156
1003
  { kind: "button", label: "Button" },
1004
+ { kind: "link", label: "Link" },
1157
1005
  { kind: "image", label: "Image" },
1158
- { kind: "box", label: "Box" }
1006
+ { kind: "card", label: "Card" },
1007
+ { kind: "callout", label: "Callout" },
1008
+ { kind: "stack", label: "Stack" },
1009
+ { kind: "grid", label: "Grid" },
1010
+ { kind: "box", label: "Box" },
1011
+ { kind: "divider", label: "Divider" },
1012
+ { kind: "code", label: "Code" },
1013
+ { kind: "richtext", label: "Rich text" },
1014
+ { kind: "repeater", label: "Repeater" }
1159
1015
  ];
1160
1016
  function EditBar({
1161
1017
  editing,
@@ -1171,9 +1027,9 @@ function EditBar({
1171
1027
  const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
1172
1028
  return /* @__PURE__ */ jsxs("div", { style: bar, children: [
1173
1029
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
1174
- editing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1030
+ editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
1175
1031
  /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1176
- /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
1032
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an Element (or drag from the right-edge palette)", children: "\uFF0B Element \u25BE" }),
1177
1033
  open ? /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: "calc(100% + 6px)", right: 0, background: "#0f172a", border: "1px solid #1e293b", borderRadius: 10, padding: 4, display: "flex", flexDirection: "column", minWidth: 132, boxShadow: "0 12px 32px -10px rgba(0,0,0,0.6)" }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsx(
1178
1034
  "button",
1179
1035
  {
@@ -1208,13 +1064,13 @@ function EditMenuItems({
1208
1064
  }) {
1209
1065
  const editable = node && (node.type === "text" || node.type === "heading");
1210
1066
  const nested = Boolean(node && node.parent !== null);
1211
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1067
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1212
1068
  editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
1213
1069
  /* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
1214
- /* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
1070
+ /* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
1215
1071
  /* @__PURE__ */ jsx(ContextMenu.SubContent, { className: "!z-[2147483647]", children: ADD_MENU.map((m) => /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onAdd(m.kind), children: m.label }, m.kind)) })
1216
1072
  ] }),
1217
- node ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1073
+ node ? /* @__PURE__ */ jsxs(Fragment, { children: [
1218
1074
  /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
1219
1075
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
1220
1076
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
@@ -1223,7 +1079,7 @@ function EditMenuItems({
1223
1079
  /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
1224
1080
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
1225
1081
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
1226
- nested ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1082
+ nested ? /* @__PURE__ */ jsxs(Fragment, { children: [
1227
1083
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
1228
1084
  /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
1229
1085
  ] }) : null,
@@ -1248,10 +1104,10 @@ function applyTransform(el, t) {
1248
1104
  function normalizeColor(v) {
1249
1105
  return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1250
1106
  }
1251
- function cssEscape2(value) {
1107
+ function cssEscape(value) {
1252
1108
  return value.replace(/["\\]/g, "\\$&");
1253
1109
  }
1254
1110
 
1255
- export { Canvas, EditablePage, Editor, Inspector, LayersPanel, NodeInspector, editorReduce, initEditor, useEditor };
1111
+ export { EditablePage, NodeInspector };
1256
1112
  //# sourceMappingURL=editor.js.map
1257
1113
  //# sourceMappingURL=editor.js.map