@particle-academy/fancy-cms-ui 0.0.1-dev.5 → 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,35 +598,23 @@ 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
- let raf = 0;
418
607
  const compute = () => {
419
- raf = 0;
420
- const len = spacer.offsetHeight - window.innerHeight;
608
+ const len2 = spacer.offsetHeight - window.innerHeight;
421
609
  const top = spacer.getBoundingClientRect().top;
422
- onProgress(len > 0 ? Math.min(1, Math.max(0, -top / len)) : 0);
423
- };
424
- const onScroll = () => {
425
- if (!raf) raf = requestAnimationFrame(compute);
610
+ onProgress(len2 > 0 ? Math.min(1, Math.max(0, -top / len2)) : 0);
426
611
  };
427
612
  compute();
428
- window.addEventListener("scroll", onScroll, { passive: true });
429
- window.addEventListener("resize", onScroll);
613
+ window.addEventListener("scroll", compute, { passive: true });
614
+ window.addEventListener("resize", compute);
430
615
  return () => {
431
- window.removeEventListener("scroll", onScroll);
432
- window.removeEventListener("resize", onScroll);
433
- if (raf) cancelAnimationFrame(raf);
616
+ window.removeEventListener("scroll", compute);
617
+ window.removeEventListener("resize", compute);
434
618
  };
435
619
  }, [onProgress, frames]);
436
620
  const measureBox = useCallback(() => {
@@ -448,7 +632,7 @@ function EditablePage({
448
632
  window.removeEventListener("scroll", measureBox);
449
633
  window.removeEventListener("resize", measureBox);
450
634
  };
451
- }, [measureBox, ed.state.doc, transforms]);
635
+ }, [measureBox, ed.state.doc]);
452
636
  useEffect(() => {
453
637
  if (!editing || !selection || !pageRef.current || selNode?.type !== "text") return;
454
638
  const el = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
@@ -473,17 +657,29 @@ function EditablePage({
473
657
  ed.select(el.getAttribute("data-cms"));
474
658
  }
475
659
  };
476
- const addText = useCallback(() => {
477
- const parent = selNode && selNode.type !== "text" ? selNode.id : ed.state.doc.sections[0] ?? null;
478
- const siblings = childrenOf(ed.state.doc, parent);
479
- const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
480
- const id = `n${ed.state.doc.seq + 1}-${Math.floor(performance.now())}`;
481
- ed.apply({ t: "insert_node", node: { id, type: "text", parent, order, props: { content: "New text" }, style: { base: { color: "inherit" } } } });
482
- ed.select(id);
483
- }, [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
+ );
484
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 }) });
485
681
  return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
486
- /* @__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,
487
683
  editing && box && selection ? /* @__PURE__ */ jsx(
488
684
  SelectionOverlay,
489
685
  {
@@ -495,7 +691,20 @@ function EditablePage({
495
691
  onLive: measureBox
496
692
  }
497
693
  ) : null,
498
- 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,
499
708
  revealed || editing ? /* @__PURE__ */ jsx(
500
709
  EditBar,
501
710
  {
@@ -508,12 +717,30 @@ function EditablePage({
508
717
  canRedo: ed.canRedo,
509
718
  onUndo: ed.undo,
510
719
  onRedo: ed.redo,
511
- onAddText: addText
720
+ onAdd: addNode
512
721
  }
513
722
  ) : null,
514
- 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
515
724
  ] });
516
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
+ ];
517
744
  function SelectionOverlay({
518
745
  box,
519
746
  base,
@@ -525,29 +752,48 @@ function SelectionOverlay({
525
752
  const drag = useRef(null);
526
753
  const apply = (t) => {
527
754
  const el = getEl();
528
- if (el) {
529
- el.style.transform = `translate3d(${t.x ?? 0}px, ${t.y ?? 0}px, 0) scale(${t.scale ?? 1})`;
530
- el.style.opacity = t.opacity != null ? String(t.opacity) : "";
531
- }
755
+ if (el) applyTransform(el, t);
532
756
  };
533
- const start = (kind) => (e) => {
757
+ const startMove = (e) => {
534
758
  e.stopPropagation();
535
759
  e.preventDefault();
536
760
  e.currentTarget.setPointerCapture(e.pointerId);
537
- 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 } };
538
762
  };
539
- const move = (e) => {
763
+ const moveMove = (e) => {
540
764
  const d = drag.current;
541
765
  if (!d) return;
542
- if (d.kind === "move") {
543
- d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
544
- } else {
545
- const factor = Math.max(0.1, (d.w + (e.clientX - d.x)) / d.w);
546
- d.live = { ...base, scale: Number(((base.scale ?? 1) * factor).toFixed(3)) };
547
- }
766
+ d.live = { ...base, x: (base.x ?? 0) + (e.clientX - d.x), y: (base.y ?? 0) + (e.clientY - d.y) };
548
767
  apply(d.live);
549
768
  onLive();
550
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
+ };
551
797
  const end = () => {
552
798
  const d = drag.current;
553
799
  if (d) onTransform(d.live);
@@ -562,17 +808,23 @@ function SelectionOverlay({
562
808
  outline: "2px solid #8b5cf6",
563
809
  borderRadius: 4,
564
810
  pointerEvents: "none",
565
- zIndex: Z
811
+ zIndex: Z2
566
812
  };
567
813
  const handle = {
568
814
  position: "fixed",
569
- width: 16,
570
- height: 16,
571
- background: "#8b5cf6",
572
- border: "2px solid #fff",
573
- borderRadius: 5,
574
- zIndex: Z + 1,
575
- 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 };
576
828
  };
577
829
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
578
830
  /* @__PURE__ */ jsx("div", { style: outline }),
@@ -581,22 +833,23 @@ function SelectionOverlay({
581
833
  "div",
582
834
  {
583
835
  title: "Drag to move",
584
- onPointerDown: start("move"),
585
- onPointerMove: move,
836
+ onPointerDown: startMove,
837
+ onPointerMove: moveMove,
586
838
  onPointerUp: end,
587
- 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" }
588
840
  }
589
841
  ),
590
- /* @__PURE__ */ jsx(
842
+ GRIPS.map((g) => /* @__PURE__ */ jsx(
591
843
  "div",
592
844
  {
593
845
  title: "Drag to resize",
594
- onPointerDown: start("resize"),
595
- onPointerMove: move,
846
+ onPointerDown: startResize(g),
847
+ onPointerMove: resizeMove(g),
596
848
  onPointerUp: end,
597
- style: { ...handle, left: box.x + box.w - 7, top: box.y + box.h - 7, cursor: "nwse-resize" }
598
- }
599
- )
849
+ style: { ...handle, ...gripPos(g) }
850
+ },
851
+ g.key
852
+ ))
600
853
  ] }) : null
601
854
  ] });
602
855
  }
@@ -619,7 +872,7 @@ function FloatingToolbar({
619
872
  border: "1px solid rgba(255,255,255,0.12)",
620
873
  borderRadius: 999,
621
874
  boxShadow: "0 8px 24px -8px rgba(0,0,0,0.5)",
622
- zIndex: Z + 2,
875
+ zIndex: Z2 + 2,
623
876
  fontFamily: "system-ui, sans-serif",
624
877
  fontSize: 12
625
878
  };
@@ -637,6 +890,13 @@ function FloatingToolbar({
637
890
  ] })
638
891
  ] });
639
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
+ ];
640
900
  function EditBar({
641
901
  editing,
642
902
  onToggle,
@@ -644,19 +904,48 @@ function EditBar({
644
904
  canRedo,
645
905
  onUndo,
646
906
  onRedo,
647
- onAddText
907
+ onAdd
648
908
  }) {
649
- 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 };
650
911
  const btn = { font: "inherit", border: "none", background: "transparent", color: "inherit", cursor: "pointer", padding: "4px 8px", borderRadius: 999 };
651
912
  return /* @__PURE__ */ jsxs("div", { style: bar, children: [
652
913
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
653
914
  editing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
654
- /* @__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
+ ] }),
655
931
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canUndo ? 1 : 0.4 }, disabled: !canUndo, onClick: onUndo, children: "\u21B6" }),
656
932
  /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, opacity: canRedo ? 1 : 0.4 }, disabled: !canRedo, onClick: onRedo, children: "\u21B7" })
657
933
  ] }) : null
658
934
  ] });
659
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
+ }
660
949
  function normalizeColor(v) {
661
950
  return v && /^#[0-9a-fA-F]{6}$/.test(v) ? v : "#ffffff";
662
951
  }
@@ -664,6 +953,6 @@ function cssEscape2(value) {
664
953
  return value.replace(/["\\]/g, "\\$&");
665
954
  }
666
955
 
667
- export { Canvas, EditablePage, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor };
956
+ export { Canvas, EditablePage, Editor, Inspector, LayersPanel, NodeInspector, editorReduce, initEditor, useEditor };
668
957
  //# sourceMappingURL=editor.js.map
669
958
  //# sourceMappingURL=editor.js.map