@particle-academy/fancy-cms-ui 0.0.1-dev.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/{chunk-HYHNMW47.js → chunk-GOXVGEKU.js} +103 -3
- package/dist/chunk-GOXVGEKU.js.map +1 -0
- package/dist/chunk-N22BQAY3.js +442 -0
- package/dist/chunk-N22BQAY3.js.map +1 -0
- package/dist/chunk-QYNWSECG.js +19 -0
- package/dist/chunk-QYNWSECG.js.map +1 -0
- package/dist/editor.cjs +475 -116
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +10 -91
- package/dist/editor.d.ts +10 -91
- package/dist/editor.js +364 -420
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +517 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/react.cjs +45 -25
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +13 -6
- package/dist/react.d.ts +13 -6
- package/dist/react.js +2 -19
- package/dist/react.js.map +1 -1
- package/dist/{types-CpfPEq3V.d.cts → registry-D5y5IgCR.d.cts} +30 -1
- package/dist/{types-CpfPEq3V.d.ts → registry-D5y5IgCR.d.ts} +30 -1
- package/dist/useEditor-DLH7hU6G.d.cts +162 -0
- package/dist/useEditor-qmF9n2e6.d.ts +162 -0
- package/package.json +6 -6
- package/dist/chunk-5O6U4VIU.js +0 -78
- package/dist/chunk-5O6U4VIU.js.map +0 -1
- package/dist/chunk-7YYLYBLI.js +0 -47
- package/dist/chunk-7YYLYBLI.js.map +0 -1
- package/dist/chunk-HYHNMW47.js.map +0 -1
- package/dist/ops-C_s0FGeV.d.cts +0 -71
- package/dist/ops-nevYkDzK.d.ts +0 -71
- package/dist/registry-Bk7GN1rA.d.cts +0 -19
- package/dist/registry-Dht9JBLU.d.ts +0 -19
package/dist/editor.cjs
CHANGED
|
@@ -98,6 +98,10 @@ function reduce(doc, op, opts = {}) {
|
|
|
98
98
|
props: { ...doc.nodes[op.id].props, ...op.patch }
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
case "set_layout": {
|
|
102
|
+
if (!doc.nodes[op.id]) return invalid("node not found");
|
|
103
|
+
return patchNode(doc, op.id, { layout: op.layout });
|
|
104
|
+
}
|
|
101
105
|
case "set_style": {
|
|
102
106
|
const node = doc.nodes[op.id];
|
|
103
107
|
if (!node) return invalid("node not found");
|
|
@@ -357,10 +361,13 @@ ${inner}
|
|
|
357
361
|
}
|
|
358
362
|
return blocks.join("\n\n") + (blocks.length ? "\n" : "");
|
|
359
363
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
+
function getPath(data, path) {
|
|
365
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
|
|
366
|
+
}
|
|
367
|
+
function resolveValue(v, data) {
|
|
368
|
+
if (v === void 0 || v === null) return v;
|
|
369
|
+
if (isBinding(v)) return getPath(data, v.$bind);
|
|
370
|
+
return v;
|
|
364
371
|
}
|
|
365
372
|
var defaultRegistry = {
|
|
366
373
|
section: ({ children }) => children,
|
|
@@ -368,18 +375,29 @@ var defaultRegistry = {
|
|
|
368
375
|
grid: ({ children }) => children,
|
|
369
376
|
frame: ({ children }) => children,
|
|
370
377
|
shape: ({ children }) => children,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
card: ({ children }) => children,
|
|
379
|
+
text: ({ node, text }) => text(node.props.content),
|
|
380
|
+
heading: ({ node, text }) => text(node.props.content) || "Heading",
|
|
381
|
+
// Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that
|
|
382
|
+
// plain `text` can't express. The author owns the markup (it's their content).
|
|
383
|
+
richtext: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: text(node.props.html) } }),
|
|
384
|
+
link: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
|
|
385
|
+
divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
|
|
386
|
+
callout: ({ node, text }) => {
|
|
387
|
+
const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
|
|
388
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: text(node.props.content) || "Callout" }) });
|
|
389
|
+
},
|
|
390
|
+
code: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: text(node.props.content) }) }),
|
|
391
|
+
button: ({ node, text }) => {
|
|
392
|
+
const label2 = text(node.props.label) || text(node.props.content) || "Button";
|
|
393
|
+
const variant = text(node.props.variant) || "primary";
|
|
376
394
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
|
|
377
395
|
},
|
|
378
|
-
image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
396
|
+
image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
379
397
|
"img",
|
|
380
398
|
{
|
|
381
|
-
src:
|
|
382
|
-
alt:
|
|
399
|
+
src: text(node.props.src),
|
|
400
|
+
alt: text(node.props.alt),
|
|
383
401
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
384
402
|
}
|
|
385
403
|
)
|
|
@@ -401,30 +419,34 @@ function buttonStyle(variant) {
|
|
|
401
419
|
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
402
420
|
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
403
421
|
}
|
|
404
|
-
function RenderNode({ doc, id, registry = defaultRegistry }) {
|
|
422
|
+
function RenderNode({ doc, id, registry = defaultRegistry, data }) {
|
|
405
423
|
const node = doc.nodes[id];
|
|
406
424
|
if (!node) return null;
|
|
407
|
-
|
|
425
|
+
if (node.type === "repeater") {
|
|
426
|
+
const arr = resolveValue(node.props.items, data);
|
|
427
|
+
const items = Array.isArray(arr) ? arr : [];
|
|
428
|
+
const template = childrenOf(doc, id)[0];
|
|
429
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, className: node.className, children: template ? items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: template.id, registry, data: { ...data, item, index } }, index)) : null });
|
|
430
|
+
}
|
|
431
|
+
const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry, data }, child.id));
|
|
432
|
+
const resolve = (v) => resolveValue(v, data);
|
|
433
|
+
const text = (v) => {
|
|
434
|
+
const r = resolve(v);
|
|
435
|
+
return r === void 0 || r === null ? "" : String(r);
|
|
436
|
+
};
|
|
408
437
|
const renderer = registry[node.type];
|
|
409
|
-
const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
|
|
410
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
411
|
-
"div",
|
|
412
|
-
{
|
|
413
|
-
"data-cms": node.id,
|
|
414
|
-
"data-cms-island": node.island ? node.type : void 0,
|
|
415
|
-
className: node.className,
|
|
416
|
-
children: inner
|
|
417
|
-
}
|
|
418
|
-
);
|
|
438
|
+
const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
|
|
439
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, className: node.className, children: inner });
|
|
419
440
|
}
|
|
420
441
|
function CmsPage({
|
|
421
442
|
doc,
|
|
422
443
|
registry = defaultRegistry,
|
|
444
|
+
data,
|
|
423
445
|
includeStyles = true
|
|
424
446
|
}) {
|
|
425
447
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
426
448
|
includeStyles ? /* @__PURE__ */ jsxRuntime.jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
|
|
427
|
-
doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry }, id))
|
|
449
|
+
doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry, data }, id))
|
|
428
450
|
] });
|
|
429
451
|
}
|
|
430
452
|
function Canvas({ doc, selection, onSelect, apply }) {
|
|
@@ -513,31 +535,110 @@ function Canvas({ doc, selection, onSelect, apply }) {
|
|
|
513
535
|
function cssEscape(value) {
|
|
514
536
|
return value.replace(/["\\]/g, "\\$&");
|
|
515
537
|
}
|
|
516
|
-
|
|
517
|
-
|
|
538
|
+
|
|
539
|
+
// src/document/fractional.ts
|
|
540
|
+
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
541
|
+
var RADIX = BASE.length;
|
|
542
|
+
var MID = Math.floor(RADIX / 2);
|
|
543
|
+
function val(c) {
|
|
544
|
+
const i = BASE.indexOf(c);
|
|
545
|
+
return i < 0 ? 0 : i;
|
|
546
|
+
}
|
|
547
|
+
function keyBetween(a, b) {
|
|
548
|
+
if (a === null) a = "";
|
|
549
|
+
if (b === null) {
|
|
550
|
+
return a + BASE[MID];
|
|
551
|
+
}
|
|
552
|
+
if (a >= b) {
|
|
553
|
+
throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
|
|
554
|
+
}
|
|
555
|
+
let result = "";
|
|
556
|
+
let i = 0;
|
|
557
|
+
for (; ; ) {
|
|
558
|
+
const ca = i < a.length ? val(a[i]) : 0;
|
|
559
|
+
const cb = i < b.length ? val(b[i]) : RADIX;
|
|
560
|
+
if (cb - ca > 1) {
|
|
561
|
+
result += BASE[ca + Math.floor((cb - ca) / 2)];
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
if (ca === cb) {
|
|
565
|
+
result += BASE[ca];
|
|
566
|
+
i++;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
result += BASE[ca];
|
|
570
|
+
i++;
|
|
571
|
+
for (; ; ) {
|
|
572
|
+
const da = i < a.length ? val(a[i]) : 0;
|
|
573
|
+
if (RADIX - da > 1) {
|
|
574
|
+
result += BASE[da + Math.floor((RADIX - da) / 2)];
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
result += BASE[da];
|
|
578
|
+
i++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
var MIME = "application/x-cms-node";
|
|
583
|
+
function isAncestorOrSelf(doc, id, maybeAncestor) {
|
|
584
|
+
for (let cur = id; cur; cur = doc.nodes[cur]?.parent ?? null) {
|
|
585
|
+
if (cur === maybeAncestor) return true;
|
|
586
|
+
}
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
function LayersPanel({ doc, selection, onSelect, onMove }) {
|
|
590
|
+
const [overId, setOverId] = react.useState(null);
|
|
591
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id, depth: 0, selection, onSelect, onMove, overId, setOverId }, id)) });
|
|
518
592
|
}
|
|
519
|
-
function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
593
|
+
function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
|
|
520
594
|
const node = doc.nodes[id];
|
|
521
595
|
if (!node) return null;
|
|
522
596
|
const selected = selection === id;
|
|
597
|
+
const draggable = Boolean(onMove);
|
|
598
|
+
const drop = (e) => {
|
|
599
|
+
e.preventDefault();
|
|
600
|
+
e.stopPropagation();
|
|
601
|
+
setOverId(null);
|
|
602
|
+
if (!onMove) return;
|
|
603
|
+
const dragId = e.dataTransfer.getData(MIME);
|
|
604
|
+
if (!dragId || dragId === id || isAncestorOrSelf(doc, id, dragId)) return;
|
|
605
|
+
const siblings = childrenOf(doc, node.parent);
|
|
606
|
+
const idx = siblings.findIndex((n) => n.id === id);
|
|
607
|
+
const next = siblings[idx + 1];
|
|
608
|
+
onMove(dragId, node.parent, keyBetween(node.order, next ? next.order : null));
|
|
609
|
+
};
|
|
523
610
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
524
611
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
525
612
|
"button",
|
|
526
613
|
{
|
|
527
614
|
type: "button",
|
|
615
|
+
draggable,
|
|
528
616
|
onClick: () => onSelect(id),
|
|
617
|
+
onDragStart: (e) => {
|
|
618
|
+
e.dataTransfer.setData(MIME, id);
|
|
619
|
+
e.dataTransfer.effectAllowed = "move";
|
|
620
|
+
},
|
|
621
|
+
onDragOver: (e) => {
|
|
622
|
+
if (!onMove) return;
|
|
623
|
+
e.preventDefault();
|
|
624
|
+
e.dataTransfer.dropEffect = "move";
|
|
625
|
+
if (overId !== id) setOverId(id);
|
|
626
|
+
},
|
|
627
|
+
onDragLeave: () => overId === id && setOverId(null),
|
|
628
|
+
onDrop: drop,
|
|
529
629
|
style: {
|
|
530
630
|
display: "block",
|
|
531
631
|
width: "100%",
|
|
532
632
|
textAlign: "left",
|
|
533
633
|
border: "none",
|
|
534
634
|
borderRadius: 6,
|
|
535
|
-
cursor: "pointer",
|
|
635
|
+
cursor: draggable ? "grab" : "pointer",
|
|
536
636
|
padding: "4px 8px",
|
|
537
637
|
paddingLeft: 8 + depth * 14,
|
|
538
638
|
background: selected ? "#ede9fe" : "transparent",
|
|
539
639
|
color: selected ? "#5b21b6" : "#334155",
|
|
540
|
-
font: "inherit"
|
|
640
|
+
font: "inherit",
|
|
641
|
+
boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
|
|
541
642
|
},
|
|
542
643
|
children: [
|
|
543
644
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: node.type }),
|
|
@@ -546,17 +647,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
|
|
|
546
647
|
]
|
|
547
648
|
}
|
|
548
649
|
),
|
|
549
|
-
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
550
|
-
LayerRow,
|
|
551
|
-
{
|
|
552
|
-
doc,
|
|
553
|
-
id: child.id,
|
|
554
|
-
depth: depth + 1,
|
|
555
|
-
selection,
|
|
556
|
-
onSelect
|
|
557
|
-
},
|
|
558
|
-
child.id
|
|
559
|
-
))
|
|
650
|
+
childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
|
|
560
651
|
] });
|
|
561
652
|
}
|
|
562
653
|
var label = {
|
|
@@ -688,7 +779,15 @@ function Editor({ defaultValue, onChange }) {
|
|
|
688
779
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shell, children: [
|
|
689
780
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
690
781
|
/* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
|
|
691
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
782
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
783
|
+
LayersPanel,
|
|
784
|
+
{
|
|
785
|
+
doc: ed.state.doc,
|
|
786
|
+
selection: ed.state.selection,
|
|
787
|
+
onSelect: ed.select,
|
|
788
|
+
onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
|
|
789
|
+
}
|
|
790
|
+
)
|
|
692
791
|
] }),
|
|
693
792
|
/* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
|
|
694
793
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
|
|
@@ -721,66 +820,23 @@ function Toolbar({ ed }) {
|
|
|
721
820
|
}
|
|
722
821
|
);
|
|
723
822
|
}
|
|
724
|
-
|
|
725
|
-
// src/document/fractional.ts
|
|
726
|
-
var BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
727
|
-
var RADIX = BASE.length;
|
|
728
|
-
var MID = Math.floor(RADIX / 2);
|
|
729
|
-
function val(c) {
|
|
730
|
-
const i = BASE.indexOf(c);
|
|
731
|
-
return i < 0 ? 0 : i;
|
|
732
|
-
}
|
|
733
|
-
function keyBetween(a, b) {
|
|
734
|
-
if (a === null) a = "";
|
|
735
|
-
if (b === null) {
|
|
736
|
-
return a + BASE[MID];
|
|
737
|
-
}
|
|
738
|
-
if (a >= b) {
|
|
739
|
-
throw new Error(`keyBetween: lower bound "${a}" is not below upper bound "${b}"`);
|
|
740
|
-
}
|
|
741
|
-
let result = "";
|
|
742
|
-
let i = 0;
|
|
743
|
-
for (; ; ) {
|
|
744
|
-
const ca = i < a.length ? val(a[i]) : 0;
|
|
745
|
-
const cb = i < b.length ? val(b[i]) : RADIX;
|
|
746
|
-
if (cb - ca > 1) {
|
|
747
|
-
result += BASE[ca + Math.floor((cb - ca) / 2)];
|
|
748
|
-
return result;
|
|
749
|
-
}
|
|
750
|
-
if (ca === cb) {
|
|
751
|
-
result += BASE[ca];
|
|
752
|
-
i++;
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
result += BASE[ca];
|
|
756
|
-
i++;
|
|
757
|
-
for (; ; ) {
|
|
758
|
-
const da = i < a.length ? val(a[i]) : 0;
|
|
759
|
-
if (RADIX - da > 1) {
|
|
760
|
-
result += BASE[da + Math.floor((RADIX - da) / 2)];
|
|
761
|
-
return result;
|
|
762
|
-
}
|
|
763
|
-
result += BASE[da];
|
|
764
|
-
i++;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape"]);
|
|
823
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
|
|
769
824
|
function NodeInspector({
|
|
770
825
|
node,
|
|
771
826
|
transform,
|
|
772
827
|
measured,
|
|
773
828
|
onProps,
|
|
774
829
|
onStyle,
|
|
830
|
+
onLayout,
|
|
775
831
|
onTransform,
|
|
776
832
|
onRemove,
|
|
777
833
|
onClose
|
|
778
834
|
}) {
|
|
779
835
|
const base = node.style.base ?? {};
|
|
780
836
|
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
781
|
-
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
837
|
+
const handled = /* @__PURE__ */ new Set(["content", "label", "html"]);
|
|
782
838
|
const extraKeys = Object.keys(node.props).filter(
|
|
783
|
-
(k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
|
|
839
|
+
(k) => !handled.has(k) && (["string", "number", "boolean"].includes(typeof node.props[k]) || isBinding(node.props[k]))
|
|
784
840
|
);
|
|
785
841
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
786
842
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
|
|
@@ -793,14 +849,11 @@ function NodeInspector({
|
|
|
793
849
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
|
|
794
850
|
] }),
|
|
795
851
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
|
|
796
|
-
"content" in node.props ? /* @__PURE__ */ jsxRuntime.
|
|
797
|
-
"
|
|
798
|
-
{
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
onChange: (e) => onProps({ content: e.target.value })
|
|
802
|
-
}
|
|
803
|
-
) }) : null,
|
|
852
|
+
"content" in node.props || node.type === "richtext" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 11, color: "var(--fg-3, #94a3b8)", margin: "0 0 12px", lineHeight: 1.45, paddingBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
|
|
853
|
+
"\u270E Edit the text ",
|
|
854
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: "directly on the page" }),
|
|
855
|
+
" \u2014 click in and type. This panel is for layout, style, and bindings."
|
|
856
|
+
] }) : null,
|
|
804
857
|
"label" in node.props ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Label", children: /* @__PURE__ */ jsxRuntime.jsx("input", { style: input2, value: String(node.props.label ?? ""), onChange: (e) => onProps({ label: e.target.value }) }) }) : null,
|
|
805
858
|
extraKeys.length ? /* @__PURE__ */ jsxRuntime.jsx(Group, { label: "Properties", children: extraKeys.map((k) => /* @__PURE__ */ jsxRuntime.jsx(PropField, { name: k, value: node.props[k], onChange: (v) => onProps({ [k]: v }) }, k)) }) : null,
|
|
806
859
|
/* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Position & size", children: [
|
|
@@ -854,9 +907,44 @@ function NodeInspector({
|
|
|
854
907
|
] }),
|
|
855
908
|
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
856
909
|
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
|
|
910
|
+
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
|
|
911
|
+
] }),
|
|
912
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
|
|
913
|
+
] }),
|
|
914
|
+
CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Layout", children: [
|
|
915
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
917
|
+
Sel,
|
|
918
|
+
{
|
|
919
|
+
label: "Mode",
|
|
920
|
+
value: node.layout ?? "free",
|
|
921
|
+
options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
|
|
922
|
+
onChange: (v) => onLayout?.(v)
|
|
923
|
+
}
|
|
924
|
+
),
|
|
857
925
|
/* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
|
|
926
|
+
] }),
|
|
927
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
928
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
929
|
+
Sel,
|
|
930
|
+
{
|
|
931
|
+
label: "Align",
|
|
932
|
+
value: base.align ?? "",
|
|
933
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
|
|
934
|
+
onChange: (v) => onStyle({ align: v || void 0 })
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
|
+
Sel,
|
|
939
|
+
{
|
|
940
|
+
label: "Justify",
|
|
941
|
+
value: base.justify ?? "",
|
|
942
|
+
options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
|
|
943
|
+
onChange: (v) => onStyle({ justify: v || void 0 })
|
|
944
|
+
}
|
|
945
|
+
)
|
|
858
946
|
] })
|
|
859
|
-
] })
|
|
947
|
+
] }) : null
|
|
860
948
|
] })
|
|
861
949
|
] });
|
|
862
950
|
}
|
|
@@ -870,24 +958,50 @@ function Row({ children }) {
|
|
|
870
958
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
871
959
|
}
|
|
872
960
|
function PropField({ name, value, onChange }) {
|
|
961
|
+
const bound = isBinding(value);
|
|
962
|
+
const link = /* @__PURE__ */ jsxRuntime.jsx(
|
|
963
|
+
"button",
|
|
964
|
+
{
|
|
965
|
+
type: "button",
|
|
966
|
+
title: bound ? "Unbind (use a literal value)" : "Bind to data (e.g. item.name)",
|
|
967
|
+
onClick: () => onChange(bound ? "" : { $bind: "" }),
|
|
968
|
+
style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 11, color: bound ? "#a78bfa" : "#64748b" },
|
|
969
|
+
children: "\u{1F517}"
|
|
970
|
+
}
|
|
971
|
+
);
|
|
972
|
+
if (bound) {
|
|
973
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
974
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
975
|
+
name,
|
|
976
|
+
" ",
|
|
977
|
+
link
|
|
978
|
+
] }),
|
|
979
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, borderColor: "#7c3aed" }, placeholder: "data path \xB7 e.g. item.name", value: value.$bind, onChange: (e) => onChange({ $bind: e.target.value }) })
|
|
980
|
+
] });
|
|
981
|
+
}
|
|
873
982
|
if (typeof value === "boolean") {
|
|
874
983
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
875
984
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
876
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize", flex: 1 }, children: name }),
|
|
986
|
+
link
|
|
877
987
|
] });
|
|
878
988
|
}
|
|
879
989
|
if (name === "variant") {
|
|
880
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
990
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value ?? ""), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
881
991
|
}
|
|
882
992
|
const isNum = typeof value === "number";
|
|
883
993
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
884
|
-
/* @__PURE__ */ jsxRuntime.
|
|
994
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
995
|
+
name,
|
|
996
|
+
" ",
|
|
997
|
+
link
|
|
998
|
+
] }),
|
|
885
999
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
886
1000
|
"input",
|
|
887
1001
|
{
|
|
888
1002
|
style: input2,
|
|
889
1003
|
type: isNum ? "number" : "text",
|
|
890
|
-
value: String(value),
|
|
1004
|
+
value: String(value ?? ""),
|
|
891
1005
|
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
892
1006
|
}
|
|
893
1007
|
)
|
|
@@ -911,6 +1025,12 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
|
|
|
911
1025
|
)
|
|
912
1026
|
] });
|
|
913
1027
|
}
|
|
1028
|
+
function Txt({ label: label2, value, placeholder, onChange }) {
|
|
1029
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
1030
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
1031
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
|
|
1032
|
+
] });
|
|
1033
|
+
}
|
|
914
1034
|
function Color({ label: label2, value, onChange }) {
|
|
915
1035
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
916
1036
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabel, children: label2 }),
|
|
@@ -1065,9 +1185,11 @@ function snapshotSubtree(doc, id) {
|
|
|
1065
1185
|
return { rootId: id, nodes };
|
|
1066
1186
|
}
|
|
1067
1187
|
var Z2 = 2147483e3;
|
|
1188
|
+
var SNAP = 6;
|
|
1068
1189
|
function EditablePage({
|
|
1069
1190
|
doc,
|
|
1070
1191
|
registry = defaultRegistry,
|
|
1192
|
+
data,
|
|
1071
1193
|
timelineDock,
|
|
1072
1194
|
transforms,
|
|
1073
1195
|
onNodeTransform,
|
|
@@ -1139,13 +1261,17 @@ function EditablePage({
|
|
|
1139
1261
|
};
|
|
1140
1262
|
}, [measureBox, ed.state.doc]);
|
|
1141
1263
|
react.useEffect(() => {
|
|
1142
|
-
|
|
1143
|
-
const
|
|
1144
|
-
if (!
|
|
1264
|
+
const t = selNode?.type;
|
|
1265
|
+
const editable = t === "text" || t === "heading" || t === "richtext";
|
|
1266
|
+
if (!editing || !selection || !pageRef.current || !editable) return;
|
|
1267
|
+
const wrapper = pageRef.current.querySelector(`[data-cms="${cssEscape2(selection)}"]`);
|
|
1268
|
+
if (!wrapper) return;
|
|
1269
|
+
const isRich = t === "richtext";
|
|
1270
|
+
const el = (isRich ? wrapper.firstElementChild : wrapper) ?? wrapper;
|
|
1145
1271
|
el.setAttribute("contenteditable", "true");
|
|
1146
1272
|
el.style.outline = "none";
|
|
1147
1273
|
el.focus();
|
|
1148
|
-
const commit = () => ed.apply({ t: "set_props", id: selection, patch: { content: el.innerText } });
|
|
1274
|
+
const commit = () => ed.apply({ t: "set_props", id: selection, patch: isRich ? { html: el.innerHTML } : { content: el.innerText } });
|
|
1149
1275
|
el.addEventListener("blur", commit);
|
|
1150
1276
|
return () => {
|
|
1151
1277
|
el.removeEventListener("blur", commit);
|
|
@@ -1170,13 +1296,87 @@ function EditablePage({
|
|
|
1170
1296
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
1171
1297
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1172
1298
|
const def = ADD_DEFAULTS[kind];
|
|
1173
|
-
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
1299
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
1174
1300
|
ed.select(id);
|
|
1175
1301
|
},
|
|
1176
1302
|
[ed, selNode]
|
|
1177
1303
|
);
|
|
1304
|
+
react.useCallback(
|
|
1305
|
+
(kind, targetId) => {
|
|
1306
|
+
const doc2 = ed.state.doc;
|
|
1307
|
+
const target = targetId ? doc2.nodes[targetId] : null;
|
|
1308
|
+
const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
|
|
1309
|
+
const siblings = childrenOf(doc2, parent);
|
|
1310
|
+
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
1311
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1312
|
+
const def = ADD_DEFAULTS[kind];
|
|
1313
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
1314
|
+
ed.select(id);
|
|
1315
|
+
},
|
|
1316
|
+
[ed]
|
|
1317
|
+
);
|
|
1318
|
+
const [paletteOpen, setPaletteOpen] = react.useState(false);
|
|
1319
|
+
const [draggingEl, setDraggingEl] = react.useState(false);
|
|
1320
|
+
react.useEffect(() => {
|
|
1321
|
+
if (!editing) {
|
|
1322
|
+
setPaletteOpen(false);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const onMove = (e) => {
|
|
1326
|
+
if (draggingEl) return;
|
|
1327
|
+
if (e.clientX >= window.innerWidth - 26) setPaletteOpen(true);
|
|
1328
|
+
else if (e.clientX < window.innerWidth - 236) setPaletteOpen(false);
|
|
1329
|
+
};
|
|
1330
|
+
window.addEventListener("mousemove", onMove);
|
|
1331
|
+
return () => window.removeEventListener("mousemove", onMove);
|
|
1332
|
+
}, [editing, draggingEl]);
|
|
1333
|
+
const [dropHint, setDropHint] = react.useState(null);
|
|
1334
|
+
const computeDropTarget = (clientX, clientY) => {
|
|
1335
|
+
const el = document.elementFromPoint(clientX, clientY)?.closest("[data-cms]");
|
|
1336
|
+
if (!el) return { id: null, edge: "inside", rect: null };
|
|
1337
|
+
const id = el.getAttribute("data-cms");
|
|
1338
|
+
const node = ed.state.doc.nodes[id];
|
|
1339
|
+
const r = el.getBoundingClientRect();
|
|
1340
|
+
const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
|
|
1341
|
+
if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
|
|
1342
|
+
return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
|
|
1343
|
+
};
|
|
1344
|
+
const onPageDragOver = (e) => {
|
|
1345
|
+
e.preventDefault();
|
|
1346
|
+
e.dataTransfer.dropEffect = "copy";
|
|
1347
|
+
setDropHint(computeDropTarget(e.clientX, e.clientY));
|
|
1348
|
+
};
|
|
1349
|
+
const insertRelative = (kind, hint) => {
|
|
1350
|
+
const doc2 = ed.state.doc;
|
|
1351
|
+
let parent;
|
|
1352
|
+
let order;
|
|
1353
|
+
if (!hint.id || hint.edge === "inside") {
|
|
1354
|
+
parent = hint.id ?? (doc2.sections[doc2.sections.length - 1] ?? null);
|
|
1355
|
+
const sib = childrenOf(doc2, parent);
|
|
1356
|
+
order = keyBetween(sib.length ? sib[sib.length - 1].order : null, null);
|
|
1357
|
+
} else {
|
|
1358
|
+
const target = doc2.nodes[hint.id];
|
|
1359
|
+
parent = target.parent;
|
|
1360
|
+
const sib = childrenOf(doc2, parent);
|
|
1361
|
+
const idx = sib.findIndex((n) => n.id === hint.id);
|
|
1362
|
+
order = hint.edge === "before" ? keyBetween(idx > 0 ? sib[idx - 1].order : null, target.order) : keyBetween(target.order, idx < sib.length - 1 ? sib[idx + 1].order : null);
|
|
1363
|
+
}
|
|
1364
|
+
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1365
|
+
const def = ADD_DEFAULTS[kind];
|
|
1366
|
+
ed.apply({ t: "insert_node", node: { id, type: def.type, parent, order, layout: def.layout, props: { ...def.props }, style: { base: { ...def.style } } } });
|
|
1367
|
+
ed.select(id);
|
|
1368
|
+
};
|
|
1369
|
+
const onPageDrop = (e) => {
|
|
1370
|
+
const kind = e.dataTransfer.getData("application/x-cms-element");
|
|
1371
|
+
if (!kind) return;
|
|
1372
|
+
e.preventDefault();
|
|
1373
|
+
insertRelative(kind, computeDropTarget(e.clientX, e.clientY));
|
|
1374
|
+
setDropHint(null);
|
|
1375
|
+
setDraggingEl(false);
|
|
1376
|
+
};
|
|
1178
1377
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
1179
1378
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
1379
|
+
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
1180
1380
|
const removeSelected = () => {
|
|
1181
1381
|
if (!selection) return;
|
|
1182
1382
|
ed.apply({ t: "remove_node", id: selection });
|
|
@@ -1201,6 +1401,8 @@ function EditablePage({
|
|
|
1201
1401
|
ed.select(id);
|
|
1202
1402
|
requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
|
|
1203
1403
|
};
|
|
1404
|
+
const nudgeRef = react.useRef({ transforms, onNodeTransform });
|
|
1405
|
+
nudgeRef.current = { transforms, onNodeTransform };
|
|
1204
1406
|
react.useEffect(() => {
|
|
1205
1407
|
if (!editing) return;
|
|
1206
1408
|
const onKey = (e) => {
|
|
@@ -1219,6 +1421,15 @@ function EditablePage({
|
|
|
1219
1421
|
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
|
|
1220
1422
|
e.preventDefault();
|
|
1221
1423
|
paste(selection);
|
|
1424
|
+
} else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
|
|
1425
|
+
const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
|
|
1426
|
+
if (!commit) return;
|
|
1427
|
+
e.preventDefault();
|
|
1428
|
+
const step = e.shiftKey ? 10 : 1;
|
|
1429
|
+
const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
1430
|
+
const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
|
|
1431
|
+
const cur = tf?.[selection] ?? {};
|
|
1432
|
+
commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
|
|
1222
1433
|
}
|
|
1223
1434
|
};
|
|
1224
1435
|
window.addEventListener("keydown", onKey);
|
|
@@ -1230,8 +1441,23 @@ function EditablePage({
|
|
|
1230
1441
|
if (el) ed.select(el.getAttribute("data-cms"));
|
|
1231
1442
|
else ed.select(null);
|
|
1232
1443
|
};
|
|
1233
|
-
const page = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1234
|
-
|
|
1444
|
+
const page = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1445
|
+
"div",
|
|
1446
|
+
{
|
|
1447
|
+
ref: pageRef,
|
|
1448
|
+
onClickCapture,
|
|
1449
|
+
onContextMenu: onContextMenuSelect,
|
|
1450
|
+
onDragOver: editing ? onPageDragOver : void 0,
|
|
1451
|
+
onDragLeave: editing ? () => setDropHint(null) : void 0,
|
|
1452
|
+
onDrop: editing ? onPageDrop : void 0,
|
|
1453
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry, data })
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
const pageShell = pinned ? /* @__PURE__ */ jsxRuntime.jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : frames > 1 ? (
|
|
1457
|
+
// Normal flow, but "expanded" — extra scroll length below the content so a
|
|
1458
|
+
// page can be made longer than its content (the + Frame control).
|
|
1459
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { minHeight: `${frames * 100}vh` }, children: page })
|
|
1460
|
+
) : page;
|
|
1235
1461
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1236
1462
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
|
|
1237
1463
|
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { children: pageShell }),
|
|
@@ -1272,6 +1498,7 @@ function EditablePage({
|
|
|
1272
1498
|
measured: { w: box.w, h: box.h },
|
|
1273
1499
|
onProps: setProps,
|
|
1274
1500
|
onStyle: setStyle,
|
|
1501
|
+
onLayout: setLayout,
|
|
1275
1502
|
onTransform: (t) => selection && onNodeTransform?.(selection, t),
|
|
1276
1503
|
onRemove: removeSelected,
|
|
1277
1504
|
onClose: () => ed.select(null)
|
|
@@ -1292,16 +1519,101 @@ function EditablePage({
|
|
|
1292
1519
|
onAdd: addNode
|
|
1293
1520
|
}
|
|
1294
1521
|
) : null,
|
|
1522
|
+
editing && dropHint?.rect ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, { hint: dropHint }) : null,
|
|
1523
|
+
editing ? /* @__PURE__ */ jsxRuntime.jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
|
|
1295
1524
|
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
1296
1525
|
] });
|
|
1297
1526
|
}
|
|
1298
|
-
|
|
1527
|
+
function DropIndicator({
|
|
1528
|
+
hint
|
|
1529
|
+
}) {
|
|
1530
|
+
const r = hint.rect;
|
|
1531
|
+
if (!r) return null;
|
|
1532
|
+
const color = "#ec4899";
|
|
1533
|
+
if (hint.edge === "inside") {
|
|
1534
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: r.x, top: r.y, width: r.w, height: r.h, outline: `2px dashed ${color}`, outlineOffset: -2, borderRadius: 6, pointerEvents: "none", zIndex: Z2 + 4 } });
|
|
1535
|
+
}
|
|
1536
|
+
const y = hint.edge === "before" ? r.y : r.y + r.h;
|
|
1537
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: r.x, top: y - 1.5, width: r.w, height: 3, background: color, borderRadius: 2, boxShadow: "0 0 0 1px #fff", pointerEvents: "none", zIndex: Z2 + 4 } });
|
|
1538
|
+
}
|
|
1539
|
+
function ElementPalette({ open, onDragChange }) {
|
|
1540
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1541
|
+
"div",
|
|
1542
|
+
{
|
|
1543
|
+
style: {
|
|
1544
|
+
position: "fixed",
|
|
1545
|
+
top: 64,
|
|
1546
|
+
bottom: 96,
|
|
1547
|
+
right: 0,
|
|
1548
|
+
width: 216,
|
|
1549
|
+
transform: open ? "translateX(0)" : "translateX(100%)",
|
|
1550
|
+
transition: "transform 0.18s ease",
|
|
1551
|
+
background: "#0b1220",
|
|
1552
|
+
color: "#e2e8f0",
|
|
1553
|
+
borderLeft: "1px solid #1e293b",
|
|
1554
|
+
borderTopLeftRadius: 12,
|
|
1555
|
+
borderBottomLeftRadius: 12,
|
|
1556
|
+
boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
|
|
1557
|
+
zIndex: Z2 + 2,
|
|
1558
|
+
fontFamily: "system-ui, sans-serif",
|
|
1559
|
+
padding: 12,
|
|
1560
|
+
overflowY: "auto"
|
|
1561
|
+
},
|
|
1562
|
+
children: [
|
|
1563
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5, marginBottom: 10 }, children: "Elements" }),
|
|
1564
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1565
|
+
"div",
|
|
1566
|
+
{
|
|
1567
|
+
draggable: true,
|
|
1568
|
+
onDragStart: (e) => {
|
|
1569
|
+
e.dataTransfer.setData("application/x-cms-element", m.kind);
|
|
1570
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
1571
|
+
onDragChange(true);
|
|
1572
|
+
},
|
|
1573
|
+
onDragEnd: () => onDragChange(false),
|
|
1574
|
+
title: `Drag "${m.label}" onto the page`,
|
|
1575
|
+
style: {
|
|
1576
|
+
display: "flex",
|
|
1577
|
+
flexDirection: "column",
|
|
1578
|
+
alignItems: "center",
|
|
1579
|
+
justifyContent: "center",
|
|
1580
|
+
gap: 4,
|
|
1581
|
+
height: 56,
|
|
1582
|
+
borderRadius: 8,
|
|
1583
|
+
background: "#0f172a",
|
|
1584
|
+
border: "1px solid #1e293b",
|
|
1585
|
+
cursor: "grab",
|
|
1586
|
+
fontSize: 12,
|
|
1587
|
+
userSelect: "none"
|
|
1588
|
+
},
|
|
1589
|
+
children: [
|
|
1590
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 16, opacity: 0.7 }, children: "\uFF0B" }),
|
|
1591
|
+
m.label
|
|
1592
|
+
]
|
|
1593
|
+
},
|
|
1594
|
+
m.kind
|
|
1595
|
+
)) }),
|
|
1596
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 10.5, opacity: 0.45, marginTop: 12, lineHeight: 1.4 }, children: "Drag an Element onto the page. It drops into the container you release over." })
|
|
1597
|
+
]
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
|
|
1299
1602
|
var ADD_DEFAULTS = {
|
|
1300
1603
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
1301
1604
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
1302
1605
|
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
1606
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
1303
1607
|
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
1304
|
-
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } }
|
|
1608
|
+
box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
|
|
1609
|
+
stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
|
|
1610
|
+
grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
|
|
1611
|
+
card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
|
|
1612
|
+
callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
|
|
1613
|
+
divider: { type: "divider", props: {}, style: {} },
|
|
1614
|
+
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
|
|
1615
|
+
richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
|
|
1616
|
+
repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
|
|
1305
1617
|
};
|
|
1306
1618
|
var GRIPS = [
|
|
1307
1619
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -1322,20 +1634,55 @@ function SelectionOverlay({
|
|
|
1322
1634
|
onLive
|
|
1323
1635
|
}) {
|
|
1324
1636
|
const drag = react.useRef(null);
|
|
1637
|
+
const targets = react.useRef({ vx: [], hy: [] });
|
|
1638
|
+
const [guides, setGuides] = react.useState({ x: null, y: null });
|
|
1325
1639
|
const apply = (t) => {
|
|
1326
1640
|
const el = getEl();
|
|
1327
1641
|
if (el) applyTransform(el, t);
|
|
1328
1642
|
};
|
|
1643
|
+
const snap = (cands, lines) => {
|
|
1644
|
+
let best = { delta: 0, at: null, dist: SNAP + 1 };
|
|
1645
|
+
for (const line of lines) {
|
|
1646
|
+
for (const c of cands) {
|
|
1647
|
+
const diff = line - c;
|
|
1648
|
+
if (Math.abs(diff) < best.dist) best = { delta: diff, at: line, dist: Math.abs(diff) };
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return best.dist <= SNAP ? { delta: best.delta, at: best.at } : { delta: 0, at: null };
|
|
1652
|
+
};
|
|
1329
1653
|
const startMove = (e) => {
|
|
1330
1654
|
e.stopPropagation();
|
|
1331
1655
|
e.preventDefault();
|
|
1332
1656
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1333
1657
|
drag.current = { x: e.clientX, y: e.clientY, w: box.w, h: box.h, live: { ...base } };
|
|
1658
|
+
const sel = getEl();
|
|
1659
|
+
const vx = /* @__PURE__ */ new Set();
|
|
1660
|
+
const hy = /* @__PURE__ */ new Set();
|
|
1661
|
+
document.querySelectorAll("[data-cms]").forEach((el) => {
|
|
1662
|
+
if (sel && (el === sel || sel.contains(el) || el.contains(sel))) return;
|
|
1663
|
+
const r = el.getBoundingClientRect();
|
|
1664
|
+
vx.add(r.left).add(r.left + r.width / 2).add(r.right);
|
|
1665
|
+
hy.add(r.top).add(r.top + r.height / 2).add(r.bottom);
|
|
1666
|
+
});
|
|
1667
|
+
targets.current = { vx: [...vx], hy: [...hy] };
|
|
1334
1668
|
};
|
|
1335
1669
|
const moveMove = (e) => {
|
|
1336
1670
|
const d = drag.current;
|
|
1337
1671
|
if (!d) return;
|
|
1338
|
-
|
|
1672
|
+
let dx = e.clientX - d.x;
|
|
1673
|
+
let dy = e.clientY - d.y;
|
|
1674
|
+
if (!e.altKey) {
|
|
1675
|
+
const l = box.x + dx;
|
|
1676
|
+
const t = box.y + dy;
|
|
1677
|
+
const sx = snap([l, l + box.w / 2, l + box.w], targets.current.vx);
|
|
1678
|
+
const sy = snap([t, t + box.h / 2, t + box.h], targets.current.hy);
|
|
1679
|
+
dx += sx.delta;
|
|
1680
|
+
dy += sy.delta;
|
|
1681
|
+
setGuides({ x: sx.at, y: sy.at });
|
|
1682
|
+
} else if (guides.x !== null || guides.y !== null) {
|
|
1683
|
+
setGuides({ x: null, y: null });
|
|
1684
|
+
}
|
|
1685
|
+
d.live = { ...base, x: (base.x ?? 0) + dx, y: (base.y ?? 0) + dy };
|
|
1339
1686
|
apply(d.live);
|
|
1340
1687
|
onLive();
|
|
1341
1688
|
};
|
|
@@ -1370,6 +1717,7 @@ function SelectionOverlay({
|
|
|
1370
1717
|
const d = drag.current;
|
|
1371
1718
|
if (d) onTransform(d.live);
|
|
1372
1719
|
drag.current = null;
|
|
1720
|
+
setGuides({ x: null, y: null });
|
|
1373
1721
|
};
|
|
1374
1722
|
const outline = {
|
|
1375
1723
|
position: "fixed",
|
|
@@ -1399,6 +1747,8 @@ function SelectionOverlay({
|
|
|
1399
1747
|
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
1400
1748
|
};
|
|
1401
1749
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1750
|
+
guides.x !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: guides.x, top: 0, bottom: 0, width: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
|
|
1751
|
+
guides.y !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", top: guides.y, left: 0, right: 0, height: 1, background: "#ec4899", zIndex: Z2 + 3, pointerEvents: "none" } }) : null,
|
|
1402
1752
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
1403
1753
|
movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1404
1754
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1463,11 +1813,20 @@ function FloatingToolbar({
|
|
|
1463
1813
|
] });
|
|
1464
1814
|
}
|
|
1465
1815
|
var ADD_MENU = [
|
|
1466
|
-
{ kind: "text", label: "Text" },
|
|
1467
1816
|
{ kind: "heading", label: "Heading" },
|
|
1817
|
+
{ kind: "text", label: "Text" },
|
|
1468
1818
|
{ kind: "button", label: "Button" },
|
|
1819
|
+
{ kind: "link", label: "Link" },
|
|
1469
1820
|
{ kind: "image", label: "Image" },
|
|
1470
|
-
{ kind: "
|
|
1821
|
+
{ kind: "card", label: "Card" },
|
|
1822
|
+
{ kind: "callout", label: "Callout" },
|
|
1823
|
+
{ kind: "stack", label: "Stack" },
|
|
1824
|
+
{ kind: "grid", label: "Grid" },
|
|
1825
|
+
{ kind: "box", label: "Box" },
|
|
1826
|
+
{ kind: "divider", label: "Divider" },
|
|
1827
|
+
{ kind: "code", label: "Code" },
|
|
1828
|
+
{ kind: "richtext", label: "Rich text" },
|
|
1829
|
+
{ kind: "repeater", label: "Repeater" }
|
|
1471
1830
|
];
|
|
1472
1831
|
function EditBar({
|
|
1473
1832
|
editing,
|
|
@@ -1485,7 +1844,7 @@ function EditBar({
|
|
|
1485
1844
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
1486
1845
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1487
1846
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1488
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an
|
|
1847
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: open ? "#1e293b" : "transparent" }, onClick: () => setOpen((v) => !v), title: "Add an Element (or drag from the right-edge palette)", children: "\uFF0B Element \u25BE" }),
|
|
1489
1848
|
open ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
|
|
1490
1849
|
"button",
|
|
1491
1850
|
{
|
|
@@ -1523,7 +1882,7 @@ function EditMenuItems({
|
|
|
1523
1882
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1524
1883
|
editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1525
1884
|
/* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
|
|
1526
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add inside" : "Add" }),
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubTrigger, { children: isContainer ? "Add Element inside" : "Add Element" }),
|
|
1527
1886
|
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.SubContent, { className: "!z-[2147483647]", children: ADD_MENU.map((m) => /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: () => onAdd(m.kind), children: m.label }, m.kind)) })
|
|
1528
1887
|
] }),
|
|
1529
1888
|
node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|