@base44-preview/vite-plugin 0.2.26-pr.42.7e00d38 → 0.2.26-pr.42.e4df6d7

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.
Files changed (27) hide show
  1. package/dist/capabilities/inline-edit/controller.d.ts +3 -0
  2. package/dist/capabilities/inline-edit/controller.d.ts.map +1 -0
  3. package/dist/capabilities/inline-edit/controller.js +190 -0
  4. package/dist/capabilities/inline-edit/controller.js.map +1 -0
  5. package/dist/capabilities/inline-edit/dom-utils.d.ts +6 -0
  6. package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
  7. package/dist/capabilities/inline-edit/dom-utils.js +49 -0
  8. package/dist/capabilities/inline-edit/dom-utils.js.map +1 -0
  9. package/dist/capabilities/inline-edit/index.d.ts +3 -0
  10. package/dist/capabilities/inline-edit/index.d.ts.map +1 -0
  11. package/dist/capabilities/inline-edit/index.js +2 -0
  12. package/dist/capabilities/inline-edit/index.js.map +1 -0
  13. package/dist/capabilities/inline-edit/types.d.ts +25 -0
  14. package/dist/capabilities/inline-edit/types.d.ts.map +1 -0
  15. package/dist/capabilities/inline-edit/types.js +2 -0
  16. package/dist/capabilities/inline-edit/types.js.map +1 -0
  17. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  18. package/dist/injections/visual-edit-agent.js +41 -274
  19. package/dist/injections/visual-edit-agent.js.map +1 -1
  20. package/dist/statics/index.mjs +5 -5
  21. package/dist/statics/index.mjs.map +1 -1
  22. package/package.json +1 -1
  23. package/src/capabilities/inline-edit/controller.ts +241 -0
  24. package/src/capabilities/inline-edit/dom-utils.ts +48 -0
  25. package/src/capabilities/inline-edit/index.ts +2 -0
  26. package/src/capabilities/inline-edit/types.ts +30 -0
  27. package/src/injections/visual-edit-agent.ts +41 -327
@@ -0,0 +1,48 @@
1
+ const FOCUS_STYLE_ID = "visual-edit-focus-styles";
2
+
3
+ const EDITABLE_TAGS = [
4
+ "div", "p", "h1", "h2", "h3", "h4", "h5", "h6",
5
+ "span", "li", "td", "a", "button", "label",
6
+ ];
7
+
8
+ export const injectFocusOutlineCSS = () => {
9
+ if (document.getElementById(FOCUS_STYLE_ID)) return;
10
+
11
+ const style = document.createElement("style");
12
+ style.id = FOCUS_STYLE_ID;
13
+ style.textContent = `
14
+ [data-selected="true"][contenteditable="true"]:focus {
15
+ outline: none !important;
16
+ }
17
+ `;
18
+ document.head.appendChild(style);
19
+ };
20
+
21
+ export const removeFocusOutlineCSS = () => {
22
+ document.getElementById(FOCUS_STYLE_ID)?.remove();
23
+ };
24
+
25
+ export const selectText = (element: HTMLElement) => {
26
+ const range = document.createRange();
27
+ range.selectNodeContents(element);
28
+ const selection = window.getSelection();
29
+ selection?.removeAllRanges();
30
+ selection?.addRange(range);
31
+ };
32
+
33
+ export const isEditableTextElement = (element: Element): boolean => {
34
+ if (!(element instanceof HTMLElement)) return false;
35
+ if (!EDITABLE_TAGS.includes(element.tagName.toLowerCase())) return false;
36
+ if (!element.textContent?.trim()) return false;
37
+ if (element.querySelector("img, video, canvas, svg")) return false;
38
+ if (element.children.length > 0) return false;
39
+ if (element.dataset.dynamicContent === "true") return false;
40
+ return true;
41
+ };
42
+
43
+ export const shouldEnterInlineEditingMode = (element: Element): boolean => {
44
+ if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
45
+ return false;
46
+ }
47
+ return isEditableTextElement(element);
48
+ };
@@ -0,0 +1,2 @@
1
+ export { createInlineEditController } from "./controller.js";
2
+ export type { InlineEditController, InlineEditHost } from "./types.js";
@@ -0,0 +1,30 @@
1
+ export interface InlineEditHost {
2
+ findElementsById(id: string | null): Element[];
3
+ getSelectedElementId(): string | null;
4
+ getSelectedOverlays(): HTMLDivElement[];
5
+ positionOverlay(
6
+ overlay: HTMLDivElement,
7
+ element: Element,
8
+ isSelected?: boolean
9
+ ): void;
10
+ clearSelection(): void;
11
+ createSelectionOverlays(elements: Element[], elementId: string): void;
12
+ }
13
+
14
+ export interface ToggleInlineEditData {
15
+ dataSourceLocation: string;
16
+ inlineEditingMode: boolean;
17
+ }
18
+
19
+ export interface InlineEditController {
20
+ enabled: boolean;
21
+ isEditing(): boolean;
22
+ getCurrentElement(): HTMLElement | null;
23
+ canEdit(element: Element): boolean;
24
+ startEditing(element: HTMLElement): void;
25
+ stopEditing(): void;
26
+ markElementsSelected(elements: Element[]): void;
27
+ clearSelectedMarks(elementId: string | null): void;
28
+ handleToggleMessage(data: ToggleInlineEditData): void;
29
+ cleanup(): void;
30
+ }
@@ -1,6 +1,7 @@
1
1
  import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
+ import { createInlineEditController } from "../capabilities/inline-edit/index.js";
4
5
 
5
6
  export function setupVisualEditAgent() {
6
7
  // State variables (replacing React useState/useRef)
@@ -11,16 +12,9 @@ export function setupVisualEditAgent() {
11
12
  let selectedOverlays: HTMLDivElement[] = [];
12
13
  let currentHighlightedElements: Element[] = [];
13
14
  let selectedElementId: string | null = null;
14
- let currentEditingElement: HTMLElement | null = null;
15
- let debouncedSendTimeout: ReturnType<typeof setTimeout> | null = null;
16
- let isInlineEditExperimentEnabled = false;
17
15
 
18
- const INLINE_EDIT_DEBOUNCE_MS = 500;
19
16
  const REPOSITION_DELAY_MS = 50;
20
17
 
21
- // WeakMap to track AbortControllers for each element's input listener
22
- const listenerAbortControllers = new WeakMap<HTMLElement, AbortController>();
23
-
24
18
  // Create overlay element
25
19
  const createOverlay = (isSelected = false): HTMLDivElement => {
26
20
  const overlay = document.createElement("div");
@@ -78,255 +72,31 @@ export function setupVisualEditAgent() {
78
72
  }
79
73
  };
80
74
 
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);
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) => {
317
87
  elements.forEach((el) => {
318
- if (el instanceof HTMLElement) {
319
- delete el.dataset.selected;
320
- }
88
+ const overlay = createOverlay(true);
89
+ document.body.appendChild(overlay);
90
+ selectedOverlays.push(overlay);
91
+ positionOverlay(overlay, el, true);
321
92
  });
322
- }
93
+ selectedElementId = elementId;
94
+ },
95
+ });
323
96
 
324
- selectedOverlays.forEach((overlay) => {
325
- if (overlay && overlay.parentNode) {
326
- overlay.remove();
327
- }
328
- });
329
- selectedOverlays = [];
97
+ const clearSelection = () => {
98
+ inlineEdit.clearSelectedMarks(selectedElementId);
99
+ clearSelectedOverlays();
330
100
  selectedElementId = null;
331
101
  };
332
102
 
@@ -413,10 +183,8 @@ export function setupVisualEditAgent() {
413
183
 
414
184
  // Handle mouse over event
415
185
  const handleMouseOver = (e: MouseEvent) => {
416
- if (!isVisualEditMode || isPopoverDragging) return;
186
+ if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
417
187
 
418
- // Skip hover effects when inline editing
419
- if (currentEditingElement) return;
420
188
 
421
189
  const target = e.target as Element;
422
190
 
@@ -485,17 +253,18 @@ export function setupVisualEditAgent() {
485
253
  // Let layer dropdown clicks pass through without interference
486
254
  if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
487
255
 
488
- // Allow normal editing when clicking inside a contentEditable element
489
- if (isInlineEditExperimentEnabled && target instanceof HTMLElement && target.contentEditable === "true") {
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") {
490
259
  return;
491
260
  }
492
261
 
493
- // If currently editing, clicking outside should exit editing mode
494
- if (currentEditingElement) {
262
+ // Clicking outside the editable element exits inline editing mode.
263
+ if (inlineEdit.isEditing()) {
495
264
  e.preventDefault();
496
265
  e.stopPropagation();
497
266
  e.stopImmediatePropagation();
498
- handleClearInlineEditingMode();
267
+ inlineEdit.stopEditing();
499
268
  return;
500
269
  }
501
270
 
@@ -530,41 +299,27 @@ export function setupVisualEditAgent() {
530
299
  const htmlElement = element as HTMLElement;
531
300
  const visualSelectorId = getElementSelectorId(element);
532
301
 
533
- // Check if this element is already selected (second click scenario)
534
302
  const isAlreadySelected =
535
303
  selectedElementId === visualSelectorId &&
536
304
  htmlElement.dataset.selected === "true";
537
305
 
538
- if (isAlreadySelected) {
539
- if (isInlineEditExperimentEnabled && shouldEnterInlineEditingMode(htmlElement)) {
540
- handleEnterInlineEditingMode(htmlElement);
541
- return;
542
- }
306
+ if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
307
+ inlineEdit.startEditing(htmlElement);
308
+ return;
543
309
  }
544
310
 
545
- // Select the element, mark for inline editing, and attach layer dropdown
546
- if (currentEditingElement) {
547
- handleClearInlineEditingMode();
548
- }
311
+ inlineEdit.stopEditing();
549
312
 
550
- if (isInlineEditExperimentEnabled) {
551
- const elements = findElementsById(visualSelectorId);
552
- elements.forEach((el) => {
553
- if (el instanceof HTMLElement) {
554
- el.dataset.selected = "true";
555
- }
556
- });
313
+ if (inlineEdit.enabled) {
314
+ inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
557
315
  }
558
316
 
559
317
  const selectedOverlay = selectElement(element);
560
318
  layerController.attachToOverlay(selectedOverlay, element);
561
319
  };
562
320
 
563
- // Unselect the current element
564
321
  const unselectElement = () => {
565
- if (currentEditingElement) {
566
- handleClearInlineEditingMode();
567
- }
322
+ inlineEdit.stopEditing();
568
323
  clearSelection();
569
324
  };
570
325
 
@@ -665,13 +420,10 @@ export function setupVisualEditAgent() {
665
420
  isVisualEditMode = isEnabled;
666
421
 
667
422
  if (!isEnabled) {
668
- if (currentEditingElement) {
669
- handleClearInlineEditingMode();
670
- }
423
+ inlineEdit.stopEditing();
671
424
  clearSelection();
672
425
  layerController.cleanup();
673
426
  clearHoverOverlays();
674
- clearSelectedOverlays();
675
427
 
676
428
  currentHighlightedElements = [];
677
429
  document.body.style.cursor = "default";
@@ -735,7 +487,7 @@ export function setupVisualEditAgent() {
735
487
  case "toggle-visual-edit-mode":
736
488
  toggleVisualEditMode(message.data.enabled);
737
489
  if (message.data.specs?.newInlineEditEnabled !== undefined) {
738
- isInlineEditExperimentEnabled = message.data.specs.newInlineEditEnabled;
490
+ inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
739
491
  }
740
492
  break;
741
493
 
@@ -853,46 +605,8 @@ export function setupVisualEditAgent() {
853
605
  break;
854
606
 
855
607
  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
- }
608
+ if (message.data) {
609
+ inlineEdit.handleToggleMessage(message.data);
896
610
  }
897
611
  break;
898
612