vite-plugin-visual-selector 0.1.0 → 0.1.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 +235 -661
- package/dist/index.d.ts +6 -2
- package/dist/index.js +900 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime-rRIWQKz-.d.ts +204 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +846 -106
- package/dist/runtime.js.map +1 -1
- package/package.json +7 -5
- package/dist/types-DlgpChn_.d.ts +0 -16
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
// src/plugin.ts
|
|
2
2
|
import { parse } from "@babel/parser";
|
|
3
|
-
import
|
|
3
|
+
import _traverse from "@babel/traverse";
|
|
4
4
|
import * as t from "@babel/types";
|
|
5
5
|
import MagicString from "magic-string";
|
|
6
6
|
import path from "path";
|
|
7
7
|
|
|
8
8
|
// src/shared/constants.ts
|
|
9
9
|
var DEFAULT_ATTRIBUTE_NAME = "data-source-location";
|
|
10
|
+
var CONTROL_MESSAGE_TYPE = "visual-selector:set-active";
|
|
11
|
+
var AGENT_ATTR = "data-vite-plugin-element";
|
|
10
12
|
|
|
11
13
|
// src/plugin.ts
|
|
14
|
+
var traverse = typeof _traverse === "function" ? _traverse : _traverse.default;
|
|
12
15
|
function toRelativeUnixPath(root, filePath) {
|
|
13
16
|
return path.relative(root, filePath).split(path.sep).join("/");
|
|
14
17
|
}
|
|
@@ -28,6 +31,7 @@ function visualSelectorPlugin(options = {}) {
|
|
|
28
31
|
return {
|
|
29
32
|
name: "vite-plugin-visual-selector",
|
|
30
33
|
enforce: "pre",
|
|
34
|
+
// 在其他插件(如 @vitejs/plugin-react)之前运行,确保 JSX 还未被转译
|
|
31
35
|
transform(code, id) {
|
|
32
36
|
if (!include.some((pattern) => pattern.test(id))) {
|
|
33
37
|
return null;
|
|
@@ -35,13 +39,19 @@ function visualSelectorPlugin(options = {}) {
|
|
|
35
39
|
if (exclude.some((pattern) => pattern.test(id))) {
|
|
36
40
|
return null;
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
let ast;
|
|
43
|
+
try {
|
|
44
|
+
ast = parse(code, {
|
|
45
|
+
sourceType: "module",
|
|
46
|
+
plugins: ["jsx", "typescript"]
|
|
47
|
+
});
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
42
51
|
const magicString = new MagicString(code);
|
|
43
52
|
let mutated = false;
|
|
44
53
|
traverse(ast, {
|
|
54
|
+
// 遍历 AST 中所有 JSX 开标签节点
|
|
45
55
|
JSXOpeningElement(jsxPath) {
|
|
46
56
|
const node = jsxPath.node;
|
|
47
57
|
if (!isIntrinsicElement(node)) {
|
|
@@ -59,16 +69,900 @@ function visualSelectorPlugin(options = {}) {
|
|
|
59
69
|
}
|
|
60
70
|
});
|
|
61
71
|
if (!mutated) {
|
|
62
|
-
return
|
|
72
|
+
return null;
|
|
63
73
|
}
|
|
64
74
|
return {
|
|
65
75
|
code: magicString.toString(),
|
|
66
76
|
map: magicString.generateMap({ hires: true })
|
|
77
|
+
// 高精度 sourcemap,保证调试准确
|
|
67
78
|
};
|
|
68
79
|
}
|
|
69
80
|
};
|
|
70
81
|
}
|
|
82
|
+
|
|
83
|
+
// src/runtime/utils.ts
|
|
84
|
+
function debounce(fn, ms) {
|
|
85
|
+
let timer = null;
|
|
86
|
+
return ((...args) => {
|
|
87
|
+
if (timer) clearTimeout(timer);
|
|
88
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function getSourceId(element) {
|
|
92
|
+
return element.dataset?.sourceLocation ?? element.dataset?.visualSelectorId ?? null;
|
|
93
|
+
}
|
|
94
|
+
function findAllElementsById(id) {
|
|
95
|
+
let elements = Array.from(document.querySelectorAll(`[data-source-location="${id}"]`));
|
|
96
|
+
if (elements.length === 0) {
|
|
97
|
+
elements = Array.from(document.querySelectorAll(`[data-visual-selector-id="${id}"]`));
|
|
98
|
+
}
|
|
99
|
+
return elements;
|
|
100
|
+
}
|
|
101
|
+
function hasSourceLocation(el) {
|
|
102
|
+
return el.hasAttribute("data-source-location") || el.hasAttribute("data-visual-selector-id");
|
|
103
|
+
}
|
|
104
|
+
function getElementPosition(element) {
|
|
105
|
+
const rect = element.getBoundingClientRect();
|
|
106
|
+
return {
|
|
107
|
+
top: rect.top,
|
|
108
|
+
left: rect.left,
|
|
109
|
+
right: rect.right,
|
|
110
|
+
bottom: rect.bottom,
|
|
111
|
+
width: rect.width,
|
|
112
|
+
height: rect.height,
|
|
113
|
+
centerX: rect.left + rect.width / 2,
|
|
114
|
+
centerY: rect.top + rect.height / 2
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
var TEXT_TAGS = ["P", "H1", "H2", "H3", "H4", "H5", "H6", "SPAN", "A", "LABEL"];
|
|
118
|
+
var INLINE_EDIT_TAGS = [
|
|
119
|
+
"div",
|
|
120
|
+
"p",
|
|
121
|
+
"h1",
|
|
122
|
+
"h2",
|
|
123
|
+
"h3",
|
|
124
|
+
"h4",
|
|
125
|
+
"h5",
|
|
126
|
+
"h6",
|
|
127
|
+
"span",
|
|
128
|
+
"li",
|
|
129
|
+
"td",
|
|
130
|
+
"a",
|
|
131
|
+
"button",
|
|
132
|
+
"label"
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
// src/runtime/inline-edit.ts
|
|
136
|
+
function canInlineEdit(element) {
|
|
137
|
+
return INLINE_EDIT_TAGS.includes(element.tagName.toLowerCase()) && !!element.textContent?.trim() && !element.querySelector("img, video, canvas, svg") && element.children.length === 0;
|
|
138
|
+
}
|
|
139
|
+
function startInlineEditing(state, element) {
|
|
140
|
+
if (!canInlineEdit(element)) return;
|
|
141
|
+
state.editingElement = element;
|
|
142
|
+
element.dataset.originalTextContent = element.textContent || "";
|
|
143
|
+
element.contentEditable = "true";
|
|
144
|
+
element.setAttribute(AGENT_ATTR, "");
|
|
145
|
+
element.style.cursor = "text";
|
|
146
|
+
const range = document.createRange();
|
|
147
|
+
range.selectNodeContents(element);
|
|
148
|
+
const sel = window.getSelection();
|
|
149
|
+
sel?.removeAllRanges();
|
|
150
|
+
sel?.addRange(range);
|
|
151
|
+
element.focus();
|
|
152
|
+
state.selectionOverlays.forEach((o) => o.style.display = "none");
|
|
153
|
+
const debouncedReport = debounce(() => {
|
|
154
|
+
try {
|
|
155
|
+
window.parent.postMessage(
|
|
156
|
+
{
|
|
157
|
+
type: "inline-edit",
|
|
158
|
+
elementInfo: {
|
|
159
|
+
tagName: element.tagName,
|
|
160
|
+
visualSelectorId: state.selectedId,
|
|
161
|
+
dataSourceLocation: element.dataset?.sourceLocation
|
|
162
|
+
},
|
|
163
|
+
originalContent: element.dataset.originalTextContent || "",
|
|
164
|
+
newContent: element.textContent || ""
|
|
165
|
+
},
|
|
166
|
+
state.targetOrigin
|
|
167
|
+
);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}, 500);
|
|
171
|
+
element.addEventListener("input", debouncedReport);
|
|
172
|
+
element.__inlineEditHandler = debouncedReport;
|
|
173
|
+
try {
|
|
174
|
+
window.parent.postMessage(
|
|
175
|
+
{
|
|
176
|
+
type: "content-editing-started",
|
|
177
|
+
visualSelectorId: state.selectedId
|
|
178
|
+
},
|
|
179
|
+
state.targetOrigin
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function stopInlineEditing(state, updatePositions) {
|
|
185
|
+
if (!state.editingElement) return;
|
|
186
|
+
state.editingElement.contentEditable = "false";
|
|
187
|
+
state.editingElement.removeAttribute(AGENT_ATTR);
|
|
188
|
+
state.editingElement.style.cursor = "";
|
|
189
|
+
const handler = state.editingElement.__inlineEditHandler;
|
|
190
|
+
if (handler) {
|
|
191
|
+
state.editingElement.removeEventListener("input", handler);
|
|
192
|
+
delete state.editingElement.__inlineEditHandler;
|
|
193
|
+
}
|
|
194
|
+
state.selectionOverlays.forEach((o) => o.style.display = "");
|
|
195
|
+
updatePositions();
|
|
196
|
+
try {
|
|
197
|
+
window.parent.postMessage(
|
|
198
|
+
{
|
|
199
|
+
type: "content-editing-ended",
|
|
200
|
+
visualSelectorId: state.selectedId
|
|
201
|
+
},
|
|
202
|
+
state.targetOrigin
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
state.editingElement = null;
|
|
207
|
+
}
|
|
208
|
+
function handleInlineEdit(state, data, updatePositions) {
|
|
209
|
+
if (data.inlineEditingMode) {
|
|
210
|
+
const el = document.querySelector(
|
|
211
|
+
`[data-source-location="${data.dataSourceLocation}"]`
|
|
212
|
+
);
|
|
213
|
+
if (el) {
|
|
214
|
+
startInlineEditing(state, el);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
stopInlineEditing(state, updatePositions);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/runtime/layer-navigation.ts
|
|
222
|
+
function buildLayerTree(element) {
|
|
223
|
+
const tree = [];
|
|
224
|
+
const ancestors = [];
|
|
225
|
+
let parent = element.parentElement;
|
|
226
|
+
while (parent && parent !== document.body) {
|
|
227
|
+
if (hasSourceLocation(parent)) {
|
|
228
|
+
ancestors.push(parent);
|
|
229
|
+
}
|
|
230
|
+
parent = parent.parentElement;
|
|
231
|
+
}
|
|
232
|
+
ancestors.reverse();
|
|
233
|
+
ancestors.forEach((el) => {
|
|
234
|
+
tree.push({ element: el, tagName: el.tagName.toLowerCase(), depth: 0 });
|
|
235
|
+
});
|
|
236
|
+
tree.push({ element, tagName: element.tagName.toLowerCase(), depth: 0 });
|
|
237
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
238
|
+
const child = element.children[i];
|
|
239
|
+
if (hasSourceLocation(child)) {
|
|
240
|
+
tree.push({ element: child, tagName: child.tagName.toLowerCase(), depth: 0 });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return tree;
|
|
244
|
+
}
|
|
245
|
+
function removeLayerDropdown(state) {
|
|
246
|
+
if (state.layerDropdown) {
|
|
247
|
+
const parentOverlay = state.layerDropdown.parentElement;
|
|
248
|
+
if (parentOverlay) {
|
|
249
|
+
const arrowEl = parentOverlay.querySelector("[data-tag-arrow]");
|
|
250
|
+
if (arrowEl) arrowEl.textContent = "\u2304";
|
|
251
|
+
}
|
|
252
|
+
state.layerDropdown.remove();
|
|
253
|
+
state.layerDropdown = null;
|
|
254
|
+
document.removeEventListener("keydown", handleLayerKeyboard);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function toggleLayerDropdown(state, element, anchor, onSelectElement) {
|
|
258
|
+
if (state.layerDropdown) {
|
|
259
|
+
removeLayerDropdown(state);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const layers = buildLayerTree(element);
|
|
263
|
+
renderLayerDropdown(state, anchor, layers, element, onSelectElement);
|
|
264
|
+
}
|
|
265
|
+
function renderLayerDropdown(state, anchor, layers, currentElement, onSelectElement) {
|
|
266
|
+
const dropdown = document.createElement("div");
|
|
267
|
+
dropdown.setAttribute("data-layer-dropdown", "true");
|
|
268
|
+
dropdown.setAttribute(AGENT_ATTR, "");
|
|
269
|
+
Object.assign(dropdown.style, {
|
|
270
|
+
position: "absolute",
|
|
271
|
+
backgroundColor: "#ffffff",
|
|
272
|
+
border: "1px solid #e2e8f0",
|
|
273
|
+
borderRadius: "6px",
|
|
274
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
275
|
+
fontSize: "12px",
|
|
276
|
+
zIndex: "99999",
|
|
277
|
+
pointerEvents: "auto",
|
|
278
|
+
padding: "4px 0",
|
|
279
|
+
minWidth: "120px",
|
|
280
|
+
maxHeight: "320px",
|
|
281
|
+
overflowY: "auto"
|
|
282
|
+
});
|
|
283
|
+
let focusedIndex = -1;
|
|
284
|
+
const items = [];
|
|
285
|
+
layers.forEach((layer, idx) => {
|
|
286
|
+
const item = document.createElement("div");
|
|
287
|
+
item.setAttribute(AGENT_ATTR, "");
|
|
288
|
+
item.textContent = layer.tagName;
|
|
289
|
+
item.style.padding = "6px 16px";
|
|
290
|
+
item.style.cursor = "pointer";
|
|
291
|
+
item.style.whiteSpace = "nowrap";
|
|
292
|
+
if (layer.element === currentElement) {
|
|
293
|
+
item.style.color = "#000000";
|
|
294
|
+
item.style.backgroundColor = "rgba(255, 204, 0, 0.2)";
|
|
295
|
+
item.style.fontWeight = "600";
|
|
296
|
+
focusedIndex = idx;
|
|
297
|
+
} else {
|
|
298
|
+
item.style.color = "#64748b";
|
|
299
|
+
}
|
|
300
|
+
item.addEventListener("click", (e) => {
|
|
301
|
+
e.stopPropagation();
|
|
302
|
+
removeLayerDropdown(state);
|
|
303
|
+
onSelectElement(layer.element);
|
|
304
|
+
});
|
|
305
|
+
item.addEventListener("mouseenter", () => {
|
|
306
|
+
if (layer.element !== currentElement) {
|
|
307
|
+
item.style.backgroundColor = "#f1f5f9";
|
|
308
|
+
item.style.color = "#334155";
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
item.addEventListener("mouseleave", () => {
|
|
312
|
+
if (layer.element !== currentElement) {
|
|
313
|
+
item.style.backgroundColor = "transparent";
|
|
314
|
+
item.style.color = "#64748b";
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
items.push(item);
|
|
318
|
+
dropdown.appendChild(item);
|
|
319
|
+
});
|
|
320
|
+
document.body.appendChild(dropdown);
|
|
321
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
322
|
+
dropdown.style.top = `${anchorRect.bottom + window.scrollY + 2}px`;
|
|
323
|
+
dropdown.style.left = `${anchorRect.left + window.scrollX}px`;
|
|
324
|
+
requestAnimationFrame(() => {
|
|
325
|
+
const ddRect = dropdown.getBoundingClientRect();
|
|
326
|
+
if (ddRect.right > window.innerWidth - 4) {
|
|
327
|
+
dropdown.style.left = `${window.innerWidth - ddRect.width - 4 + window.scrollX}px`;
|
|
328
|
+
}
|
|
329
|
+
if (ddRect.left < 4) {
|
|
330
|
+
dropdown.style.left = `${4 + window.scrollX}px`;
|
|
331
|
+
}
|
|
332
|
+
if (ddRect.bottom > window.innerHeight) {
|
|
333
|
+
dropdown.style.top = `${anchorRect.top + window.scrollY - ddRect.height - 2}px`;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
state.layerDropdown = dropdown;
|
|
337
|
+
const handleKeydown = (e) => {
|
|
338
|
+
if (!state.layerDropdown) return;
|
|
339
|
+
switch (e.key) {
|
|
340
|
+
case "ArrowDown":
|
|
341
|
+
e.preventDefault();
|
|
342
|
+
focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
|
|
343
|
+
highlightItem(items, focusedIndex, layers, currentElement);
|
|
344
|
+
break;
|
|
345
|
+
case "ArrowUp":
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
focusedIndex = Math.max(focusedIndex - 1, 0);
|
|
348
|
+
highlightItem(items, focusedIndex, layers, currentElement);
|
|
349
|
+
break;
|
|
350
|
+
case "Enter":
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
if (focusedIndex >= 0 && focusedIndex < layers.length) {
|
|
353
|
+
removeLayerDropdown(state);
|
|
354
|
+
onSelectElement(layers[focusedIndex].element);
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
case "Escape":
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
removeLayerDropdown(state);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
handleLayerKeyboard.current = handleKeydown;
|
|
364
|
+
document.addEventListener("keydown", handleLayerKeyboard);
|
|
365
|
+
}
|
|
366
|
+
function handleLayerKeyboard(e) {
|
|
367
|
+
const current = handleLayerKeyboard.current;
|
|
368
|
+
if (current) current(e);
|
|
369
|
+
}
|
|
370
|
+
function highlightItem(items, index, layers, currentElement) {
|
|
371
|
+
items.forEach((item, i) => {
|
|
372
|
+
if (layers[i].element === currentElement) {
|
|
373
|
+
item.style.color = "#000000";
|
|
374
|
+
item.style.backgroundColor = i === index ? "rgba(255, 204, 0, 0.35)" : "rgba(255, 204, 0, 0.2)";
|
|
375
|
+
item.style.fontWeight = "600";
|
|
376
|
+
} else {
|
|
377
|
+
item.style.backgroundColor = i === index ? "#f1f5f9" : "transparent";
|
|
378
|
+
item.style.color = i === index ? "#334155" : "#64748b";
|
|
379
|
+
item.style.fontWeight = "normal";
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/runtime/messages.ts
|
|
385
|
+
function reportElementSelected(state, element) {
|
|
386
|
+
const el = element;
|
|
387
|
+
const tagName = element.tagName;
|
|
388
|
+
const isTextElement = TEXT_TAGS.includes(tagName);
|
|
389
|
+
const computed = window.getComputedStyle(el);
|
|
390
|
+
const computedStyles = {
|
|
391
|
+
fontFamily: computed.fontFamily,
|
|
392
|
+
fontSize: computed.fontSize,
|
|
393
|
+
fontWeight: computed.fontWeight,
|
|
394
|
+
textAlign: computed.textAlign,
|
|
395
|
+
textDecoration: computed.textDecoration,
|
|
396
|
+
textDecorationLine: computed.textDecorationLine,
|
|
397
|
+
color: computed.color,
|
|
398
|
+
backgroundColor: computed.backgroundColor,
|
|
399
|
+
lineHeight: computed.lineHeight,
|
|
400
|
+
letterSpacing: computed.letterSpacing,
|
|
401
|
+
opacity: computed.opacity
|
|
402
|
+
};
|
|
403
|
+
try {
|
|
404
|
+
window.parent.postMessage(
|
|
405
|
+
{
|
|
406
|
+
type: "element-selected",
|
|
407
|
+
tagName,
|
|
408
|
+
classes: typeof el.className === "string" ? el.className : "",
|
|
409
|
+
visualSelectorId: getSourceId(element),
|
|
410
|
+
content: isTextElement ? el.innerText : void 0,
|
|
411
|
+
dataSourceLocation: el.dataset?.sourceLocation,
|
|
412
|
+
isDynamicContent: el.dataset?.dynamicContent === "true",
|
|
413
|
+
position: getElementPosition(element),
|
|
414
|
+
attributes: {},
|
|
415
|
+
isTextElement,
|
|
416
|
+
staticArrayName: null,
|
|
417
|
+
collectionId: null,
|
|
418
|
+
computedStyles
|
|
419
|
+
},
|
|
420
|
+
state.targetOrigin
|
|
421
|
+
);
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function reportPositionUpdate(state) {
|
|
426
|
+
if (!state.selectedId || !state.selectedElement?.isConnected) return;
|
|
427
|
+
const rect = state.selectedElement.getBoundingClientRect();
|
|
428
|
+
const isInViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
429
|
+
try {
|
|
430
|
+
window.parent.postMessage(
|
|
431
|
+
{
|
|
432
|
+
type: "element-position-update",
|
|
433
|
+
position: getElementPosition(state.selectedElement),
|
|
434
|
+
isInViewport,
|
|
435
|
+
visualSelectorId: state.selectedId
|
|
436
|
+
},
|
|
437
|
+
state.targetOrigin
|
|
438
|
+
);
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function handleMessage(state, e, callbacks) {
|
|
443
|
+
if (state.targetOrigin !== "*" && e.origin !== state.targetOrigin) return;
|
|
444
|
+
const msg = e.data;
|
|
445
|
+
if (!msg || typeof msg !== "object" || !msg.type) return;
|
|
446
|
+
switch (msg.type) {
|
|
447
|
+
case "toggle-visual-edit-mode":
|
|
448
|
+
callbacks.enableEditMode(msg.data?.enabled ?? false, msg.data?.specs);
|
|
449
|
+
break;
|
|
450
|
+
case "update-classes":
|
|
451
|
+
if (msg.data?.visualSelectorId && typeof msg.data?.classes === "string") {
|
|
452
|
+
findAllElementsById(msg.data.visualSelectorId).forEach(
|
|
453
|
+
(el) => el.setAttribute("class", msg.data.classes)
|
|
454
|
+
);
|
|
455
|
+
setTimeout(callbacks.updateAllOverlayPositions, 50);
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
case "update-attribute":
|
|
459
|
+
if (msg.data?.visualSelectorId && msg.data?.attribute && msg.data?.value !== void 0) {
|
|
460
|
+
findAllElementsById(msg.data.visualSelectorId).forEach(
|
|
461
|
+
(el) => el.setAttribute(msg.data.attribute, msg.data.value)
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
465
|
+
case "update-content":
|
|
466
|
+
if (msg.data?.visualSelectorId && msg.data?.content !== void 0) {
|
|
467
|
+
findAllElementsById(msg.data.visualSelectorId).forEach((el) => {
|
|
468
|
+
el.innerText = msg.data.content;
|
|
469
|
+
});
|
|
470
|
+
setTimeout(callbacks.updateAllOverlayPositions, 50);
|
|
471
|
+
}
|
|
472
|
+
break;
|
|
473
|
+
case "unselect-element":
|
|
474
|
+
if (state.editingElement) callbacks.stopInlineEditing();
|
|
475
|
+
state.selectionOverlays.forEach((o) => o.remove());
|
|
476
|
+
state.selectionOverlays = [];
|
|
477
|
+
state.selectedId = null;
|
|
478
|
+
state.selectedElement = null;
|
|
479
|
+
break;
|
|
480
|
+
case "update-theme-variables":
|
|
481
|
+
if (msg.data?.variables) {
|
|
482
|
+
const root = msg.data.mode === "dark" ? document.querySelector(".dark") : document.documentElement;
|
|
483
|
+
if (root) {
|
|
484
|
+
for (const [name, value] of Object.entries(msg.data.variables)) {
|
|
485
|
+
root.style.setProperty(name, value);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
case "toggle-inline-edit-mode":
|
|
491
|
+
if (msg.data) {
|
|
492
|
+
callbacks.handleInlineEdit(msg.data);
|
|
493
|
+
}
|
|
494
|
+
break;
|
|
495
|
+
case "inject-font-import":
|
|
496
|
+
if (msg.data?.fontUrl && /^https?:\/\//.test(msg.data.fontUrl)) {
|
|
497
|
+
const link = document.createElement("link");
|
|
498
|
+
link.rel = "stylesheet";
|
|
499
|
+
link.href = msg.data.fontUrl;
|
|
500
|
+
link.setAttribute(AGENT_ATTR, "");
|
|
501
|
+
document.head.appendChild(link);
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
case "popover-drag-state":
|
|
505
|
+
case "dropdown-state":
|
|
506
|
+
break;
|
|
507
|
+
case "refresh-page":
|
|
508
|
+
window.location.reload();
|
|
509
|
+
break;
|
|
510
|
+
case "request-element-position":
|
|
511
|
+
reportPositionUpdate(state);
|
|
512
|
+
break;
|
|
513
|
+
case CONTROL_MESSAGE_TYPE: {
|
|
514
|
+
const controlMsg = msg;
|
|
515
|
+
if (typeof controlMsg.active === "boolean") {
|
|
516
|
+
callbacks.enableEditMode(controlMsg.active);
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
case "update-element-style":
|
|
521
|
+
case "update-element-text":
|
|
522
|
+
case "update-element-class":
|
|
523
|
+
handleLegacyUpdateMessage(state, msg);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function handleLegacyUpdateMessage(state, msg) {
|
|
528
|
+
if (!msg.dataSourceLocation) return;
|
|
529
|
+
const el = document.querySelector(
|
|
530
|
+
`[${state.attributeName}="${msg.dataSourceLocation}"]`
|
|
531
|
+
);
|
|
532
|
+
if (!el) return;
|
|
533
|
+
if (msg.type === "update-element-style" && msg.styles) {
|
|
534
|
+
for (const [prop, value] of Object.entries(msg.styles)) {
|
|
535
|
+
el.style.setProperty(
|
|
536
|
+
prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
|
|
537
|
+
value
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
} else if (msg.type === "update-element-text" && typeof msg.text === "string") {
|
|
541
|
+
el.textContent = msg.text;
|
|
542
|
+
} else if (msg.type === "update-element-class" && typeof msg.className === "string") {
|
|
543
|
+
el.className = msg.className;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/runtime/overlay.ts
|
|
548
|
+
function createOverlay(opts) {
|
|
549
|
+
const overlay = document.createElement("div");
|
|
550
|
+
overlay.setAttribute(AGENT_ATTR, "");
|
|
551
|
+
overlay.style.position = "absolute";
|
|
552
|
+
overlay.style.pointerEvents = "none";
|
|
553
|
+
overlay.style.zIndex = "9999";
|
|
554
|
+
overlay.style.boxSizing = "border-box";
|
|
555
|
+
if (opts.selected) {
|
|
556
|
+
overlay.style.boxShadow = "inset 0 0 0 2px #FC0";
|
|
557
|
+
} else {
|
|
558
|
+
overlay.style.boxShadow = "inset 0 0 0 2px rgba(255, 204, 0, 0.5)";
|
|
559
|
+
overlay.style.backgroundColor = "rgba(255, 204, 0, 0.05)";
|
|
560
|
+
}
|
|
561
|
+
return overlay;
|
|
562
|
+
}
|
|
563
|
+
function positionOverlay(overlay, target, tagMode = "none", callbacks) {
|
|
564
|
+
const rect = target.getBoundingClientRect();
|
|
565
|
+
overlay.style.top = `${rect.top + window.scrollY}px`;
|
|
566
|
+
overlay.style.left = `${rect.left + window.scrollX}px`;
|
|
567
|
+
overlay.style.width = `${rect.width}px`;
|
|
568
|
+
overlay.style.height = `${rect.height}px`;
|
|
569
|
+
if (tagMode !== "none") {
|
|
570
|
+
let tag = overlay.querySelector("[data-tag-label]");
|
|
571
|
+
if (!tag) {
|
|
572
|
+
tag = document.createElement("div");
|
|
573
|
+
tag.setAttribute(AGENT_ATTR, "");
|
|
574
|
+
tag.setAttribute("data-tag-label", "");
|
|
575
|
+
Object.assign(tag.style, {
|
|
576
|
+
position: "absolute",
|
|
577
|
+
padding: "2px 8px",
|
|
578
|
+
fontSize: "11px",
|
|
579
|
+
fontWeight: "500",
|
|
580
|
+
borderRadius: "3px",
|
|
581
|
+
minWidth: "24px",
|
|
582
|
+
textAlign: "center",
|
|
583
|
+
display: "flex",
|
|
584
|
+
alignItems: "center",
|
|
585
|
+
gap: "3px",
|
|
586
|
+
lineHeight: "1.4"
|
|
587
|
+
});
|
|
588
|
+
if (tagMode === "selected") {
|
|
589
|
+
tag.style.backgroundColor = "#FC0";
|
|
590
|
+
tag.style.color = "#000000";
|
|
591
|
+
tag.style.cursor = "pointer";
|
|
592
|
+
tag.style.pointerEvents = "auto";
|
|
593
|
+
const textSpan = document.createElement("span");
|
|
594
|
+
textSpan.textContent = target.tagName.toLowerCase();
|
|
595
|
+
tag.appendChild(textSpan);
|
|
596
|
+
const arrow = document.createElement("span");
|
|
597
|
+
arrow.setAttribute("data-tag-arrow", "");
|
|
598
|
+
arrow.textContent = "\u2304";
|
|
599
|
+
arrow.style.fontSize = "10px";
|
|
600
|
+
arrow.style.lineHeight = "1";
|
|
601
|
+
tag.appendChild(arrow);
|
|
602
|
+
if (callbacks?.onTagClick) {
|
|
603
|
+
const onTagClick = callbacks.onTagClick;
|
|
604
|
+
tag.addEventListener("click", (e) => {
|
|
605
|
+
e.stopPropagation();
|
|
606
|
+
onTagClick(target, tag);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
tag.style.backgroundColor = "rgba(255, 204, 0, 0.7)";
|
|
611
|
+
tag.style.color = "#000000";
|
|
612
|
+
tag.style.pointerEvents = "none";
|
|
613
|
+
tag.textContent = target.tagName.toLowerCase();
|
|
614
|
+
}
|
|
615
|
+
overlay.appendChild(tag);
|
|
616
|
+
}
|
|
617
|
+
positionTag(tag, rect);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function positionTag(tag, rect) {
|
|
621
|
+
const isNearTop = rect.top < 27;
|
|
622
|
+
const isTallEnough = rect.height >= 54;
|
|
623
|
+
const isFullWidth = rect.width >= window.innerWidth - 4;
|
|
624
|
+
const leftOffset = isFullWidth ? 8 : 4;
|
|
625
|
+
if (isNearTop && isTallEnough) {
|
|
626
|
+
tag.style.top = "2px";
|
|
627
|
+
} else if (isNearTop) {
|
|
628
|
+
tag.style.top = `${rect.height + 2}px`;
|
|
629
|
+
} else {
|
|
630
|
+
tag.style.top = "-27px";
|
|
631
|
+
}
|
|
632
|
+
tag.style.left = `${leftOffset}px`;
|
|
633
|
+
requestAnimationFrame(() => {
|
|
634
|
+
const tagRect = tag.getBoundingClientRect();
|
|
635
|
+
if (tagRect.right > window.innerWidth - 4) {
|
|
636
|
+
const shift = tagRect.right - window.innerWidth + 8;
|
|
637
|
+
tag.style.left = `${leftOffset - shift}px`;
|
|
638
|
+
}
|
|
639
|
+
if (tagRect.left < 4) {
|
|
640
|
+
const shift = 4 - tagRect.left;
|
|
641
|
+
tag.style.left = `${leftOffset + shift}px`;
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
function clearHoverOverlays(state) {
|
|
646
|
+
state.hoverOverlays.forEach((o) => o.remove());
|
|
647
|
+
state.hoverOverlays = [];
|
|
648
|
+
state.currentHoverId = null;
|
|
649
|
+
}
|
|
650
|
+
function clearSelectionOverlays(state, removeDropdown) {
|
|
651
|
+
state.selectionOverlays.forEach((o) => o.remove());
|
|
652
|
+
state.selectionOverlays = [];
|
|
653
|
+
removeDropdown();
|
|
654
|
+
}
|
|
655
|
+
function clearAllOverlays(state, removeDropdown) {
|
|
656
|
+
clearHoverOverlays(state);
|
|
657
|
+
clearSelectionOverlays(state, removeDropdown);
|
|
658
|
+
state.selectedId = null;
|
|
659
|
+
state.selectedElement = null;
|
|
660
|
+
}
|
|
661
|
+
function freezeAnimations() {
|
|
662
|
+
document.documentElement.setAttribute("data-visual-edit-active", "");
|
|
663
|
+
if (!document.getElementById("freeze-animations")) {
|
|
664
|
+
const style = document.createElement("style");
|
|
665
|
+
style.id = "freeze-animations";
|
|
666
|
+
style.setAttribute(AGENT_ATTR, "");
|
|
667
|
+
style.textContent = `
|
|
668
|
+
[data-visual-edit-active] *:not([${AGENT_ATTR}]):not([${AGENT_ATTR}] *),
|
|
669
|
+
[data-visual-edit-active] *:not([${AGENT_ATTR}]):not([${AGENT_ATTR}] *)::before,
|
|
670
|
+
[data-visual-edit-active] *:not([${AGENT_ATTR}]):not([${AGENT_ATTR}] *)::after {
|
|
671
|
+
animation-play-state: paused !important;
|
|
672
|
+
transition: none !important;
|
|
673
|
+
}
|
|
674
|
+
`;
|
|
675
|
+
document.head.appendChild(style);
|
|
676
|
+
}
|
|
677
|
+
if (!document.getElementById("freeze-overflow")) {
|
|
678
|
+
const overflowStyle = document.createElement("style");
|
|
679
|
+
overflowStyle.id = "freeze-overflow";
|
|
680
|
+
overflowStyle.setAttribute(AGENT_ATTR, "");
|
|
681
|
+
overflowStyle.textContent = `
|
|
682
|
+
html[data-visual-edit-active] {
|
|
683
|
+
overflow-y: scroll !important;
|
|
684
|
+
overflow-x: hidden !important;
|
|
685
|
+
}
|
|
686
|
+
`;
|
|
687
|
+
document.head.appendChild(overflowStyle);
|
|
688
|
+
}
|
|
689
|
+
if (!document.getElementById("freeze-pointer-events")) {
|
|
690
|
+
const pointerStyle = document.createElement("style");
|
|
691
|
+
pointerStyle.id = "freeze-pointer-events";
|
|
692
|
+
pointerStyle.setAttribute(AGENT_ATTR, "");
|
|
693
|
+
pointerStyle.textContent = `
|
|
694
|
+
[data-visual-edit-active] * { pointer-events: none !important; }
|
|
695
|
+
[${AGENT_ATTR}], [${AGENT_ATTR}] * { pointer-events: auto !important; }
|
|
696
|
+
`;
|
|
697
|
+
document.head.appendChild(pointerStyle);
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
document.getAnimations().forEach((anim) => {
|
|
701
|
+
const effect = anim.effect;
|
|
702
|
+
const target = effect?.target;
|
|
703
|
+
if (target instanceof Element && target.closest(`[${AGENT_ATTR}]`)) return;
|
|
704
|
+
try {
|
|
705
|
+
anim.finish();
|
|
706
|
+
} catch {
|
|
707
|
+
anim.pause();
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function unfreezeAnimations() {
|
|
714
|
+
document.documentElement.removeAttribute("data-visual-edit-active");
|
|
715
|
+
const freezeStyle = document.getElementById("freeze-animations");
|
|
716
|
+
if (freezeStyle) freezeStyle.remove();
|
|
717
|
+
const pointerStyle = document.getElementById("freeze-pointer-events");
|
|
718
|
+
if (pointerStyle) pointerStyle.remove();
|
|
719
|
+
const overflowStyle = document.getElementById("freeze-overflow");
|
|
720
|
+
if (overflowStyle) overflowStyle.remove();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/runtime/state.ts
|
|
724
|
+
function createAgentState(options = {}) {
|
|
725
|
+
return {
|
|
726
|
+
editModeEnabled: false,
|
|
727
|
+
selectedId: null,
|
|
728
|
+
selectedElement: null,
|
|
729
|
+
hoverOverlays: [],
|
|
730
|
+
selectionOverlays: [],
|
|
731
|
+
currentHoverId: null,
|
|
732
|
+
editingElement: null,
|
|
733
|
+
layerDropdown: null,
|
|
734
|
+
mutationObserver: null,
|
|
735
|
+
attributeName: options.attributeName ?? DEFAULT_ATTRIBUTE_NAME,
|
|
736
|
+
targetOrigin: options.targetOrigin ?? "*"
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/runtime/index.ts
|
|
741
|
+
function setupVisualEditAgent(options = {}) {
|
|
742
|
+
const state = createAgentState(options);
|
|
743
|
+
function updateAllOverlayPositions() {
|
|
744
|
+
if (state.selectedId && state.selectedElement?.isConnected) {
|
|
745
|
+
const siblings = findAllElementsById(state.selectedId);
|
|
746
|
+
state.selectionOverlays.forEach((overlay, i) => {
|
|
747
|
+
if (siblings[i]) {
|
|
748
|
+
positionOverlay(overlay, siblings[i], i === 0 ? "selected" : "none", {
|
|
749
|
+
onTagClick
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
reportPositionUpdate(state);
|
|
755
|
+
}
|
|
756
|
+
function onTagClick(target, tag) {
|
|
757
|
+
const arrowEl = tag.querySelector("[data-tag-arrow]");
|
|
758
|
+
if (arrowEl) {
|
|
759
|
+
arrowEl.textContent = state.layerDropdown ? "\u2304" : "\u2303";
|
|
760
|
+
}
|
|
761
|
+
toggleLayerDropdown(state, target, tag, selectElement);
|
|
762
|
+
}
|
|
763
|
+
function findElementAtPoint(x, y) {
|
|
764
|
+
const freezeStyle = document.getElementById("freeze-pointer-events");
|
|
765
|
+
if (freezeStyle) freezeStyle.disabled = true;
|
|
766
|
+
const element = document.elementFromPoint(x, y);
|
|
767
|
+
if (freezeStyle) freezeStyle.disabled = false;
|
|
768
|
+
if (!element) return null;
|
|
769
|
+
if (element.closest(`[${AGENT_ATTR}]`)) return null;
|
|
770
|
+
return element.closest(`[${state.attributeName}], [data-visual-selector-id]`) ?? null;
|
|
771
|
+
}
|
|
772
|
+
function findHoverTarget(x, y, excludeId) {
|
|
773
|
+
const element = findElementAtPoint(x, y);
|
|
774
|
+
if (!element) return null;
|
|
775
|
+
const id = getSourceId(element);
|
|
776
|
+
if (!id || id === excludeId) return null;
|
|
777
|
+
return id;
|
|
778
|
+
}
|
|
779
|
+
function onMouseMove(e) {
|
|
780
|
+
if (!state.editModeEnabled) return;
|
|
781
|
+
requestAnimationFrame(() => {
|
|
782
|
+
const id = findHoverTarget(e.clientX, e.clientY, state.selectedId);
|
|
783
|
+
if (!id) {
|
|
784
|
+
clearHoverOverlays(state);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (id === state.currentHoverId) return;
|
|
788
|
+
clearHoverOverlays(state);
|
|
789
|
+
const elements = findAllElementsById(id);
|
|
790
|
+
elements.forEach((el, i) => {
|
|
791
|
+
const overlay = createOverlay({ selected: false });
|
|
792
|
+
overlay.setAttribute(AGENT_ATTR, "");
|
|
793
|
+
document.body.appendChild(overlay);
|
|
794
|
+
positionOverlay(overlay, el, i === 0 ? "hover" : "none");
|
|
795
|
+
state.hoverOverlays.push(overlay);
|
|
796
|
+
});
|
|
797
|
+
state.currentHoverId = id;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function onMouseLeave() {
|
|
801
|
+
clearHoverOverlays(state);
|
|
802
|
+
}
|
|
803
|
+
function onClick(e) {
|
|
804
|
+
if (!state.editModeEnabled) return;
|
|
805
|
+
const target = e.target;
|
|
806
|
+
if (target?.closest?.(`[data-layer-dropdown]`)) return;
|
|
807
|
+
if (target?.closest?.(`[data-tag-label]`) && state.selectionOverlays.some((o) => o.contains(target)))
|
|
808
|
+
return;
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
e.stopPropagation();
|
|
811
|
+
e.stopImmediatePropagation();
|
|
812
|
+
clearHoverOverlays(state);
|
|
813
|
+
const element = findElementAtPoint(e.clientX, e.clientY);
|
|
814
|
+
if (!element) return;
|
|
815
|
+
selectElement(element);
|
|
816
|
+
}
|
|
817
|
+
function selectElement(element) {
|
|
818
|
+
const id = getSourceId(element);
|
|
819
|
+
if (!id) return;
|
|
820
|
+
if (state.editingElement) {
|
|
821
|
+
stopInlineEditing(state, updateAllOverlayPositions);
|
|
822
|
+
}
|
|
823
|
+
clearSelectionOverlays(state, () => removeLayerDropdown(state));
|
|
824
|
+
const siblings = findAllElementsById(id);
|
|
825
|
+
siblings.forEach((el, i) => {
|
|
826
|
+
const overlay = createOverlay({ selected: true });
|
|
827
|
+
overlay.setAttribute(AGENT_ATTR, "");
|
|
828
|
+
document.body.appendChild(overlay);
|
|
829
|
+
positionOverlay(overlay, el, i === 0 ? "selected" : "none", {
|
|
830
|
+
onTagClick
|
|
831
|
+
});
|
|
832
|
+
state.selectionOverlays.push(overlay);
|
|
833
|
+
});
|
|
834
|
+
state.selectedId = id;
|
|
835
|
+
state.selectedElement = element;
|
|
836
|
+
clearHoverOverlays(state);
|
|
837
|
+
reportElementSelected(state, element);
|
|
838
|
+
}
|
|
839
|
+
let rafPending = false;
|
|
840
|
+
const throttledPositionUpdate = () => {
|
|
841
|
+
if (rafPending) return;
|
|
842
|
+
rafPending = true;
|
|
843
|
+
requestAnimationFrame(() => {
|
|
844
|
+
rafPending = false;
|
|
845
|
+
reportPositionUpdate(state);
|
|
846
|
+
});
|
|
847
|
+
};
|
|
848
|
+
function enableEditMode(enabled, _specs) {
|
|
849
|
+
state.editModeEnabled = enabled;
|
|
850
|
+
if (enabled) {
|
|
851
|
+
document.body.style.cursor = "crosshair";
|
|
852
|
+
freezeAnimations();
|
|
853
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
854
|
+
document.addEventListener("mouseleave", onMouseLeave);
|
|
855
|
+
document.addEventListener("click", onClick, true);
|
|
856
|
+
window.addEventListener("scroll", throttledPositionUpdate, true);
|
|
857
|
+
document.addEventListener("scroll", throttledPositionUpdate, true);
|
|
858
|
+
window.addEventListener("resize", updateAllOverlayPositions);
|
|
859
|
+
startMutationObserver();
|
|
860
|
+
} else {
|
|
861
|
+
document.body.style.cursor = "default";
|
|
862
|
+
unfreezeAnimations();
|
|
863
|
+
clearAllOverlays(state, () => removeLayerDropdown(state));
|
|
864
|
+
if (state.editingElement) {
|
|
865
|
+
stopInlineEditing(state, updateAllOverlayPositions);
|
|
866
|
+
}
|
|
867
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
868
|
+
document.removeEventListener("mouseleave", onMouseLeave);
|
|
869
|
+
document.removeEventListener("click", onClick, true);
|
|
870
|
+
window.removeEventListener("scroll", throttledPositionUpdate, true);
|
|
871
|
+
document.removeEventListener("scroll", throttledPositionUpdate, true);
|
|
872
|
+
window.removeEventListener("resize", updateAllOverlayPositions);
|
|
873
|
+
stopMutationObserver();
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function startMutationObserver() {
|
|
877
|
+
if (state.mutationObserver) return;
|
|
878
|
+
state.mutationObserver = new MutationObserver((mutations) => {
|
|
879
|
+
const hasRelevantChange = mutations.some((m) => {
|
|
880
|
+
if (m.type === "attributes" && ["style", "class", "width", "height"].includes(m.attributeName ?? "") && containsTrackedElement(m.target))
|
|
881
|
+
return true;
|
|
882
|
+
if (m.type === "childList" && containsTrackedElement(m.target)) return true;
|
|
883
|
+
return false;
|
|
884
|
+
});
|
|
885
|
+
if (hasRelevantChange) {
|
|
886
|
+
setTimeout(updateAllOverlayPositions, 50);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
state.mutationObserver.observe(document.body, {
|
|
890
|
+
attributes: true,
|
|
891
|
+
childList: true,
|
|
892
|
+
subtree: true,
|
|
893
|
+
attributeFilter: ["style", "class", "width", "height"]
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function stopMutationObserver() {
|
|
897
|
+
if (state.mutationObserver) {
|
|
898
|
+
state.mutationObserver.disconnect();
|
|
899
|
+
state.mutationObserver = null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function containsTrackedElement(node) {
|
|
903
|
+
if (!(node instanceof Element)) return false;
|
|
904
|
+
if (node.hasAttribute(AGENT_ATTR)) return false;
|
|
905
|
+
return hasSourceLocation(node) || !!node.querySelector(`[${state.attributeName}]`);
|
|
906
|
+
}
|
|
907
|
+
function setupSandboxMountObserver() {
|
|
908
|
+
if (window.self === window.top) return;
|
|
909
|
+
const mountObserver = new MutationObserver((mutations) => {
|
|
910
|
+
const hasChanges = mutations.some(
|
|
911
|
+
(m) => m.addedNodes.length > 0 || m.removedNodes.length > 0
|
|
912
|
+
);
|
|
913
|
+
if (!hasChanges) return;
|
|
914
|
+
const hasTrackedElements = document.body.querySelectorAll("[data-source-location], [data-dynamic-content]").length > 0;
|
|
915
|
+
try {
|
|
916
|
+
window.parent.postMessage(
|
|
917
|
+
{
|
|
918
|
+
type: hasTrackedElements ? "sandbox:onMounted" : "sandbox:onUnmounted"
|
|
919
|
+
},
|
|
920
|
+
state.targetOrigin
|
|
921
|
+
);
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
if (document.body) {
|
|
926
|
+
mountObserver.observe(document.body, { childList: true, subtree: true });
|
|
927
|
+
} else {
|
|
928
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
929
|
+
mountObserver.observe(document.body, { childList: true, subtree: true });
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const onMessage = (e) => {
|
|
934
|
+
handleMessage(state, e, {
|
|
935
|
+
enableEditMode,
|
|
936
|
+
updateAllOverlayPositions,
|
|
937
|
+
stopInlineEditing: () => stopInlineEditing(state, updateAllOverlayPositions),
|
|
938
|
+
handleInlineEdit: (data) => handleInlineEdit(state, data, updateAllOverlayPositions)
|
|
939
|
+
});
|
|
940
|
+
};
|
|
941
|
+
window.addEventListener("message", onMessage);
|
|
942
|
+
setupSandboxMountObserver();
|
|
943
|
+
try {
|
|
944
|
+
window.parent.postMessage({ type: "visual-edit-agent-ready" }, state.targetOrigin);
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
destroy() {
|
|
949
|
+
window.removeEventListener("message", onMessage);
|
|
950
|
+
if (state.editModeEnabled) {
|
|
951
|
+
enableEditMode(false);
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
enableEditMode,
|
|
955
|
+
selectElement,
|
|
956
|
+
clearSelection() {
|
|
957
|
+
clearAllOverlays(state, () => removeLayerDropdown(state));
|
|
958
|
+
},
|
|
959
|
+
getSelectedId() {
|
|
960
|
+
return state.selectedId;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
71
964
|
export {
|
|
965
|
+
setupVisualEditAgent,
|
|
72
966
|
visualSelectorPlugin
|
|
73
967
|
};
|
|
74
968
|
//# sourceMappingURL=index.js.map
|