@particle-academy/fancy-cms-ui 0.0.1-dev.1 → 0.0.1-dev.10

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,7 +1,9 @@
1
- import { CmsPage } from './chunk-MRWUUNJR.js';
2
- import { reduce, childrenOf } from './chunk-NWH2AJLH.js';
1
+ import { keyBetween } from './chunk-7YYLYBLI.js';
2
+ import { CmsPage, defaultRegistry } from './chunk-ZV7CV54P.js';
3
+ import { reduce, childrenOf } from './chunk-3YZMELJJ.js';
3
4
  import { useReducer, useCallback, useMemo, useRef, useState, useLayoutEffect, Fragment, useEffect } from 'react';
4
5
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
6
+ import { ContextMenu } from '@particle-academy/react-fancy';
5
7
 
6
8
  function initEditor(doc) {
7
9
  return { doc, past: [], future: [], selection: null };
@@ -155,31 +157,66 @@ function Canvas({ doc, selection, onSelect, apply }) {
155
157
  function cssEscape(value) {
156
158
  return value.replace(/["\\]/g, "\\$&");
157
159
  }
158
- function LayersPanel({ doc, selection, onSelect }) {
159
- 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)) });
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)) });
160
170
  }
161
- function LayerRow({ doc, id, depth, selection, onSelect }) {
171
+ function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
162
172
  const node = doc.nodes[id];
163
173
  if (!node) return null;
164
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
+ };
165
188
  return /* @__PURE__ */ jsxs(Fragment, { children: [
166
189
  /* @__PURE__ */ jsxs(
167
190
  "button",
168
191
  {
169
192
  type: "button",
193
+ draggable,
170
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,
171
207
  style: {
172
208
  display: "block",
173
209
  width: "100%",
174
210
  textAlign: "left",
175
211
  border: "none",
176
212
  borderRadius: 6,
177
- cursor: "pointer",
213
+ cursor: draggable ? "grab" : "pointer",
178
214
  padding: "4px 8px",
179
215
  paddingLeft: 8 + depth * 14,
180
216
  background: selected ? "#ede9fe" : "transparent",
181
217
  color: selected ? "#5b21b6" : "#334155",
182
- font: "inherit"
218
+ font: "inherit",
219
+ boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
183
220
  },
184
221
  children: [
185
222
  /* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: node.type }),
@@ -188,17 +225,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
188
225
  ]
189
226
  }
190
227
  ),
191
- childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(
192
- LayerRow,
193
- {
194
- doc,
195
- id: child.id,
196
- depth: depth + 1,
197
- selection,
198
- onSelect
199
- },
200
- child.id
201
- ))
228
+ childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
202
229
  ] });
203
230
  }
204
231
  var label = {
@@ -330,7 +357,15 @@ function Editor({ defaultValue, onChange }) {
330
357
  return /* @__PURE__ */ jsxs("div", { style: shell, children: [
331
358
  /* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
332
359
  /* @__PURE__ */ jsx(Toolbar, { ed }),
333
- /* @__PURE__ */ jsx(LayersPanel, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select })
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
+ )
334
369
  ] }),
335
370
  /* @__PURE__ */ jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
336
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 }) })
@@ -363,7 +398,899 @@ function Toolbar({ ed }) {
363
398
  }
364
399
  );
365
400
  }
401
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
402
+ function NodeInspector({
403
+ node,
404
+ transform,
405
+ measured,
406
+ onProps,
407
+ onStyle,
408
+ onLayout,
409
+ onTransform,
410
+ onRemove,
411
+ onClose
412
+ }) {
413
+ const base = node.style.base ?? {};
414
+ const setT = (patch) => onTransform({ ...transform, ...patch });
415
+ const handled = /* @__PURE__ */ new Set(["content", "label"]);
416
+ const extraKeys = Object.keys(node.props).filter(
417
+ (k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
418
+ );
419
+ return /* @__PURE__ */ jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
420
+ /* @__PURE__ */ jsxs("div", { style: headRow, children: [
421
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
422
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 12, textTransform: "capitalize" }, children: node.type }),
423
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
424
+ ] }),
425
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
426
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
427
+ /* @__PURE__ */ jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
428
+ ] }),
429
+ /* @__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,
439
+ 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
+ /* @__PURE__ */ jsxs(Group, { label: "Position & size", children: [
441
+ /* @__PURE__ */ jsxs(Row, { children: [
442
+ /* @__PURE__ */ jsx(Num, { label: "X", value: transform.x, placeholder: "0", onChange: (v) => setT({ x: v }) }),
443
+ /* @__PURE__ */ jsx(Num, { label: "Y", value: transform.y, placeholder: "0", onChange: (v) => setT({ y: v }) })
444
+ ] }),
445
+ /* @__PURE__ */ jsxs(Row, { children: [
446
+ /* @__PURE__ */ jsx(Num, { label: "W", value: transform.w, placeholder: `${Math.round(measured.w)}`, onChange: (v) => setT({ w: v }) }),
447
+ /* @__PURE__ */ jsx(Num, { label: "H", value: transform.h, placeholder: `${Math.round(measured.h)}`, onChange: (v) => setT({ h: v }) })
448
+ ] }),
449
+ /* @__PURE__ */ jsxs(Row, { children: [
450
+ /* @__PURE__ */ jsx(Num, { label: "Scale", value: transform.scale, placeholder: "1", step: 0.05, onChange: (v) => setT({ scale: v }) }),
451
+ /* @__PURE__ */ jsx(Num, { label: "Rotate", value: transform.rotate, placeholder: "0", onChange: (v) => setT({ rotate: v }) })
452
+ ] }),
453
+ /* @__PURE__ */ jsxs(Row, { children: [
454
+ /* @__PURE__ */ jsx(Num, { label: "Opacity", value: transform.opacity, placeholder: "1", min: 0, max: 1, step: 0.05, onChange: (v) => setT({ opacity: v }) }),
455
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
456
+ ] })
457
+ ] }),
458
+ !CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Text", children: [
459
+ /* @__PURE__ */ jsxs(Row, { children: [
460
+ /* @__PURE__ */ jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
461
+ /* @__PURE__ */ jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
462
+ ] }),
463
+ /* @__PURE__ */ jsxs(Row, { children: [
464
+ /* @__PURE__ */ jsx(
465
+ Sel,
466
+ {
467
+ label: "Weight",
468
+ value: String(base.fontWeight ?? ""),
469
+ options: [["", "\u2014"], ["400", "Regular"], ["500", "Medium"], ["600", "Semibold"], ["700", "Bold"], ["800", "Heavy"]],
470
+ onChange: (v) => onStyle({ fontWeight: v ? Number(v) : void 0 })
471
+ }
472
+ ),
473
+ /* @__PURE__ */ jsx(
474
+ Sel,
475
+ {
476
+ label: "Align",
477
+ value: base.textAlign ?? "",
478
+ options: [["", "\u2014"], ["left", "Left"], ["center", "Center"], ["right", "Right"], ["justify", "Justify"]],
479
+ onChange: (v) => onStyle({ textAlign: v || void 0 })
480
+ }
481
+ )
482
+ ] })
483
+ ] }) : null,
484
+ /* @__PURE__ */ jsxs(Group, { label: "Box", children: [
485
+ /* @__PURE__ */ jsxs(Row, { children: [
486
+ /* @__PURE__ */ jsx(Color, { label: "Background", value: hex(base.background), onChange: (v) => onStyle({ background: v }) }),
487
+ /* @__PURE__ */ jsx(Num, { label: "Radius", value: base.radius?.value, placeholder: "0", onChange: (v) => onStyle({ radius: v == null ? void 0 : len(v) }) })
488
+ ] }),
489
+ /* @__PURE__ */ jsxs(Row, { children: [
490
+ /* @__PURE__ */ jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
491
+ /* @__PURE__ */ jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
492
+ ] }),
493
+ /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
494
+ ] }),
495
+ CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Layout", children: [
496
+ /* @__PURE__ */ jsxs(Row, { children: [
497
+ /* @__PURE__ */ jsx(
498
+ Sel,
499
+ {
500
+ label: "Mode",
501
+ value: node.layout ?? "free",
502
+ options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
503
+ onChange: (v) => onLayout?.(v)
504
+ }
505
+ ),
506
+ /* @__PURE__ */ jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
507
+ ] }),
508
+ /* @__PURE__ */ jsxs(Row, { children: [
509
+ /* @__PURE__ */ jsx(
510
+ Sel,
511
+ {
512
+ label: "Align",
513
+ value: base.align ?? "",
514
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
515
+ onChange: (v) => onStyle({ align: v || void 0 })
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsx(
519
+ Sel,
520
+ {
521
+ label: "Justify",
522
+ value: base.justify ?? "",
523
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
524
+ onChange: (v) => onStyle({ justify: v || void 0 })
525
+ }
526
+ )
527
+ ] })
528
+ ] }) : null
529
+ ] })
530
+ ] });
531
+ }
532
+ function Group({ label: label2, children }) {
533
+ 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 }),
535
+ children
536
+ ] });
537
+ }
538
+ function Row({ children }) {
539
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children });
540
+ }
541
+ function PropField({ name, value, onChange }) {
542
+ if (typeof value === "boolean") {
543
+ return /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
544
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
545
+ /* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize" }, children: name })
546
+ ] });
547
+ }
548
+ if (name === "variant") {
549
+ return /* @__PURE__ */ jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
550
+ }
551
+ const isNum = typeof value === "number";
552
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
553
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: name }),
554
+ /* @__PURE__ */ jsx(
555
+ "input",
556
+ {
557
+ style: input2,
558
+ type: isNum ? "number" : "text",
559
+ value: String(value),
560
+ onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
561
+ }
562
+ )
563
+ ] });
564
+ }
565
+ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
566
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
567
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
568
+ /* @__PURE__ */ jsx(
569
+ "input",
570
+ {
571
+ style: input2,
572
+ type: "number",
573
+ step,
574
+ min,
575
+ max,
576
+ value: value ?? "",
577
+ placeholder,
578
+ onChange: (e) => onChange(e.target.value === "" ? void 0 : Number(e.target.value))
579
+ }
580
+ )
581
+ ] });
582
+ }
583
+ function Txt({ label: label2, value, placeholder, onChange }) {
584
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
585
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
586
+ /* @__PURE__ */ jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
587
+ ] });
588
+ }
589
+ function Color({ label: label2, value, onChange }) {
590
+ 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) })
593
+ ] });
594
+ }
595
+ function Sel({ label: label2, value, options, onChange }) {
596
+ 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)) })
599
+ ] });
600
+ }
601
+ var len = (value) => ({ value, unit: "px" });
602
+ var hex = (v) => v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
603
+ var Z = 2147483e3;
604
+ var panel = {
605
+ position: "fixed",
606
+ top: 60,
607
+ right: 12,
608
+ bottom: 96,
609
+ width: 264,
610
+ display: "flex",
611
+ flexDirection: "column",
612
+ background: "#0b1220",
613
+ color: "#e2e8f0",
614
+ border: "1px solid #1e293b",
615
+ borderRadius: 12,
616
+ boxShadow: "0 18px 48px -16px rgba(0,0,0,0.6)",
617
+ zIndex: Z + 2,
618
+ fontFamily: "system-ui, sans-serif",
619
+ overflow: "hidden"
620
+ };
621
+ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
622
+ var body = { padding: 12, overflowY: "auto" };
623
+ var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
624
+ var fieldLabel = { fontSize: 10, opacity: 0.6 };
625
+ var input2 = {
626
+ width: "100%",
627
+ boxSizing: "border-box",
628
+ background: "#0f172a",
629
+ color: "#e2e8f0",
630
+ border: "1px solid #334155",
631
+ borderRadius: 6,
632
+ padding: "5px 7px",
633
+ font: "inherit",
634
+ fontSize: 12
635
+ };
636
+ var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
637
+ var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
638
+
639
+ // src/editor/editorOps.ts
640
+ function childrenOfMap(nodes, parent) {
641
+ return Object.values(nodes).filter((n) => n.parent === parent).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
642
+ }
643
+ function subtreeBfs(nodes, root) {
644
+ const out = [];
645
+ const queue = [root];
646
+ while (queue.length) {
647
+ const cur = queue.shift();
648
+ out.push(cur);
649
+ for (const child of childrenOfMap(nodes, cur)) queue.push(child.id);
650
+ }
651
+ return out;
652
+ }
653
+ function cloneFrom(nodes, rootId, newParent, newOrder, seqBase) {
654
+ if (!nodes[rootId]) return { ops: [], newRootId: null };
655
+ const order = subtreeBfs(nodes, rootId);
656
+ const idMap = {};
657
+ order.forEach((old, i) => idMap[old] = `${old}_c${seqBase}_${i}`);
658
+ const ops = order.map((old) => {
659
+ const src = nodes[old];
660
+ const cloned = JSON.parse(JSON.stringify(src));
661
+ cloned.id = idMap[old];
662
+ cloned.parent = old === rootId ? newParent : idMap[src.parent];
663
+ if (old === rootId) cloned.order = newOrder;
664
+ return { t: "insert_node", node: cloned };
665
+ });
666
+ return { ops, newRootId: idMap[rootId] };
667
+ }
668
+ function duplicateOps(doc, id) {
669
+ const node = doc.nodes[id];
670
+ if (!node) return { ops: [], newRootId: null };
671
+ const sibs = childrenOf(doc, node.parent);
672
+ const idx = sibs.findIndex((s) => s.id === id);
673
+ const newOrder = keyBetween(node.order, sibs[idx + 1]?.order ?? null);
674
+ const { ops, newRootId } = cloneFrom(doc.nodes, id, node.parent, newOrder, doc.seq);
675
+ if (node.parent === null && newRootId) {
676
+ const base = doc.sections;
677
+ const at = base.indexOf(id);
678
+ ops.push({ t: "reorder_sections", order: [...base.slice(0, at + 1), newRootId, ...base.slice(at + 1)] });
679
+ }
680
+ return { ops, newRootId };
681
+ }
682
+ function pasteOps(doc, clip, target, containerTypes) {
683
+ const sel = target ? doc.nodes[target] : null;
684
+ const parent = sel ? containerTypes.has(sel.type) ? sel.id : sel.parent : doc.sections[doc.sections.length - 1] ?? null;
685
+ const sibs = childrenOf(doc, parent);
686
+ const newOrder = keyBetween(sibs.length ? sibs[sibs.length - 1].order : null, null);
687
+ const { ops, newRootId } = cloneFrom(clip.nodes, clip.rootId, parent, newOrder, doc.seq);
688
+ return { ops, newRootId };
689
+ }
690
+ function reorderOps(doc, id, dir) {
691
+ const node = doc.nodes[id];
692
+ if (!node) return [];
693
+ if (node.parent === null) {
694
+ const s = [...doc.sections];
695
+ const i = s.indexOf(id);
696
+ if (i < 0) return [];
697
+ s.splice(i, 1);
698
+ if (dir === "front") s.unshift(id);
699
+ else if (dir === "back") s.push(id);
700
+ else s.splice(Math.max(0, Math.min(s.length, dir === "up" ? i - 1 : i + 1)), 0, id);
701
+ return [{ t: "reorder_sections", order: s }];
702
+ }
703
+ const sibs = childrenOf(doc, node.parent);
704
+ const idx = sibs.findIndex((sb) => sb.id === id);
705
+ if (idx < 0) return [];
706
+ const last = sibs.length - 1;
707
+ let newOrder;
708
+ if (dir === "up") {
709
+ if (idx <= 0) return [];
710
+ newOrder = keyBetween(sibs[idx - 2]?.order ?? null, sibs[idx - 1].order);
711
+ } else if (dir === "down") {
712
+ if (idx >= last) return [];
713
+ newOrder = keyBetween(sibs[idx + 1].order, sibs[idx + 2]?.order ?? null);
714
+ } else if (dir === "front") {
715
+ if (idx >= last) return [];
716
+ newOrder = keyBetween(sibs[last].order, null);
717
+ } else {
718
+ if (idx <= 0) return [];
719
+ newOrder = keyBetween(null, sibs[0].order);
720
+ }
721
+ return [{ t: "move_node", id, parent: node.parent, order: newOrder }];
722
+ }
723
+ function wrapInBoxOps(doc, id) {
724
+ const node = doc.nodes[id];
725
+ if (!node || node.parent === null) return { ops: [], newRootId: null };
726
+ const frameId = `box_${doc.seq}`;
727
+ return {
728
+ ops: [
729
+ { t: "insert_node", node: { id: frameId, type: "frame", parent: node.parent, order: node.order, props: {}, style: { base: { padding: { value: 16, unit: "px" } } } } },
730
+ { t: "move_node", id, parent: frameId, order: "a" }
731
+ ],
732
+ newRootId: frameId
733
+ };
734
+ }
735
+ function snapshotSubtree(doc, id) {
736
+ if (!doc.nodes[id]) return null;
737
+ const ids = subtreeBfs(doc.nodes, id);
738
+ const nodes = {};
739
+ for (const nid of ids) nodes[nid] = JSON.parse(JSON.stringify(doc.nodes[nid]));
740
+ return { rootId: id, nodes };
741
+ }
742
+ var Z2 = 2147483e3;
743
+ var SNAP = 6;
744
+ function EditablePage({
745
+ doc,
746
+ registry = defaultRegistry,
747
+ timelineDock,
748
+ transforms,
749
+ onNodeTransform,
750
+ onSelect,
751
+ frames = 1,
752
+ onProgress,
753
+ pinned = true
754
+ }) {
755
+ const ed = useEditor(doc);
756
+ const [editing, setEditing] = useState(false);
757
+ const [revealed, setRevealed] = useState(false);
758
+ const pageRef = useRef(null);
759
+ const spacerRef = useRef(null);
760
+ const [box, setBox] = useState(null);
761
+ const { selection } = ed.state;
762
+ const selNode = selection ? ed.state.doc.nodes[selection] : null;
763
+ useEffect(() => onSelect?.(selection), [selection, onSelect]);
764
+ useEffect(() => {
765
+ const onKey = (e) => setRevealed(e.ctrlKey && e.shiftKey);
766
+ const onBlur = () => setRevealed(false);
767
+ window.addEventListener("keydown", onKey);
768
+ window.addEventListener("keyup", onKey);
769
+ window.addEventListener("blur", onBlur);
770
+ return () => {
771
+ window.removeEventListener("keydown", onKey);
772
+ window.removeEventListener("keyup", onKey);
773
+ window.removeEventListener("blur", onBlur);
774
+ };
775
+ }, []);
776
+ useEffect(() => {
777
+ const root = pageRef.current;
778
+ if (!root) return;
779
+ root.querySelectorAll("[data-cms]").forEach((el) => {
780
+ const id = el.dataset.cms;
781
+ const t = id ? transforms?.[id] : void 0;
782
+ applyTransform(el, t);
783
+ });
784
+ }, [transforms, ed.state.doc]);
785
+ useEffect(() => {
786
+ const spacer = spacerRef.current;
787
+ if (!spacer || !onProgress) return;
788
+ const compute = () => {
789
+ const len2 = spacer.offsetHeight - window.innerHeight;
790
+ const top = spacer.getBoundingClientRect().top;
791
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
792
+ };
793
+ compute();
794
+ window.addEventListener("scroll", compute, { passive: true });
795
+ window.addEventListener("resize", compute);
796
+ return () => {
797
+ window.removeEventListener("scroll", compute);
798
+ window.removeEventListener("resize", compute);
799
+ };
800
+ }, [onProgress, frames]);
801
+ const measureBox = useCallback(() => {
802
+ if (!editing || !selection || !pageRef.current) return setBox(null);
803
+ const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
804
+ if (!el) return setBox(null);
805
+ const r = el.getBoundingClientRect();
806
+ setBox({ x: r.left, y: r.top, w: r.width, h: r.height });
807
+ }, [editing, selection]);
808
+ useLayoutEffect(() => {
809
+ measureBox();
810
+ window.addEventListener("scroll", measureBox, { passive: true });
811
+ window.addEventListener("resize", measureBox);
812
+ return () => {
813
+ window.removeEventListener("scroll", measureBox);
814
+ window.removeEventListener("resize", measureBox);
815
+ };
816
+ }, [measureBox, ed.state.doc]);
817
+ useEffect(() => {
818
+ if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
819
+ const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
820
+ if (!el) return;
821
+ el.setAttribute("contenteditable", "true");
822
+ el.style.outline = "none";
823
+ el.focus();
824
+ const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
825
+ el.addEventListener("blur", commit);
826
+ return () => {
827
+ el.removeEventListener("blur", commit);
828
+ el.removeAttribute("contenteditable");
829
+ };
830
+ }, [editing, selection, selNode?.type, ed]);
831
+ const onClickCapture = (e) => {
832
+ if (!editing) return;
833
+ const el = e.target.closest("[data-cms]");
834
+ if (el) {
835
+ if (el.getAttribute("contenteditable") === "true") return;
836
+ e.preventDefault();
837
+ e.stopPropagation();
838
+ ed.select(el.getAttribute("data-cms"));
839
+ }
840
+ };
841
+ const addNode = useCallback(
842
+ (kind) => {
843
+ const doc2 = ed.state.doc;
844
+ const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
845
+ const siblings = childrenOf(doc2, parent);
846
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
847
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
848
+ const def = ADD_DEFAULTS[kind];
849
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
850
+ ed.select(id);
851
+ },
852
+ [ed, selNode]
853
+ );
854
+ const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
855
+ const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
856
+ const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
857
+ const removeSelected = () => {
858
+ if (!selection) return;
859
+ ed.apply({ t: "remove_node", id: selection });
860
+ ed.select(null);
861
+ };
862
+ const clip = useRef(null);
863
+ const runOps = (result) => {
864
+ result.ops.forEach((op) => ed.apply(op));
865
+ if (result.newRootId) ed.select(result.newRootId);
866
+ };
867
+ const duplicate = (id) => id && runOps(duplicateOps(ed.state.doc, id));
868
+ const reorder = (id, dir) => id && reorderOps(ed.state.doc, id, dir).forEach((op) => ed.apply(op));
869
+ const wrapInBox = (id) => id && runOps(wrapInBoxOps(ed.state.doc, id));
870
+ const copy = (id) => {
871
+ if (id) clip.current = snapshotSubtree(ed.state.doc, id);
872
+ };
873
+ const paste = (target) => {
874
+ if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
875
+ };
876
+ const startEditText = (id) => {
877
+ if (!id) return;
878
+ ed.select(id);
879
+ requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
880
+ };
881
+ const nudgeRef = useRef({ transforms, onNodeTransform });
882
+ nudgeRef.current = { transforms, onNodeTransform };
883
+ useEffect(() => {
884
+ if (!editing) return;
885
+ const onKey = (e) => {
886
+ const t = e.target;
887
+ const typing = t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
888
+ if (e.key === "Escape") return void ed.select(null);
889
+ if (typing) return;
890
+ if ((e.key === "Delete" || e.key === "Backspace") && selection) {
891
+ e.preventDefault();
892
+ removeSelected();
893
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "d" && selection) {
894
+ e.preventDefault();
895
+ duplicate(selection);
896
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selection) {
897
+ copy(selection);
898
+ } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
899
+ e.preventDefault();
900
+ paste(selection);
901
+ } else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
902
+ const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
903
+ if (!commit) return;
904
+ e.preventDefault();
905
+ const step = e.shiftKey ? 10 : 1;
906
+ const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
907
+ const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
908
+ const cur = tf?.[selection] ?? {};
909
+ commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
910
+ }
911
+ };
912
+ window.addEventListener("keydown", onKey);
913
+ return () => window.removeEventListener("keydown", onKey);
914
+ }, [editing, selection]);
915
+ const onContextMenuSelect = (e) => {
916
+ if (!editing) return;
917
+ const el = e.target.closest("[data-cms]");
918
+ if (el) ed.select(el.getAttribute("data-cms"));
919
+ else ed.select(null);
920
+ };
921
+ const page = /* @__PURE__ */ jsx("div", { ref: pageRef, onClickCapture, onContextMenu: onContextMenuSelect, children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry }) });
922
+ 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;
923
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
924
+ editing ? /* @__PURE__ */ jsxs(ContextMenu, { children: [
925
+ /* @__PURE__ */ jsx(ContextMenu.Trigger, { children: pageShell }),
926
+ /* @__PURE__ */ jsx(ContextMenu.Content, { className: "!z-[2147483646]", children: /* @__PURE__ */ jsx(
927
+ EditMenuItems,
928
+ {
929
+ node: selNode ?? null,
930
+ hasClipboard: Boolean(clip.current),
931
+ isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
932
+ onEditText: () => startEditText(selection),
933
+ onAdd: addNode,
934
+ onDuplicate: () => duplicate(selection),
935
+ onCopy: () => copy(selection),
936
+ onPaste: () => paste(selection),
937
+ onWrap: () => wrapInBox(selection),
938
+ onReorder: (d) => reorder(selection, d),
939
+ onDelete: removeSelected
940
+ }
941
+ ) })
942
+ ] }) : pageShell,
943
+ editing && box && selection ? /* @__PURE__ */ jsx(
944
+ SelectionOverlay,
945
+ {
946
+ box,
947
+ base: transforms?.[selection] ?? {},
948
+ movable: Boolean(onNodeTransform),
949
+ getEl: () => pageRef.current?.querySelector(`[data-cms="${cssEscape2(selection)}"]`) ?? null,
950
+ onTransform: (t) => onNodeTransform?.(selection, t),
951
+ onLive: measureBox
952
+ }
953
+ ) : null,
954
+ editing && box && selNode ? /* @__PURE__ */ jsx(FloatingToolbar, { box, isText: selNode.type === "text" || selNode.type === "heading", base: selNode.style.base, setStyle }) : null,
955
+ editing && selNode && box ? /* @__PURE__ */ jsx(
956
+ NodeInspector,
957
+ {
958
+ node: selNode,
959
+ transform: (selection ? transforms?.[selection] : void 0) ?? {},
960
+ measured: { w: box.w, h: box.h },
961
+ onProps: setProps,
962
+ onStyle: setStyle,
963
+ onLayout: setLayout,
964
+ onTransform: (t) => selection && onNodeTransform?.(selection, t),
965
+ onRemove: removeSelected,
966
+ onClose: () => ed.select(null)
967
+ }
968
+ ) : null,
969
+ revealed || editing ? /* @__PURE__ */ jsx(
970
+ EditBar,
971
+ {
972
+ editing,
973
+ onToggle: () => {
974
+ setEditing((v) => !v);
975
+ ed.select(null);
976
+ },
977
+ canUndo: ed.canUndo,
978
+ canRedo: ed.canRedo,
979
+ onUndo: ed.undo,
980
+ onRedo: ed.redo,
981
+ onAdd: addNode
982
+ }
983
+ ) : null,
984
+ editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
985
+ ] });
986
+ }
987
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
988
+ var ADD_DEFAULTS = {
989
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
990
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
991
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
992
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
993
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
994
+ };
995
+ var GRIPS = [
996
+ { key: "nw", north: true, west: true, cursor: "nwse-resize" },
997
+ { key: "n", north: true, cursor: "ns-resize" },
998
+ { key: "ne", north: true, east: true, cursor: "nesw-resize" },
999
+ { key: "e", east: true, cursor: "ew-resize" },
1000
+ { key: "se", south: true, east: true, cursor: "nwse-resize" },
1001
+ { key: "s", south: true, cursor: "ns-resize" },
1002
+ { key: "sw", south: true, west: true, cursor: "nesw-resize" },
1003
+ { key: "w", west: true, cursor: "ew-resize" }
1004
+ ];
1005
+ function SelectionOverlay({
1006
+ box,
1007
+ base,
1008
+ movable,
1009
+ getEl,
1010
+ onTransform,
1011
+ onLive
1012
+ }) {
1013
+ const drag = useRef(null);
1014
+ const targets = useRef({ vx: [], hy: [] });
1015
+ const [guides, setGuides] = useState({ x: null, y: null });
1016
+ const apply = (t) => {
1017
+ const el = getEl();
1018
+ if (el) applyTransform(el, t);
1019
+ };
1020
+ const snap = (cands, lines) => {
1021
+ let best = { delta: 0, at: null, dist: SNAP + 1 };
1022
+ for (const line of lines) {
1023
+ for (const c of cands) {
1024
+ const diff = line - c;
1025
+ if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
1026
+ }
1027
+ }
1028
+ return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
1029
+ };
1030
+ const startMove = (e) => {
1031
+ e.stopPropagation();
1032
+ e.preventDefault();
1033
+ e.currentTarget.setPointerCapture(e.pointerId);
1034
+ drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
1035
+ const sel = getEl();
1036
+ const vx = /* @__PURE__ */ new Set();
1037
+ const hy = /* @__PURE__ */ new Set();
1038
+ document.querySelectorAll("[data-cms]").forEach((el) => {
1039
+ if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
1040
+ const r = el.getBoundingClientRect();
1041
+ vx.add(r.left).add(r.left + r.width / 2).add(r.right);
1042
+ hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
1043
+ });
1044
+ targets.current = { vx: [...vx], hy: [...hy] };
1045
+ };
1046
+ const moveMove = (e) => {
1047
+ const d = drag.current;
1048
+ if (!d) return;
1049
+ let dx = e.clientX - d.x;
1050
+ let dy = e.clientY - d.y;
1051
+ if (!e.altKey) {
1052
+ const l = box.x + dx;
1053
+ const t = box.y + dy;
1054
+ const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
1055
+ const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
1056
+ dx += sx.delta;
1057
+ dy += sy.delta;
1058
+ setGuides({ x: sx.at, y: sy.at });
1059
+ } else if (guides.x !== null || guides.y !== null) {
1060
+ setGuides({ x: null, y: null });
1061
+ }
1062
+ d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
1063
+ apply(d.live);
1064
+ onLive();
1065
+ };
1066
+ const startResize = (g) => (e) => {
1067
+ e.stopPropagation();
1068
+ e.preventDefault();
1069
+ e.currentTarget.setPointerCapture(e.pointerId);
1070
+ drag.current = { x: e.clientX, y: e.clientY, w: base.w ?? box.w, h: base.h ?? box.h, live: { ...base } };
1071
+ e.currentTarget.dataset.grip = g.key;
1072
+ };
1073
+ const resizeMove = (g) => (e) => {
1074
+ const d = drag.current;
1075
+ if (!d) return;
1076
+ const dx = e.clientX - d.x;
1077
+ const dy = e.clientY - d.y;
1078
+ const live = { ...base };
1079
+ if (g.east) live.w = Math.max(16, Math.round(d.w + dx));
1080
+ if (g.west) {
1081
+ live.w = Math.max(16, Math.round(d.w - dx));
1082
+ live.x = (base.x ?? 0) + (d.w - live.w);
1083
+ }
1084
+ if (g.south) live.h = Math.max(16, Math.round(d.h + dy));
1085
+ if (g.north) {
1086
+ live.h = Math.max(16, Math.round(d.h - dy));
1087
+ live.y = (base.y ?? 0) + (d.h - live.h);
1088
+ }
1089
+ d.live = live;
1090
+ apply(live);
1091
+ onLive();
1092
+ };
1093
+ const end = () => {
1094
+ const d = drag.current;
1095
+ if (d) onTransform(d.live);
1096
+ drag.current = null;
1097
+ setGuides({ x: null, y: null });
1098
+ };
1099
+ const outline = {
1100
+ position: "fixed",
1101
+ left: box.x - 1,
1102
+ top: box.y - 1,
1103
+ width: box.w + 2,
1104
+ height: box.h + 2,
1105
+ outline: "2px solid #8b5cf6",
1106
+ borderRadius: 4,
1107
+ pointerEvents: "none",
1108
+ zIndex: Z2
1109
+ };
1110
+ const handle = {
1111
+ position: "fixed",
1112
+ width: 12,
1113
+ height: 12,
1114
+ background: "#fff",
1115
+ border: "2px solid #8b5cf6",
1116
+ borderRadius: 3,
1117
+ zIndex: Z2 + 1,
1118
+ touchAction: "none",
1119
+ boxSizing: "border-box"
1120
+ };
1121
+ const gripPos = (g) => {
1122
+ const cx = g.west ? box.x : g.east ? box.x + box.w : box.x + box.w / 2;
1123
+ const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
1124
+ return { left: cx - 6, top: cy - 6, cursor: g.cursor };
1125
+ };
1126
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1127
+ 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,
1128
+ 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,
1129
+ /* @__PURE__ */ jsx("div", { style: outline }),
1130
+ movable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1131
+ /* @__PURE__ */ jsx(
1132
+ "div",
1133
+ {
1134
+ title: "Drag to move",
1135
+ onPointerDown: startMove,
1136
+ onPointerMove: moveMove,
1137
+ onPointerUp: end,
1138
+ style: { ...handle, width: 18, height: 18, borderRadius: 9, background: "#8b5cf6", border: "2px solid #fff", left: box.x - 22, top: box.y - 22, cursor: "move" }
1139
+ }
1140
+ ),
1141
+ GRIPS.map((g) => /* @__PURE__ */ jsx(
1142
+ "div",
1143
+ {
1144
+ title: "Drag to resize",
1145
+ onPointerDown: startResize(g),
1146
+ onPointerMove: resizeMove(g),
1147
+ onPointerUp: end,
1148
+ style: { ...handle, ...gripPos(g) }
1149
+ },
1150
+ g.key
1151
+ ))
1152
+ ] }) : null
1153
+ ] });
1154
+ }
1155
+ function FloatingToolbar({
1156
+ box,
1157
+ isText,
1158
+ base,
1159
+ setStyle
1160
+ }) {
1161
+ const wrap = {
1162
+ position: "fixed",
1163
+ left: box.x,
1164
+ top: Math.max(8, box.y - 46),
1165
+ display: "flex",
1166
+ alignItems: "center",
1167
+ gap: 8,
1168
+ padding: "6px 10px",
1169
+ background: "#0f172a",
1170
+ color: "#fff",
1171
+ border: "1px solid rgba(255,255,255,0.12)",
1172
+ borderRadius: 999,
1173
+ boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
1174
+ zIndex: Z2 + 2,
1175
+ fontFamily: "system-ui, sans-serif",
1176
+ fontSize: 12
1177
+ };
1178
+ const swatch = { width: 22, height: 22, border: "none", background: "transparent", padding: 0, cursor: "pointer" };
1179
+ const num = { width: 52, background: "#1e293b", color: "#fff", border: "1px solid #334155", borderRadius: 6, padding: "3px 6px", font: "inherit" };
1180
+ return /* @__PURE__ */ jsxs("div", { style: wrap, onMouseDown: (e) => e.preventDefault(), children: [
1181
+ isText ? /* @__PURE__ */ jsxs("label", { title: "Text color", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1182
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.7 }, children: "A" }),
1183
+ /* @__PURE__ */ jsx("input", { type: "color", style: swatch, value: base.color ?? "#0f172a", onChange: (e) => setStyle({ color: e.target.value }) })
1184
+ ] }) : null,
1185
+ isText ? /* @__PURE__ */ jsx("input", { type: "number", title: "Font size (px)", style: num, placeholder: "size", value: base.fontSize?.value ?? "", onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } }) }) : null,
1186
+ /* @__PURE__ */ jsxs("label", { title: "Background", style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1187
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.7 }, children: "BG" }),
1188
+ /* @__PURE__ */ jsx("input", { type: "color", style: swatch, value: normalizeColor(base.background), onChange: (e) => setStyle({ background: e.target.value }) })
1189
+ ] })
1190
+ ] });
1191
+ }
1192
+ var ADD_MENU = [
1193
+ { kind: "text", label: "Text" },
1194
+ { kind: "heading", label: "Heading" },
1195
+ { kind: "button", label: "Button" },
1196
+ { kind: "image", label: "Image" },
1197
+ { kind: "box", label: "Box" }
1198
+ ];
1199
+ function EditBar({
1200
+ editing,
1201
+ onToggle,
1202
+ canUndo,
1203
+ canRedo,
1204
+ onUndo,
1205
+ onRedo,
1206
+ onAdd
1207
+ }) {
1208
+ const [open, setOpen] = useState(false);
1209
+ const bar = { position: "fixed", top: 12, right: 12, display: "flex", gap: 6, alignItems: "center", padding: 6, background: "#0f172a", color: "#fff", borderRadius: 999, boxShadow: "0 8px 24px -8px rgba(0,0,0,0.4)", zIndex: Z2 + 3, fontFamily: "system-ui, sans-serif", fontSize: 12 };
1210
+ const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
1211
+ return /* @__PURE__ */ jsxs("div", { style: bar, children: [
1212
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
1213
+ editing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1214
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1215
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
1216
+ 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(
1217
+ "button",
1218
+ {
1219
+ type: "button",
1220
+ style: { ...btn, textAlign: "left", borderRadius: 6, padding: "6px 10px" },
1221
+ onClick: () => {
1222
+ onAdd(m.kind);
1223
+ setOpen(false);
1224
+ },
1225
+ children: m.label
1226
+ },
1227
+ m.kind
1228
+ )) }) : null
1229
+ ] }),
1230
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
1231
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
1232
+ ] }) : null
1233
+ ] });
1234
+ }
1235
+ function EditMenuItems({
1236
+ node,
1237
+ hasClipboard,
1238
+ isContainer,
1239
+ onEditText,
1240
+ onAdd,
1241
+ onDuplicate,
1242
+ onCopy,
1243
+ onPaste,
1244
+ onWrap,
1245
+ onReorder,
1246
+ onDelete
1247
+ }) {
1248
+ const editable = node && (node.type === "text" || node.type === "heading");
1249
+ const nested = Boolean(node && node.parent !== null);
1250
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1251
+ editable ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
1252
+ /* @__PURE__ */ jsxs(ContextMenu.Sub, { children: [
1253
+ /* @__PURE__ */ jsx(ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
1254
+ /* @__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)) })
1255
+ ] }),
1256
+ node ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1257
+ /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
1258
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onDuplicate, children: "Duplicate" }),
1259
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onCopy, children: "Copy" }),
1260
+ /* @__PURE__ */ jsx(ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" }),
1261
+ nested ? /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: onWrap, children: "Wrap in Box" }) : null,
1262
+ /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
1263
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("up"), children: "Move up" }),
1264
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("down"), children: "Move down" }),
1265
+ nested ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1266
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("front"), children: "Bring to front" }),
1267
+ /* @__PURE__ */ jsx(ContextMenu.Item, { onClick: () => onReorder("back"), children: "Send to back" })
1268
+ ] }) : null,
1269
+ /* @__PURE__ */ jsx(ContextMenu.Separator, {}),
1270
+ /* @__PURE__ */ jsx(ContextMenu.Item, { danger: true, onClick: onDelete, children: "Delete" })
1271
+ ] }) : /* @__PURE__ */ jsx(ContextMenu.Item, { disabled: !hasClipboard, onClick: onPaste, children: "Paste" })
1272
+ ] });
1273
+ }
1274
+ function applyTransform(el, t) {
1275
+ if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
1276
+ el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
1277
+ el.style.opacity = t.opacity != null ? String(t.opacity) : "";
1278
+ el.style.width = t.w != null ? `${t.w}px` : "";
1279
+ el.style.height = t.h != null ? `${t.h}px` : "";
1280
+ } else {
1281
+ el.style.transform = "";
1282
+ el.style.opacity = "";
1283
+ el.style.width = "";
1284
+ el.style.height = "";
1285
+ }
1286
+ }
1287
+ function normalizeColor(v) {
1288
+ return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
1289
+ }
1290
+ function cssEscape2(value) {
1291
+ return value.replace(/["\\]/g, "\\$&");
1292
+ }
366
1293
 
367
- export { Canvas, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor };
1294
+ export { Canvas, EditablePage, Editor, Inspector, LayersPanel, NodeInspector, editorReduce, initEditor, useEditor };
368
1295
  //# sourceMappingURL=editor.js.map
369
1296
  //# sourceMappingURL=editor.js.map