@base44-preview/vite-plugin 0.2.28-pr.43.f34d1cd → 0.2.29-pr.45.4649f89

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.
@@ -18,6 +18,8 @@ export function getElementSelectorId(element: Element): string | null {
18
18
 
19
19
  export const ALLOWED_ATTRIBUTES: string[] = ["src"];
20
20
 
21
+ export const PLUGIN_ELEMENT_ATTR = "data-vite-plugin-element";
22
+
21
23
  /** Find elements by ID - first try data-source-location, fallback to data-visual-selector-id */
22
24
  export function findElementsById(id: string | null): Element[] {
23
25
  if (!id) return [];
@@ -64,3 +66,76 @@ export function collectAllowedAttributes(element: Element, allowedAttributes: st
64
66
  }
65
67
  return attributes;
66
68
  }
69
+
70
+ export function stopAnimations(): void {
71
+ if (document.getElementById('freeze-animations')) return;
72
+
73
+ const animStyle = document.createElement('style');
74
+ animStyle.id = 'freeze-animations';
75
+ animStyle.textContent = `
76
+ *, *::before, *::after {
77
+ animation-play-state: paused !important;
78
+ transition: none !important;
79
+ }
80
+ [${PLUGIN_ELEMENT_ATTR}],
81
+ [${PLUGIN_ELEMENT_ATTR}] *,
82
+ [${PLUGIN_ELEMENT_ATTR}]::before,
83
+ [${PLUGIN_ELEMENT_ATTR}] *::before,
84
+ [${PLUGIN_ELEMENT_ATTR}]::after,
85
+ [${PLUGIN_ELEMENT_ATTR}] *::after {
86
+ animation-play-state: running !important;
87
+ transition: revert !important;
88
+ }
89
+ `;
90
+
91
+ const pointerStyle = document.createElement('style');
92
+ pointerStyle.id = 'freeze-pointer-events';
93
+ pointerStyle.textContent = `
94
+ body * { pointer-events: none !important; }
95
+ [${PLUGIN_ELEMENT_ATTR}], [${PLUGIN_ELEMENT_ATTR}] * { pointer-events: auto !important; }
96
+ `;
97
+
98
+ const target = document.head || document.documentElement;
99
+ target.appendChild(animStyle);
100
+ target.appendChild(pointerStyle);
101
+
102
+ document.getAnimations().forEach((a) => a.pause());
103
+ }
104
+
105
+ export function resumeAnimations(): void {
106
+ const animStyle = document.getElementById('freeze-animations');
107
+ if (!animStyle) return;
108
+
109
+ animStyle.remove();
110
+ document.getElementById('freeze-pointer-events')?.remove();
111
+
112
+ document.getAnimations().forEach((a) => {
113
+ try { a.play(); } catch { /* animation target may have been removed */ }
114
+ });
115
+ }
116
+
117
+ export function findInstrumentedElement(x: number, y: number): Element | null {
118
+ const pointerStyle = document.getElementById('freeze-pointer-events') as HTMLStyleElement | null;
119
+ if (pointerStyle) pointerStyle.disabled = true;
120
+
121
+ const el = document.elementFromPoint(x, y);
122
+
123
+ if (pointerStyle) pointerStyle.disabled = false;
124
+
125
+ return el?.closest('[data-source-location], [data-visual-selector-id]') ?? null;
126
+ }
127
+
128
+ /** Resolve which element should be hovered at (x, y), skipping the currently selected element. */
129
+ export function resolveHoverTarget(x: number, y: number, selectedElementId: string | null): string | null {
130
+ const element = findInstrumentedElement(x, y);
131
+ if (!element) return null;
132
+
133
+ const htmlElement = element as HTMLElement;
134
+ const selectorId =
135
+ htmlElement.dataset.sourceLocation ||
136
+ htmlElement.dataset.visualSelectorId || null;
137
+
138
+ if (selectorId === selectedElementId) return null;
139
+
140
+ return selectorId;
141
+ }
@@ -1,4 +1,4 @@
1
- import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
1
+ import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
4
  import { createInlineEditController } from "../capabilities/inline-edit/index.js";
@@ -191,53 +191,21 @@ export function setupVisualEditAgent() {
191
191
  window.parent.postMessage({ type: "unselect-element" }, "*");
192
192
  };
193
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;
194
+ // Hover detection via mousemove + elementFromPoint (since app elements have pointer-events: none)
195
+ let lastHoveredSelectorId: string | null = null;
196
+ let pendingMouseMoveRaf: number | null = null;
200
197
 
201
- // Prevent hover effects when a dropdown is open
202
- if (isDropdownOpen) {
198
+ const clearHoverState = () => {
199
+ if (lastHoveredSelectorId !== null) {
203
200
  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;
201
+ lastHoveredSelectorId = null;
232
202
  }
203
+ };
233
204
 
234
- // Find all elements with the same ID
235
- const elements = findElementsById(selectorId || null);
236
-
237
- // Clear previous hover overlays
205
+ const applyHoverOverlays = (selectorId: string) => {
206
+ const elements = findElementsById(selectorId);
238
207
  clearHoverOverlays();
239
208
 
240
- // Create overlays for all matching elements
241
209
  elements.forEach((el) => {
242
210
  const overlay = createOverlay(false);
243
211
  document.body.appendChild(overlay);
@@ -246,12 +214,34 @@ export function setupVisualEditAgent() {
246
214
  });
247
215
 
248
216
  currentHighlightedElements = elements;
217
+ lastHoveredSelectorId = selectorId;
218
+ };
219
+
220
+ const handleMouseMove = (e: MouseEvent) => {
221
+ if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
222
+
223
+ if (pendingMouseMoveRaf !== null) return;
224
+ pendingMouseMoveRaf = requestAnimationFrame(() => {
225
+ pendingMouseMoveRaf = null;
226
+
227
+ if (isDropdownOpen) { clearHoverState(); return; }
228
+
229
+ const selectorId = resolveHoverTarget(e.clientX, e.clientY, selectedElementId);
230
+ if (!selectorId) { clearHoverState(); return; }
231
+ if (lastHoveredSelectorId === selectorId) return;
232
+
233
+ applyHoverOverlays(selectorId);
234
+ });
249
235
  };
250
236
 
251
- // Handle mouse out event
252
- const handleMouseOut = () => {
253
- if (isPopoverDragging) return;
237
+ // Clear hover overlays when mouse leaves the viewport
238
+ const handleMouseLeave = () => {
239
+ if (pendingMouseMoveRaf !== null) {
240
+ cancelAnimationFrame(pendingMouseMoveRaf);
241
+ pendingMouseMoveRaf = null;
242
+ }
254
243
  clearHoverOverlays();
244
+ lastHoveredSelectorId = null;
255
245
  };
256
246
 
257
247
  // Handle element click
@@ -288,20 +278,12 @@ export function setupVisualEditAgent() {
288
278
  return;
289
279
  }
290
280
 
291
- // Prevent clicking on SVG path elements
292
- if (target.tagName.toLowerCase() === "path") {
293
- return;
294
- }
295
-
296
281
  // Prevent default behavior immediately when in visual edit mode
297
282
  e.preventDefault();
298
283
  e.stopPropagation();
299
284
  e.stopImmediatePropagation();
300
285
 
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
- );
286
+ const element = findInstrumentedElement(e.clientX, e.clientY);
305
287
  if (!element) {
306
288
  return;
307
289
  }
@@ -435,21 +417,28 @@ export function setupVisualEditAgent() {
435
417
  isVisualEditMode = isEnabled;
436
418
 
437
419
  if (!isEnabled) {
420
+ resumeAnimations();
438
421
  inlineEdit.stopEditing();
439
422
  clearSelection();
440
423
  layerController.cleanup();
441
424
  clearHoverOverlays();
442
425
 
443
426
  currentHighlightedElements = [];
427
+ lastHoveredSelectorId = null;
428
+ if (pendingMouseMoveRaf !== null) {
429
+ cancelAnimationFrame(pendingMouseMoveRaf);
430
+ pendingMouseMoveRaf = null;
431
+ }
444
432
  document.body.style.cursor = "default";
445
433
 
446
- document.removeEventListener("mouseover", handleMouseOver);
447
- document.removeEventListener("mouseout", handleMouseOut);
434
+ document.removeEventListener("mousemove", handleMouseMove);
435
+ document.removeEventListener("mouseleave", handleMouseLeave);
448
436
  document.removeEventListener("click", handleElementClick, true);
449
437
  } else {
450
438
  document.body.style.cursor = "crosshair";
451
- document.addEventListener("mouseover", handleMouseOver);
452
- document.addEventListener("mouseout", handleMouseOut);
439
+ stopAnimations();
440
+ document.addEventListener("mousemove", handleMouseMove);
441
+ document.addEventListener("mouseleave", handleMouseLeave);
453
442
  document.addEventListener("click", handleElementClick, true);
454
443
  }
455
444
  };
@@ -620,6 +609,19 @@ export function setupVisualEditAgent() {
620
609
  }
621
610
  break;
622
611
 
612
+ case "update-theme-variables":
613
+ if (message.data?.variables) {
614
+ const target = message.data.mode === 'dark'
615
+ ? document.querySelector('.dark') as HTMLElement
616
+ : document.documentElement;
617
+ if (target) {
618
+ for (const [name, value] of Object.entries(message.data.variables)) {
619
+ target.style.setProperty(name, value as string);
620
+ }
621
+ }
622
+ }
623
+ break;
624
+
623
625
  case "toggle-inline-edit-mode":
624
626
  if (message.data) {
625
627
  inlineEdit.handleToggleMessage(message.data);