@base44-preview/vite-plugin 0.2.27-pr.41.77be9cb → 0.2.27-pr.43.93e5e43

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.
Files changed (149) hide show
  1. package/dist/consts.d.ts +12 -0
  2. package/dist/consts.d.ts.map +1 -0
  3. package/dist/consts.js +12 -0
  4. package/dist/consts.js.map +1 -0
  5. package/dist/injections/visual-edit-agent.d.ts +1 -1
  6. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  7. package/dist/injections/visual-edit-agent.js +566 -1
  8. package/dist/injections/visual-edit-agent.js.map +1 -1
  9. package/dist/jsx-processor.d.ts +4 -1
  10. package/dist/jsx-processor.d.ts.map +1 -1
  11. package/dist/jsx-processor.js +33 -6
  12. package/dist/jsx-processor.js.map +1 -1
  13. package/dist/jsx-utils.d.ts +9 -0
  14. package/dist/jsx-utils.d.ts.map +1 -1
  15. package/dist/jsx-utils.js +86 -0
  16. package/dist/jsx-utils.js.map +1 -1
  17. package/dist/processors/collection-id-processor.d.ts +20 -0
  18. package/dist/processors/collection-id-processor.d.ts.map +1 -0
  19. package/dist/processors/collection-id-processor.js +182 -0
  20. package/dist/processors/collection-id-processor.js.map +1 -0
  21. package/dist/processors/collection-item-field-processor.d.ts +39 -0
  22. package/dist/processors/collection-item-field-processor.d.ts.map +1 -0
  23. package/dist/processors/collection-item-field-processor.js +281 -0
  24. package/dist/processors/collection-item-field-processor.js.map +1 -0
  25. package/dist/processors/collection-item-id-processor.d.ts +12 -0
  26. package/dist/processors/collection-item-id-processor.d.ts.map +1 -0
  27. package/dist/processors/collection-item-id-processor.js +50 -0
  28. package/dist/processors/collection-item-id-processor.js.map +1 -0
  29. package/dist/processors/collection-reference-field-processor.d.ts +31 -0
  30. package/dist/processors/collection-reference-field-processor.d.ts.map +1 -0
  31. package/dist/processors/collection-reference-field-processor.js +174 -0
  32. package/dist/processors/collection-reference-field-processor.js.map +1 -0
  33. package/dist/processors/collection-tracing-utils.d.ts +36 -0
  34. package/dist/processors/collection-tracing-utils.d.ts.map +1 -0
  35. package/dist/processors/collection-tracing-utils.js +390 -0
  36. package/dist/processors/collection-tracing-utils.js.map +1 -0
  37. package/dist/processors/shared-utils.d.ts +77 -0
  38. package/dist/processors/shared-utils.d.ts.map +1 -1
  39. package/dist/processors/shared-utils.js +525 -0
  40. package/dist/processors/shared-utils.js.map +1 -1
  41. package/dist/processors/static-array-processor.d.ts +2 -3
  42. package/dist/processors/static-array-processor.d.ts.map +1 -1
  43. package/dist/processors/static-array-processor.js +2 -3
  44. package/dist/processors/static-array-processor.js.map +1 -1
  45. package/dist/statics/index.mjs +2 -2
  46. package/dist/statics/index.mjs.map +1 -1
  47. package/dist/types.d.ts +5 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/visual-edit-plugin.d.ts +0 -1
  52. package/dist/visual-edit-plugin.d.ts.map +1 -1
  53. package/dist/visual-edit-plugin.js +29 -178
  54. package/dist/visual-edit-plugin.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/consts.ts +12 -0
  57. package/src/injections/visual-edit-agent.ts +698 -1
  58. package/src/jsx-processor.ts +41 -14
  59. package/src/jsx-utils.ts +116 -0
  60. package/src/processors/collection-id-processor.ts +261 -0
  61. package/src/processors/collection-item-field-processor.ts +433 -0
  62. package/src/processors/collection-item-id-processor.ts +69 -0
  63. package/src/processors/collection-reference-field-processor.ts +225 -0
  64. package/src/processors/collection-tracing-utils.ts +507 -0
  65. package/src/processors/shared-utils.ts +671 -0
  66. package/src/processors/static-array-processor.ts +6 -3
  67. package/src/types.ts +4 -0
  68. package/src/visual-edit-plugin.ts +34 -215
  69. package/dist/injections/visual-edit-agent/capabilities/inline-editing/core.d.ts +0 -25
  70. package/dist/injections/visual-edit-agent/capabilities/inline-editing/core.d.ts.map +0 -1
  71. package/dist/injections/visual-edit-agent/capabilities/inline-editing/core.js +0 -95
  72. package/dist/injections/visual-edit-agent/capabilities/inline-editing/core.js.map +0 -1
  73. package/dist/injections/visual-edit-agent/capabilities/inline-editing/index.d.ts +0 -4
  74. package/dist/injections/visual-edit-agent/capabilities/inline-editing/index.d.ts.map +0 -1
  75. package/dist/injections/visual-edit-agent/capabilities/inline-editing/index.js +0 -4
  76. package/dist/injections/visual-edit-agent/capabilities/inline-editing/index.js.map +0 -1
  77. package/dist/injections/visual-edit-agent/capabilities/inline-editing/styles.d.ts +0 -9
  78. package/dist/injections/visual-edit-agent/capabilities/inline-editing/styles.d.ts.map +0 -1
  79. package/dist/injections/visual-edit-agent/capabilities/inline-editing/styles.js +0 -26
  80. package/dist/injections/visual-edit-agent/capabilities/inline-editing/styles.js.map +0 -1
  81. package/dist/injections/visual-edit-agent/capabilities/inline-editing/validation.d.ts +0 -17
  82. package/dist/injections/visual-edit-agent/capabilities/inline-editing/validation.d.ts.map +0 -1
  83. package/dist/injections/visual-edit-agent/capabilities/inline-editing/validation.js +0 -76
  84. package/dist/injections/visual-edit-agent/capabilities/inline-editing/validation.js.map +0 -1
  85. package/dist/injections/visual-edit-agent/constants.d.ts +0 -10
  86. package/dist/injections/visual-edit-agent/constants.d.ts.map +0 -1
  87. package/dist/injections/visual-edit-agent/constants.js +0 -10
  88. package/dist/injections/visual-edit-agent/constants.js.map +0 -1
  89. package/dist/injections/visual-edit-agent/handlers/click-handlers.d.ts +0 -10
  90. package/dist/injections/visual-edit-agent/handlers/click-handlers.d.ts.map +0 -1
  91. package/dist/injections/visual-edit-agent/handlers/click-handlers.js +0 -117
  92. package/dist/injections/visual-edit-agent/handlers/click-handlers.js.map +0 -1
  93. package/dist/injections/visual-edit-agent/handlers/hover-handlers.d.ts +0 -14
  94. package/dist/injections/visual-edit-agent/handlers/hover-handlers.d.ts.map +0 -1
  95. package/dist/injections/visual-edit-agent/handlers/hover-handlers.js +0 -64
  96. package/dist/injections/visual-edit-agent/handlers/hover-handlers.js.map +0 -1
  97. package/dist/injections/visual-edit-agent/handlers/inline-edit-handlers.d.ts +0 -14
  98. package/dist/injections/visual-edit-agent/handlers/inline-edit-handlers.d.ts.map +0 -1
  99. package/dist/injections/visual-edit-agent/handlers/inline-edit-handlers.js +0 -114
  100. package/dist/injections/visual-edit-agent/handlers/inline-edit-handlers.js.map +0 -1
  101. package/dist/injections/visual-edit-agent/handlers/message-handlers.d.ts +0 -26
  102. package/dist/injections/visual-edit-agent/handlers/message-handlers.d.ts.map +0 -1
  103. package/dist/injections/visual-edit-agent/handlers/message-handlers.js +0 -148
  104. package/dist/injections/visual-edit-agent/handlers/message-handlers.js.map +0 -1
  105. package/dist/injections/visual-edit-agent/handlers/messages/toggle-inline-edit-mode.d.ts +0 -7
  106. package/dist/injections/visual-edit-agent/handlers/messages/toggle-inline-edit-mode.d.ts.map +0 -1
  107. package/dist/injections/visual-edit-agent/handlers/messages/toggle-inline-edit-mode.js +0 -54
  108. package/dist/injections/visual-edit-agent/handlers/messages/toggle-inline-edit-mode.js.map +0 -1
  109. package/dist/injections/visual-edit-agent/handlers/messages/toggle-visual-edit-mode.d.ts +0 -11
  110. package/dist/injections/visual-edit-agent/handlers/messages/toggle-visual-edit-mode.d.ts.map +0 -1
  111. package/dist/injections/visual-edit-agent/handlers/messages/toggle-visual-edit-mode.js +0 -32
  112. package/dist/injections/visual-edit-agent/handlers/messages/toggle-visual-edit-mode.js.map +0 -1
  113. package/dist/injections/visual-edit-agent/handlers/messages/types.d.ts +0 -105
  114. package/dist/injections/visual-edit-agent/handlers/messages/types.d.ts.map +0 -1
  115. package/dist/injections/visual-edit-agent/handlers/messages/types.js +0 -28
  116. package/dist/injections/visual-edit-agent/handlers/messages/types.js.map +0 -1
  117. package/dist/injections/visual-edit-agent/index.d.ts +0 -6
  118. package/dist/injections/visual-edit-agent/index.d.ts.map +0 -1
  119. package/dist/injections/visual-edit-agent/index.js +0 -109
  120. package/dist/injections/visual-edit-agent/index.js.map +0 -1
  121. package/dist/injections/visual-edit-agent/state/agent-state.d.ts +0 -17
  122. package/dist/injections/visual-edit-agent/state/agent-state.d.ts.map +0 -1
  123. package/dist/injections/visual-edit-agent/state/agent-state.js +0 -18
  124. package/dist/injections/visual-edit-agent/state/agent-state.js.map +0 -1
  125. package/dist/injections/visual-edit-agent/ui/overlay.d.ts +0 -26
  126. package/dist/injections/visual-edit-agent/ui/overlay.d.ts.map +0 -1
  127. package/dist/injections/visual-edit-agent/ui/overlay.js +0 -104
  128. package/dist/injections/visual-edit-agent/ui/overlay.js.map +0 -1
  129. package/dist/injections/visual-edit-agent/utils/dom-utils.d.ts +0 -14
  130. package/dist/injections/visual-edit-agent/utils/dom-utils.d.ts.map +0 -1
  131. package/dist/injections/visual-edit-agent/utils/dom-utils.js +0 -34
  132. package/dist/injections/visual-edit-agent/utils/dom-utils.js.map +0 -1
  133. package/src/injections/visual-edit-agent/README.md +0 -222
  134. package/src/injections/visual-edit-agent/capabilities/inline-editing/core.ts +0 -120
  135. package/src/injections/visual-edit-agent/capabilities/inline-editing/index.ts +0 -10
  136. package/src/injections/visual-edit-agent/capabilities/inline-editing/styles.ts +0 -26
  137. package/src/injections/visual-edit-agent/capabilities/inline-editing/validation.ts +0 -87
  138. package/src/injections/visual-edit-agent/constants.ts +0 -9
  139. package/src/injections/visual-edit-agent/handlers/click-handlers.ts +0 -146
  140. package/src/injections/visual-edit-agent/handlers/hover-handlers.ts +0 -78
  141. package/src/injections/visual-edit-agent/handlers/inline-edit-handlers.ts +0 -146
  142. package/src/injections/visual-edit-agent/handlers/message-handlers.ts +0 -201
  143. package/src/injections/visual-edit-agent/handlers/messages/toggle-inline-edit-mode.ts +0 -65
  144. package/src/injections/visual-edit-agent/handlers/messages/toggle-visual-edit-mode.ts +0 -40
  145. package/src/injections/visual-edit-agent/handlers/messages/types.ts +0 -134
  146. package/src/injections/visual-edit-agent/index.ts +0 -121
  147. package/src/injections/visual-edit-agent/state/agent-state.ts +0 -31
  148. package/src/injections/visual-edit-agent/ui/overlay.ts +0 -126
  149. package/src/injections/visual-edit-agent/utils/dom-utils.ts +0 -39
@@ -1 +1,698 @@
1
- export { setupVisualEditAgent } from "./visual-edit-agent/index.js";
1
+ import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
2
+ import { createLayerController } from "./layer-dropdown/controller.js";
3
+ import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
+ import { createInlineEditController } from "../capabilities/inline-edit/index.js";
5
+
6
+ export function setupVisualEditAgent() {
7
+ // State variables (replacing React useState/useRef)
8
+ let isVisualEditMode = false;
9
+ let isPopoverDragging = false;
10
+ let isDropdownOpen = false;
11
+ let hoverOverlays: HTMLDivElement[] = [];
12
+ let selectedOverlays: HTMLDivElement[] = [];
13
+ let currentHighlightedElements: Element[] = [];
14
+ let selectedElementId: string | null = null;
15
+
16
+ const REPOSITION_DELAY_MS = 50;
17
+
18
+ // Create overlay element
19
+ const createOverlay = (isSelected = false): HTMLDivElement => {
20
+ const overlay = document.createElement("div");
21
+ overlay.style.position = "absolute";
22
+ overlay.style.pointerEvents = "none";
23
+ overlay.style.transition = "all 0.1s ease-in-out";
24
+ overlay.style.zIndex = "9999";
25
+
26
+ if (isSelected) {
27
+ overlay.style.border = "2px solid #2563EB";
28
+ } else {
29
+ overlay.style.border = "2px solid #95a5fc";
30
+ overlay.style.backgroundColor = "rgba(99, 102, 241, 0.05)";
31
+ }
32
+
33
+ return overlay;
34
+ };
35
+
36
+ // Position overlay relative to element
37
+ const positionOverlay = (
38
+ overlay: HTMLDivElement,
39
+ element: Element,
40
+ isSelected = false
41
+ ) => {
42
+ if (!element || !isVisualEditMode) return;
43
+
44
+ const htmlElement = element as HTMLElement;
45
+ // Force layout recalculation
46
+ void htmlElement.offsetWidth;
47
+
48
+ const rect = element.getBoundingClientRect();
49
+ overlay.style.top = `${rect.top + window.scrollY}px`;
50
+ overlay.style.left = `${rect.left + window.scrollX}px`;
51
+ overlay.style.width = `${rect.width}px`;
52
+ overlay.style.height = `${rect.height}px`;
53
+
54
+ // Check if label already exists in overlay
55
+ let label = overlay.querySelector("div") as HTMLDivElement | null;
56
+
57
+ if (!label) {
58
+ label = document.createElement("div");
59
+ label.textContent = element.tagName.toLowerCase();
60
+ label.style.position = "absolute";
61
+ label.style.top = "-27px";
62
+ label.style.left = "-2px";
63
+ label.style.padding = "2px 8px";
64
+ label.style.fontSize = "11px";
65
+ label.style.fontWeight = isSelected ? "500" : "400";
66
+ label.style.color = isSelected ? "#ffffff" : "#526cff";
67
+ label.style.backgroundColor = isSelected ? "#526cff" : "#DBEAFE";
68
+ label.style.borderRadius = "3px";
69
+ label.style.minWidth = "24px";
70
+ label.style.textAlign = "center";
71
+ overlay.appendChild(label);
72
+ }
73
+ };
74
+
75
+ // --- Inline edit controller ---
76
+ const inlineEdit = createInlineEditController({
77
+ findElementsById,
78
+ getSelectedElementId: () => selectedElementId,
79
+ getSelectedOverlays: () => selectedOverlays,
80
+ positionOverlay,
81
+ clearSelection: () => {
82
+ inlineEdit.clearSelectedMarks(selectedElementId);
83
+ clearSelectedOverlays();
84
+ selectedElementId = null;
85
+ },
86
+ createSelectionOverlays: (elements, elementId) => {
87
+ elements.forEach((el) => {
88
+ const overlay = createOverlay(true);
89
+ document.body.appendChild(overlay);
90
+ selectedOverlays.push(overlay);
91
+ positionOverlay(overlay, el, true);
92
+ });
93
+ selectedElementId = elementId;
94
+ },
95
+ });
96
+
97
+ const clearSelection = () => {
98
+ inlineEdit.clearSelectedMarks(selectedElementId);
99
+ clearSelectedOverlays();
100
+ selectedElementId = null;
101
+ };
102
+
103
+ // Clear hover overlays
104
+ const clearHoverOverlays = () => {
105
+ hoverOverlays.forEach((overlay) => {
106
+ if (overlay && overlay.parentNode) {
107
+ overlay.remove();
108
+ }
109
+ });
110
+ hoverOverlays = [];
111
+ currentHighlightedElements = [];
112
+ };
113
+
114
+ const clearSelectedOverlays = () => {
115
+ selectedOverlays.forEach((overlay) => {
116
+ if (overlay && overlay.parentNode) {
117
+ overlay.remove();
118
+ }
119
+ });
120
+ selectedOverlays = [];
121
+ };
122
+
123
+ const TEXT_TAGS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'];
124
+
125
+ const notifyElementSelected = (element: Element) => {
126
+ const htmlElement = element as HTMLElement;
127
+ const rect = element.getBoundingClientRect();
128
+ const svgElement = element as SVGElement;
129
+ const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
130
+ window.parent.postMessage({
131
+ type: "element-selected",
132
+ tagName: element.tagName,
133
+ classes:
134
+ (svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
135
+ element.className ||
136
+ "",
137
+ visualSelectorId: getElementSelectorId(element),
138
+ content: isTextElement ? htmlElement.innerText : undefined,
139
+ dataSourceLocation: htmlElement.dataset.sourceLocation,
140
+ isDynamicContent: htmlElement.dataset.dynamicContent === "true",
141
+ linenumber: htmlElement.dataset.linenumber,
142
+ filename: htmlElement.dataset.filename,
143
+ position: {
144
+ top: rect.top,
145
+ left: rect.left,
146
+ right: rect.right,
147
+ bottom: rect.bottom,
148
+ width: rect.width,
149
+ height: rect.height,
150
+ centerX: rect.left + rect.width / 2,
151
+ centerY: rect.top + rect.height / 2,
152
+ },
153
+ attributes: collectAllowedAttributes(element, ALLOWED_ATTRIBUTES),
154
+ isTextElement,
155
+ }, "*");
156
+ };
157
+
158
+ // Select an element: create overlays, update state, notify parent
159
+ const selectElement = (element: Element): HTMLDivElement | undefined => {
160
+ const visualSelectorId = getElementSelectorId(element);
161
+
162
+ clearSelectedOverlays();
163
+
164
+ const elements = findElementsById(visualSelectorId || null);
165
+ elements.forEach((el) => {
166
+ const overlay = createOverlay(true);
167
+ document.body.appendChild(overlay);
168
+ selectedOverlays.push(overlay);
169
+ positionOverlay(overlay, el, true);
170
+ });
171
+
172
+ selectedElementId = visualSelectorId || null;
173
+ clearHoverOverlays();
174
+ notifyElementSelected(element);
175
+
176
+ return selectedOverlays[0];
177
+ };
178
+
179
+ const notifyDeselection = (): void => {
180
+ selectedElementId = null;
181
+ window.parent.postMessage({ type: "unselect-element" }, "*");
182
+ };
183
+
184
+ // Handle mouse over event
185
+ const handleMouseOver = (e: MouseEvent) => {
186
+ if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
187
+
188
+
189
+ const target = e.target as Element;
190
+
191
+ // Prevent hover effects when a dropdown is open
192
+ if (isDropdownOpen) {
193
+ clearHoverOverlays();
194
+ return;
195
+ }
196
+
197
+ // Prevent hover effects on SVG path elements
198
+ if (target.tagName.toLowerCase() === "path") {
199
+ clearHoverOverlays();
200
+ return;
201
+ }
202
+
203
+ // Support both data-source-location and data-visual-selector-id
204
+ const element = target.closest(
205
+ "[data-source-location], [data-visual-selector-id]"
206
+ );
207
+ if (!element) {
208
+ clearHoverOverlays();
209
+ return;
210
+ }
211
+
212
+ // Prefer data-source-location, fallback to data-visual-selector-id
213
+ const htmlElement = element as HTMLElement;
214
+ const selectorId =
215
+ htmlElement.dataset.sourceLocation ||
216
+ htmlElement.dataset.visualSelectorId;
217
+
218
+ // Skip if this element is already selected
219
+ if (selectedElementId === selectorId) {
220
+ clearHoverOverlays();
221
+ return;
222
+ }
223
+
224
+ // Find all elements with the same ID
225
+ const elements = findElementsById(selectorId || null);
226
+
227
+ // Clear previous hover overlays
228
+ clearHoverOverlays();
229
+
230
+ // Create overlays for all matching elements
231
+ elements.forEach((el) => {
232
+ const overlay = createOverlay(false);
233
+ document.body.appendChild(overlay);
234
+ hoverOverlays.push(overlay);
235
+ positionOverlay(overlay, el);
236
+ });
237
+
238
+ currentHighlightedElements = elements;
239
+ };
240
+
241
+ // Handle mouse out event
242
+ const handleMouseOut = () => {
243
+ if (isPopoverDragging) return;
244
+ clearHoverOverlays();
245
+ };
246
+
247
+ // Handle element click
248
+ const handleElementClick = (e: MouseEvent) => {
249
+ if (!isVisualEditMode) return;
250
+
251
+ const target = e.target as Element;
252
+
253
+ // Let layer dropdown clicks pass through without interference
254
+ if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
255
+
256
+ // Let clicks inside the editable element pass through to the browser
257
+ // so the user can reposition the cursor and select text naturally.
258
+ if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
259
+ return;
260
+ }
261
+
262
+ // Clicking outside the editable element exits inline editing mode.
263
+ if (inlineEdit.isEditing()) {
264
+ e.preventDefault();
265
+ e.stopPropagation();
266
+ e.stopImmediatePropagation();
267
+ inlineEdit.stopEditing();
268
+ return;
269
+ }
270
+
271
+ // Close dropdowns when clicking anywhere in iframe if a dropdown is open
272
+ if (isDropdownOpen) {
273
+ e.preventDefault();
274
+ e.stopPropagation();
275
+ e.stopImmediatePropagation();
276
+
277
+ window.parent.postMessage({ type: "close-dropdowns" }, "*");
278
+ return;
279
+ }
280
+
281
+ // Prevent clicking on SVG path elements
282
+ if (target.tagName.toLowerCase() === "path") {
283
+ return;
284
+ }
285
+
286
+ // Prevent default behavior immediately when in visual edit mode
287
+ e.preventDefault();
288
+ e.stopPropagation();
289
+ e.stopImmediatePropagation();
290
+
291
+ // Support both data-source-location and data-visual-selector-id
292
+ const element = target.closest(
293
+ "[data-source-location], [data-visual-selector-id]"
294
+ );
295
+ if (!element) {
296
+ return;
297
+ }
298
+
299
+ const htmlElement = element as HTMLElement;
300
+ const visualSelectorId = getElementSelectorId(element);
301
+
302
+ const isAlreadySelected =
303
+ selectedElementId === visualSelectorId &&
304
+ htmlElement.dataset.selected === "true";
305
+
306
+ if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
307
+ inlineEdit.startEditing(htmlElement);
308
+ return;
309
+ }
310
+
311
+ inlineEdit.stopEditing();
312
+
313
+ if (inlineEdit.enabled) {
314
+ inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
315
+ }
316
+
317
+ const selectedOverlay = selectElement(element);
318
+ layerController.attachToOverlay(selectedOverlay, element);
319
+ };
320
+
321
+ const unselectElement = () => {
322
+ inlineEdit.stopEditing();
323
+ clearSelection();
324
+ };
325
+
326
+ const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
327
+ const elements = findElementsById(visualSelectorId);
328
+ if (elements.length === 0) return;
329
+
330
+ updateElementClasses(elements, classes);
331
+
332
+ // Use a small delay to allow the browser to recalculate layout before repositioning
333
+ setTimeout(() => {
334
+ // Reposition selected overlays
335
+ if (selectedElementId === visualSelectorId) {
336
+ selectedOverlays.forEach((overlay, index) => {
337
+ if (index < elements.length) {
338
+ positionOverlay(overlay, elements[index]!);
339
+ }
340
+ });
341
+ }
342
+
343
+ // Reposition hover overlays if needed
344
+ if (currentHighlightedElements.length > 0) {
345
+ const hoveredElement = currentHighlightedElements[0] as HTMLElement;
346
+ const hoveredId = hoveredElement?.dataset?.visualSelectorId;
347
+ if (hoveredId === visualSelectorId) {
348
+ hoverOverlays.forEach((overlay, index) => {
349
+ if (index < currentHighlightedElements.length) {
350
+ positionOverlay(overlay, currentHighlightedElements[index]!);
351
+ }
352
+ });
353
+ }
354
+ }
355
+ }, REPOSITION_DELAY_MS);
356
+ };
357
+
358
+ // Update element attribute by visual selector ID
359
+ const updateElementAttributeAndReposition = (
360
+ visualSelectorId: string,
361
+ attribute: string,
362
+ value: string
363
+ ) => {
364
+ const elements = findElementsById(visualSelectorId);
365
+ if (elements.length === 0) return;
366
+
367
+ updateElementAttribute(elements, attribute, value);
368
+
369
+ // Reposition overlays after attribute change (e.g. image src swap can affect layout)
370
+ setTimeout(() => {
371
+ if (selectedElementId === visualSelectorId) {
372
+ selectedOverlays.forEach((overlay, index) => {
373
+ if (index < elements.length) {
374
+ positionOverlay(overlay, elements[index]!);
375
+ }
376
+ });
377
+ }
378
+ }, REPOSITION_DELAY_MS);
379
+ };
380
+
381
+ // Update element content by visual selector ID
382
+ const updateElementContent = (visualSelectorId: string, content: string) => {
383
+ const elements = findElementsById(visualSelectorId);
384
+
385
+ if (elements.length === 0) {
386
+ return;
387
+ }
388
+
389
+ elements.forEach((element) => {
390
+ (element as HTMLElement).innerText = content;
391
+ });
392
+
393
+ setTimeout(() => {
394
+ if (selectedElementId === visualSelectorId) {
395
+ selectedOverlays.forEach((overlay, index) => {
396
+ if (index < elements.length) {
397
+ positionOverlay(overlay, elements[index]!);
398
+ }
399
+ });
400
+ }
401
+ }, REPOSITION_DELAY_MS);
402
+ };
403
+
404
+ // --- Layer dropdown controller ---
405
+ const layerController = createLayerController({
406
+ createPreviewOverlay: (element: Element) => {
407
+ const overlay = createOverlay(false);
408
+ overlay.style.zIndex = "9998";
409
+ document.body.appendChild(overlay);
410
+ positionOverlay(overlay, element);
411
+ return overlay;
412
+ },
413
+ getSelectedElementId: () => selectedElementId,
414
+ selectElement,
415
+ onDeselect: notifyDeselection,
416
+ });
417
+
418
+ // Toggle visual edit mode
419
+ const toggleVisualEditMode = (isEnabled: boolean) => {
420
+ isVisualEditMode = isEnabled;
421
+
422
+ if (!isEnabled) {
423
+ inlineEdit.stopEditing();
424
+ clearSelection();
425
+ layerController.cleanup();
426
+ clearHoverOverlays();
427
+
428
+ currentHighlightedElements = [];
429
+ document.body.style.cursor = "default";
430
+
431
+ document.removeEventListener("mouseover", handleMouseOver);
432
+ document.removeEventListener("mouseout", handleMouseOut);
433
+ document.removeEventListener("click", handleElementClick, true);
434
+ } else {
435
+ document.body.style.cursor = "crosshair";
436
+ document.addEventListener("mouseover", handleMouseOver);
437
+ document.addEventListener("mouseout", handleMouseOut);
438
+ document.addEventListener("click", handleElementClick, true);
439
+ }
440
+ };
441
+
442
+ // Handle scroll events to update popover position
443
+ const handleScroll = () => {
444
+ if (selectedElementId) {
445
+ const elements = findElementsById(selectedElementId);
446
+ if (elements.length > 0) {
447
+ const element = elements[0];
448
+ const rect = element!.getBoundingClientRect();
449
+
450
+ const viewportHeight = window.innerHeight;
451
+ const viewportWidth = window.innerWidth;
452
+ const isInViewport =
453
+ rect.top < viewportHeight &&
454
+ rect.bottom > 0 &&
455
+ rect.left < viewportWidth &&
456
+ rect.right > 0;
457
+
458
+ const elementPosition = {
459
+ top: rect.top,
460
+ left: rect.left,
461
+ right: rect.right,
462
+ bottom: rect.bottom,
463
+ width: rect.width,
464
+ height: rect.height,
465
+ centerX: rect.left + rect.width / 2,
466
+ centerY: rect.top + rect.height / 2,
467
+ };
468
+
469
+ window.parent.postMessage(
470
+ {
471
+ type: "element-position-update",
472
+ position: elementPosition,
473
+ isInViewport: isInViewport,
474
+ visualSelectorId: selectedElementId,
475
+ },
476
+ "*"
477
+ );
478
+ }
479
+ }
480
+ };
481
+
482
+ // Handle messages from parent window
483
+ const handleMessage = (event: MessageEvent) => {
484
+ const message = event.data;
485
+
486
+ switch (message.type) {
487
+ case "toggle-visual-edit-mode":
488
+ toggleVisualEditMode(message.data.enabled);
489
+ if (message.data.specs?.newInlineEditEnabled !== undefined) {
490
+ inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
491
+ }
492
+ break;
493
+
494
+ case "update-classes":
495
+ if (message.data && message.data.classes !== undefined) {
496
+ updateElementClassesAndReposition(
497
+ message.data.visualSelectorId,
498
+ message.data.classes
499
+ );
500
+ } else {
501
+ console.warn(
502
+ "[VisualEditAgent] Invalid update-classes message:",
503
+ message
504
+ );
505
+ }
506
+ break;
507
+
508
+ case "update-attribute":
509
+ if (
510
+ message.data &&
511
+ message.data.visualSelectorId &&
512
+ message.data.attribute !== undefined &&
513
+ message.data.value !== undefined
514
+ ) {
515
+ updateElementAttributeAndReposition(
516
+ message.data.visualSelectorId,
517
+ message.data.attribute,
518
+ message.data.value
519
+ );
520
+ } else {
521
+ console.warn(
522
+ "[VisualEditAgent] Invalid update-attribute message:",
523
+ message
524
+ );
525
+ }
526
+ break;
527
+
528
+ case "unselect-element":
529
+ unselectElement();
530
+ break;
531
+
532
+ case "refresh-page":
533
+ window.location.reload();
534
+ break;
535
+
536
+ case "update-content":
537
+ if (message.data && message.data.content !== undefined) {
538
+ updateElementContent(
539
+ message.data.visualSelectorId,
540
+ message.data.content
541
+ );
542
+ } else {
543
+ console.warn(
544
+ "[VisualEditAgent] Invalid update-content message:",
545
+ message
546
+ );
547
+ }
548
+ break;
549
+
550
+ case "request-element-position":
551
+ if (selectedElementId) {
552
+ const elements = findElementsById(selectedElementId);
553
+ if (elements.length > 0) {
554
+ const element = elements[0];
555
+ const rect = element!.getBoundingClientRect();
556
+
557
+ const viewportHeight = window.innerHeight;
558
+ const viewportWidth = window.innerWidth;
559
+ const isInViewport =
560
+ rect.top < viewportHeight &&
561
+ rect.bottom > 0 &&
562
+ rect.left < viewportWidth &&
563
+ rect.right > 0;
564
+
565
+ const elementPosition = {
566
+ top: rect.top,
567
+ left: rect.left,
568
+ right: rect.right,
569
+ bottom: rect.bottom,
570
+ width: rect.width,
571
+ height: rect.height,
572
+ centerX: rect.left + rect.width / 2,
573
+ centerY: rect.top + rect.height / 2,
574
+ };
575
+
576
+ window.parent.postMessage(
577
+ {
578
+ type: "element-position-update",
579
+ position: elementPosition,
580
+ isInViewport: isInViewport,
581
+ visualSelectorId: selectedElementId,
582
+ },
583
+ "*"
584
+ );
585
+ }
586
+ }
587
+ break;
588
+
589
+ case "popover-drag-state":
590
+ if (message.data && message.data.isDragging !== undefined) {
591
+ isPopoverDragging = message.data.isDragging;
592
+ if (message.data.isDragging) {
593
+ clearHoverOverlays();
594
+ }
595
+ }
596
+ break;
597
+
598
+ case "dropdown-state":
599
+ if (message.data && message.data.isOpen !== undefined) {
600
+ isDropdownOpen = message.data.isOpen;
601
+ if (message.data.isOpen) {
602
+ clearHoverOverlays();
603
+ }
604
+ }
605
+ break;
606
+
607
+ case "toggle-inline-edit-mode":
608
+ if (message.data) {
609
+ inlineEdit.handleToggleMessage(message.data);
610
+ }
611
+ break;
612
+
613
+ default:
614
+ break;
615
+ }
616
+ };
617
+
618
+ // Handle window resize to reposition overlays
619
+ const handleResize = () => {
620
+ if (selectedElementId) {
621
+ const elements = findElementsById(selectedElementId);
622
+ selectedOverlays.forEach((overlay, index) => {
623
+ if (index < elements.length) {
624
+ positionOverlay(overlay, elements[index]!);
625
+ }
626
+ });
627
+ }
628
+
629
+ if (currentHighlightedElements.length > 0) {
630
+ hoverOverlays.forEach((overlay, index) => {
631
+ if (index < currentHighlightedElements.length) {
632
+ positionOverlay(overlay, currentHighlightedElements[index]!);
633
+ }
634
+ });
635
+ }
636
+ };
637
+
638
+ // Initialize: Add IDs to elements that don't have them but have linenumbers
639
+ const elementsWithLineNumber = document.querySelectorAll(
640
+ "[data-linenumber]:not([data-visual-selector-id])"
641
+ );
642
+ elementsWithLineNumber.forEach((el, index) => {
643
+ const htmlEl = el as HTMLElement;
644
+ const id = `visual-id-${htmlEl.dataset.filename}-${htmlEl.dataset.linenumber}-${index}`;
645
+ htmlEl.dataset.visualSelectorId = id;
646
+ });
647
+
648
+ // Create mutation observer to detect layout changes
649
+ const mutationObserver = new MutationObserver((mutations) => {
650
+ const needsUpdate = mutations.some((mutation) => {
651
+ const hasVisualId = (node: Node): boolean => {
652
+ if (node.nodeType === Node.ELEMENT_NODE) {
653
+ const el = node as HTMLElement;
654
+ if (el.dataset && el.dataset.visualSelectorId) {
655
+ return true;
656
+ }
657
+ for (let i = 0; i < el.children.length; i++) {
658
+ if (hasVisualId(el.children[i]!)) {
659
+ return true;
660
+ }
661
+ }
662
+ }
663
+ return false;
664
+ };
665
+
666
+ const isLayoutChange =
667
+ mutation.type === "attributes" &&
668
+ (mutation.attributeName === "style" ||
669
+ mutation.attributeName === "class" ||
670
+ mutation.attributeName === "width" ||
671
+ mutation.attributeName === "height");
672
+
673
+ return isLayoutChange && hasVisualId(mutation.target);
674
+ });
675
+
676
+ if (needsUpdate) {
677
+ setTimeout(handleResize, REPOSITION_DELAY_MS);
678
+ }
679
+ });
680
+
681
+ // Set up event listeners
682
+ window.addEventListener("message", handleMessage);
683
+ window.addEventListener("scroll", handleScroll, true);
684
+ document.addEventListener("scroll", handleScroll, true);
685
+ window.addEventListener("resize", handleResize);
686
+ window.addEventListener("scroll", handleResize);
687
+
688
+ // Start observing DOM mutations
689
+ mutationObserver.observe(document.body, {
690
+ attributes: true,
691
+ childList: true,
692
+ subtree: true,
693
+ attributeFilter: ["style", "class", "width", "height"],
694
+ });
695
+
696
+ // Send ready message to parent
697
+ window.parent.postMessage({ type: "visual-edit-agent-ready" }, "*");
698
+ }