@base44-preview/vite-plugin 0.2.23-pr.40.744483e → 0.2.24-pr.36.31362a5
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/injections/layer-dropdown/consts.d.ts +19 -0
- package/dist/injections/layer-dropdown/consts.d.ts.map +1 -0
- package/dist/injections/layer-dropdown/consts.js +40 -0
- package/dist/injections/layer-dropdown/consts.js.map +1 -0
- package/dist/injections/layer-dropdown/controller.d.ts +4 -0
- package/dist/injections/layer-dropdown/controller.d.ts.map +1 -0
- package/dist/injections/layer-dropdown/controller.js +88 -0
- package/dist/injections/layer-dropdown/controller.js.map +1 -0
- package/dist/injections/layer-dropdown/dropdown-ui.d.ts +13 -0
- package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -0
- package/dist/injections/layer-dropdown/dropdown-ui.js +172 -0
- package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -0
- package/dist/injections/layer-dropdown/types.d.ts +26 -0
- package/dist/injections/layer-dropdown/types.d.ts.map +1 -0
- package/dist/injections/layer-dropdown/types.js +3 -0
- package/dist/injections/layer-dropdown/types.js.map +1 -0
- package/dist/injections/layer-dropdown/utils.d.ts +25 -0
- package/dist/injections/layer-dropdown/utils.d.ts.map +1 -0
- package/dist/injections/layer-dropdown/utils.js +131 -0
- package/dist/injections/layer-dropdown/utils.js.map +1 -0
- package/dist/injections/utils.d.ts +4 -0
- package/dist/injections/utils.d.ts.map +1 -1
- package/dist/injections/utils.js +12 -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 +86 -69
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/statics/index.mjs +1 -1
- package/dist/statics/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/injections/layer-dropdown/LAYERS.md +258 -0
- package/src/injections/layer-dropdown/consts.ts +49 -0
- package/src/injections/layer-dropdown/controller.ts +109 -0
- package/src/injections/layer-dropdown/dropdown-ui.ts +228 -0
- package/src/injections/layer-dropdown/types.ts +30 -0
- package/src/injections/layer-dropdown/utils.ts +162 -0
- package/src/injections/utils.ts +18 -0
- package/src/injections/visual-edit-agent.ts +97 -80
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** DOM utilities for the layer-dropdown module */
|
|
2
|
+
|
|
3
|
+
import { isInstrumentedElement, getElementSelectorId } from "../utils.js";
|
|
4
|
+
import { MAX_PARENT_DEPTH, MAX_CHILD_DEPTH } from "./consts.js";
|
|
5
|
+
|
|
6
|
+
import type { LayerInfo } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/** Apply a style map to an element */
|
|
9
|
+
export function applyStyles(element: HTMLElement, styles: Record<string, string>): void {
|
|
10
|
+
Object.assign(element.style, styles);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Display name for a layer — just the real tag name */
|
|
14
|
+
export function getLayerDisplayName(layer: LayerInfo): string {
|
|
15
|
+
return layer.tagName;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toLayerInfo(element: Element, depth?: number): LayerInfo {
|
|
19
|
+
const info: LayerInfo = {
|
|
20
|
+
element,
|
|
21
|
+
tagName: element.tagName.toLowerCase(),
|
|
22
|
+
selectorId: getElementSelectorId(element),
|
|
23
|
+
};
|
|
24
|
+
if (depth !== undefined) info.depth = depth;
|
|
25
|
+
return info;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collect instrumented descendants up to `maxDepth` instrumented nesting levels.
|
|
30
|
+
* Non-instrumented wrappers are walked through without counting toward depth.
|
|
31
|
+
* Results are in DOM order.
|
|
32
|
+
* When `startDepth` is provided, assigns `depth` to each item during collection.
|
|
33
|
+
*/
|
|
34
|
+
export function getInstrumentedDescendants(
|
|
35
|
+
parent: Element,
|
|
36
|
+
maxDepth: number,
|
|
37
|
+
startDepth?: number
|
|
38
|
+
): LayerInfo[] {
|
|
39
|
+
const result: LayerInfo[] = [];
|
|
40
|
+
|
|
41
|
+
function walk(el: Element, instrDepth: number): void {
|
|
42
|
+
if (instrDepth > maxDepth) return;
|
|
43
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
44
|
+
const child = el.children[i]!;
|
|
45
|
+
if (isInstrumentedElement(child)) {
|
|
46
|
+
const info: LayerInfo = {
|
|
47
|
+
element: child,
|
|
48
|
+
tagName: child.tagName.toLowerCase(),
|
|
49
|
+
selectorId: getElementSelectorId(child),
|
|
50
|
+
};
|
|
51
|
+
if (startDepth !== undefined) {
|
|
52
|
+
info.depth = startDepth + instrDepth - 1;
|
|
53
|
+
}
|
|
54
|
+
result.push(info);
|
|
55
|
+
walk(child, instrDepth + 1);
|
|
56
|
+
} else {
|
|
57
|
+
walk(child, instrDepth);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
walk(parent, 1);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Collect instrumented ancestors from selected element up to MAX_PARENT_DEPTH (outermost first). */
|
|
67
|
+
function collectInstrumentedParents(selectedElement: Element): LayerInfo[] {
|
|
68
|
+
const parents: LayerInfo[] = [];
|
|
69
|
+
let current = selectedElement.parentElement;
|
|
70
|
+
while (
|
|
71
|
+
current &&
|
|
72
|
+
current !== document.documentElement &&
|
|
73
|
+
current !== document.body &&
|
|
74
|
+
parents.length < MAX_PARENT_DEPTH
|
|
75
|
+
) {
|
|
76
|
+
if (isInstrumentedElement(current)) {
|
|
77
|
+
parents.push(toLayerInfo(current));
|
|
78
|
+
}
|
|
79
|
+
current = current.parentElement;
|
|
80
|
+
}
|
|
81
|
+
parents.reverse();
|
|
82
|
+
return parents;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Add parents to chain with depth 0, 1, …; returns depth of selected (parents.length). */
|
|
86
|
+
function addParentsToChain(chain: LayerInfo[], parents: LayerInfo[]): number {
|
|
87
|
+
parents.forEach((p, i) => {
|
|
88
|
+
chain.push({ ...p, depth: i });
|
|
89
|
+
});
|
|
90
|
+
return parents.length;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Add selected element and its descendants at the given depth. */
|
|
94
|
+
function addSelfAndDescendantsToChain(
|
|
95
|
+
chain: LayerInfo[],
|
|
96
|
+
selectedElement: Element,
|
|
97
|
+
selfDepth: number
|
|
98
|
+
): void {
|
|
99
|
+
chain.push(toLayerInfo(selectedElement, selfDepth));
|
|
100
|
+
const descendants = getInstrumentedDescendants(
|
|
101
|
+
selectedElement,
|
|
102
|
+
MAX_CHILD_DEPTH,
|
|
103
|
+
selfDepth + 1
|
|
104
|
+
);
|
|
105
|
+
chain.push(...descendants);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Get the innermost instrumented parent's DOM element, or null if none. */
|
|
109
|
+
function getImmediateInstrParent(parents: LayerInfo[]): Element | null {
|
|
110
|
+
return parents.at(-1)?.element ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Collect instrumented siblings of the selected element from its parent (DOM order). */
|
|
114
|
+
function collectSiblings(parent: Element, selectedElement: Element): LayerInfo[] {
|
|
115
|
+
const siblings = getInstrumentedDescendants(parent, 1);
|
|
116
|
+
if (!siblings.some((s) => s.element === selectedElement)) {
|
|
117
|
+
siblings.push(toLayerInfo(selectedElement));
|
|
118
|
+
}
|
|
119
|
+
return siblings;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Add siblings at selfDepth, expanding children only for the selected element. */
|
|
123
|
+
function appendSiblingsWithSelected(
|
|
124
|
+
chain: LayerInfo[],
|
|
125
|
+
siblings: LayerInfo[],
|
|
126
|
+
selectedElement: Element,
|
|
127
|
+
selfDepth: number
|
|
128
|
+
): void {
|
|
129
|
+
for (const sibling of siblings) {
|
|
130
|
+
if (sibling.element === selectedElement) {
|
|
131
|
+
addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
|
|
132
|
+
} else {
|
|
133
|
+
chain.push({ ...sibling, depth: selfDepth });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build the layer chain for the dropdown:
|
|
140
|
+
*
|
|
141
|
+
* Parents – up to MAX_PARENT_DEPTH instrumented ancestors, outer → inner.
|
|
142
|
+
* Siblings – instrumented children of the immediate parent, at the same depth.
|
|
143
|
+
* Current – the selected element (highlighted), with children expanded.
|
|
144
|
+
* Children – instrumented descendants within MAX_CHILD_DEPTH levels, DOM order.
|
|
145
|
+
*
|
|
146
|
+
* Each item carries a `depth` for visual indentation.
|
|
147
|
+
*/
|
|
148
|
+
export function buildLayerChain(selectedElement: Element): LayerInfo[] {
|
|
149
|
+
const parents = collectInstrumentedParents(selectedElement);
|
|
150
|
+
const chain: LayerInfo[] = [];
|
|
151
|
+
const selfDepth = addParentsToChain(chain, parents);
|
|
152
|
+
|
|
153
|
+
const instrParent = getImmediateInstrParent(parents);
|
|
154
|
+
if (instrParent) {
|
|
155
|
+
const siblings = collectSiblings(instrParent, selectedElement);
|
|
156
|
+
appendSiblingsWithSelected(chain, siblings, selectedElement, selfDepth);
|
|
157
|
+
} else {
|
|
158
|
+
addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return chain;
|
|
162
|
+
}
|
package/src/injections/utils.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/** Check if an element has instrumentation attributes */
|
|
2
|
+
export function isInstrumentedElement(element: Element): boolean {
|
|
3
|
+
const htmlEl = element as HTMLElement;
|
|
4
|
+
return !!(
|
|
5
|
+
htmlEl.dataset?.sourceLocation || htmlEl.dataset?.visualSelectorId
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Get the selector ID from an element's data attributes (prefers source-location) */
|
|
10
|
+
export function getElementSelectorId(element: Element): string | null {
|
|
11
|
+
const htmlEl = element as HTMLElement;
|
|
12
|
+
return (
|
|
13
|
+
htmlEl.dataset?.sourceLocation ||
|
|
14
|
+
htmlEl.dataset?.visualSelectorId ||
|
|
15
|
+
null
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
/** Find elements by ID - first try data-source-location, fallback to data-visual-selector-id */
|
|
2
20
|
export function findElementsById(id: string | null): Element[] {
|
|
3
21
|
if (!id) return [];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { findElementsById, updateElementClasses } from "./utils.js";
|
|
1
|
+
import { findElementsById, updateElementClasses, getElementSelectorId } from "./utils.js";
|
|
2
|
+
import { createLayerController } from "./layer-dropdown/controller.js";
|
|
3
|
+
import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
|
|
2
4
|
|
|
3
5
|
export function setupVisualEditAgent() {
|
|
4
6
|
// State variables (replacing React useState/useRef)
|
|
@@ -78,6 +80,75 @@ export function setupVisualEditAgent() {
|
|
|
78
80
|
currentHighlightedElements = [];
|
|
79
81
|
};
|
|
80
82
|
|
|
83
|
+
const clearSelectedOverlays = () => {
|
|
84
|
+
selectedOverlays.forEach((overlay) => {
|
|
85
|
+
if (overlay && overlay.parentNode) {
|
|
86
|
+
overlay.remove();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
selectedOverlays = [];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const TEXT_TAGS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'];
|
|
93
|
+
|
|
94
|
+
const notifyElementSelected = (element: Element) => {
|
|
95
|
+
const htmlElement = element as HTMLElement;
|
|
96
|
+
const rect = element.getBoundingClientRect();
|
|
97
|
+
const svgElement = element as SVGElement;
|
|
98
|
+
const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
|
|
99
|
+
window.parent.postMessage({
|
|
100
|
+
type: "element-selected",
|
|
101
|
+
tagName: element.tagName,
|
|
102
|
+
classes:
|
|
103
|
+
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
104
|
+
element.className ||
|
|
105
|
+
"",
|
|
106
|
+
visualSelectorId: getElementSelectorId(element),
|
|
107
|
+
content: isTextElement ? htmlElement.innerText : undefined,
|
|
108
|
+
dataSourceLocation: htmlElement.dataset.sourceLocation,
|
|
109
|
+
isDynamicContent: htmlElement.dataset.dynamicContent === "true",
|
|
110
|
+
linenumber: htmlElement.dataset.linenumber,
|
|
111
|
+
filename: htmlElement.dataset.filename,
|
|
112
|
+
position: {
|
|
113
|
+
top: rect.top,
|
|
114
|
+
left: rect.left,
|
|
115
|
+
right: rect.right,
|
|
116
|
+
bottom: rect.bottom,
|
|
117
|
+
width: rect.width,
|
|
118
|
+
height: rect.height,
|
|
119
|
+
centerX: rect.left + rect.width / 2,
|
|
120
|
+
centerY: rect.top + rect.height / 2,
|
|
121
|
+
},
|
|
122
|
+
isTextElement,
|
|
123
|
+
}, "*");
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Select an element: create overlays, update state, notify parent
|
|
127
|
+
const selectElement = (element: Element): HTMLDivElement | undefined => {
|
|
128
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
129
|
+
|
|
130
|
+
clearSelectedOverlays();
|
|
131
|
+
|
|
132
|
+
const elements = findElementsById(visualSelectorId || null);
|
|
133
|
+
elements.forEach((el) => {
|
|
134
|
+
const overlay = createOverlay(true);
|
|
135
|
+
document.body.appendChild(overlay);
|
|
136
|
+
selectedOverlays.push(overlay);
|
|
137
|
+
positionOverlay(overlay, el, true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
selectedElementId = visualSelectorId || null;
|
|
141
|
+
clearHoverOverlays();
|
|
142
|
+
notifyElementSelected(element);
|
|
143
|
+
|
|
144
|
+
return selectedOverlays[0];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const notifyDeselection = (): void => {
|
|
148
|
+
selectedElementId = null;
|
|
149
|
+
window.parent.postMessage({ type: "unselect-element" }, "*");
|
|
150
|
+
};
|
|
151
|
+
|
|
81
152
|
// Handle mouse over event
|
|
82
153
|
const handleMouseOver = (e: MouseEvent) => {
|
|
83
154
|
if (!isVisualEditMode || isPopoverDragging) return;
|
|
@@ -146,6 +217,9 @@ export function setupVisualEditAgent() {
|
|
|
146
217
|
|
|
147
218
|
const target = e.target as Element;
|
|
148
219
|
|
|
220
|
+
// Let layer dropdown clicks pass through without interference
|
|
221
|
+
if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
|
|
222
|
+
|
|
149
223
|
// Close dropdowns when clicking anywhere in iframe if a dropdown is open
|
|
150
224
|
if (isDropdownOpen) {
|
|
151
225
|
e.preventDefault();
|
|
@@ -174,79 +248,13 @@ export function setupVisualEditAgent() {
|
|
|
174
248
|
return;
|
|
175
249
|
}
|
|
176
250
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
htmlElement.dataset.sourceLocation ||
|
|
180
|
-
htmlElement.dataset.visualSelectorId;
|
|
181
|
-
|
|
182
|
-
// Clear any existing selected overlays
|
|
183
|
-
selectedOverlays.forEach((overlay) => {
|
|
184
|
-
if (overlay && overlay.parentNode) {
|
|
185
|
-
overlay.remove();
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
selectedOverlays = [];
|
|
189
|
-
|
|
190
|
-
// Find all elements with the same ID
|
|
191
|
-
const elements = findElementsById(visualSelectorId || null);
|
|
192
|
-
|
|
193
|
-
// Create selected overlays for all matching elements
|
|
194
|
-
elements.forEach((el) => {
|
|
195
|
-
const overlay = createOverlay(true);
|
|
196
|
-
document.body.appendChild(overlay);
|
|
197
|
-
selectedOverlays.push(overlay);
|
|
198
|
-
positionOverlay(overlay, el, true);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
selectedElementId = visualSelectorId || null;
|
|
202
|
-
|
|
203
|
-
// Clear hover overlays
|
|
204
|
-
clearHoverOverlays();
|
|
205
|
-
|
|
206
|
-
// Calculate element position for popover positioning
|
|
207
|
-
const rect = element.getBoundingClientRect();
|
|
208
|
-
const elementPosition = {
|
|
209
|
-
top: rect.top,
|
|
210
|
-
left: rect.left,
|
|
211
|
-
right: rect.right,
|
|
212
|
-
bottom: rect.bottom,
|
|
213
|
-
width: rect.width,
|
|
214
|
-
height: rect.height,
|
|
215
|
-
centerX: rect.left + rect.width / 2,
|
|
216
|
-
centerY: rect.top + rect.height / 2,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const isTextElement = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'].includes(element.tagName?.toLowerCase())
|
|
220
|
-
|
|
221
|
-
// Send message to parent window with element info including position
|
|
222
|
-
const svgElement = element as SVGElement;
|
|
223
|
-
const elementData = {
|
|
224
|
-
type: "element-selected",
|
|
225
|
-
tagName: element.tagName,
|
|
226
|
-
classes:
|
|
227
|
-
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
228
|
-
element.className ||
|
|
229
|
-
"",
|
|
230
|
-
visualSelectorId: visualSelectorId,
|
|
231
|
-
content: isTextElement ? (element as HTMLElement).innerText : undefined,
|
|
232
|
-
dataSourceLocation: htmlElement.dataset.sourceLocation,
|
|
233
|
-
isDynamicContent: htmlElement.dataset.dynamicContent === "true",
|
|
234
|
-
linenumber: htmlElement.dataset.linenumber,
|
|
235
|
-
filename: htmlElement.dataset.filename,
|
|
236
|
-
position: elementPosition,
|
|
237
|
-
isTextElement: isTextElement,
|
|
238
|
-
};
|
|
239
|
-
window.parent.postMessage(elementData, "*");
|
|
251
|
+
const selectedOverlay = selectElement(element);
|
|
252
|
+
layerController.attachToOverlay(selectedOverlay, element);
|
|
240
253
|
};
|
|
241
254
|
|
|
242
|
-
//
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
if (overlay && overlay.parentNode) {
|
|
246
|
-
overlay.remove();
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
selectedOverlays = [];
|
|
255
|
+
// Clear the current selection
|
|
256
|
+
const clearSelection = () => {
|
|
257
|
+
clearSelectedOverlays();
|
|
250
258
|
selectedElementId = null;
|
|
251
259
|
};
|
|
252
260
|
|
|
@@ -305,19 +313,28 @@ export function setupVisualEditAgent() {
|
|
|
305
313
|
}, 50);
|
|
306
314
|
};
|
|
307
315
|
|
|
316
|
+
// --- Layer dropdown controller ---
|
|
317
|
+
const layerController = createLayerController({
|
|
318
|
+
createPreviewOverlay: (element: Element) => {
|
|
319
|
+
const overlay = createOverlay(false);
|
|
320
|
+
overlay.style.zIndex = "9998";
|
|
321
|
+
document.body.appendChild(overlay);
|
|
322
|
+
positionOverlay(overlay, element);
|
|
323
|
+
return overlay;
|
|
324
|
+
},
|
|
325
|
+
getSelectedElementId: () => selectedElementId,
|
|
326
|
+
selectElement,
|
|
327
|
+
onDeselect: notifyDeselection,
|
|
328
|
+
});
|
|
329
|
+
|
|
308
330
|
// Toggle visual edit mode
|
|
309
331
|
const toggleVisualEditMode = (isEnabled: boolean) => {
|
|
310
332
|
isVisualEditMode = isEnabled;
|
|
311
333
|
|
|
312
334
|
if (!isEnabled) {
|
|
335
|
+
layerController.cleanup();
|
|
313
336
|
clearHoverOverlays();
|
|
314
|
-
|
|
315
|
-
selectedOverlays.forEach((overlay) => {
|
|
316
|
-
if (overlay && overlay.parentNode) {
|
|
317
|
-
overlay.remove();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
selectedOverlays = [];
|
|
337
|
+
clearSelectedOverlays();
|
|
321
338
|
|
|
322
339
|
currentHighlightedElements = [];
|
|
323
340
|
selectedElementId = null;
|
|
@@ -398,7 +415,7 @@ export function setupVisualEditAgent() {
|
|
|
398
415
|
break;
|
|
399
416
|
|
|
400
417
|
case "unselect-element":
|
|
401
|
-
|
|
418
|
+
clearSelection();
|
|
402
419
|
break;
|
|
403
420
|
|
|
404
421
|
case "refresh-page":
|