@base44/vite-plugin 1.0.34 → 1.0.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/vite-plugin",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "The Vite plugin for base44 based applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,13 @@
1
1
  import { errorOverlayCode } from "./ErrorOverlay.js";
2
2
  import type { Plugin } from "vite";
3
3
 
4
+ // Vite <=6 emits `class ErrorOverlay extends HTMLElement`, Vite >=7
5
+ // `var ErrorOverlay = class extends HTMLElement`. This is Vite's bundled output,
6
+ // not an API, so match the shape rather than the version; `\s` so a reformatted
7
+ // or minified emit still matches.
8
+ const VITE_OVERLAY_DECL =
9
+ /class ErrorOverlay(?=\s+extends)|\b(var|let|const)\s+ErrorOverlay(?=\s*=\s*class)/;
10
+
4
11
  export function errorOverlayPlugin() {
5
12
  return {
6
13
  name: "error-overlay",
@@ -10,10 +17,23 @@ export function errorOverlayPlugin() {
10
17
 
11
18
  if (!id.includes("vite/dist/client/client.mjs")) return;
12
19
 
13
- return code.replace(
14
- "class ErrorOverlay",
15
- errorOverlayCode + "\nclass OldErrorOverlay"
20
+ const patched = code.replace(VITE_OVERLAY_DECL, (_match, declaration) =>
21
+ errorOverlayCode +
22
+ "\n" +
23
+ (declaration ? `${declaration} OldErrorOverlay` : "class OldErrorOverlay")
16
24
  );
25
+ // replace() returns the input unchanged on a miss, so a new shape would
26
+ // silently restore Vite's red overlay — as it did for every Vite >=7.
27
+ if (patched === code) {
28
+ console.warn(
29
+ "[error-overlay] Could not find Vite's ErrorOverlay declaration in " +
30
+ "client.mjs — Vite's own overlay stays active and app_error will not " +
31
+ "be sent. @base44/vite-plugin needs updating for this Vite version."
32
+ );
33
+ // Returning the identical string would still cost client.mjs its sourcemap.
34
+ return;
35
+ }
36
+ return patched;
17
37
  },
18
38
  } as Plugin;
19
39
  }
@@ -1,6 +1,6 @@
1
1
  /** Controller that encapsulates layer-dropdown integration logic */
2
2
 
3
- import { getElementSelectorId } from "../utils.js";
3
+ import { readVisualEditTagName, getElementSelectorId } from "../utils.js";
4
4
  import { buildLayerChain } from "./utils.js";
5
5
  import {
6
6
  enhanceLabelWithChevron,
@@ -62,7 +62,7 @@ export function createLayerController(config: LayerControllerConfig): LayerContr
62
62
  } else {
63
63
  dropdownSourceLayer = {
64
64
  element,
65
- tagName: element.tagName.toLowerCase(),
65
+ tagName: readVisualEditTagName(element).toLowerCase(),
66
66
  selectorId: currentId,
67
67
  };
68
68
  config.onDeselect();
@@ -1,6 +1,6 @@
1
1
  /** DOM utilities for the layer-dropdown module */
2
2
 
3
- import { isInstrumentedElement, getElementSelectorId } from "../utils.js";
3
+ import { readVisualEditTagName, isInstrumentedElement, getElementSelectorId } from "../utils.js";
4
4
  import { MAX_PARENT_DEPTH, MAX_CHILD_DEPTH } from "./consts.js";
5
5
 
6
6
  import type { LayerInfo } from "./types.js";
@@ -23,7 +23,7 @@ export function getLayerDisplayName(layer: LayerInfo): string {
23
23
  function toLayerInfo(element: Element, depth?: number): LayerInfo {
24
24
  const info: LayerInfo = {
25
25
  element,
26
- tagName: element.tagName.toLowerCase(),
26
+ tagName: readVisualEditTagName(element).toLowerCase(),
27
27
  selectorId: getElementSelectorId(element),
28
28
  };
29
29
  if (depth !== undefined) info.depth = depth;
@@ -50,7 +50,7 @@ export function getInstrumentedDescendants(
50
50
  if (isInstrumentedElement(child)) {
51
51
  const info: LayerInfo = {
52
52
  element: child,
53
- tagName: child.tagName.toLowerCase(),
53
+ tagName: readVisualEditTagName(child).toLowerCase(),
54
54
  selectorId: getElementSelectorId(child),
55
55
  };
56
56
  if (startDepth !== undefined) {
@@ -53,6 +53,14 @@ export function getElementSelectorId(element: Element): string | null {
53
53
  );
54
54
  }
55
55
 
56
+ export function isImageComponent(element: Element): boolean {
57
+ return element.hasAttribute("data-base44-image");
58
+ }
59
+
60
+ export function readVisualEditTagName(element: Element): string {
61
+ return isImageComponent(element) ? "IMG" : element.tagName;
62
+ }
63
+
56
64
  export const ALLOWED_ATTRIBUTES: string[] = ["src"];
57
65
 
58
66
  export const PLUGIN_ELEMENT_ATTR = "data-vite-plugin-element";
@@ -93,7 +101,11 @@ export function updateElementAttribute(elements: Element[], attribute: string, v
93
101
  targetArrIndex === null ||
94
102
  (element as HTMLElement).dataset.arrIndex === targetArrIndex
95
103
  ) {
96
- element.setAttribute(attribute, value);
104
+ if (attribute === "src" && isImageComponent(element)) {
105
+ element.dispatchEvent(new CustomEvent("base44:image-replace", { detail: { src: value } }));
106
+ } else {
107
+ element.setAttribute(attribute, value);
108
+ }
97
109
  }
98
110
  });
99
111
  }
@@ -111,7 +123,9 @@ export function readElementClasses(element: Element): string {
111
123
  export function collectAllowedAttributes(element: Element, allowedAttributes: string[]): Record<string, string> {
112
124
  const attributes: Record<string, string> = {};
113
125
  for (const attr of allowedAttributes) {
114
- const val = element.getAttribute(attr);
126
+ const val = attr === "src" && isImageComponent(element)
127
+ ? element.getAttribute("data-base44-image-src")
128
+ : element.getAttribute(attr);
115
129
  if (val !== null) {
116
130
  attributes[attr] = val;
117
131
  }
@@ -1,4 +1,4 @@
1
- import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, readElementClasses, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
1
+ import { readVisualEditTagName, findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, readElementClasses, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
2
2
  import { createLayerController } from "./layer-dropdown/controller.js";
3
3
  import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
4
4
  import { createInlineEditController } from "../capabilities/inline-edit/index.js";
@@ -65,7 +65,7 @@ export function setupVisualEditAgent() {
65
65
 
66
66
  if (!label) {
67
67
  label = document.createElement("div");
68
- label.textContent = element.tagName.toLowerCase();
68
+ label.textContent = readVisualEditTagName(element).toLowerCase();
69
69
  label.style.position = "absolute";
70
70
  label.style.left = "-2px";
71
71
  label.style.padding = "2px 8px";
@@ -137,7 +137,7 @@ export function setupVisualEditAgent() {
137
137
  const notifyElementSelected = (element: Element) => {
138
138
  const htmlElement = element as HTMLElement;
139
139
  const rect = element.getBoundingClientRect();
140
- const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
140
+ const isTextElement = TEXT_TAGS.includes(readVisualEditTagName(element).toLowerCase());
141
141
 
142
142
  const arrEl = htmlElement.closest("[data-arr-variable-name]") as HTMLElement | null;
143
143
  const staticArrayName = arrEl?.dataset?.arrVariableName || null;
@@ -151,7 +151,7 @@ export function setupVisualEditAgent() {
151
151
 
152
152
  window.parent.postMessage({
153
153
  type: "element-selected",
154
- tagName: element.tagName,
154
+ tagName: readVisualEditTagName(element),
155
155
  classes: readElementClasses(element),
156
156
  visualSelectorId: getElementSelectorId(element),
157
157
  content: isTextElement ? htmlElement.innerText : undefined,
@@ -181,7 +181,7 @@ export function setupVisualEditAgent() {
181
181
  };
182
182
 
183
183
  // Select an element: create overlays, update state, notify parent
184
- const selectElement = (element: Element): HTMLDivElement | undefined => {
184
+ const selectElement = (element: Element, notifyParent = true): HTMLDivElement | undefined => {
185
185
  const visualSelectorId = getElementSelectorId(element);
186
186
 
187
187
  clearSelectedOverlays();
@@ -197,7 +197,7 @@ export function setupVisualEditAgent() {
197
197
  selectedElementId = visualSelectorId || null;
198
198
  selectedElement = element;
199
199
  clearHoverOverlays();
200
- notifyElementSelected(element);
200
+ if (notifyParent) notifyElementSelected(element);
201
201
 
202
202
  return selectedOverlays[0];
203
203
  };
@@ -381,9 +381,20 @@ export function setupVisualEditAgent() {
381
381
  // Reposition overlays after attribute change (e.g. image src swap can affect layout)
382
382
  setTimeout(() => {
383
383
  if (selectedElementId === visualSelectorId) {
384
+ const currentElements = findElementsById(visualSelectorId);
385
+ if (selectedElement && !selectedElement.isConnected) {
386
+ const selectedIndex = (selectedElement as HTMLElement).dataset.arrIndex;
387
+ const replacement = currentElements.find(
388
+ (element) => (element as HTMLElement).dataset.arrIndex === selectedIndex
389
+ );
390
+ if (replacement) {
391
+ const overlay = selectElement(replacement, false);
392
+ layerController.attachToOverlay(overlay, replacement);
393
+ }
394
+ }
384
395
  selectedOverlays.forEach((overlay, index) => {
385
- if (index < elements.length) {
386
- positionOverlay(overlay, elements[index]!);
396
+ if (index < currentElements.length) {
397
+ positionOverlay(overlay, currentElements[index]!);
387
398
  }
388
399
  });
389
400
  }
@@ -356,3 +356,18 @@ The `transformIndexHtml` hook injects the Tailwind CSS CDN, enabling visual edit
356
356
  | `src/injections/sandbox-mount-observer.ts` | Detects when instrumented elements mount in DOM |
357
357
  | `src/injections/utils.ts` | `findElementsById()`, `updateElementClasses()` utilities |
358
358
  | `tests/visual-edit-agent.test.ts` | Unit tests for element finding and class updates |
359
+
360
+ ### Seeded Image components
361
+
362
+ A `data-base44-image` wrapper is one logical image edit target. Its classes and
363
+ source location refer to the app's `<Image>` call site; report its tag as `IMG`
364
+ for the image toolbar and layer labels. Read `src` from `data-base44-image-src`,
365
+ which preserves the original prop rather than the inner image's transformed CDN
366
+ URL. The template keeps all instrumentation on the wrapper and suppresses source
367
+ locations on the placeholder and main image.
368
+
369
+ For `src` edits, dispatch `base44:image-replace` with `detail: { src }` on the
370
+ wrapper, retaining the selected-array-index filter. The template updates React
371
+ state so the replacement survives responsive resizes. Native images continue to
372
+ use direct attribute updates. Publish this support before seeding a template
373
+ that uses the wrapper contract.