cms-renderer 0.6.2 → 0.6.4

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.
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
  // lib/client-editable-block.tsx
5
- import { useCallback, useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState } from "react";
5
+ import { useCallback, useEffect, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState } from "react";
6
6
  import { createPortal } from "react-dom";
7
7
 
8
8
  // lib/block-outline.tsx
@@ -45,126 +45,99 @@ function BlockOutline({ blockRect }) {
45
45
 
46
46
  // lib/block-toolbar.tsx
47
47
  import { ChevronDown, ChevronUp, Plus, Trash2 } from "lucide-react";
48
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
48
+ import { forwardRef } from "react";
49
49
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
50
- function BlockToolbar({ blockId, blockRect }) {
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 { width: tw, height: th } = el.getBoundingClientRect();
58
- const b = blockRect;
59
- const candidates = [
60
- { top: b.bottom + 8, left: b.left + b.width / 2 - tw / 2 },
61
- // below, centered
62
- { top: b.top - th - 8, left: b.left + b.width / 2 - tw / 2 },
63
- // above, centered
64
- { top: b.bottom + 8, left: b.right - tw },
65
- // below, right-aligned
66
- { top: b.bottom + 8, left: b.left },
67
- // below, left-aligned
68
- { top: b.top - th - 8, left: b.right - tw }
69
- // above, right-aligned
70
- ];
71
- let chosen = candidates[0] ?? { top: b.bottom + 8, left: b.left + b.width / 2 - tw / 2 };
72
- for (const c of candidates) {
73
- if (c.top >= 0 && c.left >= 0 && c.top + th <= vh && c.left + tw <= vw) {
74
- chosen = c;
75
- break;
76
- }
77
- }
78
- el.style.top = `${Math.min(Math.max(0, chosen.top), vh - th)}px`;
79
- el.style.left = `${Math.min(Math.max(0, chosen.left), vw - tw)}px`;
80
- el.style.visibility = "visible";
81
- }, [blockRect]);
50
+ var BlockToolbar = forwardRef(function BlockToolbar2({ blockId }, ref) {
82
51
  const handleAction = (action) => {
83
52
  if (typeof window !== "undefined" && window.parent && window.parent !== window) {
84
53
  window.parent.postMessage({ type: "cms-block-action", action, blockId }, "*");
85
54
  }
86
55
  };
87
- return /* @__PURE__ */ jsxs(
88
- "div",
89
- {
90
- ref: toolbarRef,
91
- className: "cms-block-toolbar",
92
- "data-cms-toolbar": "true",
93
- style: { visibility: "hidden" },
94
- children: [
95
- /* @__PURE__ */ jsx2(
96
- "button",
97
- {
98
- type: "button",
99
- title: "Move up",
100
- "aria-label": "Move up",
101
- "data-action": "move-up",
102
- onClick: (e) => {
103
- e.stopPropagation();
104
- handleAction("move-up");
105
- },
106
- children: /* @__PURE__ */ jsx2(ChevronUp, {})
107
- }
108
- ),
109
- /* @__PURE__ */ jsx2(
110
- "button",
111
- {
112
- type: "button",
113
- title: "Move down",
114
- "aria-label": "Move down",
115
- "data-action": "move-down",
116
- onClick: (e) => {
117
- e.stopPropagation();
118
- handleAction("move-down");
119
- },
120
- children: /* @__PURE__ */ jsx2(ChevronDown, {})
121
- }
122
- ),
123
- /* @__PURE__ */ jsx2(
124
- "button",
125
- {
126
- type: "button",
127
- title: "Add block",
128
- "aria-label": "Add block",
129
- "data-action": "add-block",
130
- onClick: (e) => {
131
- e.stopPropagation();
132
- handleAction("add-block");
133
- },
134
- children: /* @__PURE__ */ jsx2(Plus, {})
135
- }
136
- ),
137
- /* @__PURE__ */ jsx2(
138
- "button",
139
- {
140
- type: "button",
141
- className: "delete",
142
- title: "Delete block",
143
- "aria-label": "Delete block",
144
- "data-action": "delete",
145
- onClick: (e) => {
146
- e.stopPropagation();
147
- handleAction("delete");
148
- },
149
- children: /* @__PURE__ */ jsx2(Trash2, {})
150
- }
151
- )
152
- ]
153
- }
154
- );
155
- }
56
+ return /* @__PURE__ */ jsxs("div", { ref, className: "cms-block-toolbar", "data-cms-toolbar": "true", children: [
57
+ /* @__PURE__ */ jsx2(
58
+ "button",
59
+ {
60
+ type: "button",
61
+ title: "Move up",
62
+ "aria-label": "Move up",
63
+ "data-action": "move-up",
64
+ onClick: (e) => {
65
+ e.stopPropagation();
66
+ handleAction("move-up");
67
+ },
68
+ children: /* @__PURE__ */ jsx2(ChevronUp, {})
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsx2(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ title: "Move down",
76
+ "aria-label": "Move down",
77
+ "data-action": "move-down",
78
+ onClick: (e) => {
79
+ e.stopPropagation();
80
+ handleAction("move-down");
81
+ },
82
+ children: /* @__PURE__ */ jsx2(ChevronDown, {})
83
+ }
84
+ ),
85
+ /* @__PURE__ */ jsx2(
86
+ "button",
87
+ {
88
+ type: "button",
89
+ title: "Add block",
90
+ "aria-label": "Add block",
91
+ "data-action": "add-block",
92
+ onClick: (e) => {
93
+ e.stopPropagation();
94
+ handleAction("add-block");
95
+ },
96
+ children: /* @__PURE__ */ jsx2(Plus, {})
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx2(
100
+ "button",
101
+ {
102
+ type: "button",
103
+ className: "delete",
104
+ title: "Delete block",
105
+ "aria-label": "Delete block",
106
+ "data-action": "delete",
107
+ onClick: (e) => {
108
+ e.stopPropagation();
109
+ handleAction("delete");
110
+ },
111
+ children: /* @__PURE__ */ jsx2(Trash2, {})
112
+ }
113
+ )
114
+ ] });
115
+ });
156
116
 
157
117
  // lib/client-editable-block.tsx
158
118
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
159
119
  var CMS_STYLES = `
160
120
  [data-cms-editable] { cursor: pointer; border-radius: 2px; }
161
121
  [data-cms-editable]:hover { outline: 2px solid #3b82f6; outline-offset: 2px; }
122
+ .cms-block-cursor {
123
+ position: fixed; width: 22px; height: 22px;
124
+ background: radial-gradient(circle, #fff 5px, #000 5px);
125
+ border-radius: 50%;
126
+ pointer-events: none; z-index: 99999;
127
+ transform: translate(-50%, -50%);
128
+ opacity: 0; transition: opacity 0.1s ease;
129
+ }
130
+ .cms-block-cursor.cms-cursor-visible { opacity: 1; }
162
131
  .cms-block-toolbar {
163
132
  position: fixed;
164
133
  display: flex; gap: 4px; background: #1f2937; border-radius: 6px; padding: 4px;
165
- box-shadow: 0 2px 8px rgba(0,0,0,0.15);
166
- transition: opacity 0.15s ease; z-index: 99999;
167
- pointer-events: auto;
134
+ box-shadow: 0 4px 12px rgba(0,0,0,0.25);
135
+ z-index: 99999; pointer-events: none;
136
+ opacity: 0; transform: scale(0.9) translateY(4px);
137
+ transition: opacity 0.15s ease, transform 0.15s ease;
138
+ }
139
+ .cms-block-toolbar.cms-toolbar-visible {
140
+ opacity: 1; transform: scale(1) translateY(0); pointer-events: auto;
168
141
  }
169
142
  .cms-block-toolbar button {
170
143
  display: flex; align-items: center; justify-content: center;
@@ -221,9 +194,13 @@ function ClientEditableBlock({
221
194
  contentEntries,
222
195
  children
223
196
  }) {
224
- const sentinelRef = useRef3(null);
225
- const observerRef = useRef3(null);
226
- const isInjectingRef = useRef3(false);
197
+ const sentinelRef = useRef2(null);
198
+ const observerRef = useRef2(null);
199
+ const isInjectingRef = useRef2(false);
200
+ const cursorElRef = useRef2(null);
201
+ const toolbarElRef = useRef2(null);
202
+ const toolbarVisibleRef = useRef2(false);
203
+ const [mounted, setMounted] = useState(false);
227
204
  const [outlineRect, setOutlineRect] = useState(null);
228
205
  const getBlockRoot = useCallback(() => {
229
206
  return sentinelRef.current?.nextElementSibling ?? null;
@@ -286,17 +263,74 @@ function ClientEditableBlock({
286
263
  }
287
264
  }
288
265
  }, [blockId, blockType, contentEntries, getBlockRoot]);
289
- useLayoutEffect3(() => {
266
+ useEffect(() => {
267
+ setMounted(true);
268
+ }, []);
269
+ useLayoutEffect2(() => {
290
270
  ensureCmsGlobals();
291
271
  const blockRoot = getBlockRoot();
292
272
  if (!blockRoot) return;
293
273
  blockRoot.setAttribute("data-cms-block", "");
294
274
  blockRoot.setAttribute("data-block-id", blockId);
295
275
  blockRoot.setAttribute("data-block-type", blockType);
296
- const showToolbar = () => setOutlineRect(blockRoot.getBoundingClientRect());
297
- const hideToolbar = () => setOutlineRect(null);
298
- blockRoot.addEventListener("mouseenter", showToolbar);
299
- blockRoot.addEventListener("mouseleave", hideToolbar);
276
+ const positionToolbar = (x, y) => {
277
+ const el = toolbarElRef.current;
278
+ if (!el) return;
279
+ const { width: tw, height: th } = el.getBoundingClientRect();
280
+ const top = Math.max(4, y - th - 12);
281
+ const left = Math.max(4, Math.min(x - tw / 2, window.innerWidth - tw - 4));
282
+ el.style.transition = "opacity 0.15s ease, transform 0.15s ease";
283
+ el.style.top = `${top}px`;
284
+ el.style.left = `${left}px`;
285
+ };
286
+ const showCursor = (e) => {
287
+ if (toolbarVisibleRef.current) return;
288
+ setOutlineRect(blockRoot.getBoundingClientRect());
289
+ const el = cursorElRef.current;
290
+ if (!el) return;
291
+ el.style.top = `${e.clientY}px`;
292
+ el.style.left = `${e.clientX}px`;
293
+ el.classList.add("cms-cursor-visible");
294
+ };
295
+ const moveCursor = (e) => {
296
+ if (toolbarVisibleRef.current) return;
297
+ const el = cursorElRef.current;
298
+ if (!el) return;
299
+ el.style.top = `${e.clientY}px`;
300
+ el.style.left = `${e.clientX}px`;
301
+ };
302
+ const hideCursor = () => {
303
+ cursorElRef.current?.classList.remove("cms-cursor-visible");
304
+ setOutlineRect(null);
305
+ };
306
+ const handleContextMenu = (e) => {
307
+ if (e.target.closest("[data-cms-toolbar]")) return;
308
+ if (toolbarVisibleRef.current) return;
309
+ e.preventDefault();
310
+ hideCursor();
311
+ positionToolbar(e.clientX, e.clientY);
312
+ toolbarVisibleRef.current = true;
313
+ toolbarElRef.current?.classList.add("cms-toolbar-visible");
314
+ };
315
+ const handleClickOutside = (e) => {
316
+ if (!toolbarVisibleRef.current) return;
317
+ const target = e.target;
318
+ if (target.closest("[data-cms-toolbar]")) return;
319
+ if (blockRoot.contains(target)) return;
320
+ toolbarElRef.current?.classList.remove("cms-toolbar-visible");
321
+ toolbarVisibleRef.current = false;
322
+ };
323
+ const hideOnScroll = () => {
324
+ hideCursor();
325
+ toolbarElRef.current?.classList.remove("cms-toolbar-visible");
326
+ toolbarVisibleRef.current = false;
327
+ };
328
+ blockRoot.addEventListener("mouseenter", showCursor);
329
+ blockRoot.addEventListener("mousemove", moveCursor);
330
+ blockRoot.addEventListener("mouseleave", hideCursor);
331
+ blockRoot.addEventListener("contextmenu", handleContextMenu);
332
+ document.addEventListener("click", handleClickOutside);
333
+ window.addEventListener("scroll", hideOnScroll, { passive: true, capture: true });
300
334
  injectSpans();
301
335
  const observer = new MutationObserver(injectSpans);
302
336
  observerRef.current = observer;
@@ -308,15 +342,20 @@ function ClientEditableBlock({
308
342
  return () => {
309
343
  observer.disconnect();
310
344
  observerRef.current = null;
311
- blockRoot.removeEventListener("mouseenter", showToolbar);
312
- blockRoot.removeEventListener("mouseleave", hideToolbar);
345
+ blockRoot.removeEventListener("mouseenter", showCursor);
346
+ blockRoot.removeEventListener("mousemove", moveCursor);
347
+ blockRoot.removeEventListener("mouseleave", hideCursor);
348
+ blockRoot.removeEventListener("contextmenu", handleContextMenu);
349
+ document.removeEventListener("click", handleClickOutside);
350
+ window.removeEventListener("scroll", hideOnScroll, { capture: true });
313
351
  };
314
352
  }, [injectSpans, blockId, blockType, getBlockRoot]);
315
353
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
316
354
  /* @__PURE__ */ jsx3("span", { ref: sentinelRef, style: { display: "none" }, "aria-hidden": true }),
317
355
  children,
318
- outlineRect && createPortal(/* @__PURE__ */ jsx3(BlockOutline, { blockRect: outlineRect }), document.body),
319
- outlineRect && createPortal(/* @__PURE__ */ jsx3(BlockToolbar, { blockId, blockRect: outlineRect }), document.body)
356
+ mounted && outlineRect && createPortal(/* @__PURE__ */ jsx3(BlockOutline, { blockRect: outlineRect }), document.body),
357
+ mounted && createPortal(/* @__PURE__ */ jsx3("div", { ref: cursorElRef, className: "cms-block-cursor" }), document.body),
358
+ mounted && createPortal(/* @__PURE__ */ jsx3(BlockToolbar, { ref: toolbarElRef, blockId }), document.body)
320
359
  ] });
321
360
  }
322
361
  export {
@@ -1 +1 @@
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 [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 = () => setOutlineRect(blockRoot.getBoundingClientRect());\n const hideToolbar = () => setOutlineRect(null);\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 {outlineRect &&\n createPortal(<BlockToolbar blockId={blockId} blockRect={outlineRect} />, 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 { 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, blockRect }: { blockId: string; blockRect: DOMRect }) {\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 { width: tw, height: th } = el.getBoundingClientRect();\n const b = blockRect;\n\n // Up to 5 candidate placements tried in order; first one fully in-viewport wins.\n const candidates = [\n { top: b.bottom + 8, left: b.left + b.width / 2 - tw / 2 }, // below, centered\n { top: b.top - th - 8, left: b.left + b.width / 2 - tw / 2 }, // above, centered\n { top: b.bottom + 8, left: b.right - tw }, // below, right-aligned\n { top: b.bottom + 8, left: b.left }, // below, left-aligned\n { top: b.top - th - 8, left: b.right - tw }, // above, right-aligned\n ];\n\n let chosen = candidates[0] ?? { top: b.bottom + 8, left: b.left + b.width / 2 - tw / 2 };\n for (const c of candidates) {\n if (c.top >= 0 && c.left >= 0 && c.top + th <= vh && c.left + tw <= vw) {\n chosen = c;\n break;\n }\n }\n\n el.style.top = `${Math.min(Math.max(0, chosen.top), vh - th)}px`;\n el.style.left = `${Math.min(Math.max(0, chosen.left), vw - tw)}px`;\n el.style.visibility = 'visible';\n }, [blockRect]);\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\n ref={toolbarRef}\n className=\"cms-block-toolbar\"\n data-cms-toolbar=\"true\"\n style={{ visibility: 'hidden' }}\n >\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,mBAAAA,kBAAiB,UAAAC,SAAQ,gBAAgB;AAC/D,SAAS,oBAAoB;;;ACD7B,SAAS,iBAAiB,cAAc;AAsCpC;AA7BG,SAAS,aAAa,EAAE,UAAU,GAA2B;AAClE,QAAM,aAAa,OAAuB,IAAI;AAE9C,kBAAgB,MAAM;AACpB,UAAM,KAAK,WAAW;AACtB,QAAI,CAAC,GAAI;AAET,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAMlB,UAAM,UAAU,CAAC,GAAG,GAAG,EAAE;AAEzB,eAAW,UAAU,SAAS;AAC5B,YAAM,MAAM,SAAS;AACrB,SAAG,MAAM,MAAM,GAAG,UAAU,MAAM,GAAG;AACrC,SAAG,MAAM,OAAO,GAAG,UAAU,OAAO,GAAG;AACvC,SAAG,MAAM,QAAQ,GAAG,UAAU,QAAQ,MAAM,CAAC;AAC7C,SAAG,MAAM,SAAS,GAAG,UAAU,SAAS,MAAM,CAAC;AAE/C,YAAM,OAAO,GAAG,sBAAsB;AACtC,UAAI,KAAK,OAAO,KAAK,KAAK,QAAQ,KAAK,KAAK,SAAS,MAAM,KAAK,UAAU,GAAI;AAAA,IAChF;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,oBAAiB;AAAA,MACjB,OAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,eAAe;AAAA,QACf,QAAQ;AAAA,QACR,WAAW;AAAA,MACb;AAAA;AAAA,EACF;AAEJ;;;ACnDA,SAAS,aAAa,WAAW,MAAM,cAAc;AACrD,SAAS,mBAAAC,kBAAiB,UAAAC,eAAc;AAkDpC,SAgBI,OAAAC,MAhBJ;AAzCG,SAAS,aAAa,EAAE,SAAS,UAAU,GAA4C;AAC5F,QAAM,aAAaD,QAAuB,IAAI;AAE9C,EAAAD,iBAAgB,MAAM;AACpB,UAAM,KAAK,WAAW;AACtB,QAAI,CAAC,GAAI;AAET,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAClB,UAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI,GAAG,sBAAsB;AAC3D,UAAM,IAAI;AAGV,UAAM,aAAa;AAAA,MACjB,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE;AAAA;AAAA,MACzD,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE;AAAA;AAAA,MAC3D,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,QAAQ,GAAG;AAAA;AAAA,MACxC,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,KAAK;AAAA;AAAA,MAClC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,MAAM,EAAE,QAAQ,GAAG;AAAA;AAAA,IAC5C;AAEA,QAAI,SAAS,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,EAAE;AACvF,eAAW,KAAK,YAAY;AAC1B,UAAI,EAAE,OAAO,KAAK,EAAE,QAAQ,KAAK,EAAE,MAAM,MAAM,MAAM,EAAE,OAAO,MAAM,IAAI;AACtE,iBAAS;AACT;AAAA,MACF;AAAA,IACF;AAEA,OAAG,MAAM,MAAM,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,GAAG,KAAK,EAAE,CAAC;AAC5D,OAAG,MAAM,OAAO,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,IAAI,GAAG,KAAK,EAAE,CAAC;AAC9D,OAAG,MAAM,aAAa;AAAA,EACxB,GAAG,CAAC,SAAS,CAAC;AAEd,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;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAU;AAAA,MACV,oBAAiB;AAAA,MACjB,OAAO,EAAE,YAAY,SAAS;AAAA,MAE9B;AAAA,wBAAAE;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAM;AAAA,YACN,cAAW;AAAA,YACX,eAAY;AAAA,YACZ,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,2BAAa,SAAS;AAAA,YACxB;AAAA,YAEA,0BAAAA,KAAC,aAAU;AAAA;AAAA,QACb;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAM;AAAA,YACN,cAAW;AAAA,YACX,eAAY;AAAA,YACZ,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,2BAAa,WAAW;AAAA,YAC1B;AAAA,YAEA,0BAAAA,KAAC,eAAY;AAAA;AAAA,QACf;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAM;AAAA,YACN,cAAW;AAAA,YACX,eAAY;AAAA,YACZ,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,2BAAa,WAAW;AAAA,YAC1B;AAAA,YAEA,0BAAAA,KAAC,QAAK;AAAA;AAAA,QACR;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAM;AAAA,YACN,cAAW;AAAA,YACX,eAAY;AAAA,YACZ,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,2BAAa,QAAQ;AAAA,YACvB;AAAA,YAEA,0BAAAA,KAAC,UAAO;AAAA;AAAA,QACV;AAAA;AAAA;AAAA,EACF;AAEJ;;;AF0HI,mBACE,OAAAC,MADF,QAAAC,aAAA;AA1NJ,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBnB,IAAI,oBAAoB;AAExB,SAAS,mBAAmB;AAC1B,MAAI,kBAAmB;AACvB,sBAAoB;AAEpB,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,aAAa,YAAY,EAAE;AACjC,QAAM,cAAc;AACpB,WAAS,KAAK,YAAY,KAAK;AAE/B,MAAI,CAAE,OAA2D,0BAA0B;AACzF,IAAC,OAA2D,2BAA2B;AACvF,aAAS,iBAAiB,SAAS,CAAC,MAAM;AACxC,YAAM,SAAS,EAAE;AACjB,UAAI,OAAO,QAAQ,oBAAoB,EAAG;AAE1C,YAAM,WAAW,OAAO,QAAQ,qBAAqB;AACrD,UAAI,UAAU;AACZ,cAAM,MAAM;AAAA,UACV,MAAM;AAAA,UACN,SAAS,SAAS,aAAa,eAAe;AAAA,UAC9C,WAAW,SAAS,aAAa,iBAAiB;AAAA,UAClD,aAAa,SAAS,aAAa,mBAAmB;AAAA,QACxD;AACA,YAAI,OAAO,UAAU,OAAO,WAAW,OAAQ,QAAO,OAAO,YAAY,KAAK,GAAG;AACjF;AAAA,MACF;AAEA,YAAM,QAAQ,OAAO,QAAQ,kBAAkB;AAC/C,UAAI,OAAO;AACT,cAAM,MAAM;AAAA,UACV,MAAM;AAAA,UACN,SAAS,MAAM,aAAa,eAAe;AAAA,UAC3C,WAAW,MAAM,aAAa,iBAAiB;AAAA,UAC/C,aAAa;AAAA,QACf;AACA,YAAI,OAAO,UAAU,OAAO,WAAW,OAAQ,QAAO,OAAO,YAAY,KAAK,GAAG;AAAA,MACnF;AAAA,IACF,CAAC;AAAA,EACH;AACF;AA2BO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,cAAcC,QAAwB,IAAI;AAChD,QAAM,cAAcA,QAAgC,IAAI;AACxD,QAAM,iBAAiBA,QAAO,KAAK;AACnC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAyB,IAAI;AAEnE,QAAM,eAAe,YAAY,MAA0B;AACzD,WAAQ,YAAY,SAAS,sBAAsC;AAAA,EACrE,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI,eAAe,QAAS;AAC5B,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,mBAAe,UAAU;AAEzB,gBAAY,SAAS,WAAW;AAEhC,QAAI;AAEF,YAAM,WAAW,MAAM;AAAA,QACrB,UAAU,iBAA8B,4CAA4C;AAAA,MACtF;AACA,iBAAW,QAAQ,UAAU;AAC3B,cAAM,SAAS,KAAK;AACpB,YAAI,CAAC,OAAQ;AACb,eAAO,KAAK,WAAY,QAAO,aAAa,KAAK,YAAY,IAAI;AACjE,eAAO,YAAY,IAAI;AAAA,MACzB;AAGA,YAAM,OAAO,oBAAI,IAAY;AAC7B,YAAM,SAAS,SAAS,iBAAiB,WAAW,WAAW,WAAW,IAAI;AAC9E,YAAM,YAAoB,CAAC;AAC3B,UAAI,IAAI,OAAO,SAAS;AACxB,aAAO,MAAM,MAAM;AACjB,cAAM,WAAW;AACjB,YACE,SAAS,eAAe,QAAQ,oBAAoB,KAAK,QACzD,SAAS,WAAW,KAAK,GACzB;AACA,oBAAU,KAAK,QAAQ;AAAA,QACzB;AACA,YAAI,OAAO,SAAS;AAAA,MACtB;AAEA,iBAAW,YAAY,WAAW;AAEhC,YAAI,SAAS,eAAe,QAAQ,KAAK,KAAK,KAAM;AAEpD,cAAM,OAAO,SAAS;AACtB,YAAI,CAAC,KAAM;AACX,mBAAW,SAAS,gBAAgB;AAClC,cAAI,KAAK,IAAI,MAAM,CAAC,EAAG;AACvB,cAAI,KAAK,QAAQ,MAAM,CAAC,MAAM,MAAM,KAAK,KAAK,MAAM,MAAM,EAAE,KAAK,GAAG;AAClE,iBAAK,IAAI,MAAM,CAAC;AAChB,kBAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,iBAAK,aAAa,qBAAqB,EAAE;AACzC,iBAAK,aAAa,iBAAiB,OAAO;AAC1C,iBAAK,aAAa,mBAAmB,SAAS;AAC9C,iBAAK,aAAa,qBAAqB,MAAM,CAAC;AAC9C,qBAAS,YAAY,aAAa,MAAM,QAAQ;AAChD,iBAAK,YAAY,QAAQ;AACzB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,qBAAe,UAAU;AAEzB,YAAM,OAAO,aAAa;AAC1B,UAAI,QAAQ,YAAY,SAAS;AAC/B,oBAAY,QAAQ,QAAQ,MAAM;AAAA,UAChC,WAAW;AAAA,UACX,SAAS;AAAA,UACT,eAAe;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,GAAG,CAAC,SAAS,WAAW,gBAAgB,YAAY,CAAC;AAErD,EAAAC,iBAAgB,MAAM;AACpB,qBAAiB;AAEjB,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAGhB,cAAU,aAAa,kBAAkB,EAAE;AAC3C,cAAU,aAAa,iBAAiB,OAAO;AAC/C,cAAU,aAAa,mBAAmB,SAAS;AAInD,UAAM,cAAc,MAAM,eAAe,UAAU,sBAAsB,CAAC;AAC1E,UAAM,cAAc,MAAM,eAAe,IAAI;AAE7C,cAAU,iBAAiB,cAAc,WAAW;AACpD,cAAU,iBAAiB,cAAc,WAAW;AAGpD,gBAAY;AAGZ,UAAM,WAAW,IAAI,iBAAiB,WAAW;AACjD,gBAAY,UAAU;AACtB,aAAS,QAAQ,WAAW;AAAA,MAC1B,WAAW;AAAA,MACX,SAAS;AAAA,MACT,eAAe;AAAA,IACjB,CAAC;AAED,WAAO,MAAM;AACX,eAAS,WAAW;AACpB,kBAAY,UAAU;AACtB,gBAAU,oBAAoB,cAAc,WAAW;AACvD,gBAAU,oBAAoB,cAAc,WAAW;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,aAAa,SAAS,WAAW,YAAY,CAAC;AAElD,SACE,gBAAAF,MAAA,YACE;AAAA,oBAAAD,KAAC,UAAK,KAAK,aAAa,OAAO,EAAE,SAAS,OAAO,GAAG,eAAW,MAAC;AAAA,IAC/D;AAAA,IACA,eAAe,aAAa,gBAAAA,KAAC,gBAAa,WAAW,aAAa,GAAI,SAAS,IAAI;AAAA,IACnF,eACC,aAAa,gBAAAA,KAAC,gBAAa,SAAkB,WAAW,aAAa,GAAI,SAAS,IAAI;AAAA,KAC1F;AAEJ;","names":["useLayoutEffect","useRef","useLayoutEffect","useRef","jsx","jsx","jsxs","useRef","useLayoutEffect"]}
1
+ {"version":3,"sources":["../../lib/client-editable-block.tsx","../../lib/block-outline.tsx","../../lib/block-toolbar.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, 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-cursor {\n position: fixed; width: 22px; height: 22px;\n background: radial-gradient(circle, #fff 5px, #000 5px);\n border-radius: 50%;\n pointer-events: none; z-index: 99999;\n transform: translate(-50%, -50%);\n opacity: 0; transition: opacity 0.1s ease;\n }\n .cms-block-cursor.cms-cursor-visible { opacity: 1; }\n .cms-block-toolbar {\n position: fixed;\n display: flex; gap: 4px; background: #1f2937; border-radius: 6px; padding: 4px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n z-index: 99999; pointer-events: none;\n opacity: 0; transform: scale(0.9) translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .cms-block-toolbar.cms-toolbar-visible {\n opacity: 1; transform: scale(1) translateY(0); 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 cursorElRef = useRef<HTMLDivElement>(null);\n const toolbarElRef = useRef<HTMLDivElement>(null);\n const toolbarVisibleRef = useRef(false);\n const [mounted, setMounted] = useState(false);\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 useEffect(() => {\n setMounted(true);\n }, []);\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 // Position toolbar at (x, y) without animating the placement itself —\n // suppress top/left transitions for the instant snap, then restore.\n const positionToolbar = (x: number, y: number) => {\n const el = toolbarElRef.current;\n if (!el) return;\n const { width: tw, height: th } = el.getBoundingClientRect();\n const top = Math.max(4, y - th - 12);\n const left = Math.max(4, Math.min(x - tw / 2, window.innerWidth - tw - 4));\n el.style.transition = 'opacity 0.15s ease, transform 0.15s ease';\n el.style.top = `${top}px`;\n el.style.left = `${left}px`;\n };\n\n // Cursor circle follows the mouse to indicate the block is interactive.\n const showCursor = (e: MouseEvent) => {\n if (toolbarVisibleRef.current) return;\n setOutlineRect(blockRoot.getBoundingClientRect());\n const el = cursorElRef.current;\n if (!el) return;\n el.style.top = `${e.clientY}px`;\n el.style.left = `${e.clientX}px`;\n el.classList.add('cms-cursor-visible');\n };\n const moveCursor = (e: MouseEvent) => {\n if (toolbarVisibleRef.current) return;\n const el = cursorElRef.current;\n if (!el) return;\n el.style.top = `${e.clientY}px`;\n el.style.left = `${e.clientX}px`;\n };\n const hideCursor = () => {\n cursorElRef.current?.classList.remove('cms-cursor-visible');\n setOutlineRect(null);\n };\n\n // On right-click: first time suppresses the browser context menu and shows\n // the CMS toolbar instead. If the toolbar is already visible the event is\n // not prevented so the browser's native context menu opens as a fallback.\n const handleContextMenu = (e: MouseEvent) => {\n if ((e.target as Element).closest('[data-cms-toolbar]')) return;\n if (toolbarVisibleRef.current) return; // fall through to browser context menu\n e.preventDefault();\n hideCursor();\n positionToolbar(e.clientX, e.clientY);\n toolbarVisibleRef.current = true;\n toolbarElRef.current?.classList.add('cms-toolbar-visible');\n };\n\n // Dismiss toolbar when clicking outside this block or its toolbar.\n const handleClickOutside = (e: MouseEvent) => {\n if (!toolbarVisibleRef.current) return;\n const target = e.target as Element;\n if (target.closest('[data-cms-toolbar]')) return;\n if (blockRoot.contains(target)) return;\n toolbarElRef.current?.classList.remove('cms-toolbar-visible');\n toolbarVisibleRef.current = false;\n };\n\n const hideOnScroll = () => {\n hideCursor();\n toolbarElRef.current?.classList.remove('cms-toolbar-visible');\n toolbarVisibleRef.current = false;\n };\n\n blockRoot.addEventListener('mouseenter', showCursor);\n blockRoot.addEventListener('mousemove', moveCursor);\n blockRoot.addEventListener('mouseleave', hideCursor);\n blockRoot.addEventListener('contextmenu', handleContextMenu);\n document.addEventListener('click', handleClickOutside);\n window.addEventListener('scroll', hideOnScroll, { passive: true, capture: true });\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', showCursor);\n blockRoot.removeEventListener('mousemove', moveCursor);\n blockRoot.removeEventListener('mouseleave', hideCursor);\n blockRoot.removeEventListener('contextmenu', handleContextMenu);\n document.removeEventListener('click', handleClickOutside);\n window.removeEventListener('scroll', hideOnScroll, { capture: true });\n };\n }, [injectSpans, blockId, blockType, getBlockRoot]);\n\n return (\n <>\n <span ref={sentinelRef} style={{ display: 'none' }} aria-hidden />\n {children}\n {mounted &&\n outlineRect &&\n createPortal(<BlockOutline blockRect={outlineRect} />, document.body)}\n {mounted &&\n createPortal(<div ref={cursorElRef} className=\"cms-block-cursor\" />, document.body)}\n {mounted &&\n createPortal(<BlockToolbar ref={toolbarElRef} blockId={blockId} />, 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 { forwardRef } from 'react';\n\n/**\n * Block Toolbar Component\n *\n * Provides move up/down and delete controls for blocks in edit mode.\n * Position is managed imperatively by the parent via a forwarded ref —\n * the toolbar follows the mouse cursor and has no internal layout logic.\n */\n\nexport const BlockToolbar = forwardRef<HTMLDivElement, { blockId: string }>(function BlockToolbar(\n { blockId },\n ref\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={ref} className=\"cms-block-toolbar\" data-cms-toolbar=\"true\">\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","jsx","BlockToolbar","jsx","jsxs","useRef","useLayoutEffect"]}