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

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 -272
  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 -326
@@ -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
 
@@ -415,8 +185,7 @@ export function setupVisualEditAgent() {
415
185
  const handleMouseOver = (e: MouseEvent) => {
416
186
  if (!isVisualEditMode || isPopoverDragging) return;
417
187
 
418
- // Skip hover effects when inline editing
419
- if (currentEditingElement) return;
188
+ if (inlineEdit.isEditing()) return;
420
189
 
421
190
  const target = e.target as Element;
422
191
 
@@ -485,17 +254,18 @@ export function setupVisualEditAgent() {
485
254
  // Let layer dropdown clicks pass through without interference
486
255
  if (target.closest(`[${LAYER_DROPDOWN_ATTR}]`)) return;
487
256
 
488
- // Allow normal editing when clicking inside a contentEditable element
489
- if (isInlineEditExperimentEnabled && target instanceof HTMLElement && target.contentEditable === "true") {
257
+ // Let clicks inside the editable element pass through to the browser
258
+ // so the user can reposition the cursor and select text naturally.
259
+ if (inlineEdit.enabled && target instanceof HTMLElement && target.contentEditable === "true") {
490
260
  return;
491
261
  }
492
262
 
493
- // If currently editing, clicking outside should exit editing mode
494
- if (currentEditingElement) {
263
+ // Clicking outside the editable element exits inline editing mode.
264
+ if (inlineEdit.isEditing()) {
495
265
  e.preventDefault();
496
266
  e.stopPropagation();
497
267
  e.stopImmediatePropagation();
498
- handleClearInlineEditingMode();
268
+ inlineEdit.stopEditing();
499
269
  return;
500
270
  }
501
271
 
@@ -530,41 +300,27 @@ export function setupVisualEditAgent() {
530
300
  const htmlElement = element as HTMLElement;
531
301
  const visualSelectorId = getElementSelectorId(element);
532
302
 
533
- // Check if this element is already selected (second click scenario)
534
303
  const isAlreadySelected =
535
304
  selectedElementId === visualSelectorId &&
536
305
  htmlElement.dataset.selected === "true";
537
306
 
538
- if (isAlreadySelected) {
539
- if (isInlineEditExperimentEnabled && shouldEnterInlineEditingMode(htmlElement)) {
540
- handleEnterInlineEditingMode(htmlElement);
541
- return;
542
- }
307
+ if (isAlreadySelected && inlineEdit.enabled && inlineEdit.canEdit(htmlElement)) {
308
+ inlineEdit.startEditing(htmlElement);
309
+ return;
543
310
  }
544
311
 
545
- // Select the element, mark for inline editing, and attach layer dropdown
546
- if (currentEditingElement) {
547
- handleClearInlineEditingMode();
548
- }
312
+ inlineEdit.stopEditing();
549
313
 
550
- if (isInlineEditExperimentEnabled) {
551
- const elements = findElementsById(visualSelectorId);
552
- elements.forEach((el) => {
553
- if (el instanceof HTMLElement) {
554
- el.dataset.selected = "true";
555
- }
556
- });
314
+ if (inlineEdit.enabled) {
315
+ inlineEdit.markElementsSelected(findElementsById(visualSelectorId));
557
316
  }
558
317
 
559
318
  const selectedOverlay = selectElement(element);
560
319
  layerController.attachToOverlay(selectedOverlay, element);
561
320
  };
562
321
 
563
- // Unselect the current element
564
322
  const unselectElement = () => {
565
- if (currentEditingElement) {
566
- handleClearInlineEditingMode();
567
- }
323
+ inlineEdit.stopEditing();
568
324
  clearSelection();
569
325
  };
570
326
 
@@ -665,13 +421,10 @@ export function setupVisualEditAgent() {
665
421
  isVisualEditMode = isEnabled;
666
422
 
667
423
  if (!isEnabled) {
668
- if (currentEditingElement) {
669
- handleClearInlineEditingMode();
670
- }
424
+ inlineEdit.stopEditing();
671
425
  clearSelection();
672
426
  layerController.cleanup();
673
427
  clearHoverOverlays();
674
- clearSelectedOverlays();
675
428
 
676
429
  currentHighlightedElements = [];
677
430
  document.body.style.cursor = "default";
@@ -735,7 +488,7 @@ export function setupVisualEditAgent() {
735
488
  case "toggle-visual-edit-mode":
736
489
  toggleVisualEditMode(message.data.enabled);
737
490
  if (message.data.specs?.newInlineEditEnabled !== undefined) {
738
- isInlineEditExperimentEnabled = message.data.specs.newInlineEditEnabled;
491
+ inlineEdit.enabled = message.data.specs.newInlineEditEnabled;
739
492
  }
740
493
  break;
741
494
 
@@ -853,46 +606,8 @@ export function setupVisualEditAgent() {
853
606
  break;
854
607
 
855
608
  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
- }
609
+ if (message.data) {
610
+ inlineEdit.handleToggleMessage(message.data);
896
611
  }
897
612
  break;
898
613