@particle-academy/fancy-cms-ui 0.8.0 → 0.10.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/CHANGELOG.md +84 -0
- package/README.md +46 -0
- package/dist/{chunk-IOMIYI45.js → chunk-G7ULXTKI.js} +267 -34
- package/dist/chunk-G7ULXTKI.js.map +1 -0
- package/dist/{chunk-SJYKJ6SG.js → chunk-IRG5IKKO.js} +63 -45
- package/dist/chunk-IRG5IKKO.js.map +1 -0
- package/dist/{chunk-V6QIOJI7.js → chunk-TJL26EAX.js} +3 -3
- package/dist/{chunk-V6QIOJI7.js.map → chunk-TJL26EAX.js.map} +1 -1
- package/dist/editor.cjs +368 -117
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +2 -2
- package/dist/editor.d.ts +2 -2
- package/dist/editor.js +3 -3
- package/dist/index.cjs +324 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/react.cjs +60 -42
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/{registry-B4FaQDip.d.cts → registry-F2TAMjhx.d.cts} +7 -1
- package/dist/{registry-B4FaQDip.d.ts → registry-F2TAMjhx.d.ts} +7 -1
- package/dist/{useEditor-DZDD9qpS.d.ts → useEditor-fIQEaKzZ.d.cts} +55 -3
- package/dist/{useEditor-CIRm2oi-.d.cts → useEditor-wGEqxpJT.d.ts} +55 -3
- package/package.json +2 -2
- package/dist/chunk-IOMIYI45.js.map +0 -1
- package/dist/chunk-SJYKJ6SG.js.map +0 -1
package/dist/editor.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-
|
|
1
|
+
export { C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-fIQEaKzZ.cjs';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import { P as PageDoc, h as ElementRegistry, g as DataContext, N as Node, q as StyleProps, L as LayoutMode } from './registry-
|
|
3
|
+
import { P as PageDoc, h as ElementRegistry, g as DataContext, N as Node, q as StyleProps, L as LayoutMode } from './registry-F2TAMjhx.cjs';
|
|
4
4
|
import '@particle-academy/fancy-doc-commons';
|
|
5
5
|
|
|
6
6
|
/** Animatable per-node transform (mirrors fancy-motion's NodeState, kept local to avoid a hard dep). */
|
package/dist/editor.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-
|
|
1
|
+
export { C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-wGEqxpJT.js';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import { P as PageDoc, h as ElementRegistry, g as DataContext, N as Node, q as StyleProps, L as LayoutMode } from './registry-
|
|
3
|
+
import { P as PageDoc, h as ElementRegistry, g as DataContext, N as Node, q as StyleProps, L as LayoutMode } from './registry-F2TAMjhx.js';
|
|
4
4
|
import '@particle-academy/fancy-doc-commons';
|
|
5
5
|
|
|
6
6
|
/** Animatable per-node transform (mirrors fancy-motion's NodeState, kept local to avoid a hard dep). */
|
package/dist/editor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEditor, useLatestRef, CONTAINER_TYPES, ADD_DEFAULTS, ADD_MENU } from './chunk-
|
|
2
|
-
export { Canvas, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor } from './chunk-
|
|
3
|
-
import { isBinding, lastRootId, childrenOf, CmsPage, defaultRegistry } from './chunk-
|
|
1
|
+
import { useEditor, useLatestRef, CONTAINER_TYPES, ADD_DEFAULTS, ADD_MENU } from './chunk-G7ULXTKI.js';
|
|
2
|
+
export { Canvas, Editor, Inspector, LayersPanel, editorReduce, initEditor, useEditor } from './chunk-G7ULXTKI.js';
|
|
3
|
+
import { isBinding, lastRootId, childrenOf, CmsPage, defaultRegistry } from './chunk-IRG5IKKO.js';
|
|
4
4
|
import { useState, useRef, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
5
5
|
import { ContextMenu } from '@particle-academy/react-fancy';
|
|
6
6
|
import { fractionalKey } from '@particle-academy/fancy-doc-commons';
|
package/dist/index.cjs
CHANGED
|
@@ -13,6 +13,12 @@ function hasNodeMap(value) {
|
|
|
13
13
|
const nodes = value.nodes;
|
|
14
14
|
return typeof nodes === "object" && nodes !== null && !Array.isArray(nodes);
|
|
15
15
|
}
|
|
16
|
+
function withNodeDefaults(node) {
|
|
17
|
+
const props = typeof node.props === "object" && node.props !== null ? node.props : {};
|
|
18
|
+
const style = typeof node.style === "object" && node.style !== null ? node.style : { base: {} };
|
|
19
|
+
const base = typeof style.base === "object" && style.base !== null ? style.base : {};
|
|
20
|
+
return props === node.props && style === node.style && base === style.base ? node : { ...node, props, style: { ...style, base } };
|
|
21
|
+
}
|
|
16
22
|
var DEFAULT_BREAKPOINTS = {
|
|
17
23
|
base: 0,
|
|
18
24
|
md: 768,
|
|
@@ -332,6 +338,28 @@ function cssString(value) {
|
|
|
332
338
|
return out;
|
|
333
339
|
}
|
|
334
340
|
var selectorFor = (id) => `[data-cms="${cssString(id)}"]`;
|
|
341
|
+
var BASE_CSS = [
|
|
342
|
+
"/* fancy-cms base */",
|
|
343
|
+
":where([data-cms]) {\n box-sizing: border-box;\n}",
|
|
344
|
+
":where(h1, h2, h3, h4, h5, h6, p, ul, ol)[data-cms] {\n margin: 0;\n}",
|
|
345
|
+
":where(ul, ol)[data-cms] {\n padding-left: 1.25em;\n}",
|
|
346
|
+
":where(a[data-cms]) {\n color: inherit;\n}",
|
|
347
|
+
":where([data-cms] > img) {\n display: block;\n height: 100%;\n object-fit: cover;\n width: 100%;\n}",
|
|
348
|
+
':where([data-cms] > img[data-cms-fit="contain"]) {\n object-fit: contain;\n}',
|
|
349
|
+
':where([data-cms] > img[data-cms-fit="fill"]) {\n object-fit: fill;\n}',
|
|
350
|
+
':where([data-cms] > img[data-cms-fit="none"]) {\n object-fit: none;\n}',
|
|
351
|
+
':where([data-cms] > img[data-cms-fit="scale-down"]) {\n object-fit: scale-down;\n}',
|
|
352
|
+
":where([data-cms-button]) {\n align-items: center;\n border: 1px solid var(--cms-color-primary, #7c3aed);\n border-radius: 8px;\n cursor: pointer;\n display: inline-flex;\n font-weight: 600;\n gap: 6px;\n justify-content: center;\n line-height: 1.2;\n padding: 10px 18px;\n text-decoration: none;\n}",
|
|
353
|
+
':where([data-cms-button="primary"]) {\n background: var(--cms-color-primary, #7c3aed);\n color: #ffffff;\n}',
|
|
354
|
+
':where([data-cms-button="outline"]) {\n background: transparent;\n color: var(--cms-color-primary, #7c3aed);\n}',
|
|
355
|
+
':where([data-cms-button="ghost"]) {\n background: transparent;\n border-color: currentColor;\n color: inherit;\n}',
|
|
356
|
+
":where([data-cms-callout]) {\n background: #3b82f614;\n border-left: 3px solid #3b82f6;\n border-radius: 10px;\n padding: 12px 14px;\n}",
|
|
357
|
+
':where([data-cms-callout="success"]) {\n background: #10b98114;\n border-left-color: #10b981;\n}',
|
|
358
|
+
':where([data-cms-callout="warning"]) {\n background: #f59e0b14;\n border-left-color: #f59e0b;\n}',
|
|
359
|
+
':where([data-cms-callout="danger"]) {\n background: #ef444414;\n border-left-color: #ef4444;\n}',
|
|
360
|
+
":where([data-cms] > hr) {\n border: none;\n border-top: 1px solid #e2e8f0;\n margin: 0;\n}",
|
|
361
|
+
":where([data-cms] > pre) {\n background: #0b1220;\n border-radius: 10px;\n color: #e2e8f0;\n font-family: ui-monospace, monospace;\n font-size: 13px;\n margin: 0;\n overflow-x: auto;\n padding: 12px 14px;\n}"
|
|
362
|
+
].join("\n\n");
|
|
335
363
|
var UNITS = /* @__PURE__ */ new Set(["px", "%", "vw", "vh", "rem", "fr"]);
|
|
336
364
|
function lengthToCss(len) {
|
|
337
365
|
if (typeof len !== "object" || len === null) return null;
|
|
@@ -544,7 +572,7 @@ function emitDocCss(doc) {
|
|
|
544
572
|
const themeRule = serializeRule(":root", themeDecls(doc.theme), "");
|
|
545
573
|
if (themeRule) blocks.push(themeRule);
|
|
546
574
|
for (const id of Object.keys(doc.nodes).sort()) {
|
|
547
|
-
const node = doc.nodes[id];
|
|
575
|
+
const node = withNodeDefaults(doc.nodes[id]);
|
|
548
576
|
const selector = selectorFor(id);
|
|
549
577
|
const parentFree = isParentFree(doc, node);
|
|
550
578
|
const base = {
|
|
@@ -557,12 +585,13 @@ function emitDocCss(doc) {
|
|
|
557
585
|
if (baseRule) blocks.push(baseRule);
|
|
558
586
|
for (const bp of doc.breakpoints) {
|
|
559
587
|
if (bp === "base") continue;
|
|
560
|
-
const px = breakpointPx
|
|
561
|
-
if (px
|
|
562
|
-
const bpStyle = node.style
|
|
588
|
+
const px = own(breakpointPx, bp);
|
|
589
|
+
if (typeof px !== "number") continue;
|
|
590
|
+
const bpStyle = own(node.style, bp);
|
|
591
|
+
const bpConstraints = node.constraints ? own(node.constraints, bp) : void 0;
|
|
563
592
|
const decls = {
|
|
564
593
|
...bpStyle ? containerDecls(node, bpStyle, false) : {},
|
|
565
|
-
...
|
|
594
|
+
...bpConstraints ? constraintDecls(bpConstraints, parentFree) : {},
|
|
566
595
|
...bpStyle ? styleDecls(bpStyle) : {},
|
|
567
596
|
...bpStyle ? visibilityDecls(node, bpStyle, false) : {}
|
|
568
597
|
};
|
|
@@ -572,7 +601,8 @@ ${inner}
|
|
|
572
601
|
}`);
|
|
573
602
|
}
|
|
574
603
|
}
|
|
575
|
-
|
|
604
|
+
if (blocks.length === 0 && Object.keys(doc.nodes).length === 0) return "";
|
|
605
|
+
return [BASE_CSS, ...blocks].join("\n\n") + "\n";
|
|
576
606
|
}
|
|
577
607
|
function getPath(data, path) {
|
|
578
608
|
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
|
|
@@ -615,10 +645,17 @@ function elementFor(node, text) {
|
|
|
615
645
|
case "button":
|
|
616
646
|
case "link": {
|
|
617
647
|
const href = safeHref(text(node.props.href));
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
648
|
+
const attrs = {};
|
|
649
|
+
if (href !== null) {
|
|
650
|
+
attrs.href = href;
|
|
651
|
+
if (/^https?:\/\//i.test(href)) attrs.rel = "noopener noreferrer";
|
|
652
|
+
}
|
|
653
|
+
if (node.type === "button") attrs["data-cms-button"] = BUTTON_VARIANTS.has(text(node.props.variant)) ? text(node.props.variant) : "primary";
|
|
654
|
+
return { tag: href !== null ? "a" : node.type === "link" ? "span" : "div", attrs };
|
|
655
|
+
}
|
|
656
|
+
case "callout": {
|
|
657
|
+
const variant = text(node.props.variant);
|
|
658
|
+
return { tag: "div", attrs: { "data-cms-callout": CALLOUT_VARIANTS.has(variant) ? variant : "info" } };
|
|
622
659
|
}
|
|
623
660
|
default:
|
|
624
661
|
return { tag: CONTAINERS.has(node.type) && LANDMARKS.has(as) ? as : "div" };
|
|
@@ -642,51 +679,32 @@ var defaultRegistry = {
|
|
|
642
679
|
const items = resolve(node.props.items);
|
|
643
680
|
return Array.isArray(items) ? items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: item === null || item === void 0 ? "" : String(item) }, i)) : null;
|
|
644
681
|
},
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
},
|
|
650
|
-
|
|
651
|
-
button: ({ node, text }) => {
|
|
652
|
-
const label2 = text(node.props.label) || text(node.props.content) || "Button";
|
|
653
|
-
const variant = text(node.props.variant) || "primary";
|
|
654
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
|
|
655
|
-
},
|
|
682
|
+
// Looks come from the base stylesheet (render/css.ts BASE_CSS), never from
|
|
683
|
+
// inline style objects, so the PHP renderer emits the same markup.
|
|
684
|
+
divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", {}),
|
|
685
|
+
callout: ({ node, text }) => text(node.props.content) || "Callout",
|
|
686
|
+
code: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: text(node.props.content) }) }),
|
|
687
|
+
button: ({ node, text }) => text(node.props.label) || text(node.props.content) || "Button",
|
|
656
688
|
image: ({ node, text }) => {
|
|
657
689
|
const fit = text(node.props.fit);
|
|
658
690
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
659
691
|
"img",
|
|
660
692
|
{
|
|
661
|
-
src: text(node.props.src),
|
|
693
|
+
src: text(node.props.src) || void 0,
|
|
662
694
|
alt: text(node.props.alt),
|
|
663
695
|
loading: text(node.props.loading) === "eager" ? "eager" : "lazy",
|
|
664
|
-
|
|
696
|
+
"data-cms-fit": IMAGE_FITS.has(fit) && fit !== "cover" ? fit : void 0
|
|
665
697
|
}
|
|
666
698
|
);
|
|
667
699
|
}
|
|
668
700
|
};
|
|
669
701
|
var IMAGE_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "none", "scale-down"]);
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
display: "inline-flex",
|
|
673
|
-
alignItems: "center",
|
|
674
|
-
gap: 6,
|
|
675
|
-
padding: "9px 16px",
|
|
676
|
-
borderRadius: 8,
|
|
677
|
-
fontWeight: 600,
|
|
678
|
-
fontSize: 14,
|
|
679
|
-
lineHeight: 1,
|
|
680
|
-
cursor: "pointer",
|
|
681
|
-
userSelect: "none"
|
|
682
|
-
};
|
|
683
|
-
if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
|
|
684
|
-
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
685
|
-
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
686
|
-
}
|
|
702
|
+
var BUTTON_VARIANTS = /* @__PURE__ */ new Set(["primary", "outline", "ghost"]);
|
|
703
|
+
var CALLOUT_VARIANTS = /* @__PURE__ */ new Set(["info", "success", "warning", "danger"]);
|
|
687
704
|
function RenderNode({ doc, id, registry = defaultRegistry, data }) {
|
|
688
|
-
const
|
|
689
|
-
if (!
|
|
705
|
+
const stored = own(doc.nodes, id);
|
|
706
|
+
if (!stored) return null;
|
|
707
|
+
const node = withNodeDefaults(stored);
|
|
690
708
|
if (node.type === "repeater") {
|
|
691
709
|
const arr = resolveValue(node.props.items, data);
|
|
692
710
|
const items = Array.isArray(arr) ? arr : [];
|
|
@@ -987,16 +1005,42 @@ var input = {
|
|
|
987
1005
|
font: "inherit",
|
|
988
1006
|
fontSize: 13
|
|
989
1007
|
};
|
|
990
|
-
function Inspector({ doc, selection, apply }) {
|
|
1008
|
+
function Inspector({ doc, selection, apply, drafts }) {
|
|
991
1009
|
const uid = react.useId();
|
|
992
1010
|
const fieldId = (key) => `${uid}-${key}`;
|
|
993
1011
|
const node = selection ? doc.nodes[selection] : null;
|
|
994
1012
|
if (!node) {
|
|
995
1013
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "var(--fcms-muted)", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
|
|
996
1014
|
}
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
|
|
1015
|
+
const nodeId = node.id;
|
|
1016
|
+
const bind = (name, read, op, numeric = false) => {
|
|
1017
|
+
const key = JSON.stringify([nodeId, name]);
|
|
1018
|
+
const field = {
|
|
1019
|
+
key,
|
|
1020
|
+
label: `${nodeId} / ${name}`,
|
|
1021
|
+
op,
|
|
1022
|
+
read: (next) => {
|
|
1023
|
+
const item = next.nodes[nodeId];
|
|
1024
|
+
return item ? read(item) : void 0;
|
|
1025
|
+
},
|
|
1026
|
+
canonical: numeric ? (raw) => String(Number(raw) || 0) : (raw) => raw
|
|
1027
|
+
};
|
|
1028
|
+
const accepted = read(node);
|
|
1029
|
+
return {
|
|
1030
|
+
value: drafts ? drafts.value(key, accepted) : accepted,
|
|
1031
|
+
onChange: (event) => {
|
|
1032
|
+
if (drafts) drafts.change(field, event.target.value);
|
|
1033
|
+
else apply(op(event.target.value));
|
|
1034
|
+
},
|
|
1035
|
+
onFocus: () => drafts?.focus(key),
|
|
1036
|
+
onBlur: () => drafts?.blur(key),
|
|
1037
|
+
onCompositionStart: () => drafts?.composition(key, true),
|
|
1038
|
+
onCompositionEnd: () => drafts?.composition(key, false)
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
const prop = (key) => bind(key, (item) => typeof item.props[key] === "string" ? item.props[key] : "", (raw) => ({ t: "set_node_props", id: nodeId, patch: { [key]: raw } }));
|
|
1042
|
+
const style = (key) => bind(key, (item) => item.style.base[key] ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { [key]: raw } }));
|
|
1043
|
+
const length = (key) => bind(key, (item) => item.style.base[key]?.value?.toString() ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { [key]: { value: Number(raw) || 0, unit: "px" } } }), true);
|
|
1000
1044
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", padding: 16, fontFamily: "system-ui, sans-serif", height: "100%", boxSizing: "border-box" }, children: [
|
|
1001
1045
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--fcms-fg)" }, children: node.type }),
|
|
1002
1046
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--fcms-muted)", marginBottom: 8 }, children: node.id }),
|
|
@@ -1008,8 +1052,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1008
1052
|
id: fieldId("content"),
|
|
1009
1053
|
"data-cms-field": "content",
|
|
1010
1054
|
style: { ...input, minHeight: 64, resize: "vertical" },
|
|
1011
|
-
|
|
1012
|
-
onChange: (e) => setProp("content", e.target.value)
|
|
1055
|
+
...prop("content")
|
|
1013
1056
|
}
|
|
1014
1057
|
)
|
|
1015
1058
|
] }) : null,
|
|
@@ -1021,8 +1064,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1021
1064
|
id: fieldId("src"),
|
|
1022
1065
|
"data-cms-field": "src",
|
|
1023
1066
|
style: input,
|
|
1024
|
-
|
|
1025
|
-
onChange: (e) => setProp("src", e.target.value)
|
|
1067
|
+
...prop("src")
|
|
1026
1068
|
}
|
|
1027
1069
|
)
|
|
1028
1070
|
] }) : null,
|
|
@@ -1034,8 +1076,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1034
1076
|
"data-cms-field": "background",
|
|
1035
1077
|
style: input,
|
|
1036
1078
|
placeholder: "#ffffff | gradient | url(\u2026)",
|
|
1037
|
-
|
|
1038
|
-
onChange: (e) => setStyle({ background: e.target.value })
|
|
1079
|
+
...style("background")
|
|
1039
1080
|
}
|
|
1040
1081
|
),
|
|
1041
1082
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("color"), children: "Text color" }),
|
|
@@ -1046,8 +1087,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1046
1087
|
"data-cms-field": "color",
|
|
1047
1088
|
style: input,
|
|
1048
1089
|
placeholder: "#0f172a",
|
|
1049
|
-
|
|
1050
|
-
onChange: (e) => setStyle({ color: e.target.value })
|
|
1090
|
+
...style("color")
|
|
1051
1091
|
}
|
|
1052
1092
|
),
|
|
1053
1093
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("fontSize"), children: "Font size (px)" }),
|
|
@@ -1058,8 +1098,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1058
1098
|
"data-cms-field": "fontSize",
|
|
1059
1099
|
type: "number",
|
|
1060
1100
|
style: input,
|
|
1061
|
-
|
|
1062
|
-
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
1101
|
+
...length("fontSize")
|
|
1063
1102
|
}
|
|
1064
1103
|
),
|
|
1065
1104
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("padding"), children: "Padding (px)" }),
|
|
@@ -1070,8 +1109,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1070
1109
|
"data-cms-field": "padding",
|
|
1071
1110
|
type: "number",
|
|
1072
1111
|
style: input,
|
|
1073
|
-
|
|
1074
|
-
onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
1112
|
+
...length("padding")
|
|
1075
1113
|
}
|
|
1076
1114
|
),
|
|
1077
1115
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("opacity"), children: "Opacity" }),
|
|
@@ -1085,12 +1123,171 @@ function Inspector({ doc, selection, apply }) {
|
|
|
1085
1123
|
min: "0",
|
|
1086
1124
|
max: "1",
|
|
1087
1125
|
style: input,
|
|
1088
|
-
|
|
1089
|
-
onChange: (e) => setStyle({ opacity: Number(e.target.value) })
|
|
1126
|
+
...bind("opacity", (item) => item.style.base.opacity?.toString() ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { opacity: Number(raw) } }), true)
|
|
1090
1127
|
}
|
|
1091
1128
|
)
|
|
1092
1129
|
] });
|
|
1093
1130
|
}
|
|
1131
|
+
|
|
1132
|
+
// src/editor/InspectorDrafts.ts
|
|
1133
|
+
var InspectorDrafts = class {
|
|
1134
|
+
constructor(doc) {
|
|
1135
|
+
this.doc = doc;
|
|
1136
|
+
this.drafts = /* @__PURE__ */ new Map();
|
|
1137
|
+
this.composing = /* @__PURE__ */ new Set();
|
|
1138
|
+
this.focused = null;
|
|
1139
|
+
this.flight = null;
|
|
1140
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1141
|
+
this.version = 0;
|
|
1142
|
+
this.disabled = false;
|
|
1143
|
+
this.emit = () => false;
|
|
1144
|
+
this.subscribe = (listener) => {
|
|
1145
|
+
this.listeners.add(listener);
|
|
1146
|
+
return () => {
|
|
1147
|
+
this.listeners.delete(listener);
|
|
1148
|
+
};
|
|
1149
|
+
};
|
|
1150
|
+
this.snapshot = () => this.version;
|
|
1151
|
+
}
|
|
1152
|
+
get busy() {
|
|
1153
|
+
return this.flight !== null || [...this.drafts.values()].some((draft) => draft.dirty || draft.conflict !== null);
|
|
1154
|
+
}
|
|
1155
|
+
get conflicts() {
|
|
1156
|
+
return [...this.drafts.values()].filter((draft) => draft.conflict !== null).map((draft) => ({
|
|
1157
|
+
key: draft.field.key,
|
|
1158
|
+
label: draft.field.label,
|
|
1159
|
+
message: draft.conflict,
|
|
1160
|
+
missing: draft.field.read(this.doc) === void 0
|
|
1161
|
+
}));
|
|
1162
|
+
}
|
|
1163
|
+
value(key, accepted) {
|
|
1164
|
+
return this.drafts.get(key)?.raw ?? accepted;
|
|
1165
|
+
}
|
|
1166
|
+
configure(doc, disabled, emit) {
|
|
1167
|
+
const settled = this.disabled && !disabled;
|
|
1168
|
+
this.doc = doc;
|
|
1169
|
+
this.disabled = disabled;
|
|
1170
|
+
this.emit = emit;
|
|
1171
|
+
let changed = false;
|
|
1172
|
+
for (const [key, draft] of this.drafts) {
|
|
1173
|
+
const accepted = draft.field.read(doc);
|
|
1174
|
+
if (this.flight?.draft === draft && accepted === this.flight.expected) {
|
|
1175
|
+
this.clearFlight();
|
|
1176
|
+
draft.base = accepted;
|
|
1177
|
+
draft.dirty = draft.field.canonical(draft.raw) !== accepted;
|
|
1178
|
+
if (!draft.dirty && !draft.focused) this.drafts.delete(key);
|
|
1179
|
+
changed = true;
|
|
1180
|
+
} else if (accepted !== draft.base) {
|
|
1181
|
+
this.conflict(draft, accepted === void 0 ? "The edited element was removed. Your draft is retained." : "The server changed this field. Your draft is retained.");
|
|
1182
|
+
draft.base = accepted;
|
|
1183
|
+
changed = true;
|
|
1184
|
+
} else if (settled && this.flight?.draft === draft) {
|
|
1185
|
+
this.conflict(draft, "The save was not accepted. Your draft is retained.");
|
|
1186
|
+
changed = true;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (changed) this.notify();
|
|
1190
|
+
this.arm();
|
|
1191
|
+
}
|
|
1192
|
+
focus(key) {
|
|
1193
|
+
this.focused = key;
|
|
1194
|
+
const draft = this.drafts.get(key);
|
|
1195
|
+
if (draft) draft.focused = true;
|
|
1196
|
+
}
|
|
1197
|
+
change(field, raw) {
|
|
1198
|
+
let draft = this.drafts.get(field.key);
|
|
1199
|
+
if (!draft) {
|
|
1200
|
+
draft = { field, raw, base: field.read(this.doc), focused: this.focused === field.key, dirty: true, conflict: null, readyAt: Date.now() + 250 };
|
|
1201
|
+
this.drafts.set(field.key, draft);
|
|
1202
|
+
} else {
|
|
1203
|
+
draft.field = field;
|
|
1204
|
+
draft.raw = raw;
|
|
1205
|
+
draft.readyAt = Date.now() + 250;
|
|
1206
|
+
}
|
|
1207
|
+
draft.dirty = field.canonical(raw) !== draft.base;
|
|
1208
|
+
this.notify();
|
|
1209
|
+
this.arm();
|
|
1210
|
+
}
|
|
1211
|
+
blur(key) {
|
|
1212
|
+
if (this.focused === key) this.focused = null;
|
|
1213
|
+
const draft = this.drafts.get(key);
|
|
1214
|
+
if (draft) {
|
|
1215
|
+
draft.focused = false;
|
|
1216
|
+
draft.readyAt = Date.now();
|
|
1217
|
+
if (!draft.dirty && !draft.conflict && this.flight?.draft !== draft) this.drafts.delete(key);
|
|
1218
|
+
this.notify();
|
|
1219
|
+
this.pump();
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
composition(key, active) {
|
|
1223
|
+
if (active) this.composing.add(key);
|
|
1224
|
+
else this.composing.delete(key);
|
|
1225
|
+
const draft = this.drafts.get(key);
|
|
1226
|
+
if (draft) draft.readyAt = Date.now() + 250;
|
|
1227
|
+
this.arm();
|
|
1228
|
+
}
|
|
1229
|
+
resolve(key, keep) {
|
|
1230
|
+
if (this.disabled) return;
|
|
1231
|
+
const draft = this.drafts.get(key);
|
|
1232
|
+
if (!draft) return;
|
|
1233
|
+
if (this.flight?.draft === draft) this.clearFlight();
|
|
1234
|
+
if (!keep) this.drafts.delete(key);
|
|
1235
|
+
else {
|
|
1236
|
+
const accepted = draft.field.read(this.doc);
|
|
1237
|
+
if (accepted === void 0) return;
|
|
1238
|
+
draft.base = accepted;
|
|
1239
|
+
draft.conflict = null;
|
|
1240
|
+
draft.dirty = draft.field.canonical(draft.raw) !== accepted;
|
|
1241
|
+
draft.readyAt = Date.now();
|
|
1242
|
+
if (!draft.dirty && !draft.focused) this.drafts.delete(key);
|
|
1243
|
+
}
|
|
1244
|
+
this.notify();
|
|
1245
|
+
this.pump();
|
|
1246
|
+
}
|
|
1247
|
+
dispose() {
|
|
1248
|
+
clearTimeout(this.timer);
|
|
1249
|
+
clearTimeout(this.ackTimer);
|
|
1250
|
+
}
|
|
1251
|
+
notify() {
|
|
1252
|
+
this.version++;
|
|
1253
|
+
for (const listener of this.listeners) listener();
|
|
1254
|
+
}
|
|
1255
|
+
clearFlight() {
|
|
1256
|
+
this.flight = null;
|
|
1257
|
+
clearTimeout(this.ackTimer);
|
|
1258
|
+
this.ackTimer = void 0;
|
|
1259
|
+
}
|
|
1260
|
+
conflict(draft, message) {
|
|
1261
|
+
draft.conflict = message;
|
|
1262
|
+
if (this.flight?.draft === draft) this.clearFlight();
|
|
1263
|
+
}
|
|
1264
|
+
arm() {
|
|
1265
|
+
clearTimeout(this.timer);
|
|
1266
|
+
this.timer = void 0;
|
|
1267
|
+
if (this.disabled || this.flight || this.conflicts.length > 0) return;
|
|
1268
|
+
const ready = [...this.drafts.values()].filter((draft) => draft.dirty && !this.composing.has(draft.field.key));
|
|
1269
|
+
if (ready.length) this.timer = setTimeout(() => this.pump(), Math.max(0, Math.min(...ready.map((draft) => draft.readyAt)) - Date.now()));
|
|
1270
|
+
}
|
|
1271
|
+
pump() {
|
|
1272
|
+
if (this.disabled || this.flight || this.conflicts.length > 0) return;
|
|
1273
|
+
const draft = [...this.drafts.values()].find((item) => item.dirty && item.readyAt <= Date.now() && !this.composing.has(item.field.key));
|
|
1274
|
+
if (!draft) {
|
|
1275
|
+
this.arm();
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
this.flight = { draft, expected: draft.field.canonical(draft.raw) };
|
|
1279
|
+
this.ackTimer = setTimeout(() => {
|
|
1280
|
+
this.conflict(draft, "The save has not been acknowledged. Your draft is retained.");
|
|
1281
|
+
this.notify();
|
|
1282
|
+
}, 1e4);
|
|
1283
|
+
try {
|
|
1284
|
+
if (!this.emit(draft.field.op(draft.raw))) this.conflict(draft, "This edit could not be submitted. Your draft is retained.");
|
|
1285
|
+
} catch {
|
|
1286
|
+
this.conflict(draft, "This edit could not be submitted. Your draft is retained.");
|
|
1287
|
+
}
|
|
1288
|
+
this.notify();
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1094
1291
|
var CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
1095
1292
|
"section",
|
|
1096
1293
|
"frame",
|
|
@@ -1164,18 +1361,59 @@ function buildInsertOp(doc, kind, selectedId, targetId) {
|
|
|
1164
1361
|
}
|
|
1165
1362
|
};
|
|
1166
1363
|
}
|
|
1167
|
-
function Editor({ defaultValue, onChange, registry, data }) {
|
|
1364
|
+
function Editor({ value, defaultValue, onChange, onOp, disabled = false, registry, data }) {
|
|
1168
1365
|
const initial = react.useMemo(() => defaultValue ?? emptyDoc("untitled"), [defaultValue]);
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1366
|
+
const local = useEditor(initial);
|
|
1367
|
+
const controlled = value !== void 0;
|
|
1368
|
+
const localDoc = useLatestRef(local.state.doc);
|
|
1369
|
+
const lastNotified = react.useRef(local.state.doc);
|
|
1370
|
+
const onOpRef = useLatestRef(onOp);
|
|
1371
|
+
const [drafts] = react.useState(() => new InspectorDrafts(value ?? initial));
|
|
1372
|
+
react.useSyncExternalStore(drafts.subscribe, drafts.snapshot, drafts.snapshot);
|
|
1171
1373
|
const onChangeRef = useLatestRef(onChange);
|
|
1172
1374
|
react.useEffect(() => {
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
|
|
1375
|
+
if (controlled || lastNotified.current === local.state.doc) return;
|
|
1376
|
+
lastNotified.current = local.state.doc;
|
|
1377
|
+
onChangeRef.current?.(local.state.doc);
|
|
1378
|
+
}, [controlled, local.state.doc, onChangeRef]);
|
|
1379
|
+
const apply = (op) => {
|
|
1380
|
+
if (disabled) return false;
|
|
1381
|
+
const source = value ?? localDoc.current;
|
|
1382
|
+
const next = reduce(source, op);
|
|
1383
|
+
if (next === source) return false;
|
|
1384
|
+
if (!controlled) {
|
|
1385
|
+
localDoc.current = next;
|
|
1386
|
+
local.apply(op);
|
|
1176
1387
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1388
|
+
onOpRef.current?.(op);
|
|
1389
|
+
if (controlled) onChangeRef.current?.(next);
|
|
1390
|
+
return true;
|
|
1391
|
+
};
|
|
1392
|
+
react.useEffect(() => {
|
|
1393
|
+
if (controlled) drafts.configure(value, disabled, apply);
|
|
1394
|
+
});
|
|
1395
|
+
react.useEffect(() => () => drafts.dispose(), [drafts]);
|
|
1396
|
+
const structuralDisabled = disabled || controlled && drafts.busy;
|
|
1397
|
+
const ed = {
|
|
1398
|
+
...local,
|
|
1399
|
+
state: { ...local.state, doc: value ?? local.state.doc },
|
|
1400
|
+
apply: (op) => {
|
|
1401
|
+
if (!structuralDisabled) apply(op);
|
|
1402
|
+
},
|
|
1403
|
+
// Snapshot history belongs to the local document. Replaying it into a
|
|
1404
|
+
// server-owned value could overwrite another writer's accepted work.
|
|
1405
|
+
undo: () => {
|
|
1406
|
+
if (!controlled && !disabled) local.undo();
|
|
1407
|
+
},
|
|
1408
|
+
redo: () => {
|
|
1409
|
+
if (!controlled && !disabled) local.redo();
|
|
1410
|
+
},
|
|
1411
|
+
replace: (next) => {
|
|
1412
|
+
if (!controlled && !disabled) local.replace(next);
|
|
1413
|
+
},
|
|
1414
|
+
canUndo: !controlled && !disabled && local.canUndo,
|
|
1415
|
+
canRedo: !controlled && !disabled && local.canRedo
|
|
1416
|
+
};
|
|
1179
1417
|
const shell = {
|
|
1180
1418
|
display: "grid",
|
|
1181
1419
|
gridTemplateColumns: "220px 1fr 300px",
|
|
@@ -1187,9 +1425,9 @@ function Editor({ defaultValue, onChange, registry, data }) {
|
|
|
1187
1425
|
borderRadius: 12,
|
|
1188
1426
|
overflow: "hidden"
|
|
1189
1427
|
};
|
|
1190
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fancy-cms-editor", style: shell, children: [
|
|
1428
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fancy-cms-editor", style: shell, "aria-busy": structuralDisabled || void 0, children: [
|
|
1191
1429
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: CHROME_CSS }),
|
|
1192
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid var(--fcms-border)", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsx("fieldset", { disabled: structuralDisabled, "aria-label": "Page structure controls", style: { display: "contents", border: 0, padding: 0, margin: 0 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid var(--fcms-border)", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
1193
1431
|
/* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
|
|
1194
1432
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1195
1433
|
LayersPanel,
|
|
@@ -1200,9 +1438,22 @@ function Editor({ defaultValue, onChange, registry, data }) {
|
|
|
1200
1438
|
onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
|
|
1201
1439
|
}
|
|
1202
1440
|
)
|
|
1203
|
-
] }),
|
|
1441
|
+
] }) }),
|
|
1204
1442
|
/* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply, registry, data }),
|
|
1205
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1443
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderLeft: "1px solid var(--fcms-border)", minHeight: 0, overflow: "auto" }, children: [
|
|
1444
|
+
controlled && drafts.conflicts.map((conflict) => /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", "data-cms-draft-conflict": conflict.key, style: { padding: 12, fontFamily: "system-ui,sans-serif", fontSize: 13 }, children: [
|
|
1445
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1446
|
+
conflict.label,
|
|
1447
|
+
": ",
|
|
1448
|
+
conflict.message
|
|
1449
|
+
] }),
|
|
1450
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { "data-cms-retained-draft": "", style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: drafts.value(conflict.key, "") }),
|
|
1451
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled, onClick: () => drafts.resolve(conflict.key, false), children: "Use server value" }),
|
|
1452
|
+
" ",
|
|
1453
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: disabled || conflict.missing, onClick: () => drafts.resolve(conflict.key, true), children: "Keep draft" })
|
|
1454
|
+
] }, conflict.key)),
|
|
1455
|
+
/* @__PURE__ */ jsxRuntime.jsx("fieldset", { disabled: !controlled && disabled, "aria-label": "Page property controls", style: { border: 0, padding: 0, margin: 0, minWidth: 0, height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply, drafts: controlled ? drafts : void 0 }) })
|
|
1456
|
+
] })
|
|
1206
1457
|
] });
|
|
1207
1458
|
}
|
|
1208
1459
|
function Toolbar({ ed }) {
|
|
@@ -1355,5 +1606,6 @@ exports.resolveInsertParent = resolveInsertParent;
|
|
|
1355
1606
|
exports.resolveValue = resolveValue;
|
|
1356
1607
|
exports.rootIds = rootIds;
|
|
1357
1608
|
exports.useEditor = useEditor;
|
|
1609
|
+
exports.withNodeDefaults = withNodeDefaults;
|
|
1358
1610
|
//# sourceMappingURL=index.cjs.map
|
|
1359
1611
|
//# sourceMappingURL=index.cjs.map
|