@particle-academy/fancy-cms-ui 0.0.1-dev.6 → 0.0.1-dev.7

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,5 +1,5 @@
1
1
  import { keyBetween } from './chunk-7YYLYBLI.js';
2
- import { CmsPage, defaultRegistry } from './chunk-5PWZGIYZ.js';
2
+ import { CmsPage, defaultRegistry } from './chunk-5O6U4VIU.js';
3
3
  import { reduce, childrenOf } from './chunk-HYHNMW47.js';
4
4
  import { useReducer, useCallback, useMemo, useRef, useState, useLayoutEffect, Fragment, useEffect } from 'react';
5
5
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
@@ -364,7 +364,202 @@ function Toolbar({ ed }) {
364
364
  }
365
365
  );
366
366
  }
367
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
368
+ function NodeInspector({
369
+ node,
370
+ transform,
371
+ measured,
372
+ onProps,
373
+ onStyle,
374
+ onTransform,
375
+ onRemove,
376
+ onClose
377
+ }) {
378
+ const base = node.style.base ?? {};
379
+ const setT = (patch) => onTransform({ ...transform, ...patch });
380
+ const handled = /* @__PURE__ */ new Set(["content", "label"]);
381
+ const extraKeys = Object.keys(node.props).filter(
382
+ (k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
383
+ );
384
+ return /* @__PURE__ */ jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
385
+ /* @__PURE__ */ jsxs("div", { style: headRow, children: [
386
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
387
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 12, textTransform: "capitalize" }, children: node.type }),
388
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
389
+ ] }),
390
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
391
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
392
+ /* @__PURE__ */ jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
393
+ ] }),
394
+ /* @__PURE__ */ jsxs("div", { style: body, children: [
395
+ "content" in node.props ? /* @__PURE__ */ jsx(Group, { label: "Content", children: /* @__PURE__ */ jsx(
396
+ "textarea",
397
+ {
398
+ style: { ...input2, minHeight: 64, resize: "vertical", lineHeight: 1.4 },
399
+ value: String(node.props.content ?? ""),
400
+ onChange: (e) => onProps({ content: e.target.value })
401
+ }
402
+ ) }) : null,
403
+ "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,
404
+ 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,
405
+ /* @__PURE__ */ jsxs(Group, { label: "Position & size", children: [
406
+ /* @__PURE__ */ jsxs(Row, { children: [
407
+ /* @__PURE__ */ jsx(Num, { label: "X", value: transform.x, placeholder: "0", onChange: (v) => setT({ x: v }) }),
408
+ /* @__PURE__ */ jsx(Num, { label: "Y", value: transform.y, placeholder: "0", onChange: (v) => setT({ y: v }) })
409
+ ] }),
410
+ /* @__PURE__ */ jsxs(Row, { children: [
411
+ /* @__PURE__ */ jsx(Num, { label: "W", value: transform.w, placeholder: `${Math.round(measured.w)}`, onChange: (v) => setT({ w: v }) }),
412
+ /* @__PURE__ */ jsx(Num, { label: "H", value: transform.h, placeholder: `${Math.round(measured.h)}`, onChange: (v) => setT({ h: v }) })
413
+ ] }),
414
+ /* @__PURE__ */ jsxs(Row, { children: [
415
+ /* @__PURE__ */ jsx(Num, { label: "Scale", value: transform.scale, placeholder: "1", step: 0.05, onChange: (v) => setT({ scale: v }) }),
416
+ /* @__PURE__ */ jsx(Num, { label: "Rotate", value: transform.rotate, placeholder: "0", onChange: (v) => setT({ rotate: v }) })
417
+ ] }),
418
+ /* @__PURE__ */ jsxs(Row, { children: [
419
+ /* @__PURE__ */ jsx(Num, { label: "Opacity", value: transform.opacity, placeholder: "1", min: 0, max: 1, step: 0.05, onChange: (v) => setT({ opacity: v }) }),
420
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
421
+ ] })
422
+ ] }),
423
+ !CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Text", children: [
424
+ /* @__PURE__ */ jsxs(Row, { children: [
425
+ /* @__PURE__ */ jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
426
+ /* @__PURE__ */ jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
427
+ ] }),
428
+ /* @__PURE__ */ jsxs(Row, { children: [
429
+ /* @__PURE__ */ jsx(
430
+ Sel,
431
+ {
432
+ label: "Weight",
433
+ value: String(base.fontWeight ?? ""),
434
+ options: [["", "\u2014"], ["400", "Regular"], ["500", "Medium"], ["600", "Semibold"], ["700", "Bold"], ["800", "Heavy"]],
435
+ onChange: (v) => onStyle({ fontWeight: v ? Number(v) : void 0 })
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx(
439
+ Sel,
440
+ {
441
+ label: "Align",
442
+ value: base.textAlign ?? "",
443
+ options: [["", "\u2014"], ["left", "Left"], ["center", "Center"], ["right", "Right"], ["justify", "Justify"]],
444
+ onChange: (v) => onStyle({ textAlign: v || void 0 })
445
+ }
446
+ )
447
+ ] })
448
+ ] }) : null,
449
+ /* @__PURE__ */ jsxs(Group, { label: "Box", children: [
450
+ /* @__PURE__ */ jsxs(Row, { children: [
451
+ /* @__PURE__ */ jsx(Color, { label: "Background", value: hex(base.background), onChange: (v) => onStyle({ background: v }) }),
452
+ /* @__PURE__ */ jsx(Num, { label: "Radius", value: base.radius?.value, placeholder: "0", onChange: (v) => onStyle({ radius: v == null ? void 0 : len(v) }) })
453
+ ] }),
454
+ /* @__PURE__ */ jsxs(Row, { children: [
455
+ /* @__PURE__ */ jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
456
+ /* @__PURE__ */ jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
457
+ ] })
458
+ ] })
459
+ ] })
460
+ ] });
461
+ }
462
+ function Group({ label: label2, children }) {
463
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
464
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label2 }),
465
+ children
466
+ ] });
467
+ }
468
+ function Row({ children }) {
469
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children });
470
+ }
471
+ function PropField({ name, value, onChange }) {
472
+ if (typeof value === "boolean") {
473
+ return /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
474
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
475
+ /* @__PURE__ */ jsx("span", { style: { textTransform: "capitalize" }, children: name })
476
+ ] });
477
+ }
478
+ if (name === "variant") {
479
+ return /* @__PURE__ */ jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
480
+ }
481
+ const isNum = typeof value === "number";
482
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
483
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: name }),
484
+ /* @__PURE__ */ jsx(
485
+ "input",
486
+ {
487
+ style: input2,
488
+ type: isNum ? "number" : "text",
489
+ value: String(value),
490
+ onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
491
+ }
492
+ )
493
+ ] });
494
+ }
495
+ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
496
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
497
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
498
+ /* @__PURE__ */ jsx(
499
+ "input",
500
+ {
501
+ style: input2,
502
+ type: "number",
503
+ step,
504
+ min,
505
+ max,
506
+ value: value ?? "",
507
+ placeholder,
508
+ onChange: (e) => onChange(e.target.value === "" ? void 0 : Number(e.target.value))
509
+ }
510
+ )
511
+ ] });
512
+ }
513
+ function Color({ label: label2, value, onChange }) {
514
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
515
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
516
+ /* @__PURE__ */ jsx("input", { style: { ...input2, padding: 2, height: 30 }, type: "color", value, onChange: (e) => onChange(e.target.value) })
517
+ ] });
518
+ }
519
+ function Sel({ label: label2, value, options, onChange }) {
520
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
521
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
522
+ /* @__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)) })
523
+ ] });
524
+ }
525
+ var len = (value) => ({ value, unit: "px" });
526
+ var hex = (v) => v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
367
527
  var Z = 2147483e3;
528
+ var panel = {
529
+ position: "fixed",
530
+ top: 60,
531
+ right: 12,
532
+ bottom: 96,
533
+ width: 264,
534
+ display: "flex",
535
+ flexDirection: "column",
536
+ background: "#0b1220",
537
+ color: "#e2e8f0",
538
+ border: "1px solid #1e293b",
539
+ borderRadius: 12,
540
+ boxShadow: "0 18px 48px -16px rgba(0,0,0,0.6)",
541
+ zIndex: Z + 2,
542
+ fontFamily: "system-ui, sans-serif",
543
+ overflow: "hidden"
544
+ };
545
+ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
546
+ var body = { padding: 12, overflowY: "auto" };
547
+ var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
548
+ var fieldLabel = { fontSize: 10, opacity: 0.6 };
549
+ var input2 = {
550
+ width: "100%",
551
+ boxSizing: "border-box",
552
+ background: "#0f172a",
553
+ color: "#e2e8f0",
554
+ border: "1px solid #334155",
555
+ borderRadius: 6,
556
+ padding: "5px 7px",
557
+ font: "inherit",
558
+ fontSize: 12
559
+ };
560
+ var iconBtn = { background: "transparent", border: "none", color: "#e2e8f0", cursor: "pointer", fontSize: 13, padding: 4, borderRadius: 6 };
561
+ var miniBtn = { font: "inherit", fontSize: 11, color: "#e2e8f0", background: "#334155", border: "1px solid #475569", borderRadius: 6, padding: "5px 10px", cursor: "pointer" };
562
+ var Z2 = 2147483e3;
368
563
  function EditablePage({
369
564
  doc,
370
565
  registry = defaultRegistry,
@@ -373,7 +568,8 @@ function EditablePage({
373
568
  onNodeTransform,
374
569
  onSelect,
375
570
  frames = 1,
376
- onProgress
571
+ onProgress,
572
+ pinned = true
377
573
  }) {
378
574
  const ed = useEditor(doc);
379
575
  const [editing, setEditing] = useState(false);
@@ -402,22 +598,16 @@ function EditablePage({
402
598
  root.querySelectorAll("[data-cms]").forEach((el) => {
403
599
  const id = el.dataset.cms;
404
600
  const t = id ? transforms?.[id] : void 0;
405
- if (t) {
406
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
407
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
408
- } else {
409
- el.style.transform = "";
410
- el.style.opacity = "";
411
- }
601
+ applyTransform(el, t);
412
602
  });
413
603
  }, [transforms, ed.state.doc]);
414
604
  useEffect(() => {
415
605
  const spacer = spacerRef.current;
416
606
  if (!spacer || !onProgress) return;
417
607
  const compute = () => {
418
- const len = spacer.offsetHeight - window.innerHeight;
608
+ const len2 = spacer.offsetHeight - window.innerHeight;
419
609
  const top = spacer.getBoundingClientRect().top;
420
- onProgress(len > 0 ? Math.min(1, Math.max(0, -top / len)) : 0);
610
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
421
611
  };
422
612
  compute();
423
613
  window.addEventListener("scroll", compute, { passive: true });
@@ -442,7 +632,7 @@ function EditablePage({
442
632
  window.removeEventListener("scroll", measureBox);
443
633
  window.removeEventListener("resize", measureBox);
444
634
  };
445
- }, [measureBox, ed.state.doc, transforms]);
635
+ }, [measureBox, ed.state.doc]);
446
636
  useEffect(() => {
447
637
  if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
448
638
  const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
@@ -467,17 +657,29 @@ function EditablePage({
467
657
  ed.select(el.getAttribute("data-cms"));
468
658
  }
469
659
  };
470
- const addText = useCallback(() => {
471
- const parent = selNode && selNode.type !== "text" ? selNode.id : ed.state.doc.sections[0] ?? null;
472
- const siblings = childrenOf(ed.state.doc, parent);
473
- const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
474
- const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
475
- ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
476
- ed.select(id);
477
- }, [ed, selNode]);
660
+ const addNode = useCallback(
661
+ (kind) => {
662
+ const doc2 = ed.state.doc;
663
+ const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
664
+ const siblings = childrenOf(doc2, parent);
665
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
666
+ const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
667
+ const def = ADD_DEFAULTS[kind];
668
+ ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
669
+ ed.select(id);
670
+ },
671
+ [ed, selNode]
672
+ );
478
673
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
674
+ const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
675
+ const removeSelected = () => {
676
+ if (!selection) return;
677
+ ed.apply({ t: "remove_node", id: selection });
678
+ ed.select(null);
679
+ };
680
+ const page = /* @__PURE__ */ jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry }) });
479
681
  return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
480
- /* @__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: /* @__PURE__ */ jsx("div", { ref: pageRef, onClickCapture, children: /* @__PURE__ */ jsx(CmsPage, { doc: ed.state.doc, registry }) }) }) }),
682
+ 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,
481
683
  editing && box && selection ? /* @__PURE__ */ jsx(
482
684
  SelectionOverlay,
483
685
  {
@@ -489,7 +691,20 @@ function EditablePage({
489
691
  onLive: measureBox
490
692
  }
491
693
  ) : null,
492
- editing && box && selNode ? /* @__PURE__ */ jsx(FloatingToolbar, { box, isText: selNode.type === "text", base: selNode.style.base, setStyle }) : null,
694
+ editing && box && selNode ? /* @__PURE__ */ jsx(FloatingToolbar, { box, isText: selNode.type === "text" || selNode.type === "heading", base: selNode.style.base, setStyle }) : null,
695
+ editing && selNode && box ? /* @__PURE__ */ jsx(
696
+ NodeInspector,
697
+ {
698
+ node: selNode,
699
+ transform: (selection ? transforms?.[selection] : void 0) ?? {},
700
+ measured: { w: box.w, h: box.h },
701
+ onProps: setProps,
702
+ onStyle: setStyle,
703
+ onTransform: (t) => selection && onNodeTransform?.(selection, t),
704
+ onRemove: removeSelected,
705
+ onClose: () => ed.select(null)
706
+ }
707
+ ) : null,
493
708
  revealed || editing ? /* @__PURE__ */ jsx(
494
709
  EditBar,
495
710
  {
@@ -502,12 +717,30 @@ function EditablePage({
502
717
  canRedo: ed.canRedo,
503
718
  onUndo: ed.undo,
504
719
  onRedo: ed.redo,
505
- onAddText: addText
720
+ onAdd: addNode
506
721
  }
507
722
  ) : null,
508
- editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z }, children: timelineDock }) : null
723
+ editing && timelineDock ? /* @__PURE__ */ jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
509
724
  ] });
510
725
  }
726
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
727
+ var ADD_DEFAULTS = {
728
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
729
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
730
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
731
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
732
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
733
+ };
734
+ var GRIPS = [
735
+ { key: "nw", north: true, west: true, cursor: "nwse-resize" },
736
+ { key: "n", north: true, cursor: "ns-resize" },
737
+ { key: "ne", north: true, east: true, cursor: "nesw-resize" },
738
+ { key: "e", east: true, cursor: "ew-resize" },
739
+ { key: "se", south: true, east: true, cursor: "nwse-resize" },
740
+ { key: "s", south: true, cursor: "ns-resize" },
741
+ { key: "sw", south: true, west: true, cursor: "nesw-resize" },
742
+ { key: "w", west: true, cursor: "ew-resize" }
743
+ ];
511
744
  function SelectionOverlay({
512
745
  box,
513
746
  base,
@@ -519,29 +752,48 @@ function SelectionOverlay({
519
752
  const drag = useRef(null);
520
753
  const apply = (t) => {
521
754
  const el = getEl();
522
- if (el) {
523
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
524
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
525
- }
755
+ if (el) applyTransform(el, t);
526
756
  };
527
- const start = (kind) => (e) => {
757
+ const startMove = (e) => {
528
758
  e.stopPropagation();
529
759
  e.preventDefault();
530
760
  e.currentTarget.setPointerCapture(e.pointerId);
531
- drag.current = { kind, x: e.clientX, y: e.clientY, w: box.w, live: { ...base } };
761
+ drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
532
762
  };
533
- const move = (e) => {
763
+ const moveMove = (e) => {
534
764
  const d = drag.current;
535
765
  if (!d) return;
536
- if (d.kind === "move") {
537
- d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
538
- } else {
539
- const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
540
- d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
541
- }
766
+ d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
542
767
  apply(d.live);
543
768
  onLive();
544
769
  };
770
+ const startResize = (g) => (e) => {
771
+ e.stopPropagation();
772
+ e.preventDefault();
773
+ e.currentTarget.setPointerCapture(e.pointerId);
774
+ drag.current = { x: e.clientX, y: e.clientY, w: base.w ?? box.w, h: base.h ?? box.h, live: { ...base } };
775
+ e.currentTarget.dataset.grip = g.key;
776
+ };
777
+ const resizeMove = (g) => (e) => {
778
+ const d = drag.current;
779
+ if (!d) return;
780
+ const dx = e.clientX - d.x;
781
+ const dy = e.clientY - d.y;
782
+ const live = { ...base };
783
+ if (g.east) live.w = Math.max(16, Math.round(d.w + dx));
784
+ if (g.west) {
785
+ live.w = Math.max(16, Math.round(d.w - dx));
786
+ live.x = (base.x ?? 0) + (d.w - live.w);
787
+ }
788
+ if (g.south) live.h = Math.max(16, Math.round(d.h + dy));
789
+ if (g.north) {
790
+ live.h = Math.max(16, Math.round(d.h - dy));
791
+ live.y = (base.y ?? 0) + (d.h - live.h);
792
+ }
793
+ d.live = live;
794
+ apply(live);
795
+ onLive();
796
+ };
545
797
  const end = () => {
546
798
  const d = drag.current;
547
799
  if (d) onTransform(d.live);
@@ -556,17 +808,23 @@ function SelectionOverlay({
556
808
  outline: "2px solid #8b5cf6",
557
809
  borderRadius: 4,
558
810
  pointerEvents: "none",
559
- zIndex: Z
811
+ zIndex: Z2
560
812
  };
561
813
  const handle = {
562
814
  position: "fixed",
563
- width: 16,
564
- height: 16,
565
- background: "#8b5cf6",
566
- border: "2px solid #fff",
567
- borderRadius: 5,
568
- zIndex: Z + 1,
569
- touchAction: "none"
815
+ width: 12,
816
+ height: 12,
817
+ background: "#fff",
818
+ border: "2px solid #8b5cf6",
819
+ borderRadius: 3,
820
+ zIndex: Z2 + 1,
821
+ touchAction: "none",
822
+ boxSizing: "border-box"
823
+ };
824
+ const gripPos = (g) => {
825
+ const cx = g.west ? box.x : g.east ? box.x + box.w : box.x + box.w / 2;
826
+ const cy = g.north ? box.y : g.south ? box.y + box.h : box.y + box.h / 2;
827
+ return { left: cx - 6, top: cy - 6, cursor: g.cursor };
570
828
  };
571
829
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
572
830
  /* @__PURE__ */ jsx("div", { style: outline }),
@@ -575,22 +833,23 @@ function SelectionOverlay({
575
833
  "div",
576
834
  {
577
835
  title: "Drag to move",
578
- onPointerDown: start("move"),
579
- onPointerMove: move,
836
+ onPointerDown: startMove,
837
+ onPointerMove: moveMove,
580
838
  onPointerUp: end,
581
- style: { ...handle, left: box.x - 9, top: box.y - 9, cursor: "move", borderRadius: 9 }
839
+ style: { ...handle, width: 18, height: 18, borderRadius: 9, background: "#8b5cf6", border: "2px solid #fff", left: box.x - 22, top: box.y - 22, cursor: "move" }
582
840
  }
583
841
  ),
584
- /* @__PURE__ */ jsx(
842
+ GRIPS.map((g) => /* @__PURE__ */ jsx(
585
843
  "div",
586
844
  {
587
845
  title: "Drag to resize",
588
- onPointerDown: start("resize"),
589
- onPointerMove: move,
846
+ onPointerDown: startResize(g),
847
+ onPointerMove: resizeMove(g),
590
848
  onPointerUp: end,
591
- style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
592
- }
593
- )
849
+ style: { ...handle, ...gripPos(g) }
850
+ },
851
+ g.key
852
+ ))
594
853
  ] }) : null
595
854
  ] });
596
855
  }
@@ -613,7 +872,7 @@ function FloatingToolbar({
613
872
  border: "1px solid rgba(255,255,255,0.12)",
614
873
  borderRadius: 999,
615
874
  boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
616
- zIndex: Z + 2,
875
+ zIndex: Z2 + 2,
617
876
  fontFamily: "system-ui, sans-serif",
618
877
  fontSize: 12
619
878
  };
@@ -631,6 +890,13 @@ function FloatingToolbar({
631
890
  ] })
632
891
  ] });
633
892
  }
893
+ var ADD_MENU = [
894
+ { kind: "text", label: "Text" },
895
+ { kind: "heading", label: "Heading" },
896
+ { kind: "button", label: "Button" },
897
+ { kind: "image", label: "Image" },
898
+ { kind: "box", label: "Box" }
899
+ ];
634
900
  function EditBar({
635
901
  editing,
636
902
  onToggle,
@@ -638,19 +904,48 @@ function EditBar({
638
904
  canRedo,
639
905
  onUndo,
640
906
  onRedo,
641
- onAddText
907
+ onAdd
642
908
  }) {
643
- 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: Z + 3, fontFamily: "system-ui, sans-serif", fontSize: 12 };
909
+ const [open, setOpen] = useState(false);
910
+ 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 };
644
911
  const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
645
912
  return /* @__PURE__ */ jsxs("div", { style: bar, children: [
646
913
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
647
914
  editing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
648
- /* @__PURE__ */ jsx("button", { type: "button", style: btn, onClick: onAddText, title: "Add a text element", children: "\uFF0B Text" }),
915
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
916
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an element", children: "\uFF0B Add \u25BE" }),
917
+ 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(
918
+ "button",
919
+ {
920
+ type: "button",
921
+ style: { ...btn, textAlign: "left", borderRadius: 6, padding: "6px 10px" },
922
+ onClick: () => {
923
+ onAdd(m.kind);
924
+ setOpen(false);
925
+ },
926
+ children: m.label
927
+ },
928
+ m.kind
929
+ )) }) : null
930
+ ] }),
649
931
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
650
932
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
651
933
  ] }) : null
652
934
  ] });
653
935
  }
936
+ function applyTransform(el, t) {
937
+ if (t && (t.x != null || t.y != null || t.scale != null || t.rotate != null || t.opacity != null || t.w != null || t.h != null)) {
938
+ el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1}) rotate(${t.rotate ?? 0}deg)`;
939
+ el.style.opacity = t.opacity != null ? String(t.opacity) : "";
940
+ el.style.width = t.w != null ? `${t.w}px` : "";
941
+ el.style.height = t.h != null ? `${t.h}px` : "";
942
+ } else {
943
+ el.style.transform = "";
944
+ el.style.opacity = "";
945
+ el.style.width = "";
946
+ el.style.height = "";
947
+ }
948
+ }
654
949
  function normalizeColor(v) {
655
950
  return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
656
951
  }
@@ -658,6 +953,6 @@ function cssEscape2(value) {
658
953
  return value.replace(/["\\]/g, "\\$&");
659
954
  }
660
955
 
661
- export { Canvas, EditablePage, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor };
956
+ export { Canvas, EditablePage, Editor, Inspector, LayersPanel, NodeInspector, editorReduce, initEditor, useEditor };
662
957
  //# sourceMappingURL=editor.js.map
663
958
  //# sourceMappingURL=editor.js.map