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/runtime.js CHANGED
@@ -141,169 +141,6 @@ function handleInlineEdit(state, data, updatePositions) {
141
141
  }
142
142
  }
143
143
 
144
- // src/runtime/layer-navigation.ts
145
- function buildLayerTree(element) {
146
- const tree = [];
147
- const ancestors = [];
148
- let parent = element.parentElement;
149
- while (parent && parent !== document.body) {
150
- if (hasSourceLocation(parent)) {
151
- ancestors.push(parent);
152
- }
153
- parent = parent.parentElement;
154
- }
155
- ancestors.reverse();
156
- ancestors.forEach((el) => {
157
- tree.push({ element: el, tagName: el.tagName.toLowerCase(), depth: 0 });
158
- });
159
- tree.push({ element, tagName: element.tagName.toLowerCase(), depth: 0 });
160
- for (let i = 0; i < element.children.length; i++) {
161
- const child = element.children[i];
162
- if (hasSourceLocation(child)) {
163
- tree.push({ element: child, tagName: child.tagName.toLowerCase(), depth: 0 });
164
- }
165
- }
166
- return tree;
167
- }
168
- function removeLayerDropdown(state) {
169
- if (state.layerDropdown) {
170
- const parentOverlay = state.layerDropdown.parentElement;
171
- if (parentOverlay) {
172
- const arrowEl = parentOverlay.querySelector("[data-tag-arrow]");
173
- if (arrowEl) arrowEl.textContent = "\u2304";
174
- }
175
- state.layerDropdown.remove();
176
- state.layerDropdown = null;
177
- document.removeEventListener("keydown", handleLayerKeyboard);
178
- }
179
- }
180
- function toggleLayerDropdown(state, element, anchor, onSelectElement) {
181
- if (state.layerDropdown) {
182
- removeLayerDropdown(state);
183
- return;
184
- }
185
- const layers = buildLayerTree(element);
186
- renderLayerDropdown(state, anchor, layers, element, onSelectElement);
187
- }
188
- function renderLayerDropdown(state, anchor, layers, currentElement, onSelectElement) {
189
- const dropdown = document.createElement("div");
190
- dropdown.setAttribute("data-layer-dropdown", "true");
191
- dropdown.setAttribute(AGENT_ATTR, "");
192
- Object.assign(dropdown.style, {
193
- position: "absolute",
194
- backgroundColor: "#ffffff",
195
- border: "1px solid #e2e8f0",
196
- borderRadius: "6px",
197
- boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
198
- fontSize: "12px",
199
- zIndex: "99999",
200
- pointerEvents: "auto",
201
- padding: "4px 0",
202
- minWidth: "120px",
203
- maxHeight: "320px",
204
- overflowY: "auto"
205
- });
206
- let focusedIndex = -1;
207
- const items = [];
208
- layers.forEach((layer, idx) => {
209
- const item = document.createElement("div");
210
- item.setAttribute(AGENT_ATTR, "");
211
- item.textContent = layer.tagName;
212
- item.style.padding = "6px 16px";
213
- item.style.cursor = "pointer";
214
- item.style.whiteSpace = "nowrap";
215
- if (layer.element === currentElement) {
216
- item.style.color = "#000000";
217
- item.style.backgroundColor = "rgba(255, 204, 0, 0.2)";
218
- item.style.fontWeight = "600";
219
- focusedIndex = idx;
220
- } else {
221
- item.style.color = "#64748b";
222
- }
223
- item.addEventListener("click", (e) => {
224
- e.stopPropagation();
225
- removeLayerDropdown(state);
226
- onSelectElement(layer.element);
227
- });
228
- item.addEventListener("mouseenter", () => {
229
- if (layer.element !== currentElement) {
230
- item.style.backgroundColor = "#f1f5f9";
231
- item.style.color = "#334155";
232
- }
233
- });
234
- item.addEventListener("mouseleave", () => {
235
- if (layer.element !== currentElement) {
236
- item.style.backgroundColor = "transparent";
237
- item.style.color = "#64748b";
238
- }
239
- });
240
- items.push(item);
241
- dropdown.appendChild(item);
242
- });
243
- document.body.appendChild(dropdown);
244
- const anchorRect = anchor.getBoundingClientRect();
245
- dropdown.style.top = `${anchorRect.bottom + window.scrollY + 2}px`;
246
- dropdown.style.left = `${anchorRect.left + window.scrollX}px`;
247
- requestAnimationFrame(() => {
248
- const ddRect = dropdown.getBoundingClientRect();
249
- if (ddRect.right > window.innerWidth - 4) {
250
- dropdown.style.left = `${window.innerWidth - ddRect.width - 4 + window.scrollX}px`;
251
- }
252
- if (ddRect.left < 4) {
253
- dropdown.style.left = `${4 + window.scrollX}px`;
254
- }
255
- if (ddRect.bottom > window.innerHeight) {
256
- dropdown.style.top = `${anchorRect.top + window.scrollY - ddRect.height - 2}px`;
257
- }
258
- });
259
- state.layerDropdown = dropdown;
260
- const handleKeydown = (e) => {
261
- if (!state.layerDropdown) return;
262
- switch (e.key) {
263
- case "ArrowDown":
264
- e.preventDefault();
265
- focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
266
- highlightItem(items, focusedIndex, layers, currentElement);
267
- break;
268
- case "ArrowUp":
269
- e.preventDefault();
270
- focusedIndex = Math.max(focusedIndex - 1, 0);
271
- highlightItem(items, focusedIndex, layers, currentElement);
272
- break;
273
- case "Enter":
274
- e.preventDefault();
275
- if (focusedIndex >= 0 && focusedIndex < layers.length) {
276
- removeLayerDropdown(state);
277
- onSelectElement(layers[focusedIndex].element);
278
- }
279
- break;
280
- case "Escape":
281
- e.preventDefault();
282
- removeLayerDropdown(state);
283
- break;
284
- }
285
- };
286
- handleLayerKeyboard.current = handleKeydown;
287
- document.addEventListener("keydown", handleLayerKeyboard);
288
- }
289
- function handleLayerKeyboard(e) {
290
- const current = handleLayerKeyboard.current;
291
- if (current) current(e);
292
- }
293
- function highlightItem(items, index, layers, currentElement) {
294
- items.forEach((item, i) => {
295
- if (layers[i].element === currentElement) {
296
- item.style.color = "#000000";
297
- item.style.backgroundColor = i === index ? "rgba(255, 204, 0, 0.35)" : "rgba(255, 204, 0, 0.2)";
298
- item.style.fontWeight = "600";
299
- } else {
300
- item.style.backgroundColor = i === index ? "#f1f5f9" : "transparent";
301
- item.style.color = i === index ? "#334155" : "#64748b";
302
- item.style.fontWeight = "normal";
303
- }
304
- });
305
- }
306
-
307
144
  // src/runtime/messages.ts
308
145
  function reportElementSelected(state, element) {
309
146
  const el = element;
@@ -362,6 +199,18 @@ function reportPositionUpdate(state) {
362
199
  } catch {
363
200
  }
364
201
  }
202
+ function reportDropdownState(state, isOpen) {
203
+ try {
204
+ window.parent.postMessage(
205
+ {
206
+ type: "dropdown-state",
207
+ data: { isOpen }
208
+ },
209
+ state.targetOrigin
210
+ );
211
+ } catch {
212
+ }
213
+ }
365
214
  function handleMessage(state, e, callbacks) {
366
215
  if (state.targetOrigin !== "*" && e.origin !== state.targetOrigin) return;
367
216
  const msg = e.data;
@@ -395,10 +244,7 @@ function handleMessage(state, e, callbacks) {
395
244
  break;
396
245
  case "unselect-element":
397
246
  if (state.editingElement) callbacks.stopInlineEditing();
398
- state.selectionOverlays.forEach((o) => o.remove());
399
- state.selectionOverlays = [];
400
- state.selectedId = null;
401
- state.selectedElement = null;
247
+ callbacks.clearSelectionState();
402
248
  break;
403
249
  case "update-theme-variables":
404
250
  if (msg.data?.variables) {
@@ -433,6 +279,11 @@ function handleMessage(state, e, callbacks) {
433
279
  case "request-element-position":
434
280
  reportPositionUpdate(state);
435
281
  break;
282
+ case "select-element-by-id":
283
+ if (msg.data?.visualSelectorId) {
284
+ callbacks.selectElementById(msg.data.visualSelectorId);
285
+ }
286
+ break;
436
287
  case CONTROL_MESSAGE_TYPE: {
437
288
  const controlMsg = msg;
438
289
  if (typeof controlMsg.active === "boolean") {
@@ -483,6 +334,27 @@ function createOverlay(opts) {
483
334
  }
484
335
  return overlay;
485
336
  }
337
+ function setTagArrowExpanded(arrow, expanded) {
338
+ arrow.style.transform = expanded ? "rotate(0deg)" : "rotate(180deg)";
339
+ }
340
+ function createTagArrowSvg() {
341
+ const svgNs = "http://www.w3.org/2000/svg";
342
+ const svg = document.createElementNS(svgNs, "svg");
343
+ svg.setAttribute("xmlns", svgNs);
344
+ svg.setAttribute("width", "12");
345
+ svg.setAttribute("height", "12");
346
+ svg.setAttribute("viewBox", "0 0 12 12");
347
+ svg.setAttribute("fill", "none");
348
+ svg.style.display = "block";
349
+ const path = document.createElementNS(svgNs, "path");
350
+ path.setAttribute("stroke", "rgba(51, 51, 51, 1)");
351
+ path.setAttribute("stroke-width", "0.8325");
352
+ path.setAttribute("stroke-linejoin", "round");
353
+ path.setAttribute("stroke-linecap", "round");
354
+ path.setAttribute("d", "M3.24994 7.5L6.24994 4.5L9.24994 7.5");
355
+ svg.appendChild(path);
356
+ return svg;
357
+ }
486
358
  function positionOverlay(overlay, target, tagMode = "none", callbacks) {
487
359
  const rect = target.getBoundingClientRect();
488
360
  overlay.style.top = `${rect.top + window.scrollY}px`;
@@ -505,8 +377,10 @@ function positionOverlay(overlay, target, tagMode = "none", callbacks) {
505
377
  textAlign: "center",
506
378
  display: "flex",
507
379
  alignItems: "center",
380
+ justifyContent: "center",
508
381
  gap: "3px",
509
- lineHeight: "1.4"
382
+ lineHeight: "1.4",
383
+ whiteSpace: "nowrap"
510
384
  });
511
385
  if (tagMode === "selected") {
512
386
  tag.style.backgroundColor = "#FC0";
@@ -515,12 +389,23 @@ function positionOverlay(overlay, target, tagMode = "none", callbacks) {
515
389
  tag.style.pointerEvents = "auto";
516
390
  const textSpan = document.createElement("span");
517
391
  textSpan.textContent = target.tagName.toLowerCase();
392
+ textSpan.style.display = "inline-flex";
393
+ textSpan.style.alignItems = "center";
394
+ textSpan.style.lineHeight = "1";
518
395
  tag.appendChild(textSpan);
519
396
  const arrow = document.createElement("span");
520
397
  arrow.setAttribute("data-tag-arrow", "");
521
- arrow.textContent = "\u2304";
522
- arrow.style.fontSize = "10px";
398
+ arrow.textContent = "";
399
+ arrow.style.display = "inline-flex";
400
+ arrow.style.alignItems = "center";
401
+ arrow.style.justifyContent = "center";
402
+ arrow.style.width = "12px";
403
+ arrow.style.height = "12px";
404
+ arrow.style.flex = "0 0 auto";
405
+ arrow.style.transformOrigin = "center";
523
406
  arrow.style.lineHeight = "1";
407
+ arrow.appendChild(createTagArrowSvg());
408
+ setTagArrowExpanded(arrow, false);
524
409
  tag.appendChild(arrow);
525
410
  if (callbacks?.onTagClick) {
526
411
  const onTagClick = callbacks.onTagClick;
@@ -643,6 +528,171 @@ function unfreezeAnimations() {
643
528
  if (overflowStyle) overflowStyle.remove();
644
529
  }
645
530
 
531
+ // src/runtime/layer-navigation.ts
532
+ function buildLayerTree(element) {
533
+ const tree = [];
534
+ const ancestors = [];
535
+ let parent = element.parentElement;
536
+ while (parent && parent !== document.body) {
537
+ if (hasSourceLocation(parent)) {
538
+ ancestors.push(parent);
539
+ }
540
+ parent = parent.parentElement;
541
+ }
542
+ ancestors.reverse();
543
+ ancestors.forEach((el) => {
544
+ tree.push({ element: el, tagName: el.tagName.toLowerCase(), depth: 0 });
545
+ });
546
+ tree.push({ element, tagName: element.tagName.toLowerCase(), depth: 0 });
547
+ for (let i = 0; i < element.children.length; i++) {
548
+ const child = element.children[i];
549
+ if (hasSourceLocation(child)) {
550
+ tree.push({ element: child, tagName: child.tagName.toLowerCase(), depth: 0 });
551
+ }
552
+ }
553
+ return tree;
554
+ }
555
+ function removeLayerDropdown(state) {
556
+ if (state.layerDropdown) {
557
+ const parentOverlay = state.layerDropdown.parentElement;
558
+ if (parentOverlay) {
559
+ const arrowEl = parentOverlay.querySelector("[data-tag-arrow]");
560
+ if (arrowEl) setTagArrowExpanded(arrowEl, false);
561
+ }
562
+ state.layerDropdown.remove();
563
+ state.layerDropdown = null;
564
+ reportDropdownState(state, false);
565
+ document.removeEventListener("keydown", handleLayerKeyboard);
566
+ }
567
+ }
568
+ function toggleLayerDropdown(state, element, anchor, onSelectElement) {
569
+ if (state.layerDropdown) {
570
+ removeLayerDropdown(state);
571
+ return;
572
+ }
573
+ const layers = buildLayerTree(element);
574
+ renderLayerDropdown(state, anchor, layers, element, onSelectElement);
575
+ }
576
+ function renderLayerDropdown(state, anchor, layers, currentElement, onSelectElement) {
577
+ const dropdown = document.createElement("div");
578
+ dropdown.setAttribute("data-layer-dropdown", "true");
579
+ dropdown.setAttribute(AGENT_ATTR, "");
580
+ Object.assign(dropdown.style, {
581
+ position: "absolute",
582
+ backgroundColor: "#ffffff",
583
+ border: "1px solid #e2e8f0",
584
+ borderRadius: "6px",
585
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
586
+ fontSize: "12px",
587
+ zIndex: "2147483647",
588
+ pointerEvents: "auto",
589
+ padding: "4px 0",
590
+ minWidth: "120px",
591
+ maxHeight: "320px",
592
+ overflowY: "auto"
593
+ });
594
+ let focusedIndex = -1;
595
+ const items = [];
596
+ layers.forEach((layer, idx) => {
597
+ const item = document.createElement("div");
598
+ item.setAttribute(AGENT_ATTR, "");
599
+ item.textContent = layer.tagName;
600
+ item.style.padding = "6px 16px";
601
+ item.style.cursor = "pointer";
602
+ item.style.whiteSpace = "nowrap";
603
+ if (layer.element === currentElement) {
604
+ item.style.color = "#000000";
605
+ item.style.backgroundColor = "rgba(255, 204, 0, 0.2)";
606
+ item.style.fontWeight = "600";
607
+ focusedIndex = idx;
608
+ } else {
609
+ item.style.color = "#64748b";
610
+ }
611
+ item.addEventListener("click", (e) => {
612
+ e.stopPropagation();
613
+ removeLayerDropdown(state);
614
+ onSelectElement(layer.element);
615
+ });
616
+ item.addEventListener("mouseenter", () => {
617
+ if (layer.element !== currentElement) {
618
+ item.style.backgroundColor = "#f1f5f9";
619
+ item.style.color = "#334155";
620
+ }
621
+ });
622
+ item.addEventListener("mouseleave", () => {
623
+ if (layer.element !== currentElement) {
624
+ item.style.backgroundColor = "transparent";
625
+ item.style.color = "#64748b";
626
+ }
627
+ });
628
+ items.push(item);
629
+ dropdown.appendChild(item);
630
+ });
631
+ document.body.appendChild(dropdown);
632
+ const anchorRect = anchor.getBoundingClientRect();
633
+ dropdown.style.top = `${anchorRect.bottom + window.scrollY + 2}px`;
634
+ dropdown.style.left = `${anchorRect.left + window.scrollX}px`;
635
+ requestAnimationFrame(() => {
636
+ const ddRect = dropdown.getBoundingClientRect();
637
+ if (ddRect.right > window.innerWidth - 4) {
638
+ dropdown.style.left = `${window.innerWidth - ddRect.width - 4 + window.scrollX}px`;
639
+ }
640
+ if (ddRect.left < 4) {
641
+ dropdown.style.left = `${4 + window.scrollX}px`;
642
+ }
643
+ if (ddRect.bottom > window.innerHeight) {
644
+ dropdown.style.top = `${anchorRect.top + window.scrollY - ddRect.height - 2}px`;
645
+ }
646
+ });
647
+ state.layerDropdown = dropdown;
648
+ reportDropdownState(state, true);
649
+ const handleKeydown = (e) => {
650
+ if (!state.layerDropdown) return;
651
+ switch (e.key) {
652
+ case "ArrowDown":
653
+ e.preventDefault();
654
+ focusedIndex = Math.min(focusedIndex + 1, items.length - 1);
655
+ highlightItem(items, focusedIndex, layers, currentElement);
656
+ break;
657
+ case "ArrowUp":
658
+ e.preventDefault();
659
+ focusedIndex = Math.max(focusedIndex - 1, 0);
660
+ highlightItem(items, focusedIndex, layers, currentElement);
661
+ break;
662
+ case "Enter":
663
+ e.preventDefault();
664
+ if (focusedIndex >= 0 && focusedIndex < layers.length) {
665
+ removeLayerDropdown(state);
666
+ onSelectElement(layers[focusedIndex].element);
667
+ }
668
+ break;
669
+ case "Escape":
670
+ e.preventDefault();
671
+ removeLayerDropdown(state);
672
+ break;
673
+ }
674
+ };
675
+ handleLayerKeyboard.current = handleKeydown;
676
+ document.addEventListener("keydown", handleLayerKeyboard);
677
+ }
678
+ function handleLayerKeyboard(e) {
679
+ const current = handleLayerKeyboard.current;
680
+ if (current) current(e);
681
+ }
682
+ function highlightItem(items, index, layers, currentElement) {
683
+ items.forEach((item, i) => {
684
+ if (layers[i].element === currentElement) {
685
+ item.style.color = "#000000";
686
+ item.style.backgroundColor = i === index ? "rgba(255, 204, 0, 0.35)" : "rgba(255, 204, 0, 0.2)";
687
+ item.style.fontWeight = "600";
688
+ } else {
689
+ item.style.backgroundColor = i === index ? "#f1f5f9" : "transparent";
690
+ item.style.color = i === index ? "#334155" : "#64748b";
691
+ item.style.fontWeight = "normal";
692
+ }
693
+ });
694
+ }
695
+
646
696
  // src/runtime/state.ts
647
697
  function createAgentState(options = {}) {
648
698
  return {
@@ -656,13 +706,36 @@ function createAgentState(options = {}) {
656
706
  layerDropdown: null,
657
707
  mutationObserver: null,
658
708
  attributeName: options.attributeName ?? DEFAULT_ATTRIBUTE_NAME,
659
- targetOrigin: options.targetOrigin ?? "*"
709
+ targetOrigin: options.targetOrigin ?? "*",
710
+ runtimeIdCounter: 0
660
711
  };
661
712
  }
662
713
 
663
714
  // src/runtime/index.ts
664
715
  function setupVisualEditAgent(options = {}) {
665
716
  const state = createAgentState(options);
717
+ const trackedSelector = `[${state.attributeName}], [data-visual-selector-id]`;
718
+ function shouldAssignRuntimeId(element) {
719
+ return !element.closest(`[${AGENT_ATTR}]`) && !element.hasAttribute(state.attributeName) && !element.hasAttribute("data-visual-selector-id");
720
+ }
721
+ function assignRuntimeId(element) {
722
+ if (!shouldAssignRuntimeId(element)) return;
723
+ state.runtimeIdCounter += 1;
724
+ element.setAttribute("data-visual-selector-id", `runtime:${state.runtimeIdCounter}`);
725
+ }
726
+ function trackElementSubtree(root) {
727
+ assignRuntimeId(root);
728
+ root.querySelectorAll("*").forEach((element) => assignRuntimeId(element));
729
+ }
730
+ function trackCurrentDom() {
731
+ if (!document.body) return;
732
+ trackElementSubtree(document.body);
733
+ }
734
+ function ensureElementTracked(element) {
735
+ if (!element.closest(`[${AGENT_ATTR}]`)) {
736
+ assignRuntimeId(element);
737
+ }
738
+ }
666
739
  function updateAllOverlayPositions() {
667
740
  if (state.selectedId && state.selectedElement?.isConnected) {
668
741
  const siblings = findAllElementsById(state.selectedId);
@@ -679,7 +752,7 @@ function setupVisualEditAgent(options = {}) {
679
752
  function onTagClick(target, tag) {
680
753
  const arrowEl = tag.querySelector("[data-tag-arrow]");
681
754
  if (arrowEl) {
682
- arrowEl.textContent = state.layerDropdown ? "\u2304" : "\u2303";
755
+ setTagArrowExpanded(arrowEl, !state.layerDropdown);
683
756
  }
684
757
  toggleLayerDropdown(state, target, tag, selectElement);
685
758
  }
@@ -690,7 +763,10 @@ function setupVisualEditAgent(options = {}) {
690
763
  if (freezeStyle) freezeStyle.disabled = false;
691
764
  if (!element) return null;
692
765
  if (element.closest(`[${AGENT_ATTR}]`)) return null;
693
- return element.closest(`[${state.attributeName}], [data-visual-selector-id]`) ?? null;
766
+ const trackedElement = element.closest(trackedSelector);
767
+ if (trackedElement) return trackedElement;
768
+ ensureElementTracked(element);
769
+ return element.hasAttribute("data-visual-selector-id") ? element : null;
694
770
  }
695
771
  function findHoverTarget(x, y, excludeId) {
696
772
  const element = findElementAtPoint(x, y);
@@ -738,6 +814,7 @@ function setupVisualEditAgent(options = {}) {
738
814
  selectElement(element);
739
815
  }
740
816
  function selectElement(element) {
817
+ ensureElementTracked(element);
741
818
  const id = getSourceId(element);
742
819
  if (!id) return;
743
820
  if (state.editingElement) {
@@ -773,6 +850,7 @@ function setupVisualEditAgent(options = {}) {
773
850
  if (enabled) {
774
851
  document.body.style.cursor = "crosshair";
775
852
  freezeAnimations();
853
+ trackCurrentDom();
776
854
  document.addEventListener("mousemove", onMouseMove);
777
855
  document.addEventListener("mouseleave", onMouseLeave);
778
856
  document.addEventListener("click", onClick, true);
@@ -799,6 +877,13 @@ function setupVisualEditAgent(options = {}) {
799
877
  function startMutationObserver() {
800
878
  if (state.mutationObserver) return;
801
879
  state.mutationObserver = new MutationObserver((mutations) => {
880
+ mutations.forEach((mutation) => {
881
+ mutation.addedNodes.forEach((node) => {
882
+ if (node instanceof Element) {
883
+ trackElementSubtree(node);
884
+ }
885
+ });
886
+ });
802
887
  const hasRelevantChange = mutations.some((m) => {
803
888
  if (m.type === "attributes" && ["style", "class", "width", "height"].includes(m.attributeName ?? "") && containsTrackedElement(m.target))
804
889
  return true;
@@ -824,8 +909,8 @@ function setupVisualEditAgent(options = {}) {
824
909
  }
825
910
  function containsTrackedElement(node) {
826
911
  if (!(node instanceof Element)) return false;
827
- if (node.hasAttribute(AGENT_ATTR)) return false;
828
- return hasSourceLocation(node) || !!node.querySelector(`[${state.attributeName}]`);
912
+ if (node.closest(`[${AGENT_ATTR}]`)) return false;
913
+ return node.hasAttribute(state.attributeName) || node.hasAttribute("data-visual-selector-id") || !!node.querySelector(trackedSelector);
829
914
  }
830
915
  function setupSandboxMountObserver() {
831
916
  if (window.self === window.top) return;
@@ -834,7 +919,9 @@ function setupVisualEditAgent(options = {}) {
834
919
  (m) => m.addedNodes.length > 0 || m.removedNodes.length > 0
835
920
  );
836
921
  if (!hasChanges) return;
837
- const hasTrackedElements = document.body.querySelectorAll("[data-source-location], [data-dynamic-content]").length > 0;
922
+ const hasTrackedElements = document.body.querySelectorAll(
923
+ `[${state.attributeName}], [data-dynamic-content], [data-visual-selector-id]`
924
+ ).length > 0;
838
925
  try {
839
926
  window.parent.postMessage(
840
927
  {
@@ -853,12 +940,25 @@ function setupVisualEditAgent(options = {}) {
853
940
  });
854
941
  }
855
942
  }
943
+ function selectElementById(id) {
944
+ const element = document.querySelector(
945
+ `[${state.attributeName}="${id}"], [data-visual-selector-id="${id}"]`
946
+ );
947
+ if (element) {
948
+ selectElement(element);
949
+ }
950
+ }
951
+ function clearSelectionState() {
952
+ clearAllOverlays(state, () => removeLayerDropdown(state));
953
+ }
856
954
  const onMessage = (e) => {
857
955
  handleMessage(state, e, {
858
956
  enableEditMode,
859
957
  updateAllOverlayPositions,
860
958
  stopInlineEditing: () => stopInlineEditing(state, updateAllOverlayPositions),
861
- handleInlineEdit: (data) => handleInlineEdit(state, data, updateAllOverlayPositions)
959
+ handleInlineEdit: (data) => handleInlineEdit(state, data, updateAllOverlayPositions),
960
+ selectElementById,
961
+ clearSelectionState
862
962
  });
863
963
  };
864
964
  window.addEventListener("message", onMessage);
@@ -877,7 +977,7 @@ function setupVisualEditAgent(options = {}) {
877
977
  enableEditMode,
878
978
  selectElement,
879
979
  clearSelection() {
880
- clearAllOverlays(state, () => removeLayerDropdown(state));
980
+ clearSelectionState();
881
981
  },
882
982
  getSelectedId() {
883
983
  return state.selectedId;