@depup/base44__vite-plugin 1.0.4-depup.0

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