@base44/vite-plugin 0.2.25 → 0.2.27
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 +3 -0
- package/dist/capabilities/inline-edit/controller.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/controller.js +194 -0
- package/dist/capabilities/inline-edit/controller.js.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts +6 -0
- package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/dom-utils.js +49 -0
- package/dist/capabilities/inline-edit/dom-utils.js.map +1 -0
- package/dist/capabilities/inline-edit/index.d.ts +3 -0
- package/dist/capabilities/inline-edit/index.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/index.js +2 -0
- package/dist/capabilities/inline-edit/index.js.map +1 -0
- package/dist/capabilities/inline-edit/types.d.ts +25 -0
- package/dist/capabilities/inline-edit/types.d.ts.map +1 -0
- package/dist/capabilities/inline-edit/types.js +2 -0
- package/dist/capabilities/inline-edit/types.js.map +1 -0
- 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 +176 -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 +143 -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 +151 -76
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/statics/index.mjs +5 -1
- package/dist/statics/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/inline-edit/controller.ts +245 -0
- package/src/capabilities/inline-edit/dom-utils.ts +48 -0
- package/src/capabilities/inline-edit/index.ts +2 -0
- package/src/capabilities/inline-edit/types.ts +30 -0
- 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 +230 -0
- package/src/injections/layer-dropdown/types.ts +30 -0
- package/src/injections/layer-dropdown/utils.ts +175 -0
- package/src/injections/utils.ts +18 -0
- package/src/injections/visual-edit-agent.ts +170 -82
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
for (const key of Object.keys(styles)) {
|
|
11
|
+
element.style.setProperty(
|
|
12
|
+
key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
|
|
13
|
+
styles[key]!
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Display name for a layer — just the real tag name */
|
|
19
|
+
export function getLayerDisplayName(layer: LayerInfo): string {
|
|
20
|
+
return layer.tagName;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toLayerInfo(element: Element, depth?: number): LayerInfo {
|
|
24
|
+
const info: LayerInfo = {
|
|
25
|
+
element,
|
|
26
|
+
tagName: element.tagName.toLowerCase(),
|
|
27
|
+
selectorId: getElementSelectorId(element),
|
|
28
|
+
};
|
|
29
|
+
if (depth !== undefined) info.depth = depth;
|
|
30
|
+
return info;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collect instrumented descendants up to `maxDepth` instrumented nesting levels.
|
|
35
|
+
* Non-instrumented wrappers are walked through without counting toward depth.
|
|
36
|
+
* Results are in DOM order.
|
|
37
|
+
* When `startDepth` is provided, assigns `depth` to each item during collection.
|
|
38
|
+
*/
|
|
39
|
+
export function getInstrumentedDescendants(
|
|
40
|
+
parent: Element,
|
|
41
|
+
maxDepth: number,
|
|
42
|
+
startDepth?: number
|
|
43
|
+
): LayerInfo[] {
|
|
44
|
+
const result: LayerInfo[] = [];
|
|
45
|
+
|
|
46
|
+
function walk(el: Element, instrDepth: number): void {
|
|
47
|
+
if (instrDepth > maxDepth) return;
|
|
48
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
49
|
+
const child = el.children[i]!;
|
|
50
|
+
if (isInstrumentedElement(child)) {
|
|
51
|
+
const info: LayerInfo = {
|
|
52
|
+
element: child,
|
|
53
|
+
tagName: child.tagName.toLowerCase(),
|
|
54
|
+
selectorId: getElementSelectorId(child),
|
|
55
|
+
};
|
|
56
|
+
if (startDepth !== undefined) {
|
|
57
|
+
info.depth = startDepth + instrDepth - 1;
|
|
58
|
+
}
|
|
59
|
+
result.push(info);
|
|
60
|
+
walk(child, instrDepth + 1);
|
|
61
|
+
} else {
|
|
62
|
+
walk(child, instrDepth);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
walk(parent, 1);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Collect instrumented ancestors from selected element up to MAX_PARENT_DEPTH (outermost first). */
|
|
72
|
+
function collectInstrumentedParents(selectedElement: Element): LayerInfo[] {
|
|
73
|
+
const parents: LayerInfo[] = [];
|
|
74
|
+
let current = selectedElement.parentElement;
|
|
75
|
+
while (
|
|
76
|
+
current &&
|
|
77
|
+
current !== document.documentElement &&
|
|
78
|
+
current !== document.body &&
|
|
79
|
+
parents.length < MAX_PARENT_DEPTH
|
|
80
|
+
) {
|
|
81
|
+
if (isInstrumentedElement(current)) {
|
|
82
|
+
parents.push(toLayerInfo(current));
|
|
83
|
+
}
|
|
84
|
+
current = current.parentElement;
|
|
85
|
+
}
|
|
86
|
+
parents.reverse();
|
|
87
|
+
return parents;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Add parents to chain with depth 0, 1, …; returns depth of selected (parents.length). */
|
|
91
|
+
function addParentsToChain(chain: LayerInfo[], parents: LayerInfo[]): number {
|
|
92
|
+
parents.forEach((p, i) => {
|
|
93
|
+
chain.push({ ...p, depth: i });
|
|
94
|
+
});
|
|
95
|
+
return parents.length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Add selected element and its descendants at the given depth. */
|
|
99
|
+
function addSelfAndDescendantsToChain(
|
|
100
|
+
chain: LayerInfo[],
|
|
101
|
+
selectedElement: Element,
|
|
102
|
+
selfDepth: number
|
|
103
|
+
): void {
|
|
104
|
+
chain.push(toLayerInfo(selectedElement, selfDepth));
|
|
105
|
+
const descendants = getInstrumentedDescendants(
|
|
106
|
+
selectedElement,
|
|
107
|
+
MAX_CHILD_DEPTH,
|
|
108
|
+
selfDepth + 1
|
|
109
|
+
);
|
|
110
|
+
chain.push(...descendants);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Get the innermost instrumented parent's DOM element, or null if none. */
|
|
114
|
+
function getImmediateInstrParent(parents: LayerInfo[]): Element | null {
|
|
115
|
+
return parents.at(-1)?.element ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Collect instrumented siblings of the selected element from its parent (DOM order). */
|
|
119
|
+
function collectSiblings(parent: Element, selectedElement: Element): LayerInfo[] {
|
|
120
|
+
const siblings = getInstrumentedDescendants(parent, 1);
|
|
121
|
+
if (!siblings.some((s) => s.element === selectedElement)) {
|
|
122
|
+
siblings.push(toLayerInfo(selectedElement));
|
|
123
|
+
}
|
|
124
|
+
return siblings;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Add siblings at selfDepth, expanding children only for the selected element. */
|
|
128
|
+
function appendSiblingsWithSelected(
|
|
129
|
+
chain: LayerInfo[],
|
|
130
|
+
siblings: LayerInfo[],
|
|
131
|
+
selectedElement: Element,
|
|
132
|
+
selfDepth: number
|
|
133
|
+
): void {
|
|
134
|
+
const selectedSelectorId = getElementSelectorId(selectedElement);
|
|
135
|
+
const seen = new Set<string>();
|
|
136
|
+
for (const sibling of siblings) {
|
|
137
|
+
if (sibling.element === selectedElement) {
|
|
138
|
+
addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
|
|
139
|
+
if (selectedSelectorId) seen.add(selectedSelectorId);
|
|
140
|
+
} else {
|
|
141
|
+
const id = sibling.selectorId;
|
|
142
|
+
if (id != null) {
|
|
143
|
+
if (id === selectedSelectorId || seen.has(id)) continue;
|
|
144
|
+
seen.add(id);
|
|
145
|
+
}
|
|
146
|
+
chain.push({ ...sibling, depth: selfDepth });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build the layer chain for the dropdown:
|
|
153
|
+
*
|
|
154
|
+
* Parents – up to MAX_PARENT_DEPTH instrumented ancestors, outer → inner.
|
|
155
|
+
* Siblings – instrumented children of the immediate parent, at the same depth.
|
|
156
|
+
* Current – the selected element (highlighted), with children expanded.
|
|
157
|
+
* Children – instrumented descendants within MAX_CHILD_DEPTH levels, DOM order.
|
|
158
|
+
*
|
|
159
|
+
* Each item carries a `depth` for visual indentation.
|
|
160
|
+
*/
|
|
161
|
+
export function buildLayerChain(selectedElement: Element): LayerInfo[] {
|
|
162
|
+
const parents = collectInstrumentedParents(selectedElement);
|
|
163
|
+
const chain: LayerInfo[] = [];
|
|
164
|
+
const selfDepth = addParentsToChain(chain, parents);
|
|
165
|
+
|
|
166
|
+
const instrParent = getImmediateInstrParent(parents);
|
|
167
|
+
if (instrParent) {
|
|
168
|
+
const siblings = collectSiblings(instrParent, selectedElement);
|
|
169
|
+
appendSiblingsWithSelected(chain, siblings, selectedElement, selfDepth);
|
|
170
|
+
} else {
|
|
171
|
+
addSelfAndDescendantsToChain(chain, selectedElement, selfDepth);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return chain;
|
|
175
|
+
}
|
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
|
export const ALLOWED_ATTRIBUTES: string[] = ["src"];
|
|
2
20
|
|
|
3
21
|
/** Find elements by ID - first try data-source-location, fallback to data-visual-selector-id */
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES } from "./utils.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";
|
|
2
5
|
|
|
3
6
|
export function setupVisualEditAgent() {
|
|
4
7
|
// State variables (replacing React useState/useRef)
|
|
@@ -10,6 +13,8 @@ export function setupVisualEditAgent() {
|
|
|
10
13
|
let currentHighlightedElements: Element[] = [];
|
|
11
14
|
let selectedElementId: string | null = null;
|
|
12
15
|
|
|
16
|
+
const REPOSITION_DELAY_MS = 50;
|
|
17
|
+
|
|
13
18
|
// Create overlay element
|
|
14
19
|
const createOverlay = (isSelected = false): HTMLDivElement => {
|
|
15
20
|
const overlay = document.createElement("div");
|
|
@@ -67,6 +72,34 @@ export function setupVisualEditAgent() {
|
|
|
67
72
|
}
|
|
68
73
|
};
|
|
69
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
|
+
|
|
70
103
|
// Clear hover overlays
|
|
71
104
|
const clearHoverOverlays = () => {
|
|
72
105
|
hoverOverlays.forEach((overlay) => {
|
|
@@ -78,9 +111,80 @@ export function setupVisualEditAgent() {
|
|
|
78
111
|
currentHighlightedElements = [];
|
|
79
112
|
};
|
|
80
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
|
+
window.parent.postMessage({
|
|
131
|
+
type: "element-selected",
|
|
132
|
+
tagName: element.tagName,
|
|
133
|
+
classes:
|
|
134
|
+
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
135
|
+
element.className ||
|
|
136
|
+
"",
|
|
137
|
+
visualSelectorId: getElementSelectorId(element),
|
|
138
|
+
content: isTextElement ? htmlElement.innerText : undefined,
|
|
139
|
+
dataSourceLocation: htmlElement.dataset.sourceLocation,
|
|
140
|
+
isDynamicContent: htmlElement.dataset.dynamicContent === "true",
|
|
141
|
+
linenumber: htmlElement.dataset.linenumber,
|
|
142
|
+
filename: htmlElement.dataset.filename,
|
|
143
|
+
position: {
|
|
144
|
+
top: rect.top,
|
|
145
|
+
left: rect.left,
|
|
146
|
+
right: rect.right,
|
|
147
|
+
bottom: rect.bottom,
|
|
148
|
+
width: rect.width,
|
|
149
|
+
height: rect.height,
|
|
150
|
+
centerX: rect.left + rect.width / 2,
|
|
151
|
+
centerY: rect.top + rect.height / 2,
|
|
152
|
+
},
|
|
153
|
+
attributes: collectAllowedAttributes(element, ALLOWED_ATTRIBUTES),
|
|
154
|
+
isTextElement,
|
|
155
|
+
}, "*");
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Select an element: create overlays, update state, notify parent
|
|
159
|
+
const selectElement = (element: Element): HTMLDivElement | undefined => {
|
|
160
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
161
|
+
|
|
162
|
+
clearSelectedOverlays();
|
|
163
|
+
|
|
164
|
+
const elements = findElementsById(visualSelectorId || null);
|
|
165
|
+
elements.forEach((el) => {
|
|
166
|
+
const overlay = createOverlay(true);
|
|
167
|
+
document.body.appendChild(overlay);
|
|
168
|
+
selectedOverlays.push(overlay);
|
|
169
|
+
positionOverlay(overlay, el, true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
selectedElementId = visualSelectorId || null;
|
|
173
|
+
clearHoverOverlays();
|
|
174
|
+
notifyElementSelected(element);
|
|
175
|
+
|
|
176
|
+
return selectedOverlays[0];
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const notifyDeselection = (): void => {
|
|
180
|
+
selectedElementId = null;
|
|
181
|
+
window.parent.postMessage({ type: "unselect-element" }, "*");
|
|
182
|
+
};
|
|
183
|
+
|
|
81
184
|
// Handle mouse over event
|
|
82
185
|
const handleMouseOver = (e: MouseEvent) => {
|
|
83
|
-
if (!isVisualEditMode || isPopoverDragging) return;
|
|
186
|
+
if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
|
|
187
|
+
|
|
84
188
|
|
|
85
189
|
const target = e.target as Element;
|
|
86
190
|
|
|
@@ -146,6 +250,24 @@ export function setupVisualEditAgent() {
|
|
|
146
250
|
|
|
147
251
|
const target = e.target as Element;
|
|
148
252
|
|
|
253
|
+
// Let layer dropdown clicks pass through without interference
|
|
254
|
+
if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
|
|
255
|
+
|
|
256
|
+
// Let clicks inside the editable element pass through to the browser
|
|
257
|
+
// so the user can reposition the cursor and select text naturally.
|
|
258
|
+
if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clicking outside the editable element exits inline editing mode.
|
|
263
|
+
if (inlineEdit.isEditing()) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
e.stopImmediatePropagation();
|
|
267
|
+
inlineEdit.stopEditing();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
149
271
|
// Close dropdowns when clicking anywhere in iframe if a dropdown is open
|
|
150
272
|
if (isDropdownOpen) {
|
|
151
273
|
e.preventDefault();
|
|
@@ -175,82 +297,30 @@ export function setupVisualEditAgent() {
|
|
|
175
297
|
}
|
|
176
298
|
|
|
177
299
|
const htmlElement = element as HTMLElement;
|
|
178
|
-
const visualSelectorId =
|
|
179
|
-
htmlElement.dataset.sourceLocation ||
|
|
180
|
-
htmlElement.dataset.visualSelectorId;
|
|
300
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
181
301
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
overlay.remove();
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
selectedOverlays = [];
|
|
189
|
-
|
|
190
|
-
// Find all elements with the same ID
|
|
191
|
-
const elements = findElementsById(visualSelectorId || null);
|
|
302
|
+
const isAlreadySelected =
|
|
303
|
+
selectedElementId === visualSelectorId &&
|
|
304
|
+
htmlElement.dataset.selected === "true";
|
|
192
305
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
selectedOverlays.push(overlay);
|
|
198
|
-
positionOverlay(overlay, el, true);
|
|
199
|
-
});
|
|
306
|
+
if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
|
|
307
|
+
inlineEdit.startEditing(htmlElement);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
200
310
|
|
|
201
|
-
|
|
311
|
+
inlineEdit.stopEditing();
|
|
202
312
|
|
|
203
|
-
|
|
204
|
-
|
|
313
|
+
if (inlineEdit.enabled) {
|
|
314
|
+
inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
|
|
315
|
+
}
|
|
205
316
|
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
// Collect allowed element attributes
|
|
220
|
-
const attributes = collectAllowedAttributes(element, ALLOWED_ATTRIBUTES);
|
|
221
|
-
const isTextElement = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label'].includes(element.tagName?.toLowerCase());
|
|
222
|
-
|
|
223
|
-
// Send message to parent window with element info including position
|
|
224
|
-
const svgElement = element as SVGElement;
|
|
225
|
-
const elementData = {
|
|
226
|
-
type: "element-selected",
|
|
227
|
-
tagName: element.tagName,
|
|
228
|
-
classes:
|
|
229
|
-
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
230
|
-
element.className ||
|
|
231
|
-
"",
|
|
232
|
-
visualSelectorId: visualSelectorId,
|
|
233
|
-
content: isTextElement ? (element as HTMLElement).innerText : undefined,
|
|
234
|
-
dataSourceLocation: htmlElement.dataset.sourceLocation,
|
|
235
|
-
isDynamicContent: htmlElement.dataset.dynamicContent === "true",
|
|
236
|
-
linenumber: htmlElement.dataset.linenumber,
|
|
237
|
-
filename: htmlElement.dataset.filename,
|
|
238
|
-
position: elementPosition,
|
|
239
|
-
attributes,
|
|
240
|
-
isTextElement: isTextElement,
|
|
241
|
-
};
|
|
242
|
-
window.parent.postMessage(elementData, "*");
|
|
317
|
+
const selectedOverlay = selectElement(element);
|
|
318
|
+
layerController.attachToOverlay(selectedOverlay, element);
|
|
243
319
|
};
|
|
244
320
|
|
|
245
|
-
// Unselect the current element
|
|
246
321
|
const unselectElement = () => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
overlay.remove();
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
selectedOverlays = [];
|
|
253
|
-
selectedElementId = null;
|
|
322
|
+
inlineEdit.stopEditing();
|
|
323
|
+
clearSelection();
|
|
254
324
|
};
|
|
255
325
|
|
|
256
326
|
const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
|
|
@@ -282,7 +352,7 @@ export function setupVisualEditAgent() {
|
|
|
282
352
|
});
|
|
283
353
|
}
|
|
284
354
|
}
|
|
285
|
-
},
|
|
355
|
+
}, REPOSITION_DELAY_MS);
|
|
286
356
|
};
|
|
287
357
|
|
|
288
358
|
// Update element attribute by visual selector ID
|
|
@@ -305,7 +375,7 @@ export function setupVisualEditAgent() {
|
|
|
305
375
|
}
|
|
306
376
|
});
|
|
307
377
|
}
|
|
308
|
-
},
|
|
378
|
+
}, REPOSITION_DELAY_MS);
|
|
309
379
|
};
|
|
310
380
|
|
|
311
381
|
// Update element content by visual selector ID
|
|
@@ -328,25 +398,34 @@ export function setupVisualEditAgent() {
|
|
|
328
398
|
}
|
|
329
399
|
});
|
|
330
400
|
}
|
|
331
|
-
},
|
|
401
|
+
}, REPOSITION_DELAY_MS);
|
|
332
402
|
};
|
|
333
403
|
|
|
404
|
+
// --- Layer dropdown controller ---
|
|
405
|
+
const layerController = createLayerController({
|
|
406
|
+
createPreviewOverlay: (element: Element) => {
|
|
407
|
+
const overlay = createOverlay(false);
|
|
408
|
+
overlay.style.zIndex = "9998";
|
|
409
|
+
document.body.appendChild(overlay);
|
|
410
|
+
positionOverlay(overlay, element);
|
|
411
|
+
return overlay;
|
|
412
|
+
},
|
|
413
|
+
getSelectedElementId: () => selectedElementId,
|
|
414
|
+
selectElement,
|
|
415
|
+
onDeselect: notifyDeselection,
|
|
416
|
+
});
|
|
417
|
+
|
|
334
418
|
// Toggle visual edit mode
|
|
335
419
|
const toggleVisualEditMode = (isEnabled: boolean) => {
|
|
336
420
|
isVisualEditMode = isEnabled;
|
|
337
421
|
|
|
338
422
|
if (!isEnabled) {
|
|
423
|
+
inlineEdit.stopEditing();
|
|
424
|
+
clearSelection();
|
|
425
|
+
layerController.cleanup();
|
|
339
426
|
clearHoverOverlays();
|
|
340
427
|
|
|
341
|
-
selectedOverlays.forEach((overlay) => {
|
|
342
|
-
if (overlay && overlay.parentNode) {
|
|
343
|
-
overlay.remove();
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
selectedOverlays = [];
|
|
347
|
-
|
|
348
428
|
currentHighlightedElements = [];
|
|
349
|
-
selectedElementId = null;
|
|
350
429
|
document.body.style.cursor = "default";
|
|
351
430
|
|
|
352
431
|
document.removeEventListener("mouseover", handleMouseOver);
|
|
@@ -407,6 +486,9 @@ export function setupVisualEditAgent() {
|
|
|
407
486
|
switch (message.type) {
|
|
408
487
|
case "toggle-visual-edit-mode":
|
|
409
488
|
toggleVisualEditMode(message.data.enabled);
|
|
489
|
+
if (message.data.specs?.newInlineEditEnabled !== undefined) {
|
|
490
|
+
inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
|
|
491
|
+
}
|
|
410
492
|
break;
|
|
411
493
|
|
|
412
494
|
case "update-classes":
|
|
@@ -522,6 +604,12 @@ export function setupVisualEditAgent() {
|
|
|
522
604
|
}
|
|
523
605
|
break;
|
|
524
606
|
|
|
607
|
+
case "toggle-inline-edit-mode":
|
|
608
|
+
if (message.data) {
|
|
609
|
+
inlineEdit.handleToggleMessage(message.data);
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
|
|
525
613
|
default:
|
|
526
614
|
break;
|
|
527
615
|
}
|
|
@@ -586,7 +674,7 @@ export function setupVisualEditAgent() {
|
|
|
586
674
|
});
|
|
587
675
|
|
|
588
676
|
if (needsUpdate) {
|
|
589
|
-
setTimeout(handleResize,
|
|
677
|
+
setTimeout(handleResize, REPOSITION_DELAY_MS);
|
|
590
678
|
}
|
|
591
679
|
});
|
|
592
680
|
|