@base44-preview/vite-plugin 1.0.18-pr.80.1ee8db5 → 1.0.18-pr.80.79b32f2

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-preview/vite-plugin",
3
- "version": "1.0.18-pr.80.1ee8db5",
3
+ "version": "1.0.18-pr.80.79b32f2",
4
4
  "description": "The Vite plugin for base44 based applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,9 +6,9 @@
6
6
  // Two-stage heuristic:
7
7
  //
8
8
  // 1. BACKDROP MATCH — element must be `position: fixed`, cover ≥90% of the
9
- // viewport on both axes, have z-index ≥40, and show backdrop-like styles
10
- // (translucent background OR backdrop-filter). This is the modal-overlay
11
- // fingerprint.
9
+ // viewport on both axes, have a popup-level z-index, and show backdrop-
10
+ // like styles (translucent background OR backdrop-filter). This is the
11
+ // modal-overlay fingerprint.
12
12
  //
13
13
  // 2. PANEL SWEEP — once we've hidden any backdrop, sweep the whole document
14
14
  // for fixed/high-z elements whose bbox falls in a "modal-shaped" band
@@ -33,13 +33,17 @@ const REFERENCE_VH_BASE_VAR = "--base44-reference-vh-base";
33
33
  const FULL_VIEWPORT_RATIO = 0.9;
34
34
  const MODAL_PANEL_MIN_RATIO = 0.15;
35
35
  const MODAL_PANEL_MAX_RATIO = 0.95;
36
- const MIN_Z_INDEX = 40;
36
+ const POPUP_Z_INDEX_THRESHOLD = 40;
37
+
38
+ type Viewport = { vw: number; vh: number };
37
39
 
38
40
  export type AutoPopupSuppressorController = {
39
41
  suppress: () => void;
40
42
  teardown: () => void;
41
43
  };
42
44
 
45
+ // ── Public API ──────────────────────────────────────────────────────────────
46
+
43
47
  export function createAutoPopupSuppressorController(): AutoPopupSuppressorController {
44
48
  let observer: MutationObserver | null = null;
45
49
  let domReadyHandler: (() => void) | null = null;
@@ -49,70 +53,76 @@ export function createAutoPopupSuppressorController(): AutoPopupSuppressorContro
49
53
  // mounts after its Overlay's batch wouldn't be matched in-flight.
50
54
  let backdropEverDetected = false;
51
55
 
56
+ // Single decision per element. One getComputedStyle call — backdrop and
57
+ // panel checks share the position/z-index preconditions. Without sharing,
58
+ // every non-matching node in a React commit's added subtree would pay for
59
+ // two style recalcs, which is the MutationObserver hot path.
60
+ const classify = (el: Element, vp: Viewport): void => {
61
+ if (!(el instanceof HTMLElement)) return;
62
+ if (el.hasAttribute(SUPPRESSED_ATTR)) return;
63
+ const style = window.getComputedStyle(el);
64
+ if (style.position !== "fixed") return;
65
+ if (!hasPopupZIndex(style)) return;
66
+ if (coversFullViewport(el, vp) && hasBackdropLook(el, style)) {
67
+ hideBackdrop(el);
68
+ return;
69
+ }
70
+ if (backdropEverDetected && hasModalShapeBbox(el, vp)) {
71
+ applyHide(el);
72
+ }
73
+ };
74
+
52
75
  const hideBackdrop = (el: Element): void => {
53
76
  if (el.hasAttribute(SUPPRESSED_ATTR)) return;
54
77
  applyHide(el);
55
78
  backdropEverDetected = true;
56
- sweepDocumentForModalPanels();
57
- };
58
-
59
- const scanNode = (root: Element | Document): void => {
60
- if (!document.body) return;
61
- const { vw, vh } = viewportForGates();
62
- const startRoots: Element[] = root === document
63
- ? [document.body]
64
- : root instanceof Element
65
- ? [root]
66
- : [];
67
- for (const startRoot of startRoots) {
68
- checkNode(startRoot, vw, vh);
69
- const candidates = startRoot.querySelectorAll<HTMLElement>(
70
- `*:not([${SUPPRESSED_ATTR}])`,
71
- );
72
- for (let i = 0; i < candidates.length; i++) {
73
- const el = candidates[i];
74
- if (el) checkNode(el, vw, vh);
75
- }
76
- }
79
+ sweepDocumentForModalPanels(viewportForGates());
77
80
  };
78
81
 
79
- const checkNode = (el: Element, vw: number, vh: number): void => {
80
- if (matchesBackdrop(el, vw, vh)) {
81
- hideBackdrop(el);
82
- } else if (
83
- backdropEverDetected &&
84
- el instanceof HTMLElement &&
85
- matchesModalPanel(el, vw, vh)
86
- ) {
87
- applyHide(el);
82
+ const scanSubtree = (root: Element, vp: Viewport): void => {
83
+ classify(root, vp);
84
+ const descendants = root.querySelectorAll<HTMLElement>(
85
+ `*:not([${SUPPRESSED_ATTR}])`,
86
+ );
87
+ for (let i = 0; i < descendants.length; i++) {
88
+ const el = descendants[i];
89
+ if (el) classify(el, vp);
88
90
  }
89
91
  };
90
92
 
91
- const attach = (): void => {
92
- scanNode(document);
93
+ const attachObserver = (): void => {
93
94
  if (observer) return;
95
+ // viewportForGates is read once per mutation batch, not per added node —
96
+ // a busy React commit can deliver dozens of added subtrees in one tick.
94
97
  observer = new MutationObserver((mutations) => {
98
+ const vp = viewportForGates();
95
99
  for (const m of mutations) {
96
100
  if (m.type !== "childList") continue;
97
101
  for (let i = 0; i < m.addedNodes.length; i++) {
98
102
  const node = m.addedNodes[i];
99
- if (node instanceof Element) scanNode(node);
103
+ if (node instanceof Element) scanSubtree(node, vp);
100
104
  }
101
105
  }
102
106
  });
103
107
  observer.observe(document.documentElement, { childList: true, subtree: true });
104
108
  };
105
109
 
110
+ const start = (): void => {
111
+ if (!document.body) return;
112
+ scanSubtree(document.body, viewportForGates());
113
+ attachObserver();
114
+ };
115
+
106
116
  return {
107
117
  suppress: (): void => {
108
118
  if (active) return;
109
119
  active = true;
110
120
  if (document.body) {
111
- attach();
121
+ start();
112
122
  } else {
113
123
  domReadyHandler = (): void => {
114
124
  domReadyHandler = null;
115
- attach();
125
+ start();
116
126
  };
117
127
  document.addEventListener("DOMContentLoaded", domReadyHandler);
118
128
  }
@@ -132,56 +142,40 @@ export function createAutoPopupSuppressorController(): AutoPopupSuppressorContro
132
142
  };
133
143
  }
134
144
 
135
- // ── Heuristic ───────────────────────────────────────────────────────────────
145
+ // ── Document-wide panel sweep ───────────────────────────────────────────────
136
146
 
137
- // Stage 1: identify a modal backdrop.
138
- //
139
- // Gate order matters for perf — `getComputedStyle` forces a style recalc, so
140
- // we run the position check first (drops 99%+ of nodes), then the cheaper
141
- // bbox check, then style-derived gates. `matchesOverlay` is called for every
142
- // node in every added subtree, so this is the hot path.
143
- function matchesBackdrop(el: Element, vw: number, vh: number): boolean {
144
- if (!(el instanceof HTMLElement)) return false;
145
- if (el.hasAttribute(SUPPRESSED_ATTR)) return false;
146
- const style = window.getComputedStyle(el);
147
- if (style.position !== "fixed") return false;
148
- if (!coversFullViewport(el, vw, vh)) return false;
149
- if (!hasHighZIndex(style)) return false;
150
- return hasBackdropLook(el, style);
151
- }
152
-
153
- // Stage 2: identify the content panel paired with a backdrop. Runs both as a
154
- // document-wide sweep after a backdrop hide AND as an inline check on each
155
- // newly-added node once `backdropEverDetected` is set.
156
- function matchesModalPanel(el: HTMLElement, vw: number, vh: number): boolean {
157
- if (el.hasAttribute(SUPPRESSED_ATTR)) return false;
158
- const style = window.getComputedStyle(el);
159
- if (style.position !== "fixed") return false;
160
- if (!hasHighZIndex(style)) return false;
161
- return hasModalShapeBbox(el, vw, vh);
162
- }
163
-
164
- function sweepDocumentForModalPanels(): void {
147
+ // Triggered once per backdrop hide. Walks the whole document for unhidden
148
+ // fixed/high-z elements whose bbox falls inside the modal-shape band — picks
149
+ // up content panels regardless of how they're nested or which portal they
150
+ // went through.
151
+ function sweepDocumentForModalPanels(vp: Viewport): void {
165
152
  if (!document.body) return;
166
- const { vw, vh } = viewportForGates();
167
- if (vw <= 0 || vh <= 0) return;
153
+ if (vp.vw <= 0 || vp.vh <= 0) return;
168
154
  const candidates = document.body.querySelectorAll<HTMLElement>(
169
155
  `*:not([${SUPPRESSED_ATTR}])`,
170
156
  );
171
157
  for (let i = 0; i < candidates.length; i++) {
172
158
  const el = candidates[i];
173
- if (el && matchesModalPanel(el, vw, vh)) applyHide(el);
159
+ if (el && isModalPanel(el, vp)) applyHide(el);
174
160
  }
175
161
  }
176
162
 
163
+ function isModalPanel(el: HTMLElement, vp: Viewport): boolean {
164
+ if (el.hasAttribute(SUPPRESSED_ATTR)) return false;
165
+ const style = window.getComputedStyle(el);
166
+ if (style.position !== "fixed") return false;
167
+ if (!hasPopupZIndex(style)) return false;
168
+ return hasModalShapeBbox(el, vp);
169
+ }
170
+
177
171
  // ── Gate predicates ─────────────────────────────────────────────────────────
178
172
 
179
- function coversFullViewport(el: HTMLElement, vw: number, vh: number): boolean {
180
- if (vw <= 0 || vh <= 0) return false;
173
+ function coversFullViewport(el: HTMLElement, vp: Viewport): boolean {
174
+ if (vp.vw <= 0 || vp.vh <= 0) return false;
181
175
  const rect = el.getBoundingClientRect();
182
176
  return (
183
- rect.width >= vw * FULL_VIEWPORT_RATIO &&
184
- rect.height >= vh * FULL_VIEWPORT_RATIO
177
+ rect.width >= vp.vw * FULL_VIEWPORT_RATIO &&
178
+ rect.height >= vp.vh * FULL_VIEWPORT_RATIO
185
179
  );
186
180
  }
187
181
 
@@ -190,19 +184,19 @@ function coversFullViewport(el: HTMLElement, vw: number, vh: number): boolean {
190
184
  // axes). Calibrated against shadcn Dialog content (≈25–50% × 30–50%), top
191
185
  // nav (height fails ≤15%), sidebar (height fails ≥95%), toast (height fails
192
186
  // ≤15%).
193
- function hasModalShapeBbox(el: HTMLElement, vw: number, vh: number): boolean {
187
+ function hasModalShapeBbox(el: HTMLElement, vp: Viewport): boolean {
194
188
  const rect = el.getBoundingClientRect();
195
189
  return (
196
- rect.width >= vw * MODAL_PANEL_MIN_RATIO &&
197
- rect.height >= vh * MODAL_PANEL_MIN_RATIO &&
198
- rect.width <= vw * MODAL_PANEL_MAX_RATIO &&
199
- rect.height <= vh * MODAL_PANEL_MAX_RATIO
190
+ rect.width >= vp.vw * MODAL_PANEL_MIN_RATIO &&
191
+ rect.height >= vp.vh * MODAL_PANEL_MIN_RATIO &&
192
+ rect.width <= vp.vw * MODAL_PANEL_MAX_RATIO &&
193
+ rect.height <= vp.vh * MODAL_PANEL_MAX_RATIO
200
194
  );
201
195
  }
202
196
 
203
- function hasHighZIndex(style: CSSStyleDeclaration): boolean {
197
+ function hasPopupZIndex(style: CSSStyleDeclaration): boolean {
204
198
  const z = parseInt(style.zIndex, 10);
205
- return Number.isFinite(z) && z >= MIN_Z_INDEX;
199
+ return Number.isFinite(z) && z >= POPUP_Z_INDEX_THRESHOLD;
206
200
  }
207
201
 
208
202
  // True if the element looks like a modal backdrop: translucent fill OR a
@@ -218,7 +212,7 @@ function hasBackdropLook(el: HTMLElement, style: CSSStyleDeclaration): boolean {
218
212
  if (!child) continue;
219
213
  const cs = window.getComputedStyle(child);
220
214
  if (cs.position !== "fixed" && cs.position !== "absolute") continue;
221
- if (!hasHighZIndex(cs)) continue;
215
+ if (!hasPopupZIndex(cs)) continue;
222
216
  if (isTranslucent(cs.backgroundColor)) return true;
223
217
  if (cs.backdropFilter && cs.backdropFilter !== "none") return true;
224
218
  }
@@ -253,20 +247,14 @@ function parseAlpha(value: string): number | null {
253
247
  return null;
254
248
  }
255
249
 
256
- // ── DOM ops ─────────────────────────────────────────────────────────────────
257
-
258
- function applyHide(el: Element): void {
259
- if (el.hasAttribute(SUPPRESSED_ATTR)) return;
260
- el.setAttribute(SUPPRESSED_ATTR, "1");
261
- (el as HTMLElement).style.setProperty("display", "none", "important");
262
- }
250
+ // ── Viewport + DOM ops ──────────────────────────────────────────────────────
263
251
 
264
252
  // Viewport dimensions used for percentage gates. Width comes from
265
253
  // `innerWidth` (unaffected by canvas auto-fit). Height prefers the reference
266
254
  // vh set by page-height-bridge — without that, after the iframe auto-resizes
267
255
  // to document content, `innerHeight` is the document height and every modal
268
256
  // panel falls below the 15% floor.
269
- function viewportForGates(): { vw: number; vh: number } {
257
+ function viewportForGates(): Viewport {
270
258
  const vw = window.innerWidth || document.documentElement.clientWidth || 0;
271
259
  const refStr = document.documentElement.style.getPropertyValue(REFERENCE_VH_BASE_VAR);
272
260
  const ref = parseFloat(refStr);
@@ -275,3 +263,9 @@ function viewportForGates(): { vw: number; vh: number } {
275
263
  : (window.innerHeight || document.documentElement.clientHeight || 0);
276
264
  return { vw, vh };
277
265
  }
266
+
267
+ function applyHide(el: Element): void {
268
+ if (el.hasAttribute(SUPPRESSED_ATTR)) return;
269
+ el.setAttribute(SUPPRESSED_ATTR, "1");
270
+ (el as HTMLElement).style.setProperty("display", "none", "important");
271
+ }