@base44-preview/vite-plugin 0.2.25-pr.36.792e85b → 0.2.26-pr.42.7e00d38
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/visual-edit-agent.d.ts.map +1 -1
- package/dist/injections/visual-edit-agent.js +303 -10
- 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/injections/visual-edit-agent.ts +369 -10
|
@@ -11,6 +11,15 @@ export function setupVisualEditAgent() {
|
|
|
11
11
|
let selectedOverlays: HTMLDivElement[] = [];
|
|
12
12
|
let currentHighlightedElements: Element[] = [];
|
|
13
13
|
let selectedElementId: string | null = null;
|
|
14
|
+
let currentEditingElement: HTMLElement | null = null;
|
|
15
|
+
let debouncedSendTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
16
|
+
let isInlineEditExperimentEnabled = false;
|
|
17
|
+
|
|
18
|
+
const INLINE_EDIT_DEBOUNCE_MS = 500;
|
|
19
|
+
const REPOSITION_DELAY_MS = 50;
|
|
20
|
+
|
|
21
|
+
// WeakMap to track AbortControllers for each element's input listener
|
|
22
|
+
const listenerAbortControllers = new WeakMap<HTMLElement, AbortController>();
|
|
14
23
|
|
|
15
24
|
// Create overlay element
|
|
16
25
|
const createOverlay = (isSelected = false): HTMLDivElement => {
|
|
@@ -69,6 +78,258 @@ export function setupVisualEditAgent() {
|
|
|
69
78
|
}
|
|
70
79
|
};
|
|
71
80
|
|
|
81
|
+
// --- Inline editing utilities ---
|
|
82
|
+
|
|
83
|
+
const injectFocusOutlineCSS = () => {
|
|
84
|
+
const existingStyle = document.getElementById("visual-edit-focus-styles");
|
|
85
|
+
if (existingStyle) return;
|
|
86
|
+
|
|
87
|
+
const style = document.createElement("style");
|
|
88
|
+
style.id = "visual-edit-focus-styles";
|
|
89
|
+
style.textContent = `
|
|
90
|
+
[data-selected="true"][contenteditable="true"]:focus {
|
|
91
|
+
outline: none !important;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
document.head.appendChild(style);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const removeFocusOutlineCSS = () => {
|
|
98
|
+
const style = document.getElementById("visual-edit-focus-styles");
|
|
99
|
+
if (style) {
|
|
100
|
+
style.remove();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const selectText = (element: HTMLElement) => {
|
|
105
|
+
const range = document.createRange();
|
|
106
|
+
range.selectNodeContents(element);
|
|
107
|
+
const selection = window.getSelection();
|
|
108
|
+
selection?.removeAllRanges();
|
|
109
|
+
selection?.addRange(range);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const isEditableTextElement = (element: Element): boolean => {
|
|
113
|
+
if (!(element instanceof HTMLElement)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const allowedTags = [
|
|
118
|
+
"div", "p", "h1", "h2", "h3", "h4", "h5", "h6",
|
|
119
|
+
"span", "li", "td", "a", "button", "label",
|
|
120
|
+
];
|
|
121
|
+
if (!allowedTags.includes(element.tagName.toLowerCase())) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const textContent = element.textContent?.trim() || "";
|
|
126
|
+
if (textContent.length === 0) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (element.querySelector("img, video, canvas, svg") !== null) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (element.children.length > 0) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (element.dataset.dynamicContent === "true") {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const shouldEnterInlineEditingMode = (element: Element): boolean => {
|
|
146
|
+
if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return isEditableTextElement(element);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const handleInputEvent = function (this: HTMLElement) {
|
|
153
|
+
onTextInputChange(this);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const enterInlineEditingMode = (element: HTMLElement) => {
|
|
157
|
+
injectFocusOutlineCSS();
|
|
158
|
+
|
|
159
|
+
element.dataset.originalTextContent = element.textContent || "";
|
|
160
|
+
element.dataset.originalCursor = element.style.cursor;
|
|
161
|
+
|
|
162
|
+
element.contentEditable = "true";
|
|
163
|
+
|
|
164
|
+
const abortController = new AbortController();
|
|
165
|
+
listenerAbortControllers.set(element, abortController);
|
|
166
|
+
element.addEventListener("input", handleInputEvent, { signal: abortController.signal });
|
|
167
|
+
|
|
168
|
+
element.style.cursor = "text";
|
|
169
|
+
selectText(element);
|
|
170
|
+
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
element.focus();
|
|
173
|
+
}, 0);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const clearInlineEditingMode = (element: HTMLElement) => {
|
|
177
|
+
const abortController = listenerAbortControllers.get(element);
|
|
178
|
+
if (abortController) {
|
|
179
|
+
abortController.abort();
|
|
180
|
+
listenerAbortControllers.delete(element);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!element.isConnected) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
removeFocusOutlineCSS();
|
|
188
|
+
element.contentEditable = "false";
|
|
189
|
+
delete element.dataset.originalTextContent;
|
|
190
|
+
|
|
191
|
+
if (element.dataset.originalCursor !== undefined) {
|
|
192
|
+
element.style.cursor = element.dataset.originalCursor;
|
|
193
|
+
delete element.dataset.originalCursor;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const repositionSelectedOverlays = () => {
|
|
198
|
+
if (selectedElementId) {
|
|
199
|
+
const elements = findElementsById(selectedElementId);
|
|
200
|
+
selectedOverlays.forEach((overlay, index) => {
|
|
201
|
+
if (index < elements.length) {
|
|
202
|
+
positionOverlay(overlay, elements[index]!);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const handleEnterInlineEditingMode = (element: HTMLElement) => {
|
|
209
|
+
currentEditingElement = element;
|
|
210
|
+
|
|
211
|
+
selectedOverlays.forEach((overlay) => {
|
|
212
|
+
overlay.style.display = "none";
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
enterInlineEditingMode(element);
|
|
216
|
+
|
|
217
|
+
window.parent.postMessage(
|
|
218
|
+
{
|
|
219
|
+
type: "content-editing-started",
|
|
220
|
+
visualSelectorId: selectedElementId,
|
|
221
|
+
},
|
|
222
|
+
"*"
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const handleClearInlineEditingMode = () => {
|
|
227
|
+
if (!currentEditingElement) return;
|
|
228
|
+
|
|
229
|
+
if (debouncedSendTimeout) {
|
|
230
|
+
clearTimeout(debouncedSendTimeout);
|
|
231
|
+
debouncedSendTimeout = null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const element = currentEditingElement;
|
|
235
|
+
clearInlineEditingMode(element);
|
|
236
|
+
|
|
237
|
+
selectedOverlays.forEach((overlay) => {
|
|
238
|
+
overlay.style.display = "";
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
repositionSelectedOverlays();
|
|
242
|
+
|
|
243
|
+
window.parent.postMessage(
|
|
244
|
+
{
|
|
245
|
+
type: "content-editing-ended",
|
|
246
|
+
visualSelectorId: selectedElementId,
|
|
247
|
+
},
|
|
248
|
+
"*"
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
currentEditingElement = null;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const reportInlineEdit = (element: HTMLElement) => {
|
|
255
|
+
const originalContent = element.dataset.originalTextContent;
|
|
256
|
+
const newContent = element.textContent;
|
|
257
|
+
|
|
258
|
+
const svgElement = element as unknown as SVGElement;
|
|
259
|
+
const elementInfo = {
|
|
260
|
+
tagName: element.tagName,
|
|
261
|
+
classes:
|
|
262
|
+
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
263
|
+
element.className ||
|
|
264
|
+
"",
|
|
265
|
+
visualSelectorId: selectedElementId,
|
|
266
|
+
content: newContent,
|
|
267
|
+
dataSourceLocation: element.dataset.sourceLocation,
|
|
268
|
+
isDynamicContent: element.dataset.dynamicContent === "true",
|
|
269
|
+
linenumber: element.dataset.linenumber,
|
|
270
|
+
filename: element.dataset.filename,
|
|
271
|
+
position: (() => {
|
|
272
|
+
const rect = element.getBoundingClientRect();
|
|
273
|
+
return {
|
|
274
|
+
top: rect.top,
|
|
275
|
+
left: rect.left,
|
|
276
|
+
right: rect.right,
|
|
277
|
+
bottom: rect.bottom,
|
|
278
|
+
width: rect.width,
|
|
279
|
+
height: rect.height,
|
|
280
|
+
centerX: rect.left + rect.width / 2,
|
|
281
|
+
centerY: rect.top + rect.height / 2,
|
|
282
|
+
};
|
|
283
|
+
})(),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
window.parent.postMessage(
|
|
287
|
+
{
|
|
288
|
+
type: "inline-edit",
|
|
289
|
+
elementInfo,
|
|
290
|
+
originalContent,
|
|
291
|
+
newContent,
|
|
292
|
+
},
|
|
293
|
+
"*"
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
element.dataset.originalTextContent = newContent || "";
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const debouncedSendInlineEditMessage = (element: HTMLElement) => {
|
|
300
|
+
if (debouncedSendTimeout) {
|
|
301
|
+
clearTimeout(debouncedSendTimeout);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
debouncedSendTimeout = setTimeout(() => {
|
|
305
|
+
reportInlineEdit(element);
|
|
306
|
+
}, INLINE_EDIT_DEBOUNCE_MS);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const onTextInputChange = (element: HTMLElement) => {
|
|
310
|
+
repositionSelectedOverlays();
|
|
311
|
+
debouncedSendInlineEditMessage(element);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const clearSelection = () => {
|
|
315
|
+
if (selectedElementId) {
|
|
316
|
+
const elements = findElementsById(selectedElementId);
|
|
317
|
+
elements.forEach((el) => {
|
|
318
|
+
if (el instanceof HTMLElement) {
|
|
319
|
+
delete el.dataset.selected;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
selectedOverlays.forEach((overlay) => {
|
|
325
|
+
if (overlay && overlay.parentNode) {
|
|
326
|
+
overlay.remove();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
selectedOverlays = [];
|
|
330
|
+
selectedElementId = null;
|
|
331
|
+
};
|
|
332
|
+
|
|
72
333
|
// Clear hover overlays
|
|
73
334
|
const clearHoverOverlays = () => {
|
|
74
335
|
hoverOverlays.forEach((overlay) => {
|
|
@@ -154,6 +415,9 @@ export function setupVisualEditAgent() {
|
|
|
154
415
|
const handleMouseOver = (e: MouseEvent) => {
|
|
155
416
|
if (!isVisualEditMode || isPopoverDragging) return;
|
|
156
417
|
|
|
418
|
+
// Skip hover effects when inline editing
|
|
419
|
+
if (currentEditingElement) return;
|
|
420
|
+
|
|
157
421
|
const target = e.target as Element;
|
|
158
422
|
|
|
159
423
|
// Prevent hover effects when a dropdown is open
|
|
@@ -221,6 +485,20 @@ export function setupVisualEditAgent() {
|
|
|
221
485
|
// Let layer dropdown clicks pass through without interference
|
|
222
486
|
if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
|
|
223
487
|
|
|
488
|
+
// Allow normal editing when clicking inside a contentEditable element
|
|
489
|
+
if (isInlineEditExperimentEnabled && target instanceof HTMLElement && target.contentEditable === "true") {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// If currently editing, clicking outside should exit editing mode
|
|
494
|
+
if (currentEditingElement) {
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
e.stopPropagation();
|
|
497
|
+
e.stopImmediatePropagation();
|
|
498
|
+
handleClearInlineEditingMode();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
224
502
|
// Close dropdowns when clicking anywhere in iframe if a dropdown is open
|
|
225
503
|
if (isDropdownOpen) {
|
|
226
504
|
e.preventDefault();
|
|
@@ -249,14 +527,45 @@ export function setupVisualEditAgent() {
|
|
|
249
527
|
return;
|
|
250
528
|
}
|
|
251
529
|
|
|
530
|
+
const htmlElement = element as HTMLElement;
|
|
531
|
+
const visualSelectorId = getElementSelectorId(element);
|
|
532
|
+
|
|
533
|
+
// Check if this element is already selected (second click scenario)
|
|
534
|
+
const isAlreadySelected =
|
|
535
|
+
selectedElementId === visualSelectorId &&
|
|
536
|
+
htmlElement.dataset.selected === "true";
|
|
537
|
+
|
|
538
|
+
if (isAlreadySelected) {
|
|
539
|
+
if (isInlineEditExperimentEnabled && shouldEnterInlineEditingMode(htmlElement)) {
|
|
540
|
+
handleEnterInlineEditingMode(htmlElement);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Select the element, mark for inline editing, and attach layer dropdown
|
|
546
|
+
if (currentEditingElement) {
|
|
547
|
+
handleClearInlineEditingMode();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (isInlineEditExperimentEnabled) {
|
|
551
|
+
const elements = findElementsById(visualSelectorId);
|
|
552
|
+
elements.forEach((el) => {
|
|
553
|
+
if (el instanceof HTMLElement) {
|
|
554
|
+
el.dataset.selected = "true";
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
252
559
|
const selectedOverlay = selectElement(element);
|
|
253
560
|
layerController.attachToOverlay(selectedOverlay, element);
|
|
254
561
|
};
|
|
255
562
|
|
|
256
|
-
//
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
563
|
+
// Unselect the current element
|
|
564
|
+
const unselectElement = () => {
|
|
565
|
+
if (currentEditingElement) {
|
|
566
|
+
handleClearInlineEditingMode();
|
|
567
|
+
}
|
|
568
|
+
clearSelection();
|
|
260
569
|
};
|
|
261
570
|
|
|
262
571
|
const updateElementClassesAndReposition = (visualSelectorId: string, classes: string) => {
|
|
@@ -288,7 +597,7 @@ export function setupVisualEditAgent() {
|
|
|
288
597
|
});
|
|
289
598
|
}
|
|
290
599
|
}
|
|
291
|
-
},
|
|
600
|
+
}, REPOSITION_DELAY_MS);
|
|
292
601
|
};
|
|
293
602
|
|
|
294
603
|
// Update element attribute by visual selector ID
|
|
@@ -311,7 +620,7 @@ export function setupVisualEditAgent() {
|
|
|
311
620
|
}
|
|
312
621
|
});
|
|
313
622
|
}
|
|
314
|
-
},
|
|
623
|
+
}, REPOSITION_DELAY_MS);
|
|
315
624
|
};
|
|
316
625
|
|
|
317
626
|
// Update element content by visual selector ID
|
|
@@ -334,7 +643,7 @@ export function setupVisualEditAgent() {
|
|
|
334
643
|
}
|
|
335
644
|
});
|
|
336
645
|
}
|
|
337
|
-
},
|
|
646
|
+
}, REPOSITION_DELAY_MS);
|
|
338
647
|
};
|
|
339
648
|
|
|
340
649
|
// --- Layer dropdown controller ---
|
|
@@ -356,12 +665,15 @@ export function setupVisualEditAgent() {
|
|
|
356
665
|
isVisualEditMode = isEnabled;
|
|
357
666
|
|
|
358
667
|
if (!isEnabled) {
|
|
668
|
+
if (currentEditingElement) {
|
|
669
|
+
handleClearInlineEditingMode();
|
|
670
|
+
}
|
|
671
|
+
clearSelection();
|
|
359
672
|
layerController.cleanup();
|
|
360
673
|
clearHoverOverlays();
|
|
361
674
|
clearSelectedOverlays();
|
|
362
675
|
|
|
363
676
|
currentHighlightedElements = [];
|
|
364
|
-
selectedElementId = null;
|
|
365
677
|
document.body.style.cursor = "default";
|
|
366
678
|
|
|
367
679
|
document.removeEventListener("mouseover", handleMouseOver);
|
|
@@ -422,6 +734,9 @@ export function setupVisualEditAgent() {
|
|
|
422
734
|
switch (message.type) {
|
|
423
735
|
case "toggle-visual-edit-mode":
|
|
424
736
|
toggleVisualEditMode(message.data.enabled);
|
|
737
|
+
if (message.data.specs?.newInlineEditEnabled !== undefined) {
|
|
738
|
+
isInlineEditExperimentEnabled = message.data.specs.newInlineEditEnabled;
|
|
739
|
+
}
|
|
425
740
|
break;
|
|
426
741
|
|
|
427
742
|
case "update-classes":
|
|
@@ -459,7 +774,7 @@ export function setupVisualEditAgent() {
|
|
|
459
774
|
break;
|
|
460
775
|
|
|
461
776
|
case "unselect-element":
|
|
462
|
-
|
|
777
|
+
unselectElement();
|
|
463
778
|
break;
|
|
464
779
|
|
|
465
780
|
case "refresh-page":
|
|
@@ -537,6 +852,50 @@ export function setupVisualEditAgent() {
|
|
|
537
852
|
}
|
|
538
853
|
break;
|
|
539
854
|
|
|
855
|
+
case "toggle-inline-edit-mode":
|
|
856
|
+
if (!isInlineEditExperimentEnabled) break;
|
|
857
|
+
if (!message.data || !message.data.dataSourceLocation) break;
|
|
858
|
+
|
|
859
|
+
{
|
|
860
|
+
const elements = findElementsById(message.data.dataSourceLocation);
|
|
861
|
+
if (elements.length === 0 || !(elements[0] instanceof HTMLElement)) break;
|
|
862
|
+
|
|
863
|
+
const element = elements[0];
|
|
864
|
+
|
|
865
|
+
if (message.data.inlineEditingMode) {
|
|
866
|
+
if (shouldEnterInlineEditingMode(element)) {
|
|
867
|
+
// Select the element first if not already selected
|
|
868
|
+
if (selectedElementId !== message.data.dataSourceLocation) {
|
|
869
|
+
if (currentEditingElement) {
|
|
870
|
+
handleClearInlineEditingMode();
|
|
871
|
+
}
|
|
872
|
+
clearSelection();
|
|
873
|
+
|
|
874
|
+
elements.forEach((el) => {
|
|
875
|
+
if (el instanceof HTMLElement) {
|
|
876
|
+
el.dataset.selected = "true";
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
elements.forEach((el) => {
|
|
881
|
+
const overlay = createOverlay(true);
|
|
882
|
+
document.body.appendChild(overlay);
|
|
883
|
+
selectedOverlays.push(overlay);
|
|
884
|
+
positionOverlay(overlay, el, true);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
selectedElementId = message.data.dataSourceLocation;
|
|
888
|
+
}
|
|
889
|
+
handleEnterInlineEditingMode(element);
|
|
890
|
+
}
|
|
891
|
+
} else {
|
|
892
|
+
if (currentEditingElement === element) {
|
|
893
|
+
handleClearInlineEditingMode();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
break;
|
|
898
|
+
|
|
540
899
|
default:
|
|
541
900
|
break;
|
|
542
901
|
}
|
|
@@ -601,7 +960,7 @@ export function setupVisualEditAgent() {
|
|
|
601
960
|
});
|
|
602
961
|
|
|
603
962
|
if (needsUpdate) {
|
|
604
|
-
setTimeout(handleResize,
|
|
963
|
+
setTimeout(handleResize, REPOSITION_DELAY_MS);
|
|
605
964
|
}
|
|
606
965
|
});
|
|
607
966
|
|