@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/dist/injections/auto-popup-suppressor.d.ts.map +1 -1
- package/dist/injections/auto-popup-suppressor.js +85 -94
- package/dist/injections/auto-popup-suppressor.js.map +1 -1
- package/dist/statics/index.mjs +7 -7
- package/dist/statics/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/injections/auto-popup-suppressor.ts +85 -91
package/package.json
CHANGED
|
@@ -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
|
|
10
|
-
// (translucent background OR backdrop-filter). This is the
|
|
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
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
121
|
+
start();
|
|
112
122
|
} else {
|
|
113
123
|
domReadyHandler = (): void => {
|
|
114
124
|
domReadyHandler = null;
|
|
115
|
-
|
|
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
|
-
// ──
|
|
145
|
+
// ── Document-wide panel sweep ───────────────────────────────────────────────
|
|
136
146
|
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
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
|
-
|
|
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 &&
|
|
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,
|
|
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,
|
|
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
|
|
197
|
+
function hasPopupZIndex(style: CSSStyleDeclaration): boolean {
|
|
204
198
|
const z = parseInt(style.zIndex, 10);
|
|
205
|
-
return Number.isFinite(z) && z >=
|
|
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 (!
|
|
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():
|
|
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
|
+
}
|