vite-plugin-visual-selector 0.1.2 → 0.1.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.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +278 -178
- package/dist/index.js.map +1 -1
- package/dist/{runtime-CpZH1D8y.d.ts → runtime-D695qvel.d.ts} +9 -3
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +278 -178
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { V as VisualSelectorPluginOptions } from './runtime-
|
|
3
|
-
export { A as AgentReadyMessage, C as ContentEditingMessage, D as DropdownStateMessage, E as ElementPosition, a as ElementPositionUpdateMessage, b as ElementSelectedMessage, c as ElementUpdateMessage, I as InjectFontImportMessage, d as InlineEditMessage, L as LayerItem, P as PopoverDragStateMessage, R as RefreshPageMessage, e as RequestElementPositionMessage, S as SandboxMountMessage, T as ToggleInlineEditModeMessage,
|
|
2
|
+
import { V as VisualSelectorPluginOptions } from './runtime-D695qvel.js';
|
|
3
|
+
export { A as AgentReadyMessage, C as ContentEditingMessage, D as DropdownStateMessage, E as ElementPosition, a as ElementPositionUpdateMessage, b as ElementSelectedMessage, c as ElementUpdateMessage, I as InjectFontImportMessage, d as InlineEditMessage, L as LayerItem, P as PopoverDragStateMessage, R as RefreshPageMessage, e as RequestElementPositionMessage, S as SandboxMountMessage, f as SelectElementByIdMessage, T as ToggleInlineEditModeMessage, g as ToggleVisualEditModeMessage, U as UnselectElementMessage, h as UpdateAttributeMessage, i as UpdateClassesMessage, j as UpdateContentMessage, k as UpdateThemeVariablesMessage, l as VisualEditAgentInstance, m as VisualEditAgentOptions, n as VisualSelectorControlMessage, s as setupVisualEditAgent } from './runtime-D695qvel.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 创建 Vite 插件:在编译阶段(transform hook)向每个原生 JSX 元素注入
|
package/dist/index.js
CHANGED
|
@@ -218,169 +218,6 @@ function handleInlineEdit(state, data, updatePositions) {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
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
221
|
// src/runtime/messages.ts
|
|
385
222
|
function reportElementSelected(state, element) {
|
|
386
223
|
const el = element;
|
|
@@ -439,6 +276,18 @@ function reportPositionUpdate(state) {
|
|
|
439
276
|
} catch {
|
|
440
277
|
}
|
|
441
278
|
}
|
|
279
|
+
function reportDropdownState(state, isOpen) {
|
|
280
|
+
try {
|
|
281
|
+
window.parent.postMessage(
|
|
282
|
+
{
|
|
283
|
+
type: "dropdown-state",
|
|
284
|
+
data: { isOpen }
|
|
285
|
+
},
|
|
286
|
+
state.targetOrigin
|
|
287
|
+
);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
442
291
|
function handleMessage(state, e, callbacks) {
|
|
443
292
|
if (state.targetOrigin !== "*" && e.origin !== state.targetOrigin) return;
|
|
444
293
|
const msg = e.data;
|
|
@@ -472,10 +321,7 @@ function handleMessage(state, e, callbacks) {
|
|
|
472
321
|
break;
|
|
473
322
|
case "unselect-element":
|
|
474
323
|
if (state.editingElement) callbacks.stopInlineEditing();
|
|
475
|
-
|
|
476
|
-
state.selectionOverlays = [];
|
|
477
|
-
state.selectedId = null;
|
|
478
|
-
state.selectedElement = null;
|
|
324
|
+
callbacks.clearSelectionState();
|
|
479
325
|
break;
|
|
480
326
|
case "update-theme-variables":
|
|
481
327
|
if (msg.data?.variables) {
|
|
@@ -510,6 +356,11 @@ function handleMessage(state, e, callbacks) {
|
|
|
510
356
|
case "request-element-position":
|
|
511
357
|
reportPositionUpdate(state);
|
|
512
358
|
break;
|
|
359
|
+
case "select-element-by-id":
|
|
360
|
+
if (msg.data?.visualSelectorId) {
|
|
361
|
+
callbacks.selectElementById(msg.data.visualSelectorId);
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
513
364
|
case CONTROL_MESSAGE_TYPE: {
|
|
514
365
|
const controlMsg = msg;
|
|
515
366
|
if (typeof controlMsg.active === "boolean") {
|
|
@@ -560,6 +411,27 @@ function createOverlay(opts) {
|
|
|
560
411
|
}
|
|
561
412
|
return overlay;
|
|
562
413
|
}
|
|
414
|
+
function setTagArrowExpanded(arrow, expanded) {
|
|
415
|
+
arrow.style.transform = expanded ? "rotate(0deg)" : "rotate(180deg)";
|
|
416
|
+
}
|
|
417
|
+
function createTagArrowSvg() {
|
|
418
|
+
const svgNs = "http://www.w3.org/2000/svg";
|
|
419
|
+
const svg = document.createElementNS(svgNs, "svg");
|
|
420
|
+
svg.setAttribute("xmlns", svgNs);
|
|
421
|
+
svg.setAttribute("width", "12");
|
|
422
|
+
svg.setAttribute("height", "12");
|
|
423
|
+
svg.setAttribute("viewBox", "0 0 12 12");
|
|
424
|
+
svg.setAttribute("fill", "none");
|
|
425
|
+
svg.style.display = "block";
|
|
426
|
+
const path2 = document.createElementNS(svgNs, "path");
|
|
427
|
+
path2.setAttribute("stroke", "rgba(51, 51, 51, 1)");
|
|
428
|
+
path2.setAttribute("stroke-width", "0.8325");
|
|
429
|
+
path2.setAttribute("stroke-linejoin", "round");
|
|
430
|
+
path2.setAttribute("stroke-linecap", "round");
|
|
431
|
+
path2.setAttribute("d", "M3.24994 7.5L6.24994 4.5L9.24994 7.5");
|
|
432
|
+
svg.appendChild(path2);
|
|
433
|
+
return svg;
|
|
434
|
+
}
|
|
563
435
|
function positionOverlay(overlay, target, tagMode = "none", callbacks) {
|
|
564
436
|
const rect = target.getBoundingClientRect();
|
|
565
437
|
overlay.style.top = `${rect.top + window.scrollY}px`;
|
|
@@ -582,8 +454,10 @@ function positionOverlay(overlay, target, tagMode = "none", callbacks) {
|
|
|
582
454
|
textAlign: "center",
|
|
583
455
|
display: "flex",
|
|
584
456
|
alignItems: "center",
|
|
457
|
+
justifyContent: "center",
|
|
585
458
|
gap: "3px",
|
|
586
|
-
lineHeight: "1.4"
|
|
459
|
+
lineHeight: "1.4",
|
|
460
|
+
whiteSpace: "nowrap"
|
|
587
461
|
});
|
|
588
462
|
if (tagMode === "selected") {
|
|
589
463
|
tag.style.backgroundColor = "#FC0";
|
|
@@ -592,12 +466,23 @@ function positionOverlay(overlay, target, tagMode = "none", callbacks) {
|
|
|
592
466
|
tag.style.pointerEvents = "auto";
|
|
593
467
|
const textSpan = document.createElement("span");
|
|
594
468
|
textSpan.textContent = target.tagName.toLowerCase();
|
|
469
|
+
textSpan.style.display = "inline-flex";
|
|
470
|
+
textSpan.style.alignItems = "center";
|
|
471
|
+
textSpan.style.lineHeight = "1";
|
|
595
472
|
tag.appendChild(textSpan);
|
|
596
473
|
const arrow = document.createElement("span");
|
|
597
474
|
arrow.setAttribute("data-tag-arrow", "");
|
|
598
|
-
arrow.textContent = "
|
|
599
|
-
arrow.style.
|
|
475
|
+
arrow.textContent = "";
|
|
476
|
+
arrow.style.display = "inline-flex";
|
|
477
|
+
arrow.style.alignItems = "center";
|
|
478
|
+
arrow.style.justifyContent = "center";
|
|
479
|
+
arrow.style.width = "12px";
|
|
480
|
+
arrow.style.height = "12px";
|
|
481
|
+
arrow.style.flex = "0 0 auto";
|
|
482
|
+
arrow.style.transformOrigin = "center";
|
|
600
483
|
arrow.style.lineHeight = "1";
|
|
484
|
+
arrow.appendChild(createTagArrowSvg());
|
|
485
|
+
setTagArrowExpanded(arrow, false);
|
|
601
486
|
tag.appendChild(arrow);
|
|
602
487
|
if (callbacks?.onTagClick) {
|
|
603
488
|
const onTagClick = callbacks.onTagClick;
|
|
@@ -720,6 +605,171 @@ function unfreezeAnimations() {
|
|
|
720
605
|
if (overflowStyle) overflowStyle.remove();
|
|
721
606
|
}
|
|
722
607
|
|
|
608
|
+
// src/runtime/layer-navigation.ts
|
|
609
|
+
function buildLayerTree(element) {
|
|
610
|
+
const tree = [];
|
|
611
|
+
const ancestors = [];
|
|
612
|
+
let parent = element.parentElement;
|
|
613
|
+
while (parent && parent !== document.body) {
|
|
614
|
+
if (hasSourceLocation(parent)) {
|
|
615
|
+
ancestors.push(parent);
|
|
616
|
+
}
|
|
617
|
+
parent = parent.parentElement;
|
|
618
|
+
}
|
|
619
|
+
ancestors.reverse();
|
|
620
|
+
ancestors.forEach((el) => {
|
|
621
|
+
tree.push({ element: el, tagName: el.tagName.toLowerCase(), depth: 0 });
|
|
622
|
+
});
|
|
623
|
+
tree.push({ element, tagName: element.tagName.toLowerCase(), depth: 0 });
|
|
624
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
625
|
+
const child = element.children[i];
|
|
626
|
+
if (hasSourceLocation(child)) {
|
|
627
|
+
tree.push({ element: child, tagName: child.tagName.toLowerCase(), depth: 0 });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return tree;
|
|
631
|
+
}
|
|
632
|
+
function removeLayerDropdown(state) {
|
|
633
|
+
if (state.layerDropdown) {
|
|
634
|
+
const parentOverlay = state.layerDropdown.parentElement;
|
|
635
|
+
if (parentOverlay) {
|
|
636
|
+
const arrowEl = parentOverlay.querySelector("[data-tag-arrow]");
|
|
637
|
+
if (arrowEl) setTagArrowExpanded(arrowEl, false);
|
|
638
|
+
}
|
|
639
|
+
state.layerDropdown.remove();
|
|
640
|
+
state.layerDropdown = null;
|
|
641
|
+
reportDropdownState(state, false);
|
|
642
|
+
document.removeEventListener("keydown", handleLayerKeyboard);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function toggleLayerDropdown(state, element, anchor, onSelectElement) {
|
|
646
|
+
if (state.layerDropdown) {
|
|
647
|
+
removeLayerDropdown(state);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const layers = buildLayerTree(element);
|
|
651
|
+
renderLayerDropdown(state, anchor, layers, element, onSelectElement);
|
|
652
|
+
}
|
|
653
|
+
function renderLayerDropdown(state, anchor, layers, currentElement, onSelectElement) {
|
|
654
|
+
const dropdown = document.createElement("div");
|
|
655
|
+
dropdown.setAttribute("data-layer-dropdown", "true");
|
|
656
|
+
dropdown.setAttribute(AGENT_ATTR, "");
|
|
657
|
+
Object.assign(dropdown.style, {
|
|
658
|
+
position: "absolute",
|
|
659
|
+
backgroundColor: "#ffffff",
|
|
660
|
+
border: "1px solid #e2e8f0",
|
|
661
|
+
borderRadius: "6px",
|
|
662
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
663
|
+
fontSize: "12px",
|
|
664
|
+
zIndex: "2147483647",
|
|
665
|
+
pointerEvents: "auto",
|
|
666
|
+
padding: "4px 0",
|
|
667
|
+
minWidth: "120px",
|
|
668
|
+
maxHeight: "320px",
|
|
669
|
+
overflowY: "auto"
|
|
670
|
+
});
|
|
671
|
+
let focusedIndex = -1;
|
|
672
|
+
const items = [];
|
|
673
|
+
layers.forEach((layer, idx) => {
|
|
674
|
+
const item = document.createElement("div");
|
|
675
|
+
item.setAttribute(AGENT_ATTR, "");
|
|
676
|
+
item.textContent = layer.tagName;
|
|
677
|
+
item.style.padding = "6px 16px";
|
|
678
|
+
item.style.cursor = "pointer";
|
|
679
|
+
item.style.whiteSpace = "nowrap";
|
|
680
|
+
if (layer.element === currentElement) {
|
|
681
|
+
item.style.color = "#000000";
|
|
682
|
+
item.style.backgroundColor = "rgba(255, 204, 0, 0.2)";
|
|
683
|
+
item.style.fontWeight = "600";
|
|
684
|
+
focusedIndex = idx;
|
|
685
|
+
} else {
|
|
686
|
+
item.style.color = "#64748b";
|
|
687
|
+
}
|
|
688
|
+
item.addEventListener("click", (e) => {
|
|
689
|
+
e.stopPropagation();
|
|
690
|
+
removeLayerDropdown(state);
|
|
691
|
+
onSelectElement(layer.element);
|
|
692
|
+
});
|
|
693
|
+
item.addEventListener("mouseenter", () => {
|
|
694
|
+
if (layer.element !== currentElement) {
|
|
695
|
+
item.style.backgroundColor = "#f1f5f9";
|
|
696
|
+
item.style.color = "#334155";
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
item.addEventListener("mouseleave", () => {
|
|
700
|
+
if (layer.element !== currentElement) {
|
|
701
|
+
item.style.backgroundColor = "transparent";
|
|
702
|
+
item.style.color = "#64748b";
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
items.push(item);
|
|
706
|
+
dropdown.appendChild(item);
|
|
707
|
+
});
|
|
708
|
+
document.body.appendChild(dropdown);
|
|
709
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
710
|
+
dropdown.style.top = `${anchorRect.bottom + window.scrollY + 2}px`;
|
|
711
|
+
dropdown.style.left = `${anchorRect.left + window.scrollX}px`;
|
|
712
|
+
requestAnimationFrame(() => {
|
|
713
|
+
const ddRect = dropdown.getBoundingClientRect();
|
|
714
|
+
if (ddRect.right > window.innerWidth - 4) {
|
|
715
|
+
dropdown.style.left = `${window.innerWidth - ddRect.width - 4 + window.scrollX}px`;
|
|
716
|
+
}
|
|
717
|
+
if (ddRect.left < 4) {
|
|
718
|
+
dropdown.style.left = `${4 + window.scrollX}px`;
|
|
719
|
+
}
|
|
720
|
+
if (ddRect.bottom > window.innerHeight) {
|
|
721
|
+
dropdown.style.top = `${anchorRect.top + window.scrollY - ddRect.height - 2}px`;
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
state.layerDropdown = dropdown;
|
|
725
|
+
reportDropdownState(state, true);
|
|
726
|
+
const handleKeydown = (e) => {
|
|
727
|
+
if (!state.layerDropdown) return;
|
|
728
|
+
switch (e.key) {
|
|
729
|
+
case "ArrowDown":
|
|
730
|
+
e.preventDefault();
|
|
731
|
+
focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
|
|
732
|
+
highlightItem(items, focusedIndex, layers, currentElement);
|
|
733
|
+
break;
|
|
734
|
+
case "ArrowUp":
|
|
735
|
+
e.preventDefault();
|
|
736
|
+
focusedIndex = Math.max(focusedIndex - 1, 0);
|
|
737
|
+
highlightItem(items, focusedIndex, layers, currentElement);
|
|
738
|
+
break;
|
|
739
|
+
case "Enter":
|
|
740
|
+
e.preventDefault();
|
|
741
|
+
if (focusedIndex >= 0 && focusedIndex < layers.length) {
|
|
742
|
+
removeLayerDropdown(state);
|
|
743
|
+
onSelectElement(layers[focusedIndex].element);
|
|
744
|
+
}
|
|
745
|
+
break;
|
|
746
|
+
case "Escape":
|
|
747
|
+
e.preventDefault();
|
|
748
|
+
removeLayerDropdown(state);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
handleLayerKeyboard.current = handleKeydown;
|
|
753
|
+
document.addEventListener("keydown", handleLayerKeyboard);
|
|
754
|
+
}
|
|
755
|
+
function handleLayerKeyboard(e) {
|
|
756
|
+
const current = handleLayerKeyboard.current;
|
|
757
|
+
if (current) current(e);
|
|
758
|
+
}
|
|
759
|
+
function highlightItem(items, index, layers, currentElement) {
|
|
760
|
+
items.forEach((item, i) => {
|
|
761
|
+
if (layers[i].element === currentElement) {
|
|
762
|
+
item.style.color = "#000000";
|
|
763
|
+
item.style.backgroundColor = i === index ? "rgba(255, 204, 0, 0.35)" : "rgba(255, 204, 0, 0.2)";
|
|
764
|
+
item.style.fontWeight = "600";
|
|
765
|
+
} else {
|
|
766
|
+
item.style.backgroundColor = i === index ? "#f1f5f9" : "transparent";
|
|
767
|
+
item.style.color = i === index ? "#334155" : "#64748b";
|
|
768
|
+
item.style.fontWeight = "normal";
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
723
773
|
// src/runtime/state.ts
|
|
724
774
|
function createAgentState(options = {}) {
|
|
725
775
|
return {
|
|
@@ -733,13 +783,36 @@ function createAgentState(options = {}) {
|
|
|
733
783
|
layerDropdown: null,
|
|
734
784
|
mutationObserver: null,
|
|
735
785
|
attributeName: options.attributeName ?? DEFAULT_ATTRIBUTE_NAME,
|
|
736
|
-
targetOrigin: options.targetOrigin ?? "*"
|
|
786
|
+
targetOrigin: options.targetOrigin ?? "*",
|
|
787
|
+
runtimeIdCounter: 0
|
|
737
788
|
};
|
|
738
789
|
}
|
|
739
790
|
|
|
740
791
|
// src/runtime/index.ts
|
|
741
792
|
function setupVisualEditAgent(options = {}) {
|
|
742
793
|
const state = createAgentState(options);
|
|
794
|
+
const trackedSelector = `[${state.attributeName}], [data-visual-selector-id]`;
|
|
795
|
+
function shouldAssignRuntimeId(element) {
|
|
796
|
+
return !element.closest(`[${AGENT_ATTR}]`) && !element.hasAttribute(state.attributeName) && !element.hasAttribute("data-visual-selector-id");
|
|
797
|
+
}
|
|
798
|
+
function assignRuntimeId(element) {
|
|
799
|
+
if (!shouldAssignRuntimeId(element)) return;
|
|
800
|
+
state.runtimeIdCounter += 1;
|
|
801
|
+
element.setAttribute("data-visual-selector-id", `runtime:${state.runtimeIdCounter}`);
|
|
802
|
+
}
|
|
803
|
+
function trackElementSubtree(root) {
|
|
804
|
+
assignRuntimeId(root);
|
|
805
|
+
root.querySelectorAll("*").forEach((element) => assignRuntimeId(element));
|
|
806
|
+
}
|
|
807
|
+
function trackCurrentDom() {
|
|
808
|
+
if (!document.body) return;
|
|
809
|
+
trackElementSubtree(document.body);
|
|
810
|
+
}
|
|
811
|
+
function ensureElementTracked(element) {
|
|
812
|
+
if (!element.closest(`[${AGENT_ATTR}]`)) {
|
|
813
|
+
assignRuntimeId(element);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
743
816
|
function updateAllOverlayPositions() {
|
|
744
817
|
if (state.selectedId && state.selectedElement?.isConnected) {
|
|
745
818
|
const siblings = findAllElementsById(state.selectedId);
|
|
@@ -756,7 +829,7 @@ function setupVisualEditAgent(options = {}) {
|
|
|
756
829
|
function onTagClick(target, tag) {
|
|
757
830
|
const arrowEl = tag.querySelector("[data-tag-arrow]");
|
|
758
831
|
if (arrowEl) {
|
|
759
|
-
arrowEl
|
|
832
|
+
setTagArrowExpanded(arrowEl, !state.layerDropdown);
|
|
760
833
|
}
|
|
761
834
|
toggleLayerDropdown(state, target, tag, selectElement);
|
|
762
835
|
}
|
|
@@ -767,7 +840,10 @@ function setupVisualEditAgent(options = {}) {
|
|
|
767
840
|
if (freezeStyle) freezeStyle.disabled = false;
|
|
768
841
|
if (!element) return null;
|
|
769
842
|
if (element.closest(`[${AGENT_ATTR}]`)) return null;
|
|
770
|
-
|
|
843
|
+
const trackedElement = element.closest(trackedSelector);
|
|
844
|
+
if (trackedElement) return trackedElement;
|
|
845
|
+
ensureElementTracked(element);
|
|
846
|
+
return element.hasAttribute("data-visual-selector-id") ? element : null;
|
|
771
847
|
}
|
|
772
848
|
function findHoverTarget(x, y, excludeId) {
|
|
773
849
|
const element = findElementAtPoint(x, y);
|
|
@@ -815,6 +891,7 @@ function setupVisualEditAgent(options = {}) {
|
|
|
815
891
|
selectElement(element);
|
|
816
892
|
}
|
|
817
893
|
function selectElement(element) {
|
|
894
|
+
ensureElementTracked(element);
|
|
818
895
|
const id = getSourceId(element);
|
|
819
896
|
if (!id) return;
|
|
820
897
|
if (state.editingElement) {
|
|
@@ -850,6 +927,7 @@ function setupVisualEditAgent(options = {}) {
|
|
|
850
927
|
if (enabled) {
|
|
851
928
|
document.body.style.cursor = "crosshair";
|
|
852
929
|
freezeAnimations();
|
|
930
|
+
trackCurrentDom();
|
|
853
931
|
document.addEventListener("mousemove", onMouseMove);
|
|
854
932
|
document.addEventListener("mouseleave", onMouseLeave);
|
|
855
933
|
document.addEventListener("click", onClick, true);
|
|
@@ -876,6 +954,13 @@ function setupVisualEditAgent(options = {}) {
|
|
|
876
954
|
function startMutationObserver() {
|
|
877
955
|
if (state.mutationObserver) return;
|
|
878
956
|
state.mutationObserver = new MutationObserver((mutations) => {
|
|
957
|
+
mutations.forEach((mutation) => {
|
|
958
|
+
mutation.addedNodes.forEach((node) => {
|
|
959
|
+
if (node instanceof Element) {
|
|
960
|
+
trackElementSubtree(node);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
});
|
|
879
964
|
const hasRelevantChange = mutations.some((m) => {
|
|
880
965
|
if (m.type === "attributes" && ["style", "class", "width", "height"].includes(m.attributeName ?? "") && containsTrackedElement(m.target))
|
|
881
966
|
return true;
|
|
@@ -901,8 +986,8 @@ function setupVisualEditAgent(options = {}) {
|
|
|
901
986
|
}
|
|
902
987
|
function containsTrackedElement(node) {
|
|
903
988
|
if (!(node instanceof Element)) return false;
|
|
904
|
-
if (node.
|
|
905
|
-
return
|
|
989
|
+
if (node.closest(`[${AGENT_ATTR}]`)) return false;
|
|
990
|
+
return node.hasAttribute(state.attributeName) || node.hasAttribute("data-visual-selector-id") || !!node.querySelector(trackedSelector);
|
|
906
991
|
}
|
|
907
992
|
function setupSandboxMountObserver() {
|
|
908
993
|
if (window.self === window.top) return;
|
|
@@ -911,7 +996,9 @@ function setupVisualEditAgent(options = {}) {
|
|
|
911
996
|
(m) => m.addedNodes.length > 0 || m.removedNodes.length > 0
|
|
912
997
|
);
|
|
913
998
|
if (!hasChanges) return;
|
|
914
|
-
const hasTrackedElements = document.body.querySelectorAll(
|
|
999
|
+
const hasTrackedElements = document.body.querySelectorAll(
|
|
1000
|
+
`[${state.attributeName}], [data-dynamic-content], [data-visual-selector-id]`
|
|
1001
|
+
).length > 0;
|
|
915
1002
|
try {
|
|
916
1003
|
window.parent.postMessage(
|
|
917
1004
|
{
|
|
@@ -930,12 +1017,25 @@ function setupVisualEditAgent(options = {}) {
|
|
|
930
1017
|
});
|
|
931
1018
|
}
|
|
932
1019
|
}
|
|
1020
|
+
function selectElementById(id) {
|
|
1021
|
+
const element = document.querySelector(
|
|
1022
|
+
`[${state.attributeName}="${id}"], [data-visual-selector-id="${id}"]`
|
|
1023
|
+
);
|
|
1024
|
+
if (element) {
|
|
1025
|
+
selectElement(element);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function clearSelectionState() {
|
|
1029
|
+
clearAllOverlays(state, () => removeLayerDropdown(state));
|
|
1030
|
+
}
|
|
933
1031
|
const onMessage = (e) => {
|
|
934
1032
|
handleMessage(state, e, {
|
|
935
1033
|
enableEditMode,
|
|
936
1034
|
updateAllOverlayPositions,
|
|
937
1035
|
stopInlineEditing: () => stopInlineEditing(state, updateAllOverlayPositions),
|
|
938
|
-
handleInlineEdit: (data) => handleInlineEdit(state, data, updateAllOverlayPositions)
|
|
1036
|
+
handleInlineEdit: (data) => handleInlineEdit(state, data, updateAllOverlayPositions),
|
|
1037
|
+
selectElementById,
|
|
1038
|
+
clearSelectionState
|
|
939
1039
|
});
|
|
940
1040
|
};
|
|
941
1041
|
window.addEventListener("message", onMessage);
|
|
@@ -954,7 +1054,7 @@ function setupVisualEditAgent(options = {}) {
|
|
|
954
1054
|
enableEditMode,
|
|
955
1055
|
selectElement,
|
|
956
1056
|
clearSelection() {
|
|
957
|
-
|
|
1057
|
+
clearSelectionState();
|
|
958
1058
|
},
|
|
959
1059
|
getSelectedId() {
|
|
960
1060
|
return state.selectedId;
|