@base44-preview/vite-plugin 0.2.27-pr.41.77be9cb → 0.2.27-pr.44.489d834

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