@base44/vite-plugin 0.2.29 → 0.3.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.
- package/dist/capabilities/inline-edit/controller.d.ts.map +1 -1
- package/dist/capabilities/inline-edit/controller.js +3 -0
- package/dist/capabilities/inline-edit/controller.js.map +1 -1
- package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -1
- package/dist/injections/layer-dropdown/dropdown-ui.js +3 -0
- package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -1
- package/dist/injections/utils.d.ts +31 -0
- package/dist/injections/utils.d.ts.map +1 -1
- package/dist/injections/utils.js +97 -0
- package/dist/injections/utils.js.map +1 -1
- package/dist/injections/visual-edit-agent.d.ts.map +1 -1
- package/dist/injections/visual-edit-agent.js +46 -51
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/statics/index.mjs +12 -2
- package/dist/statics/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/inline-edit/controller.ts +3 -0
- package/src/injections/layer-dropdown/dropdown-ui.ts +3 -0
- package/src/injections/utils.ts +105 -0
- package/src/injections/visual-edit-agent.ts +43 -64
package/src/injections/utils.ts
CHANGED
|
@@ -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,106 @@ export function collectAllowedAttributes(element: Element, allowedAttributes: st
|
|
|
64
66
|
}
|
|
65
67
|
return attributes;
|
|
66
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Freeze all CSS animations and transitions on the page by injecting
|
|
72
|
+
* scoped styles under `[data-visual-edit-active]` and programmatically
|
|
73
|
+
* finishing (or pausing) every running animation.
|
|
74
|
+
*
|
|
75
|
+
* Plugin-owned elements (`[data-vite-plugin-element]`) are excluded so
|
|
76
|
+
* the plugin UI stays animated.
|
|
77
|
+
*/
|
|
78
|
+
export function stopAnimations(): void {
|
|
79
|
+
if (document.getElementById('freeze-animations')) return;
|
|
80
|
+
|
|
81
|
+
document.documentElement.setAttribute('data-visual-edit-active', '');
|
|
82
|
+
|
|
83
|
+
const animStyle = document.createElement('style');
|
|
84
|
+
animStyle.id = 'freeze-animations';
|
|
85
|
+
animStyle.textContent = `
|
|
86
|
+
[data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *),
|
|
87
|
+
[data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *)::before,
|
|
88
|
+
[data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *)::after {
|
|
89
|
+
animation-play-state: paused !important;
|
|
90
|
+
transition: none !important;
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const pointerStyle = document.createElement('style');
|
|
95
|
+
pointerStyle.id = 'freeze-pointer-events';
|
|
96
|
+
pointerStyle.textContent = `
|
|
97
|
+
[data-visual-edit-active] * { pointer-events: none !important; }
|
|
98
|
+
[${PLUGIN_ELEMENT_ATTR}], [${PLUGIN_ELEMENT_ATTR}] * { pointer-events: auto !important; }
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
const target = document.head || document.documentElement;
|
|
102
|
+
target.appendChild(animStyle);
|
|
103
|
+
target.appendChild(pointerStyle);
|
|
104
|
+
|
|
105
|
+
document.getAnimations().forEach((a) => {
|
|
106
|
+
// Skip animations on plugin UI elements
|
|
107
|
+
const animTarget = (a.effect as KeyframeEffect)?.target;
|
|
108
|
+
if (animTarget instanceof Element && animTarget.closest(`[${PLUGIN_ELEMENT_ATTR}]`)) return;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
a.finish(); // fast-forward to end state
|
|
112
|
+
} catch {
|
|
113
|
+
a.pause(); // finish() throws on infinite animations — pause instead
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resume all previously frozen animations and remove the injected
|
|
120
|
+
* freeze styles. Cleans up the `data-visual-edit-active` attribute
|
|
121
|
+
* from `<html>` so scoped selectors no longer match.
|
|
122
|
+
*/
|
|
123
|
+
export function resumeAnimations(): void {
|
|
124
|
+
const animStyle = document.getElementById('freeze-animations');
|
|
125
|
+
if (!animStyle) return;
|
|
126
|
+
|
|
127
|
+
animStyle.remove();
|
|
128
|
+
document.getElementById('freeze-pointer-events')?.remove();
|
|
129
|
+
document.documentElement.removeAttribute('data-visual-edit-active');
|
|
130
|
+
|
|
131
|
+
document.getAnimations().forEach((a) => {
|
|
132
|
+
if (a.playState === 'paused') {
|
|
133
|
+
try { a.play(); } catch { /* animation target may have been removed */ }
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Hit-test the page at (`x`, `y`) and walk up the DOM to find the
|
|
140
|
+
* nearest ancestor that carries instrumentation attributes
|
|
141
|
+
* (`data-source-location` or `data-visual-selector-id`).
|
|
142
|
+
*
|
|
143
|
+
* Temporarily disables the pointer-events freeze sheet so the
|
|
144
|
+
* browser's native `elementFromPoint` can reach the real target.
|
|
145
|
+
*/
|
|
146
|
+
export function findInstrumentedElement(x: number, y: number): Element | null {
|
|
147
|
+
const pointerStyle = document.getElementById('freeze-pointer-events') as HTMLStyleElement | null;
|
|
148
|
+
if (pointerStyle) pointerStyle.disabled = true;
|
|
149
|
+
|
|
150
|
+
const el = document.elementFromPoint(x, y);
|
|
151
|
+
|
|
152
|
+
if (pointerStyle) pointerStyle.disabled = false;
|
|
153
|
+
|
|
154
|
+
return el?.closest('[data-source-location], [data-visual-selector-id]') ?? null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve which element should be hovered at (`x`, `y`), skipping the
|
|
159
|
+
* currently selected element. Returns the selector ID of the hovered
|
|
160
|
+
* element, or `null` if the point is empty or hits the selected element.
|
|
161
|
+
*/
|
|
162
|
+
export function resolveHoverTarget(x: number, y: number, selectedElementId: string | null): string | null {
|
|
163
|
+
const element = findInstrumentedElement(x, y);
|
|
164
|
+
if (!element) return null;
|
|
165
|
+
|
|
166
|
+
const selectorId = getElementSelectorId(element);
|
|
167
|
+
|
|
168
|
+
if (selectorId === selectedElementId) return null;
|
|
169
|
+
|
|
170
|
+
return selectorId;
|
|
171
|
+
}
|
|
@@ -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,19 @@ export function setupVisualEditAgent() {
|
|
|
191
191
|
window.parent.postMessage({ type: "unselect-element" }, "*");
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
}
|
|
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;
|
|
233
197
|
|
|
234
|
-
|
|
235
|
-
|
|
198
|
+
const clearHoverState = () => {
|
|
199
|
+
clearHoverOverlays();
|
|
200
|
+
lastHoveredSelectorId = null;
|
|
201
|
+
};
|
|
236
202
|
|
|
237
|
-
|
|
203
|
+
const applyHoverOverlays = (selectorId: string) => {
|
|
204
|
+
const elements = findElementsById(selectorId);
|
|
238
205
|
clearHoverOverlays();
|
|
239
206
|
|
|
240
|
-
// Create overlays for all matching elements
|
|
241
207
|
elements.forEach((el) => {
|
|
242
208
|
const overlay = createOverlay(false);
|
|
243
209
|
document.body.appendChild(overlay);
|
|
@@ -246,12 +212,33 @@ export function setupVisualEditAgent() {
|
|
|
246
212
|
});
|
|
247
213
|
|
|
248
214
|
currentHighlightedElements = elements;
|
|
215
|
+
lastHoveredSelectorId = selectorId;
|
|
249
216
|
};
|
|
250
217
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
218
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
219
|
+
if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
|
|
220
|
+
|
|
221
|
+
if (pendingMouseMoveRaf !== null) return;
|
|
222
|
+
pendingMouseMoveRaf = requestAnimationFrame(() => {
|
|
223
|
+
pendingMouseMoveRaf = null;
|
|
224
|
+
|
|
225
|
+
if (isDropdownOpen) { clearHoverState(); return; }
|
|
226
|
+
|
|
227
|
+
const selectorId = resolveHoverTarget(e.clientX, e.clientY, selectedElementId);
|
|
228
|
+
if (!selectorId) { clearHoverState(); return; }
|
|
229
|
+
if (lastHoveredSelectorId === selectorId) return;
|
|
230
|
+
|
|
231
|
+
applyHoverOverlays(selectorId);
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Clear hover overlays when mouse leaves the viewport
|
|
236
|
+
const handleMouseLeave = () => {
|
|
237
|
+
if (pendingMouseMoveRaf !== null) {
|
|
238
|
+
cancelAnimationFrame(pendingMouseMoveRaf);
|
|
239
|
+
pendingMouseMoveRaf = null;
|
|
240
|
+
}
|
|
241
|
+
clearHoverState();
|
|
255
242
|
};
|
|
256
243
|
|
|
257
244
|
// Handle element click
|
|
@@ -288,20 +275,12 @@ export function setupVisualEditAgent() {
|
|
|
288
275
|
return;
|
|
289
276
|
}
|
|
290
277
|
|
|
291
|
-
// Prevent clicking on SVG path elements
|
|
292
|
-
if (target.tagName.toLowerCase() === "path") {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
278
|
// Prevent default behavior immediately when in visual edit mode
|
|
297
279
|
e.preventDefault();
|
|
298
280
|
e.stopPropagation();
|
|
299
281
|
e.stopImmediatePropagation();
|
|
300
282
|
|
|
301
|
-
|
|
302
|
-
const element = target.closest(
|
|
303
|
-
"[data-source-location], [data-visual-selector-id]"
|
|
304
|
-
);
|
|
283
|
+
const element = findInstrumentedElement(e.clientX, e.clientY);
|
|
305
284
|
if (!element) {
|
|
306
285
|
return;
|
|
307
286
|
}
|
|
@@ -435,21 +414,21 @@ export function setupVisualEditAgent() {
|
|
|
435
414
|
isVisualEditMode = isEnabled;
|
|
436
415
|
|
|
437
416
|
if (!isEnabled) {
|
|
417
|
+
resumeAnimations();
|
|
438
418
|
inlineEdit.stopEditing();
|
|
439
419
|
clearSelection();
|
|
440
420
|
layerController.cleanup();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
currentHighlightedElements = [];
|
|
421
|
+
handleMouseLeave();
|
|
444
422
|
document.body.style.cursor = "default";
|
|
445
423
|
|
|
446
|
-
document.removeEventListener("
|
|
447
|
-
document.removeEventListener("
|
|
424
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
425
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
448
426
|
document.removeEventListener("click", handleElementClick, true);
|
|
449
427
|
} else {
|
|
450
428
|
document.body.style.cursor = "crosshair";
|
|
451
|
-
|
|
452
|
-
document.addEventListener("
|
|
429
|
+
stopAnimations();
|
|
430
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
431
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
453
432
|
document.addEventListener("click", handleElementClick, true);
|
|
454
433
|
}
|
|
455
434
|
};
|