@particle-academy/fancy-cms-ui 0.0.1-dev.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/{chunk-3YZMELJJ.js → chunk-GOXVGEKU.js} +94 -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 +321 -54
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +7 -92
- package/dist/editor.d.ts +7 -92
- package/dist/editor.js +311 -455
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +508 -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-3YZMELJJ.js.map +0 -1
- package/dist/chunk-7YYLYBLI.js +0 -47
- package/dist/chunk-7YYLYBLI.js.map +0 -1
- package/dist/chunk-ZV7CV54P.js +0 -78
- package/dist/chunk-ZV7CV54P.js.map +0 -1
- package/dist/ops-BsM6kZxY.d.ts +0 -75
- package/dist/ops-CdBZarAT.d.cts +0 -75
- package/dist/registry-Bk7GN1rA.d.cts +0 -19
- package/dist/registry-Dht9JBLU.d.ts +0 -19
package/dist/editor.cjs
CHANGED
|
@@ -361,10 +361,13 @@ ${inner}
|
|
|
361
361
|
}
|
|
362
362
|
return blocks.join("\n\n") + (blocks.length ? "\n" : "");
|
|
363
363
|
}
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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;
|
|
368
371
|
}
|
|
369
372
|
var defaultRegistry = {
|
|
370
373
|
section: ({ children }) => children,
|
|
@@ -372,18 +375,29 @@ var defaultRegistry = {
|
|
|
372
375
|
grid: ({ children }) => children,
|
|
373
376
|
frame: ({ children }) => children,
|
|
374
377
|
shape: ({ children }) => children,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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";
|
|
380
394
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
|
|
381
395
|
},
|
|
382
|
-
image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
396
|
+
image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
383
397
|
"img",
|
|
384
398
|
{
|
|
385
|
-
src:
|
|
386
|
-
alt:
|
|
399
|
+
src: text(node.props.src),
|
|
400
|
+
alt: text(node.props.alt),
|
|
387
401
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
388
402
|
}
|
|
389
403
|
)
|
|
@@ -405,30 +419,34 @@ function buttonStyle(variant) {
|
|
|
405
419
|
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
406
420
|
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
407
421
|
}
|
|
408
|
-
function RenderNode({ doc, id, registry = defaultRegistry }) {
|
|
422
|
+
function RenderNode({ doc, id, registry = defaultRegistry, data }) {
|
|
409
423
|
const node = doc.nodes[id];
|
|
410
424
|
if (!node) return null;
|
|
411
|
-
|
|
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
|
+
};
|
|
412
437
|
const renderer = registry[node.type];
|
|
413
|
-
const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
|
|
414
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
415
|
-
"div",
|
|
416
|
-
{
|
|
417
|
-
"data-cms": node.id,
|
|
418
|
-
"data-cms-island": node.island ? node.type : void 0,
|
|
419
|
-
className: node.className,
|
|
420
|
-
children: inner
|
|
421
|
-
}
|
|
422
|
-
);
|
|
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 });
|
|
423
440
|
}
|
|
424
441
|
function CmsPage({
|
|
425
442
|
doc,
|
|
426
443
|
registry = defaultRegistry,
|
|
444
|
+
data,
|
|
427
445
|
includeStyles = true
|
|
428
446
|
}) {
|
|
429
447
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
430
448
|
includeStyles ? /* @__PURE__ */ jsxRuntime.jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
|
|
431
|
-
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))
|
|
432
450
|
] });
|
|
433
451
|
}
|
|
434
452
|
function Canvas({ doc, selection, onSelect, apply }) {
|
|
@@ -802,7 +820,7 @@ function Toolbar({ ed }) {
|
|
|
802
820
|
}
|
|
803
821
|
);
|
|
804
822
|
}
|
|
805
|
-
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"]);
|
|
806
824
|
function NodeInspector({
|
|
807
825
|
node,
|
|
808
826
|
transform,
|
|
@@ -816,9 +834,9 @@ function NodeInspector({
|
|
|
816
834
|
}) {
|
|
817
835
|
const base = node.style.base ?? {};
|
|
818
836
|
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
819
|
-
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
837
|
+
const handled = /* @__PURE__ */ new Set(["content", "label", "html"]);
|
|
820
838
|
const extraKeys = Object.keys(node.props).filter(
|
|
821
|
-
(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]))
|
|
822
840
|
);
|
|
823
841
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
824
842
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
|
|
@@ -831,14 +849,11 @@ function NodeInspector({
|
|
|
831
849
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
|
|
832
850
|
] }),
|
|
833
851
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
|
|
834
|
-
"content" in node.props ? /* @__PURE__ */ jsxRuntime.
|
|
835
|
-
"
|
|
836
|
-
{
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
onChange: (e) => onProps({ content: e.target.value })
|
|
840
|
-
}
|
|
841
|
-
) }) : 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,
|
|
842
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,
|
|
843
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,
|
|
844
859
|
/* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Position & size", children: [
|
|
@@ -943,24 +958,50 @@ function Row({ children }) {
|
|
|
943
958
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
944
959
|
}
|
|
945
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
|
+
}
|
|
946
982
|
if (typeof value === "boolean") {
|
|
947
983
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
948
984
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
949
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize", flex: 1 }, children: name }),
|
|
986
|
+
link
|
|
950
987
|
] });
|
|
951
988
|
}
|
|
952
989
|
if (name === "variant") {
|
|
953
|
-
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 });
|
|
954
991
|
}
|
|
955
992
|
const isNum = typeof value === "number";
|
|
956
993
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
957
|
-
/* @__PURE__ */ jsxRuntime.
|
|
994
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
995
|
+
name,
|
|
996
|
+
" ",
|
|
997
|
+
link
|
|
998
|
+
] }),
|
|
958
999
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
959
1000
|
"input",
|
|
960
1001
|
{
|
|
961
1002
|
style: input2,
|
|
962
1003
|
type: isNum ? "number" : "text",
|
|
963
|
-
value: String(value),
|
|
1004
|
+
value: String(value ?? ""),
|
|
964
1005
|
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
965
1006
|
}
|
|
966
1007
|
)
|
|
@@ -1144,9 +1185,11 @@ function snapshotSubtree(doc, id) {
|
|
|
1144
1185
|
return { rootId: id, nodes };
|
|
1145
1186
|
}
|
|
1146
1187
|
var Z2 = 2147483e3;
|
|
1188
|
+
var SNAP = 6;
|
|
1147
1189
|
function EditablePage({
|
|
1148
1190
|
doc,
|
|
1149
1191
|
registry = defaultRegistry,
|
|
1192
|
+
data,
|
|
1150
1193
|
timelineDock,
|
|
1151
1194
|
transforms,
|
|
1152
1195
|
onNodeTransform,
|
|
@@ -1218,13 +1261,17 @@ function EditablePage({
|
|
|
1218
1261
|
};
|
|
1219
1262
|
}, [measureBox, ed.state.doc]);
|
|
1220
1263
|
react.useEffect(() => {
|
|
1221
|
-
|
|
1222
|
-
const
|
|
1223
|
-
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;
|
|
1224
1271
|
el.setAttribute("contenteditable", "true");
|
|
1225
1272
|
el.style.outline = "none";
|
|
1226
1273
|
el.focus();
|
|
1227
|
-
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 } });
|
|
1228
1275
|
el.addEventListener("blur", commit);
|
|
1229
1276
|
return () => {
|
|
1230
1277
|
el.removeEventListener("blur", commit);
|
|
@@ -1249,11 +1296,84 @@ function EditablePage({
|
|
|
1249
1296
|
const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
|
|
1250
1297
|
const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
|
|
1251
1298
|
const def = ADD_DEFAULTS[kind];
|
|
1252
|
-
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 } } } });
|
|
1253
1300
|
ed.select(id);
|
|
1254
1301
|
},
|
|
1255
1302
|
[ed, selNode]
|
|
1256
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
|
+
};
|
|
1257
1377
|
const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
|
|
1258
1378
|
const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
|
|
1259
1379
|
const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
|
|
@@ -1321,8 +1441,23 @@ function EditablePage({
|
|
|
1321
1441
|
if (el) ed.select(el.getAttribute("data-cms"));
|
|
1322
1442
|
else ed.select(null);
|
|
1323
1443
|
};
|
|
1324
|
-
const page = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1325
|
-
|
|
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;
|
|
1326
1461
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1327
1462
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu, { children: [
|
|
1328
1463
|
/* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Trigger, { children: pageShell }),
|
|
@@ -1384,16 +1519,101 @@ function EditablePage({
|
|
|
1384
1519
|
onAdd: addNode
|
|
1385
1520
|
}
|
|
1386
1521
|
) : null,
|
|
1522
|
+
editing && dropHint?.rect ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, { hint: dropHint }) : null,
|
|
1523
|
+
editing ? /* @__PURE__ */ jsxRuntime.jsx(ElementPalette, { open: paletteOpen, onDragChange: setDraggingEl }) : null,
|
|
1387
1524
|
editing && timelineDock ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: 0, right: 0, bottom: 0, zIndex: Z2 }, children: timelineDock }) : null
|
|
1388
1525
|
] });
|
|
1389
1526
|
}
|
|
1390
|
-
|
|
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"]);
|
|
1391
1602
|
var ADD_DEFAULTS = {
|
|
1392
1603
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
1393
1604
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
1394
1605
|
button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
|
|
1606
|
+
link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
|
|
1395
1607
|
image: { type: "image", props: { src: "", alt: "" }, style: {} },
|
|
1396
|
-
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" }
|
|
1397
1617
|
};
|
|
1398
1618
|
var GRIPS = [
|
|
1399
1619
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -1414,20 +1634,55 @@ function SelectionOverlay({
|
|
|
1414
1634
|
onLive
|
|
1415
1635
|
}) {
|
|
1416
1636
|
const drag = react.useRef(null);
|
|
1637
|
+
const targets = react.useRef({ vx: [], hy: [] });
|
|
1638
|
+
const [guides, setGuides] = react.useState({ x: null, y: null });
|
|
1417
1639
|
const apply = (t) => {
|
|
1418
1640
|
const el = getEl();
|
|
1419
1641
|
if (el) applyTransform(el, t);
|
|
1420
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
|
+
};
|
|
1421
1653
|
const startMove = (e) => {
|
|
1422
1654
|
e.stopPropagation();
|
|
1423
1655
|
e.preventDefault();
|
|
1424
1656
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1425
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] };
|
|
1426
1668
|
};
|
|
1427
1669
|
const moveMove = (e) => {
|
|
1428
1670
|
const d = drag.current;
|
|
1429
1671
|
if (!d) return;
|
|
1430
|
-
|
|
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 };
|
|
1431
1686
|
apply(d.live);
|
|
1432
1687
|
onLive();
|
|
1433
1688
|
};
|
|
@@ -1462,6 +1717,7 @@ function SelectionOverlay({
|
|
|
1462
1717
|
const d = drag.current;
|
|
1463
1718
|
if (d) onTransform(d.live);
|
|
1464
1719
|
drag.current = null;
|
|
1720
|
+
setGuides({ x: null, y: null });
|
|
1465
1721
|
};
|
|
1466
1722
|
const outline = {
|
|
1467
1723
|
position: "fixed",
|
|
@@ -1491,6 +1747,8 @@ function SelectionOverlay({
|
|
|
1491
1747
|
return { left: cx - 6, top: cy - 6, cursor: g.cursor };
|
|
1492
1748
|
};
|
|
1493
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,
|
|
1494
1752
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: outline }),
|
|
1495
1753
|
movable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1496
1754
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1555,11 +1813,20 @@ function FloatingToolbar({
|
|
|
1555
1813
|
] });
|
|
1556
1814
|
}
|
|
1557
1815
|
var ADD_MENU = [
|
|
1558
|
-
{ kind: "text", label: "Text" },
|
|
1559
1816
|
{ kind: "heading", label: "Heading" },
|
|
1817
|
+
{ kind: "text", label: "Text" },
|
|
1560
1818
|
{ kind: "button", label: "Button" },
|
|
1819
|
+
{ kind: "link", label: "Link" },
|
|
1561
1820
|
{ kind: "image", label: "Image" },
|
|
1562
|
-
{ 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" }
|
|
1563
1830
|
];
|
|
1564
1831
|
function EditBar({
|
|
1565
1832
|
editing,
|
|
@@ -1577,7 +1844,7 @@ function EditBar({
|
|
|
1577
1844
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, background: editing ? "#8b5cf6" : "transparent" }, onClick: onToggle, children: editing ? "\u25CF Editing" : "Edit" }),
|
|
1578
1845
|
editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1579
1846
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
1580
|
-
/* @__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" }),
|
|
1581
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(
|
|
1582
1849
|
"button",
|
|
1583
1850
|
{
|
|
@@ -1615,7 +1882,7 @@ function EditMenuItems({
|
|
|
1615
1882
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1616
1883
|
editable ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ContextMenu.Item, { onClick: onEditText, children: "Edit text" }) : null,
|
|
1617
1884
|
/* @__PURE__ */ jsxRuntime.jsxs(reactFancy.ContextMenu.Sub, { children: [
|
|
1618
|
-
/* @__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" }),
|
|
1619
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)) })
|
|
1620
1887
|
] }),
|
|
1621
1888
|
node ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|