cms-renderer 0.5.2 → 0.6.1
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 +278 -154
- package/dist/lib/block-renderer.d.ts +13 -10
- package/dist/lib/block-renderer.js +105 -239
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/block-toolbar.d.ts +5 -5
- package/dist/lib/block-toolbar.js +89 -70
- package/dist/lib/block-toolbar.js.map +1 -1
- package/dist/lib/client-editable-block.d.ts +11 -10
- package/dist/lib/client-editable-block.js +248 -32
- package/dist/lib/client-editable-block.js.map +1 -1
- package/dist/lib/custom-schemas.js +77 -39
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/renderer.js +105 -284
- package/dist/lib/renderer.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,82 +3,101 @@
|
|
|
3
3
|
|
|
4
4
|
// lib/block-toolbar.tsx
|
|
5
5
|
import { ChevronDown, ChevronUp, Plus, Trash2 } from "lucide-react";
|
|
6
|
-
import {
|
|
7
|
-
import { createPortal } from "react-dom";
|
|
6
|
+
import { useLayoutEffect, useRef } from "react";
|
|
8
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
function BlockToolbar({ blockId }) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
function BlockToolbar({ blockId, style }) {
|
|
9
|
+
const toolbarRef = useRef(null);
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
const el = toolbarRef.current;
|
|
12
|
+
if (!el) return;
|
|
13
|
+
const vw = window.innerWidth;
|
|
14
|
+
const vh = window.innerHeight;
|
|
15
|
+
const MAX_ATTEMPTS = 3;
|
|
16
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
17
|
+
const rect = el.getBoundingClientRect();
|
|
18
|
+
const offRight = rect.right > vw;
|
|
19
|
+
const offLeft = rect.left < 0;
|
|
20
|
+
const offTop = rect.top < 0;
|
|
21
|
+
const offBottom = rect.bottom > vh;
|
|
22
|
+
if (!offRight && !offLeft && !offTop && !offBottom) break;
|
|
23
|
+
if (offRight) {
|
|
24
|
+
el.style.left = `${vw - rect.width}px`;
|
|
25
|
+
el.style.transform = "none";
|
|
26
|
+
} else if (offLeft) {
|
|
27
|
+
el.style.left = "0px";
|
|
28
|
+
el.style.transform = "none";
|
|
29
|
+
}
|
|
30
|
+
if (offTop) {
|
|
31
|
+
el.style.bottom = `${vh - rect.height}px`;
|
|
32
|
+
} else if (offBottom) {
|
|
33
|
+
el.style.bottom = "0px";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
13
36
|
}, []);
|
|
14
37
|
const handleAction = (action) => {
|
|
15
|
-
if (window.parent && window.parent !== window) {
|
|
38
|
+
if (typeof window !== "undefined" && window.parent && window.parent !== window) {
|
|
16
39
|
window.parent.postMessage({ type: "cms-block-action", action, blockId }, "*");
|
|
17
40
|
}
|
|
18
41
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"button",
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"button",
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"button",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"button",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
] }),
|
|
80
|
-
document.body
|
|
81
|
-
);
|
|
42
|
+
return /* @__PURE__ */ jsxs("div", { ref: toolbarRef, className: "cms-block-toolbar", "data-cms-toolbar": "true", style, children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type: "button",
|
|
47
|
+
title: "Move up",
|
|
48
|
+
"aria-label": "Move up",
|
|
49
|
+
"data-action": "move-up",
|
|
50
|
+
onClick: (e) => {
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
handleAction("move-up");
|
|
53
|
+
},
|
|
54
|
+
children: /* @__PURE__ */ jsx(ChevronUp, {})
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
"button",
|
|
59
|
+
{
|
|
60
|
+
type: "button",
|
|
61
|
+
title: "Move down",
|
|
62
|
+
"aria-label": "Move down",
|
|
63
|
+
"data-action": "move-down",
|
|
64
|
+
onClick: (e) => {
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
handleAction("move-down");
|
|
67
|
+
},
|
|
68
|
+
children: /* @__PURE__ */ jsx(ChevronDown, {})
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
title: "Add block",
|
|
76
|
+
"aria-label": "Add block",
|
|
77
|
+
"data-action": "add-block",
|
|
78
|
+
onClick: (e) => {
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
handleAction("add-block");
|
|
81
|
+
},
|
|
82
|
+
children: /* @__PURE__ */ jsx(Plus, {})
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
type: "button",
|
|
89
|
+
className: "delete",
|
|
90
|
+
title: "Delete block",
|
|
91
|
+
"aria-label": "Delete block",
|
|
92
|
+
"data-action": "delete",
|
|
93
|
+
onClick: (e) => {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
handleAction("delete");
|
|
96
|
+
},
|
|
97
|
+
children: /* @__PURE__ */ jsx(Trash2, {})
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
] });
|
|
82
101
|
}
|
|
83
102
|
export {
|
|
84
103
|
BlockToolbar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react';\nimport
|
|
1
|
+
{"version":3,"sources":["../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react';\nimport type React from 'react';\nimport { useLayoutEffect, useRef } from 'react';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * This is a Client Component because it requires onClick handlers.\n */\n\nexport function BlockToolbar({ blockId, style }: { blockId: string; style?: React.CSSProperties }) {\n const toolbarRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const el = toolbarRef.current;\n if (!el) return;\n\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const MAX_ATTEMPTS = 3;\n\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const rect = el.getBoundingClientRect();\n\n const offRight = rect.right > vw;\n const offLeft = rect.left < 0;\n const offTop = rect.top < 0;\n const offBottom = rect.bottom > vh;\n\n if (!offRight && !offLeft && !offTop && !offBottom) break;\n\n // Horizontal: remove the centering transform and pin to the edge that's clipped\n if (offRight) {\n el.style.left = `${vw - rect.width}px`;\n el.style.transform = 'none';\n } else if (offLeft) {\n el.style.left = '0px';\n el.style.transform = 'none';\n }\n\n // Vertical: toolbar uses `bottom` (distance from viewport bottom), so\n // top < 0 means bottom is too large — clamp it so top aligns to viewport edge\n if (offTop) {\n el.style.bottom = `${vh - rect.height}px`;\n } else if (offBottom) {\n el.style.bottom = '0px';\n }\n }\n }, []);\n\n const handleAction = (action: string) => {\n if (typeof window !== 'undefined' && window.parent && window.parent !== window) {\n window.parent.postMessage({ type: 'cms-block-action', action, blockId }, '*');\n }\n };\n\n return (\n <div ref={toolbarRef} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\" style={style}>\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUp />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDown />\n </button>\n <button\n type=\"button\"\n title=\"Add block\"\n aria-label=\"Add block\"\n data-action=\"add-block\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block');\n }}\n >\n <Plus />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <Trash2 />\n </button>\n </div>\n );\n}\n"],"mappings":";;;;AAEA,SAAS,aAAa,WAAW,MAAM,cAAc;AAErD,SAAS,iBAAiB,cAAc;AAwDpC,SAWI,KAXJ;AA/CG,SAAS,aAAa,EAAE,SAAS,MAAM,GAAqD;AACjG,QAAM,aAAa,OAAuB,IAAI;AAE9C,kBAAgB,MAAM;AACpB,UAAM,KAAK,WAAW;AACtB,QAAI,CAAC,GAAI;AAET,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAClB,UAAM,eAAe;AAErB,aAAS,UAAU,GAAG,UAAU,cAAc,WAAW;AACvD,YAAM,OAAO,GAAG,sBAAsB;AAEtC,YAAM,WAAW,KAAK,QAAQ;AAC9B,YAAM,UAAU,KAAK,OAAO;AAC5B,YAAM,SAAS,KAAK,MAAM;AAC1B,YAAM,YAAY,KAAK,SAAS;AAEhC,UAAI,CAAC,YAAY,CAAC,WAAW,CAAC,UAAU,CAAC,UAAW;AAGpD,UAAI,UAAU;AACZ,WAAG,MAAM,OAAO,GAAG,KAAK,KAAK,KAAK;AAClC,WAAG,MAAM,YAAY;AAAA,MACvB,WAAW,SAAS;AAClB,WAAG,MAAM,OAAO;AAChB,WAAG,MAAM,YAAY;AAAA,MACvB;AAIA,UAAI,QAAQ;AACV,WAAG,MAAM,SAAS,GAAG,KAAK,KAAK,MAAM;AAAA,MACvC,WAAW,WAAW;AACpB,WAAG,MAAM,SAAS;AAAA,MACpB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,CAAC,WAAmB;AACvC,QAAI,OAAO,WAAW,eAAe,OAAO,UAAU,OAAO,WAAW,QAAQ;AAC9E,aAAO,OAAO,YAAY,EAAE,MAAM,oBAAoB,QAAQ,QAAQ,GAAG,GAAG;AAAA,IAC9E;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,KAAK,YAAY,WAAU,qBAAoB,oBAAiB,QAAO,OAC1E;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,SAAS;AAAA,QACxB;AAAA,QAEA,8BAAC,aAAU;AAAA;AAAA,IACb;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,eAAY;AAAA;AAAA,IACf;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,WAAW;AAAA,QAC1B;AAAA,QAEA,8BAAC,QAAK;AAAA;AAAA,IACR;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QACZ,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,uBAAa,QAAQ;AAAA,QACvB;AAAA,QAEA,8BAAC,UAAO;AAAA;AAAA,IACV;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as React$1 from 'react';
|
|
2
2
|
|
|
3
3
|
interface ContentEntry {
|
|
4
4
|
v: string;
|
|
@@ -9,19 +9,20 @@ interface ClientEditableBlockProps {
|
|
|
9
9
|
blockType: string;
|
|
10
10
|
contentEntries: ContentEntry[];
|
|
11
11
|
children: React.ReactNode;
|
|
12
|
-
blockProps?: Record<string, string>;
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
15
|
-
* Client-side
|
|
16
|
-
* and therefore can't be walked server-side.
|
|
14
|
+
* Client-side block editable wrapper.
|
|
17
15
|
*
|
|
18
|
-
* Renders a hidden sentinel
|
|
19
|
-
*
|
|
16
|
+
* Renders a hidden sentinel span followed by the block's children (no wrapper div).
|
|
17
|
+
* On mount, finds the component's root element via nextElementSibling and:
|
|
18
|
+
* - Stamps data-cms-block, data-block-id, data-block-type directly on it
|
|
19
|
+
* - Injects data-cms-editable spans around matching text nodes
|
|
20
|
+
* - Portals the BlockToolbar into document.body with position:fixed so it is
|
|
21
|
+
* never clipped by overflow:hidden or stacking contexts on the block itself
|
|
20
22
|
*
|
|
21
|
-
* Uses a MutationObserver to re-inject
|
|
22
|
-
*
|
|
23
|
-
* overlays survive React's reconciliation.
|
|
23
|
+
* Uses a MutationObserver to re-inject spans after every DOM mutation
|
|
24
|
+
* (e.g. animated state changes that swap visible elements).
|
|
24
25
|
*/
|
|
25
|
-
declare function ClientEditableBlock({ blockId, blockType, contentEntries, children,
|
|
26
|
+
declare function ClientEditableBlock({ blockId, blockType, contentEntries, children, }: ClientEditableBlockProps): React$1.JSX.Element;
|
|
26
27
|
|
|
27
28
|
export { ClientEditableBlock };
|
|
@@ -2,30 +2,230 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
// lib/client-editable-block.tsx
|
|
5
|
-
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
6
|
-
import {
|
|
5
|
+
import { useCallback, useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState } from "react";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
7
|
+
|
|
8
|
+
// lib/block-outline.tsx
|
|
9
|
+
import { useLayoutEffect, useRef } from "react";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
function BlockOutline({ blockRect }) {
|
|
12
|
+
const outlineRef = useRef(null);
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
const el = outlineRef.current;
|
|
15
|
+
if (!el) return;
|
|
16
|
+
const vw = window.innerWidth;
|
|
17
|
+
const vh = window.innerHeight;
|
|
18
|
+
const offsets = [4, 0, -2];
|
|
19
|
+
for (const offset of offsets) {
|
|
20
|
+
const pad = offset + 2;
|
|
21
|
+
el.style.top = `${blockRect.top - pad}px`;
|
|
22
|
+
el.style.left = `${blockRect.left - pad}px`;
|
|
23
|
+
el.style.width = `${blockRect.width + pad * 2}px`;
|
|
24
|
+
el.style.height = `${blockRect.height + pad * 2}px`;
|
|
25
|
+
const rect = el.getBoundingClientRect();
|
|
26
|
+
if (rect.top >= 0 && rect.left >= 0 && rect.right <= vw && rect.bottom <= vh) break;
|
|
27
|
+
}
|
|
28
|
+
}, [blockRect]);
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
"div",
|
|
31
|
+
{
|
|
32
|
+
ref: outlineRef,
|
|
33
|
+
"data-cms-outline": "true",
|
|
34
|
+
style: {
|
|
35
|
+
position: "fixed",
|
|
36
|
+
border: "2px solid #3b82f6",
|
|
37
|
+
borderRadius: "2px",
|
|
38
|
+
pointerEvents: "none",
|
|
39
|
+
zIndex: 99998,
|
|
40
|
+
boxSizing: "border-box"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// lib/block-toolbar.tsx
|
|
47
|
+
import { ChevronDown, ChevronUp, Plus, Trash2 } from "lucide-react";
|
|
48
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
|
|
49
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
50
|
+
function BlockToolbar({ blockId, style }) {
|
|
51
|
+
const toolbarRef = useRef2(null);
|
|
52
|
+
useLayoutEffect2(() => {
|
|
53
|
+
const el = toolbarRef.current;
|
|
54
|
+
if (!el) return;
|
|
55
|
+
const vw = window.innerWidth;
|
|
56
|
+
const vh = window.innerHeight;
|
|
57
|
+
const MAX_ATTEMPTS = 3;
|
|
58
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
59
|
+
const rect = el.getBoundingClientRect();
|
|
60
|
+
const offRight = rect.right > vw;
|
|
61
|
+
const offLeft = rect.left < 0;
|
|
62
|
+
const offTop = rect.top < 0;
|
|
63
|
+
const offBottom = rect.bottom > vh;
|
|
64
|
+
if (!offRight && !offLeft && !offTop && !offBottom) break;
|
|
65
|
+
if (offRight) {
|
|
66
|
+
el.style.left = `${vw - rect.width}px`;
|
|
67
|
+
el.style.transform = "none";
|
|
68
|
+
} else if (offLeft) {
|
|
69
|
+
el.style.left = "0px";
|
|
70
|
+
el.style.transform = "none";
|
|
71
|
+
}
|
|
72
|
+
if (offTop) {
|
|
73
|
+
el.style.bottom = `${vh - rect.height}px`;
|
|
74
|
+
} else if (offBottom) {
|
|
75
|
+
el.style.bottom = "0px";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
const handleAction = (action) => {
|
|
80
|
+
if (typeof window !== "undefined" && window.parent && window.parent !== window) {
|
|
81
|
+
window.parent.postMessage({ type: "cms-block-action", action, blockId }, "*");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return /* @__PURE__ */ jsxs("div", { ref: toolbarRef, className: "cms-block-toolbar", "data-cms-toolbar": "true", style, children: [
|
|
85
|
+
/* @__PURE__ */ jsx2(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
type: "button",
|
|
89
|
+
title: "Move up",
|
|
90
|
+
"aria-label": "Move up",
|
|
91
|
+
"data-action": "move-up",
|
|
92
|
+
onClick: (e) => {
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
handleAction("move-up");
|
|
95
|
+
},
|
|
96
|
+
children: /* @__PURE__ */ jsx2(ChevronUp, {})
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
/* @__PURE__ */ jsx2(
|
|
100
|
+
"button",
|
|
101
|
+
{
|
|
102
|
+
type: "button",
|
|
103
|
+
title: "Move down",
|
|
104
|
+
"aria-label": "Move down",
|
|
105
|
+
"data-action": "move-down",
|
|
106
|
+
onClick: (e) => {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
handleAction("move-down");
|
|
109
|
+
},
|
|
110
|
+
children: /* @__PURE__ */ jsx2(ChevronDown, {})
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ jsx2(
|
|
114
|
+
"button",
|
|
115
|
+
{
|
|
116
|
+
type: "button",
|
|
117
|
+
title: "Add block",
|
|
118
|
+
"aria-label": "Add block",
|
|
119
|
+
"data-action": "add-block",
|
|
120
|
+
onClick: (e) => {
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
handleAction("add-block");
|
|
123
|
+
},
|
|
124
|
+
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
/* @__PURE__ */ jsx2(
|
|
128
|
+
"button",
|
|
129
|
+
{
|
|
130
|
+
type: "button",
|
|
131
|
+
className: "delete",
|
|
132
|
+
title: "Delete block",
|
|
133
|
+
"aria-label": "Delete block",
|
|
134
|
+
"data-action": "delete",
|
|
135
|
+
onClick: (e) => {
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
handleAction("delete");
|
|
138
|
+
},
|
|
139
|
+
children: /* @__PURE__ */ jsx2(Trash2, {})
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
] });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// lib/client-editable-block.tsx
|
|
146
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
147
|
+
var CMS_STYLES = `
|
|
148
|
+
[data-cms-editable] { cursor: pointer; border-radius: 2px; }
|
|
149
|
+
[data-cms-editable]:hover { outline: 2px solid #3b82f6; outline-offset: 2px; }
|
|
150
|
+
.cms-block-toolbar {
|
|
151
|
+
position: fixed;
|
|
152
|
+
display: flex; gap: 4px; background: #1f2937; border-radius: 6px; padding: 4px;
|
|
153
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
154
|
+
transition: opacity 0.15s ease; z-index: 99999;
|
|
155
|
+
pointer-events: auto;
|
|
156
|
+
}
|
|
157
|
+
.cms-block-toolbar button {
|
|
158
|
+
display: flex; align-items: center; justify-content: center;
|
|
159
|
+
width: 28px; height: 28px; border: none; background: transparent;
|
|
160
|
+
color: #9ca3af; border-radius: 4px; cursor: pointer;
|
|
161
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
162
|
+
}
|
|
163
|
+
.cms-block-toolbar button:hover { background: #374151; color: #fff; }
|
|
164
|
+
.cms-block-toolbar button.delete:hover { background: #dc2626; color: #fff; }
|
|
165
|
+
.cms-block-toolbar button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
166
|
+
.cms-block-toolbar button:disabled:hover { background: transparent; color: #9ca3af; }
|
|
167
|
+
.cms-block-toolbar svg { width: 16px; height: 16px; }
|
|
168
|
+
`;
|
|
169
|
+
var cmsGlobalInjected = false;
|
|
170
|
+
function ensureCmsGlobals() {
|
|
171
|
+
if (cmsGlobalInjected) return;
|
|
172
|
+
cmsGlobalInjected = true;
|
|
173
|
+
const style = document.createElement("style");
|
|
174
|
+
style.setAttribute("data-cms", "");
|
|
175
|
+
style.textContent = CMS_STYLES;
|
|
176
|
+
document.head.appendChild(style);
|
|
177
|
+
if (!window.__cmsEditableInitialized) {
|
|
178
|
+
window.__cmsEditableInitialized = true;
|
|
179
|
+
document.addEventListener("click", (e) => {
|
|
180
|
+
const target = e.target;
|
|
181
|
+
if (target.closest(".cms-block-toolbar")) return;
|
|
182
|
+
const editable = target.closest("[data-cms-editable]");
|
|
183
|
+
if (editable) {
|
|
184
|
+
const msg = {
|
|
185
|
+
type: "cms-editable-click",
|
|
186
|
+
blockId: editable.getAttribute("data-block-id"),
|
|
187
|
+
blockType: editable.getAttribute("data-block-type"),
|
|
188
|
+
contentPath: editable.getAttribute("data-content-path")
|
|
189
|
+
};
|
|
190
|
+
if (window.parent && window.parent !== window) window.parent.postMessage(msg, "*");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const block = target.closest("[data-cms-block]");
|
|
194
|
+
if (block) {
|
|
195
|
+
const msg = {
|
|
196
|
+
type: "cms-editable-click",
|
|
197
|
+
blockId: block.getAttribute("data-block-id"),
|
|
198
|
+
blockType: block.getAttribute("data-block-type"),
|
|
199
|
+
contentPath: null
|
|
200
|
+
};
|
|
201
|
+
if (window.parent && window.parent !== window) window.parent.postMessage(msg, "*");
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
7
206
|
function ClientEditableBlock({
|
|
8
207
|
blockId,
|
|
9
208
|
blockType,
|
|
10
209
|
contentEntries,
|
|
11
|
-
children
|
|
12
|
-
blockProps
|
|
210
|
+
children
|
|
13
211
|
}) {
|
|
14
|
-
const sentinelRef =
|
|
15
|
-
const observerRef =
|
|
16
|
-
const isInjectingRef =
|
|
212
|
+
const sentinelRef = useRef3(null);
|
|
213
|
+
const observerRef = useRef3(null);
|
|
214
|
+
const isInjectingRef = useRef3(false);
|
|
215
|
+
const [toolbarStyle, setToolbarStyle] = useState(null);
|
|
216
|
+
const [outlineRect, setOutlineRect] = useState(null);
|
|
17
217
|
const getBlockRoot = useCallback(() => {
|
|
18
218
|
return sentinelRef.current?.nextElementSibling ?? null;
|
|
19
219
|
}, []);
|
|
20
220
|
const injectSpans = useCallback(() => {
|
|
21
221
|
if (isInjectingRef.current) return;
|
|
22
|
-
const
|
|
23
|
-
if (!
|
|
222
|
+
const blockRoot = getBlockRoot();
|
|
223
|
+
if (!blockRoot) return;
|
|
24
224
|
isInjectingRef.current = true;
|
|
25
225
|
observerRef.current?.disconnect();
|
|
26
226
|
try {
|
|
27
227
|
const existing = Array.from(
|
|
28
|
-
|
|
228
|
+
blockRoot.querySelectorAll("span[data-cms-editable][data-content-path]")
|
|
29
229
|
);
|
|
30
230
|
for (const span of existing) {
|
|
31
231
|
const parent = span.parentNode;
|
|
@@ -34,7 +234,7 @@ function ClientEditableBlock({
|
|
|
34
234
|
parent.removeChild(span);
|
|
35
235
|
}
|
|
36
236
|
const used = /* @__PURE__ */ new Set();
|
|
37
|
-
const walker = document.createTreeWalker(
|
|
237
|
+
const walker = document.createTreeWalker(blockRoot, NodeFilter.SHOW_TEXT, null);
|
|
38
238
|
const textNodes = [];
|
|
39
239
|
let n = walker.nextNode();
|
|
40
240
|
while (n !== null) {
|
|
@@ -53,7 +253,6 @@ function ClientEditableBlock({
|
|
|
53
253
|
if (text.indexOf(entry.v) !== -1 && text.trim() === entry.v.trim()) {
|
|
54
254
|
used.add(entry.p);
|
|
55
255
|
const span = document.createElement("span");
|
|
56
|
-
span.style.display = "contents";
|
|
57
256
|
span.setAttribute("data-cms-editable", "");
|
|
58
257
|
span.setAttribute("data-block-id", blockId);
|
|
59
258
|
span.setAttribute("data-block-type", blockType);
|
|
@@ -66,9 +265,9 @@ function ClientEditableBlock({
|
|
|
66
265
|
}
|
|
67
266
|
} finally {
|
|
68
267
|
isInjectingRef.current = false;
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
observerRef.current.observe(
|
|
268
|
+
const root = getBlockRoot();
|
|
269
|
+
if (root && observerRef.current) {
|
|
270
|
+
observerRef.current.observe(root, {
|
|
72
271
|
childList: true,
|
|
73
272
|
subtree: true,
|
|
74
273
|
characterData: true
|
|
@@ -76,31 +275,48 @@ function ClientEditableBlock({
|
|
|
76
275
|
}
|
|
77
276
|
}
|
|
78
277
|
}, [blockId, blockType, contentEntries, getBlockRoot]);
|
|
79
|
-
|
|
278
|
+
useLayoutEffect3(() => {
|
|
279
|
+
ensureCmsGlobals();
|
|
80
280
|
const blockRoot = getBlockRoot();
|
|
81
|
-
if (blockRoot
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
281
|
+
if (!blockRoot) return;
|
|
282
|
+
blockRoot.setAttribute("data-cms-block", "");
|
|
283
|
+
blockRoot.setAttribute("data-block-id", blockId);
|
|
284
|
+
blockRoot.setAttribute("data-block-type", blockType);
|
|
285
|
+
const showToolbar = () => {
|
|
286
|
+
const rect = blockRoot.getBoundingClientRect();
|
|
287
|
+
setToolbarStyle({
|
|
288
|
+
bottom: window.innerHeight - rect.bottom + 8,
|
|
289
|
+
left: rect.left + rect.width / 2,
|
|
290
|
+
transform: "translateX(-50%)"
|
|
291
|
+
});
|
|
292
|
+
setOutlineRect(rect);
|
|
293
|
+
};
|
|
294
|
+
const hideToolbar = () => {
|
|
295
|
+
setToolbarStyle(null);
|
|
296
|
+
setOutlineRect(null);
|
|
297
|
+
};
|
|
298
|
+
blockRoot.addEventListener("mouseenter", showToolbar);
|
|
299
|
+
blockRoot.addEventListener("mouseleave", hideToolbar);
|
|
86
300
|
injectSpans();
|
|
87
301
|
const observer = new MutationObserver(injectSpans);
|
|
88
302
|
observerRef.current = observer;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
}
|
|
303
|
+
observer.observe(blockRoot, {
|
|
304
|
+
childList: true,
|
|
305
|
+
subtree: true,
|
|
306
|
+
characterData: true
|
|
307
|
+
});
|
|
96
308
|
return () => {
|
|
97
309
|
observer.disconnect();
|
|
98
310
|
observerRef.current = null;
|
|
311
|
+
blockRoot.removeEventListener("mouseenter", showToolbar);
|
|
312
|
+
blockRoot.removeEventListener("mouseleave", hideToolbar);
|
|
99
313
|
};
|
|
100
|
-
}, [injectSpans,
|
|
101
|
-
return /* @__PURE__ */
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
children
|
|
314
|
+
}, [injectSpans, blockId, blockType, getBlockRoot]);
|
|
315
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
316
|
+
/* @__PURE__ */ jsx3("span", { ref: sentinelRef, style: { display: "none" }, "aria-hidden": true }),
|
|
317
|
+
children,
|
|
318
|
+
outlineRect && createPortal(/* @__PURE__ */ jsx3(BlockOutline, { blockRect: outlineRect }), document.body),
|
|
319
|
+
toolbarStyle && createPortal(/* @__PURE__ */ jsx3(BlockToolbar, { blockId, style: toolbarStyle }), document.body)
|
|
104
320
|
] });
|
|
105
321
|
}
|
|
106
322
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../lib/client-editable-block.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useLayoutEffect, useRef } from 'react';\n\ninterface ContentEntry {\n v: string;\n p: string;\n}\n\ninterface ClientEditableBlockProps {\n blockId: string;\n blockType: string;\n contentEntries: ContentEntry[];\n children: React.ReactNode;\n blockProps?: Record<string, string>;\n}\n\n/**\n * Client-side span injector for blocks whose components use React hooks\n * and therefore can't be walked server-side.\n *\n * Renders a hidden sentinel element, then locates the block's actual root DOM\n * element via nextElementSibling — no wrapper div is added to the tree.\n *\n * Uses a MutationObserver to re-inject [data-cms-editable] spans after every\n * React commit (including state-driven re-renders like animations), so the\n * overlays survive React's reconciliation.\n */\nexport function ClientEditableBlock({\n blockId,\n blockType,\n contentEntries,\n children,\n blockProps,\n}: ClientEditableBlockProps) {\n const sentinelRef = useRef<HTMLSpanElement>(null);\n const observerRef = useRef<MutationObserver | null>(null);\n const isInjectingRef = useRef(false);\n\n const getBlockRoot = useCallback((): Element | null => {\n return sentinelRef.current?.nextElementSibling ?? null;\n }, []);\n\n const injectSpans = useCallback(() => {\n if (isInjectingRef.current) return;\n const container = getBlockRoot();\n if (!container) return;\n\n isInjectingRef.current = true;\n // Disconnect observer while we mutate the DOM to avoid re-entrant calls.\n observerRef.current?.disconnect();\n\n try {\n // Remove previously injected text-level spans, restoring the original text nodes.\n const existing = Array.from(\n container.querySelectorAll<HTMLElement>('span[data-cms-editable][data-content-path]')\n );\n for (const span of existing) {\n const parent = span.parentNode;\n if (!parent) continue;\n while (span.firstChild) parent.insertBefore(span.firstChild, span);\n parent.removeChild(span);\n }\n\n // Walk all visible text nodes and wrap the ones that match content values.\n const used = new Set<string>();\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);\n const textNodes: Text[] = [];\n let n = walker.nextNode();\n while (n !== null) {\n const textNode = n as Text;\n if (\n textNode.parentElement?.closest('.cms-block-toolbar') == null &&\n textNode.nodeValue?.trim()\n ) {\n textNodes.push(textNode);\n }\n n = walker.nextNode();\n }\n\n for (const textNode of textNodes) {\n // Never inject spans inside SVG — <span> is not valid SVG content\n if (textNode.parentElement?.closest('svg') != null) continue;\n\n const text = textNode.nodeValue;\n if (!text) continue;\n for (const entry of contentEntries) {\n if (used.has(entry.p)) continue;\n if (text.indexOf(entry.v) !== -1 && text.trim() === entry.v.trim()) {\n used.add(entry.p);\n const span = document.createElement('span');\n // Inline style ensures layout-transparency before the stylesheet cascade applies\n span.style.display = 'contents';\n span.setAttribute('data-cms-editable', '');\n span.setAttribute('data-block-id', blockId);\n span.setAttribute('data-block-type', blockType);\n span.setAttribute('data-content-path', entry.p);\n textNode.parentNode?.insertBefore(span, textNode);\n span.appendChild(textNode);\n break;\n }\n }\n }\n } finally {\n isInjectingRef.current = false;\n // Reconnect after our mutations are done.\n const blockRoot = getBlockRoot();\n if (blockRoot && observerRef.current) {\n observerRef.current.observe(blockRoot, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n }\n }, [blockId, blockType, contentEntries, getBlockRoot]);\n\n useLayoutEffect(() => {\n const blockRoot = getBlockRoot();\n\n // Stamp block data attributes directly onto the component's root DOM element.\n if (blockRoot && blockProps) {\n for (const [key, value] of Object.entries(blockProps)) {\n blockRoot.setAttribute(key, value);\n }\n }\n\n // Initial injection after React's first commit.\n injectSpans();\n\n // Re-inject whenever the child component mutates the DOM\n // (e.g. animated state changes that swap visible elements).\n const observer = new MutationObserver(injectSpans);\n observerRef.current = observer;\n\n if (blockRoot) {\n observer.observe(blockRoot, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n\n return () => {\n observer.disconnect();\n observerRef.current = null;\n };\n }, [injectSpans, getBlockRoot, blockProps]);\n\n return (\n <>\n {/* Sentinel: zero-size, invisible anchor used only to locate the block's root element via nextElementSibling */}\n <span ref={sentinelRef} style={{ display: 'none' }} aria-hidden=\"true\" />\n {children}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../lib/client-editable-block.tsx","../../lib/block-outline.tsx","../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { BlockOutline } from './block-outline';\nimport { BlockToolbar } from './block-toolbar';\n\n// ---------------------------------------------------------------------------\n// Global styles + click-routing — injected once into <head> on first mount.\n// We do this programmatically because Next.js RSC marks body-level <style> and\n// <script> tags as inactive (they appear greyed-out in DevTools and are not\n// applied by the browser).\n// ---------------------------------------------------------------------------\n\nconst CMS_STYLES = `\n [data-cms-editable] { cursor: pointer; border-radius: 2px; }\n [data-cms-editable]:hover { outline: 2px solid #3b82f6; outline-offset: 2px; }\n .cms-block-toolbar {\n position: fixed;\n display: flex; gap: 4px; background: #1f2937; border-radius: 6px; padding: 4px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n transition: opacity 0.15s ease; z-index: 99999;\n pointer-events: auto;\n }\n .cms-block-toolbar button {\n display: flex; align-items: center; justify-content: center;\n width: 28px; height: 28px; border: none; background: transparent;\n color: #9ca3af; border-radius: 4px; cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .cms-block-toolbar button:hover { background: #374151; color: #fff; }\n .cms-block-toolbar button.delete:hover { background: #dc2626; color: #fff; }\n .cms-block-toolbar button:disabled { opacity: 0.4; cursor: not-allowed; }\n .cms-block-toolbar button:disabled:hover { background: transparent; color: #9ca3af; }\n .cms-block-toolbar svg { width: 16px; height: 16px; }\n`;\n\nlet cmsGlobalInjected = false;\n\nfunction ensureCmsGlobals() {\n if (cmsGlobalInjected) return;\n cmsGlobalInjected = true;\n\n const style = document.createElement('style');\n style.setAttribute('data-cms', '');\n style.textContent = CMS_STYLES;\n document.head.appendChild(style);\n\n if (!(window as Window & { __cmsEditableInitialized?: boolean }).__cmsEditableInitialized) {\n (window as Window & { __cmsEditableInitialized?: boolean }).__cmsEditableInitialized = true;\n document.addEventListener('click', (e) => {\n const target = e.target as Element;\n if (target.closest('.cms-block-toolbar')) return;\n\n const editable = target.closest('[data-cms-editable]');\n if (editable) {\n const msg = {\n type: 'cms-editable-click',\n blockId: editable.getAttribute('data-block-id'),\n blockType: editable.getAttribute('data-block-type'),\n contentPath: editable.getAttribute('data-content-path'),\n };\n if (window.parent && window.parent !== window) window.parent.postMessage(msg, '*');\n return;\n }\n\n const block = target.closest('[data-cms-block]');\n if (block) {\n const msg = {\n type: 'cms-editable-click',\n blockId: block.getAttribute('data-block-id'),\n blockType: block.getAttribute('data-block-type'),\n contentPath: null,\n };\n if (window.parent && window.parent !== window) window.parent.postMessage(msg, '*');\n }\n });\n }\n}\n\ninterface ContentEntry {\n v: string;\n p: string;\n}\n\ninterface ClientEditableBlockProps {\n blockId: string;\n blockType: string;\n contentEntries: ContentEntry[];\n children: React.ReactNode;\n}\n\n/**\n * Client-side block editable wrapper.\n *\n * Renders a hidden sentinel span followed by the block's children (no wrapper div).\n * On mount, finds the component's root element via nextElementSibling and:\n * - Stamps data-cms-block, data-block-id, data-block-type directly on it\n * - Injects data-cms-editable spans around matching text nodes\n * - Portals the BlockToolbar into document.body with position:fixed so it is\n * never clipped by overflow:hidden or stacking contexts on the block itself\n *\n * Uses a MutationObserver to re-inject spans after every DOM mutation\n * (e.g. animated state changes that swap visible elements).\n */\nexport function ClientEditableBlock({\n blockId,\n blockType,\n contentEntries,\n children,\n}: ClientEditableBlockProps) {\n const sentinelRef = useRef<HTMLSpanElement>(null);\n const observerRef = useRef<MutationObserver | null>(null);\n const isInjectingRef = useRef(false);\n const [toolbarStyle, setToolbarStyle] = useState<React.CSSProperties | null>(null);\n const [outlineRect, setOutlineRect] = useState<DOMRect | null>(null);\n\n const getBlockRoot = useCallback((): HTMLElement | null => {\n return (sentinelRef.current?.nextElementSibling as HTMLElement) ?? null;\n }, []);\n\n const injectSpans = useCallback(() => {\n if (isInjectingRef.current) return;\n const blockRoot = getBlockRoot();\n if (!blockRoot) return;\n\n isInjectingRef.current = true;\n // Disconnect observer while we mutate the DOM to avoid re-entrant calls.\n observerRef.current?.disconnect();\n\n try {\n // Remove previously injected text-level spans, restoring the original text nodes.\n const existing = Array.from(\n blockRoot.querySelectorAll<HTMLElement>('span[data-cms-editable][data-content-path]')\n );\n for (const span of existing) {\n const parent = span.parentNode;\n if (!parent) continue;\n while (span.firstChild) parent.insertBefore(span.firstChild, span);\n parent.removeChild(span);\n }\n\n // Walk all visible text nodes and wrap the ones that match content values.\n const used = new Set<string>();\n const walker = document.createTreeWalker(blockRoot, NodeFilter.SHOW_TEXT, null);\n const textNodes: Text[] = [];\n let n = walker.nextNode();\n while (n !== null) {\n const textNode = n as Text;\n if (\n textNode.parentElement?.closest('.cms-block-toolbar') == null &&\n textNode.nodeValue?.trim()\n ) {\n textNodes.push(textNode);\n }\n n = walker.nextNode();\n }\n\n for (const textNode of textNodes) {\n // Never inject spans inside SVG — <span> is not valid SVG content\n if (textNode.parentElement?.closest('svg') != null) continue;\n\n const text = textNode.nodeValue;\n if (!text) continue;\n for (const entry of contentEntries) {\n if (used.has(entry.p)) continue;\n if (text.indexOf(entry.v) !== -1 && text.trim() === entry.v.trim()) {\n used.add(entry.p);\n const span = document.createElement('span');\n span.setAttribute('data-cms-editable', '');\n span.setAttribute('data-block-id', blockId);\n span.setAttribute('data-block-type', blockType);\n span.setAttribute('data-content-path', entry.p);\n textNode.parentNode?.insertBefore(span, textNode);\n span.appendChild(textNode);\n break;\n }\n }\n }\n } finally {\n isInjectingRef.current = false;\n // Reconnect after our mutations are done.\n const root = getBlockRoot();\n if (root && observerRef.current) {\n observerRef.current.observe(root, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n }\n }, [blockId, blockType, contentEntries, getBlockRoot]);\n\n useLayoutEffect(() => {\n ensureCmsGlobals();\n\n const blockRoot = getBlockRoot();\n if (!blockRoot) return;\n\n // Stamp CMS attributes directly on the component's root element.\n blockRoot.setAttribute('data-cms-block', '');\n blockRoot.setAttribute('data-block-id', blockId);\n blockRoot.setAttribute('data-block-type', blockType);\n\n // Toolbar: portal to document.body with position:fixed so it escapes\n // any overflow:hidden or stacking context on the block or its ancestors.\n const showToolbar = () => {\n const rect = blockRoot.getBoundingClientRect();\n setToolbarStyle({\n bottom: window.innerHeight - rect.bottom + 8,\n left: rect.left + rect.width / 2,\n transform: 'translateX(-50%)',\n });\n setOutlineRect(rect);\n };\n const hideToolbar = () => {\n setToolbarStyle(null);\n setOutlineRect(null);\n };\n\n blockRoot.addEventListener('mouseenter', showToolbar);\n blockRoot.addEventListener('mouseleave', hideToolbar);\n\n // Initial span injection after React's first commit.\n injectSpans();\n\n // Re-inject whenever the child component mutates the DOM.\n const observer = new MutationObserver(injectSpans);\n observerRef.current = observer;\n observer.observe(blockRoot, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n observerRef.current = null;\n blockRoot.removeEventListener('mouseenter', showToolbar);\n blockRoot.removeEventListener('mouseleave', hideToolbar);\n };\n }, [injectSpans, blockId, blockType, getBlockRoot]);\n\n return (\n <>\n <span ref={sentinelRef} style={{ display: 'none' }} aria-hidden />\n {children}\n {outlineRect && createPortal(<BlockOutline blockRect={outlineRect} />, document.body)}\n {toolbarStyle &&\n createPortal(<BlockToolbar blockId={blockId} style={toolbarStyle} />, document.body)}\n </>\n );\n}\n","'use client';\n\nimport { useLayoutEffect, useRef } from 'react';\n\n/**\n * Block Outline Component\n *\n * Renders a position:fixed border overlay around a hovered block.\n * Tries up to 3 offset values (4px → 0px → -2px inset) to keep every\n * edge of the border visible inside the viewport before giving up.\n */\nexport function BlockOutline({ blockRect }: { blockRect: DOMRect }) {\n const outlineRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const el = outlineRef.current;\n if (!el) return;\n\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // Attempt progressively smaller offsets until the border is fully visible.\n // offset=4 → normal outline-offset feel\n // offset=0 → border flush with block edge\n // offset=-2 → border drawn inside the block (inset)\n const offsets = [4, 0, -2];\n\n for (const offset of offsets) {\n const pad = offset + 2; // 2px border + offset\n el.style.top = `${blockRect.top - pad}px`;\n el.style.left = `${blockRect.left - pad}px`;\n el.style.width = `${blockRect.width + pad * 2}px`;\n el.style.height = `${blockRect.height + pad * 2}px`;\n\n const rect = el.getBoundingClientRect();\n if (rect.top >= 0 && rect.left >= 0 && rect.right <= vw && rect.bottom <= vh) break;\n }\n }, [blockRect]);\n\n return (\n <div\n ref={outlineRef}\n data-cms-outline=\"true\"\n style={{\n position: 'fixed',\n border: '2px solid #3b82f6',\n borderRadius: '2px',\n pointerEvents: 'none',\n zIndex: 99998,\n boxSizing: 'border-box',\n }}\n />\n );\n}\n","'use client';\n\nimport { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react';\nimport type React from 'react';\nimport { useLayoutEffect, useRef } from 'react';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * This is a Client Component because it requires onClick handlers.\n */\n\nexport function BlockToolbar({ blockId, style }: { blockId: string; style?: React.CSSProperties }) {\n const toolbarRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const el = toolbarRef.current;\n if (!el) return;\n\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const MAX_ATTEMPTS = 3;\n\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const rect = el.getBoundingClientRect();\n\n const offRight = rect.right > vw;\n const offLeft = rect.left < 0;\n const offTop = rect.top < 0;\n const offBottom = rect.bottom > vh;\n\n if (!offRight && !offLeft && !offTop && !offBottom) break;\n\n // Horizontal: remove the centering transform and pin to the edge that's clipped\n if (offRight) {\n el.style.left = `${vw - rect.width}px`;\n el.style.transform = 'none';\n } else if (offLeft) {\n el.style.left = '0px';\n el.style.transform = 'none';\n }\n\n // Vertical: toolbar uses `bottom` (distance from viewport bottom), so\n // top < 0 means bottom is too large — clamp it so top aligns to viewport edge\n if (offTop) {\n el.style.bottom = `${vh - rect.height}px`;\n } else if (offBottom) {\n el.style.bottom = '0px';\n }\n }\n }, []);\n\n const handleAction = (action: string) => {\n if (typeof window !== 'undefined' && window.parent && window.parent !== window) {\n window.parent.postMessage({ type: 'cms-block-action', action, blockId }, '*');\n }\n };\n\n return (\n <div ref={toolbarRef} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\" style={style}>\n <button\n type=\"button\"\n title=\"Move up\"\n aria-label=\"Move up\"\n data-action=\"move-up\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-up');\n }}\n >\n <ChevronUp />\n </button>\n <button\n type=\"button\"\n title=\"Move down\"\n aria-label=\"Move down\"\n data-action=\"move-down\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('move-down');\n }}\n >\n <ChevronDown />\n </button>\n <button\n type=\"button\"\n title=\"Add block\"\n aria-label=\"Add block\"\n data-action=\"add-block\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('add-block');\n }}\n >\n <Plus />\n </button>\n <button\n type=\"button\"\n className=\"delete\"\n title=\"Delete block\"\n aria-label=\"Delete block\"\n data-action=\"delete\"\n onClick={(e) => {\n e.stopPropagation();\n handleAction('delete');\n }}\n >\n <Trash2 />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["useLayoutEffect","useRef","useLayoutEffect","useRef","jsx","jsx","jsxs","useRef","useLayoutEffect"]}
|