@base44-preview/vite-plugin 1.0.6-pr.63.d7abd73 → 1.0.18-pr.80.1ee8db5

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 (56) hide show
  1. package/dist/html-injections-plugin.d.ts.map +1 -1
  2. package/dist/html-injections-plugin.js +4 -2
  3. package/dist/html-injections-plugin.js.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/injections/auto-popup-suppressor.d.ts +6 -0
  7. package/dist/injections/auto-popup-suppressor.d.ts.map +1 -0
  8. package/dist/injections/auto-popup-suppressor.js +273 -0
  9. package/dist/injections/auto-popup-suppressor.js.map +1 -0
  10. package/dist/injections/canvas-wheel-zoom-bridge.d.ts +5 -0
  11. package/dist/injections/canvas-wheel-zoom-bridge.d.ts.map +1 -0
  12. package/dist/injections/canvas-wheel-zoom-bridge.js +64 -0
  13. package/dist/injections/canvas-wheel-zoom-bridge.js.map +1 -0
  14. package/dist/injections/page-height-bridge.d.ts +14 -0
  15. package/dist/injections/page-height-bridge.d.ts.map +1 -0
  16. package/dist/injections/page-height-bridge.js +510 -0
  17. package/dist/injections/page-height-bridge.js.map +1 -0
  18. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  19. package/dist/injections/visual-edit-agent.js +21 -0
  20. package/dist/injections/visual-edit-agent.js.map +1 -1
  21. package/dist/jsx-processor.d.ts.map +1 -1
  22. package/dist/jsx-processor.js +1 -0
  23. package/dist/jsx-processor.js.map +1 -1
  24. package/dist/processors/collection-id-processor.d.ts +1 -0
  25. package/dist/processors/collection-id-processor.d.ts.map +1 -1
  26. package/dist/processors/collection-id-processor.js +17 -0
  27. package/dist/processors/collection-id-processor.js.map +1 -1
  28. package/dist/processors/collection-item-field-processor.d.ts.map +1 -1
  29. package/dist/processors/collection-item-field-processor.js +16 -5
  30. package/dist/processors/collection-item-field-processor.js.map +1 -1
  31. package/dist/processors/collection-item-id-processor.d.ts +27 -0
  32. package/dist/processors/collection-item-id-processor.d.ts.map +1 -1
  33. package/dist/processors/collection-item-id-processor.js +128 -1
  34. package/dist/processors/collection-item-id-processor.js.map +1 -1
  35. package/dist/processors/utils/collection-tracing-utils.d.ts +1 -1
  36. package/dist/processors/utils/collection-tracing-utils.d.ts.map +1 -1
  37. package/dist/processors/utils/collection-tracing-utils.js +48 -10
  38. package/dist/processors/utils/collection-tracing-utils.js.map +1 -1
  39. package/dist/processors/utils/shared-utils.js +1 -1
  40. package/dist/processors/utils/shared-utils.js.map +1 -1
  41. package/dist/statics/index.mjs +8 -8
  42. package/dist/statics/index.mjs.map +1 -1
  43. package/package.json +1 -1
  44. package/src/bridge.ts +1 -1
  45. package/src/html-injections-plugin.ts +4 -2
  46. package/src/index.ts +2 -2
  47. package/src/injections/auto-popup-suppressor.ts +277 -0
  48. package/src/injections/canvas-wheel-zoom-bridge.ts +78 -0
  49. package/src/injections/page-height-bridge.ts +566 -0
  50. package/src/injections/visual-edit-agent.ts +32 -1
  51. package/src/jsx-processor.ts +1 -0
  52. package/src/processors/collection-id-processor.ts +17 -0
  53. package/src/processors/collection-item-field-processor.ts +23 -5
  54. package/src/processors/collection-item-id-processor.ts +154 -0
  55. package/src/processors/utils/collection-tracing-utils.ts +54 -8
  56. package/src/processors/utils/shared-utils.ts +1 -1
package/src/bridge.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  * sync script tags from node_modules.
6
6
  */
7
7
 
8
- export { setupVisualEditAgent } from "./injections/visual-edit-agent.js";
8
+ export { setupVisualEditAgent } from "./injections/visual-edit-agent.js";
@@ -24,7 +24,7 @@ if (window.self === window.top) {
24
24
  const pageName = getPageNameFromPath(path) || "home";
25
25
  const appId = import.meta.env.VITE_BASE44_APP_ID;
26
26
  if (!appId) return;
27
- fetch(\`/app-logs/\${appId}/log-user-in-app/\${pageName}\`, {
27
+ fetch(\`/api/app-logs/\${appId}/log-user-in-app/\${pageName}\`, {
28
28
  method: "POST",
29
29
  }).catch(() => {});
30
30
  }
@@ -151,7 +151,9 @@ export function htmlInjectionsPlugin({
151
151
  ` ? "https://localhost:3201/index.mjs"`,
152
152
  ` : "${dist}/statics/index.mjs";`,
153
153
  ` import(url)`,
154
- ` .then(mod => { if (typeof mod.setupVisualEditAgent === "function") mod.setupVisualEditAgent(); })`,
154
+ ` .then(mod => {`,
155
+ ` if (typeof mod.setupVisualEditAgent === "function") mod.setupVisualEditAgent();`,
156
+ ` })`,
155
157
  ` .catch(e => console.error("[visual-edit-agent] Failed to load:", e));`,
156
158
  `}`,
157
159
  ].join("\n"),
package/src/index.ts CHANGED
@@ -27,7 +27,7 @@ export default function vitePlugin(
27
27
  return [
28
28
  {
29
29
  name: "base44",
30
- config: ({ mode, root = process.cwd(), server: serverConfig }) => {
30
+ config: ({ mode, root = process.cwd() }) => {
31
31
  const env = loadEnv(mode ?? "development", root, "");
32
32
 
33
33
  return {
@@ -53,7 +53,7 @@ export default function vitePlugin(
53
53
  server: {
54
54
  cors: true,
55
55
  host: "0.0.0.0", // Bind to all interfaces for container access
56
- port: serverConfig?.port ?? 5173,
56
+ port: 5173,
57
57
  strictPort: true,
58
58
  // Allow all hosts - essential for Modal tunnel URLs
59
59
  allowedHosts: true,
@@ -0,0 +1,277 @@
1
+ // auto-popup-suppressor — hides auto-opening modal popups in the canvas
2
+ // preview iframe so the page snapshot isn't obscured by overlays the user
3
+ // didn't intend to capture. Driven by `{ type: "suppress-auto-popups" }` from
4
+ // the parent; inert until that arrives.
5
+ //
6
+ // Two-stage heuristic:
7
+ //
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.
12
+ //
13
+ // 2. PANEL SWEEP — once we've hidden any backdrop, sweep the whole document
14
+ // for fixed/high-z elements whose bbox falls in a "modal-shaped" band
15
+ // (15–95% on both axes). Picks up the dialog content panel even when it
16
+ // isn't a direct sibling of the overlay (Radix wraps content in
17
+ // FocusScope + DismissableLayer + focus-guards; shadcn's portals use
18
+ // `asChild`, so structural relationships vary).
19
+ //
20
+ // A sticky `backdropEverDetected` flag keeps the panel-shape check live for
21
+ // later MutationObserver ticks — Content sometimes mounts in a different
22
+ // batch than its Overlay.
23
+ //
24
+ // Why the percentage gates aren't based on window.innerHeight: the canvas
25
+ // iframe is auto-sized to the document content (e.g. 4958px), so innerHeight
26
+ // is the document height, not the visible viewport. page-height-bridge sets
27
+ // `--base44-reference-vh-base` to the canvas tile height — the viewport the
28
+ // user actually sees — and that's what our gates use.
29
+
30
+ const SUPPRESSED_ATTR = "data-base44-suppressed-popup";
31
+ const REFERENCE_VH_BASE_VAR = "--base44-reference-vh-base";
32
+
33
+ const FULL_VIEWPORT_RATIO = 0.9;
34
+ const MODAL_PANEL_MIN_RATIO = 0.15;
35
+ const MODAL_PANEL_MAX_RATIO = 0.95;
36
+ const MIN_Z_INDEX = 40;
37
+
38
+ export type AutoPopupSuppressorController = {
39
+ suppress: () => void;
40
+ teardown: () => void;
41
+ };
42
+
43
+ export function createAutoPopupSuppressorController(): AutoPopupSuppressorController {
44
+ let observer: MutationObserver | null = null;
45
+ let domReadyHandler: (() => void) | null = null;
46
+ let active = false;
47
+ // Once a backdrop has been seen, modal-shaped panels appearing in later
48
+ // mutation batches are also hidden. Without this, a Content node that
49
+ // mounts after its Overlay's batch wouldn't be matched in-flight.
50
+ let backdropEverDetected = false;
51
+
52
+ const hideBackdrop = (el: Element): void => {
53
+ if (el.hasAttribute(SUPPRESSED_ATTR)) return;
54
+ applyHide(el);
55
+ 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
+ }
77
+ };
78
+
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);
88
+ }
89
+ };
90
+
91
+ const attach = (): void => {
92
+ scanNode(document);
93
+ if (observer) return;
94
+ observer = new MutationObserver((mutations) => {
95
+ for (const m of mutations) {
96
+ if (m.type !== "childList") continue;
97
+ for (let i = 0; i < m.addedNodes.length; i++) {
98
+ const node = m.addedNodes[i];
99
+ if (node instanceof Element) scanNode(node);
100
+ }
101
+ }
102
+ });
103
+ observer.observe(document.documentElement, { childList: true, subtree: true });
104
+ };
105
+
106
+ return {
107
+ suppress: (): void => {
108
+ if (active) return;
109
+ active = true;
110
+ if (document.body) {
111
+ attach();
112
+ } else {
113
+ domReadyHandler = (): void => {
114
+ domReadyHandler = null;
115
+ attach();
116
+ };
117
+ document.addEventListener("DOMContentLoaded", domReadyHandler);
118
+ }
119
+ },
120
+ teardown: (): void => {
121
+ active = false;
122
+ backdropEverDetected = false;
123
+ if (observer) {
124
+ observer.disconnect();
125
+ observer = null;
126
+ }
127
+ if (domReadyHandler) {
128
+ document.removeEventListener("DOMContentLoaded", domReadyHandler);
129
+ domReadyHandler = null;
130
+ }
131
+ },
132
+ };
133
+ }
134
+
135
+ // ── Heuristic ───────────────────────────────────────────────────────────────
136
+
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 {
165
+ if (!document.body) return;
166
+ const { vw, vh } = viewportForGates();
167
+ if (vw <= 0 || vh <= 0) return;
168
+ const candidates = document.body.querySelectorAll<HTMLElement>(
169
+ `*:not([${SUPPRESSED_ATTR}])`,
170
+ );
171
+ for (let i = 0; i < candidates.length; i++) {
172
+ const el = candidates[i];
173
+ if (el && matchesModalPanel(el, vw, vh)) applyHide(el);
174
+ }
175
+ }
176
+
177
+ // ── Gate predicates ─────────────────────────────────────────────────────────
178
+
179
+ function coversFullViewport(el: HTMLElement, vw: number, vh: number): boolean {
180
+ if (vw <= 0 || vh <= 0) return false;
181
+ const rect = el.getBoundingClientRect();
182
+ return (
183
+ rect.width >= vw * FULL_VIEWPORT_RATIO &&
184
+ rect.height >= vh * FULL_VIEWPORT_RATIO
185
+ );
186
+ }
187
+
188
+ // "Modal-shaped" = small enough not to be a sidebar/full-screen overlay
189
+ // (≤95% on both axes), big enough not to be a nav/footer/toast (≥15% on both
190
+ // axes). Calibrated against shadcn Dialog content (≈25–50% × 30–50%), top
191
+ // nav (height fails ≤15%), sidebar (height fails ≥95%), toast (height fails
192
+ // ≤15%).
193
+ function hasModalShapeBbox(el: HTMLElement, vw: number, vh: number): boolean {
194
+ const rect = el.getBoundingClientRect();
195
+ 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
200
+ );
201
+ }
202
+
203
+ function hasHighZIndex(style: CSSStyleDeclaration): boolean {
204
+ const z = parseInt(style.zIndex, 10);
205
+ return Number.isFinite(z) && z >= MIN_Z_INDEX;
206
+ }
207
+
208
+ // True if the element looks like a modal backdrop: translucent fill OR a
209
+ // backdrop-filter (e.g. blur). Also returns true if the element's own bg is
210
+ // transparent but a fixed/high-z descendant paints the backdrop — covers
211
+ // dialog wrappers that delegate the backdrop to a child element.
212
+ function hasBackdropLook(el: HTMLElement, style: CSSStyleDeclaration): boolean {
213
+ if (isTranslucent(style.backgroundColor)) return true;
214
+ if (style.backdropFilter && style.backdropFilter !== "none") return true;
215
+ const descendants = el.querySelectorAll<HTMLElement>("*");
216
+ for (let i = 0; i < descendants.length; i++) {
217
+ const child = descendants[i];
218
+ if (!child) continue;
219
+ const cs = window.getComputedStyle(child);
220
+ if (cs.position !== "fixed" && cs.position !== "absolute") continue;
221
+ if (!hasHighZIndex(cs)) continue;
222
+ if (isTranslucent(cs.backgroundColor)) return true;
223
+ if (cs.backdropFilter && cs.backdropFilter !== "none") return true;
224
+ }
225
+ return false;
226
+ }
227
+
228
+ function isTranslucent(color: string): boolean {
229
+ const alpha = parseAlpha(color);
230
+ return alpha !== null && alpha > 0 && alpha < 1;
231
+ }
232
+
233
+ // Extracts the alpha channel from a computed background-color across legacy
234
+ // AND modern color syntaxes:
235
+ //
236
+ // • `rgb(r, g, b)` / `rgba(r, g, b, a)` — legacy comma syntax.
237
+ // • `<func>(... / a)` / `<func>(... / a%)` — modern slash-alpha (oklch,
238
+ // oklab, lch, lab, hsl, hsla, color, modern rgb). Tailwind v4 / shadcn
239
+ // theme tokens compute to these.
240
+ // • A recognized color function call without explicit alpha → 1.
241
+ //
242
+ // Returns null when unrecognized; callers treat null as "not a backdrop".
243
+ function parseAlpha(value: string): number | null {
244
+ if (!value) return null;
245
+ const comma = /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*([0-9.]+)\s*)?\)$/.exec(value);
246
+ if (comma) return comma[1] === undefined ? 1 : parseFloat(comma[1]);
247
+ const slash = /\/\s*([0-9.]+)(%?)\s*\)$/.exec(value);
248
+ if (slash) {
249
+ const n = parseFloat(slash[1]!);
250
+ return slash[2] === "%" ? n / 100 : n;
251
+ }
252
+ if (/^[a-z]+\s*\(/.test(value)) return 1;
253
+ return null;
254
+ }
255
+
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
+ }
263
+
264
+ // Viewport dimensions used for percentage gates. Width comes from
265
+ // `innerWidth` (unaffected by canvas auto-fit). Height prefers the reference
266
+ // vh set by page-height-bridge — without that, after the iframe auto-resizes
267
+ // to document content, `innerHeight` is the document height and every modal
268
+ // panel falls below the 15% floor.
269
+ function viewportForGates(): { vw: number; vh: number } {
270
+ const vw = window.innerWidth || document.documentElement.clientWidth || 0;
271
+ const refStr = document.documentElement.style.getPropertyValue(REFERENCE_VH_BASE_VAR);
272
+ const ref = parseFloat(refStr);
273
+ const vh = Number.isFinite(ref) && ref > 0
274
+ ? ref
275
+ : (window.innerHeight || document.documentElement.clientHeight || 0);
276
+ return { vw, vh };
277
+ }
@@ -0,0 +1,78 @@
1
+ import { PLUGIN_ELEMENT_ATTR } from "./utils.js";
2
+
3
+ type CanvasWheelPanData = {
4
+ deltaX: number;
5
+ deltaY: number;
6
+ deltaMode: number;
7
+ clientX: number;
8
+ clientY: number;
9
+ shiftKey: boolean;
10
+ ctrlKey: false;
11
+ metaKey: false;
12
+ };
13
+
14
+ function elementFromEventTarget(target: EventTarget | null): Element | null {
15
+ if (target instanceof Element) return target;
16
+ if (target instanceof Node) return target.parentElement;
17
+ return null;
18
+ }
19
+
20
+ function isPluginOwnedTarget(target: EventTarget | null): boolean {
21
+ return elementFromEventTarget(target)?.closest(`[${PLUGIN_ELEMENT_ATTR}]`) != null;
22
+ }
23
+
24
+ export function createCanvasWheelZoomBridgeController() {
25
+ let isEnabled = false;
26
+
27
+ const onWheel = (event: WheelEvent): void => {
28
+ if (isPluginOwnedTarget(event.target)) return;
29
+
30
+ event.preventDefault();
31
+ if (event.ctrlKey || event.metaKey) {
32
+ window.parent.postMessage({
33
+ type: "canvas-wheel-zoom",
34
+ data: {
35
+ deltaY: event.deltaY,
36
+ deltaMode: event.deltaMode,
37
+ clientX: event.clientX,
38
+ clientY: event.clientY,
39
+ ctrlKey: event.ctrlKey,
40
+ metaKey: event.metaKey,
41
+ },
42
+ }, "*");
43
+ return;
44
+ }
45
+
46
+ const panData: CanvasWheelPanData = {
47
+ deltaX: event.deltaX,
48
+ deltaY: event.deltaY,
49
+ deltaMode: event.deltaMode,
50
+ clientX: event.clientX,
51
+ clientY: event.clientY,
52
+ shiftKey: event.shiftKey,
53
+ ctrlKey: false,
54
+ metaKey: false,
55
+ };
56
+ window.parent.postMessage({
57
+ type: "canvas-wheel-pan",
58
+ data: panData,
59
+ }, "*");
60
+ };
61
+
62
+ const enable = (): void => {
63
+ if (isEnabled) return;
64
+ isEnabled = true;
65
+ window.addEventListener("wheel", onWheel, { capture: true, passive: false });
66
+ };
67
+
68
+ const disable = (): void => {
69
+ if (!isEnabled) return;
70
+ isEnabled = false;
71
+ window.removeEventListener("wheel", onWheel, true);
72
+ };
73
+
74
+ return {
75
+ enable,
76
+ disable,
77
+ };
78
+ }