@replohq/sandbox-runtime 1.19.0 → 1.19.2
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/buildMetadata.js +3 -3
- package/package.json +4 -4
- package/previewHealth.d.ts +7 -0
- package/previewHealth.js +31 -0
- package/select-mode/EditorSelection.js +5 -0
- package/select-mode/hooks/useSelectionHover.js +7 -1
- package/select-mode/hooks/useSelectionInteractions.js +6 -1
- package/select-mode/interactions/embeddedFrames.d.ts +17 -0
- package/select-mode/interactions/embeddedFrames.js +32 -0
package/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.2",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-10-
|
|
5
|
+
"commit": "cdc23a21d2f115a5a458a13a97942dac138e692d",
|
|
6
|
+
"builtAt": "2026-10-24T17:26:05.661Z"
|
|
7
7
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.2",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
-
"version": "1.19.
|
|
6
|
+
"version": "1.19.2",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-10-
|
|
8
|
+
"commit": "cdc23a21d2f115a5a458a13a97942dac138e692d",
|
|
9
|
+
"builtAt": "2026-10-24T17:26:05.661Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
package/previewHealth.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ declare global {
|
|
|
19
19
|
__reploPreviewHealthMonitor?: PreviewHealthMonitor;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Next's error dialog locks the body's scroll while it's open, and the
|
|
24
|
+
* stylesheet above hides that dialog, so the page would stop scrolling for no
|
|
25
|
+
* visible reason. Only a lock taken while the dialog is mounted is released;
|
|
26
|
+
* a site's own lock (a mobile menu, a modal) is left alone.
|
|
27
|
+
*/
|
|
28
|
+
export declare function releaseNextOverlayScrollLock(): () => void;
|
|
22
29
|
/** The document's preview health actions; null outside an embedded preview. */
|
|
23
30
|
export declare function getPreviewHealthActions(): PreviewHealthActions | null;
|
|
24
31
|
export declare function resetPreviewHealthMonitorForTests(): void;
|
package/previewHealth.js
CHANGED
|
@@ -294,6 +294,7 @@ function createPreviewHealthMonitor() {
|
|
|
294
294
|
overlayStyle.dataset.reploPreviewHealth = "";
|
|
295
295
|
overlayStyle.textContent = "nextjs-portal{display:none!important}";
|
|
296
296
|
document.head.append(overlayStyle);
|
|
297
|
+
const stopScrollLockRelease = releaseNextOverlayScrollLock();
|
|
297
298
|
const stopBuildStatus = subscribeToBuildStatus({
|
|
298
299
|
onBuildErrors: actions.buildErrorsChanged
|
|
299
300
|
});
|
|
@@ -303,6 +304,7 @@ function createPreviewHealthMonitor() {
|
|
|
303
304
|
unsubscribe();
|
|
304
305
|
stopBuildStatus();
|
|
305
306
|
overlayStyle.remove();
|
|
307
|
+
stopScrollLockRelease();
|
|
306
308
|
cancelPendingRecovery();
|
|
307
309
|
if (window.__reploNextErrorFeed?.listener === handleNextError) {
|
|
308
310
|
window.__reploNextErrorFeed.listener = null;
|
|
@@ -310,6 +312,34 @@ function createPreviewHealthMonitor() {
|
|
|
310
312
|
}
|
|
311
313
|
};
|
|
312
314
|
}
|
|
315
|
+
function releaseNextOverlayScrollLock() {
|
|
316
|
+
const release = () => {
|
|
317
|
+
const portal = document.querySelector("nextjs-portal");
|
|
318
|
+
const isDialogOpen = Boolean(
|
|
319
|
+
portal?.shadowRoot?.querySelector("[data-nextjs-dialog-overlay]")
|
|
320
|
+
);
|
|
321
|
+
if (isDialogOpen && document.body.style.overflow === "hidden") {
|
|
322
|
+
document.body.style.overflow = "";
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const observer = new MutationObserver(release);
|
|
326
|
+
const observe = () => {
|
|
327
|
+
observer.observe(document.body, {
|
|
328
|
+
attributes: true,
|
|
329
|
+
attributeFilter: ["style"]
|
|
330
|
+
});
|
|
331
|
+
release();
|
|
332
|
+
};
|
|
333
|
+
if (document.body) {
|
|
334
|
+
observe();
|
|
335
|
+
} else {
|
|
336
|
+
document.addEventListener("DOMContentLoaded", observe, { once: true });
|
|
337
|
+
}
|
|
338
|
+
return () => {
|
|
339
|
+
observer.disconnect();
|
|
340
|
+
document.removeEventListener("DOMContentLoaded", observe);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
313
343
|
function openSocket(url) {
|
|
314
344
|
return new Promise((resolve) => {
|
|
315
345
|
const socket = new window.WebSocket(url);
|
|
@@ -427,5 +457,6 @@ if (typeof window !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
|
427
457
|
}
|
|
428
458
|
export {
|
|
429
459
|
getPreviewHealthActions,
|
|
460
|
+
releaseNextOverlayScrollLock,
|
|
430
461
|
resetPreviewHealthMonitorForTests
|
|
431
462
|
};
|
|
@@ -7,6 +7,10 @@ import { useParentMessaging } from "./hooks/useParentMessaging";
|
|
|
7
7
|
import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
8
8
|
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
9
9
|
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
10
|
+
import {
|
|
11
|
+
FRAME_SHIELD_ATTRIBUTE,
|
|
12
|
+
FRAME_SHIELD_CSS
|
|
13
|
+
} from "./interactions/embeddedFrames";
|
|
10
14
|
import { PREVIEW_RENDERED_EVENT } from "./previewRendered";
|
|
11
15
|
import { SectionCanvas } from "./section-canvas/SectionCanvas";
|
|
12
16
|
import { sectionCanvasStore } from "./section-canvas/sectionCanvasStore";
|
|
@@ -154,6 +158,7 @@ function EditorSelection() {
|
|
|
154
158
|
[data-select-mode-inline-editor][contenteditable="true"],
|
|
155
159
|
[data-select-mode-inline-editor][contenteditable="true"] * { cursor: text !important; }
|
|
156
160
|
` }),
|
|
161
|
+
displayMode === "selection" && /* @__PURE__ */ jsx("style", { ...{ [FRAME_SHIELD_ATTRIBUTE]: "" }, children: FRAME_SHIELD_CSS }),
|
|
157
162
|
/* @__PURE__ */ jsx(SelectionFrames, {}),
|
|
158
163
|
/* @__PURE__ */ jsx(SectionCanvas, {})
|
|
159
164
|
] });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import { getNearestSelectableElement } from "../descriptor/elementDescriptor";
|
|
3
3
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
4
|
+
import { getSelectionTargetForEmbeddedFrameHit } from "../interactions/embeddedFrames";
|
|
4
5
|
import {
|
|
5
6
|
CONTAINER_TAGS,
|
|
6
7
|
findContainingSection
|
|
@@ -49,10 +50,15 @@ function useSelectionHover() {
|
|
|
49
50
|
if (pointerClientPosition) {
|
|
50
51
|
const isPointerWithinViewport = pointerClientPosition.x >= 0 && pointerClientPosition.y >= 0 && pointerClientPosition.x <= window.innerWidth && pointerClientPosition.y <= window.innerHeight;
|
|
51
52
|
if (isPointerWithinViewport) {
|
|
52
|
-
|
|
53
|
+
const elementAtPoint = document.elementFromPoint(
|
|
53
54
|
pointerClientPosition.x,
|
|
54
55
|
pointerClientPosition.y
|
|
55
56
|
);
|
|
57
|
+
hoveredElement = elementAtPoint && getSelectionTargetForEmbeddedFrameHit({
|
|
58
|
+
hitElement: elementAtPoint,
|
|
59
|
+
clientX: pointerClientPosition.x,
|
|
60
|
+
clientY: pointerClientPosition.y
|
|
61
|
+
});
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
if (!hoveredElement) {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getNearestSelectableElement
|
|
5
5
|
} from "../descriptor/elementDescriptor";
|
|
6
6
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
7
|
+
import { getSelectionTargetForEmbeddedFrameHit } from "../interactions/embeddedFrames";
|
|
7
8
|
import { findContainingSection } from "../interactions/sectionGeometry";
|
|
8
9
|
import {
|
|
9
10
|
collectDragCandidates,
|
|
@@ -402,7 +403,11 @@ function useSelectionInteractions() {
|
|
|
402
403
|
return;
|
|
403
404
|
}
|
|
404
405
|
commitClickSelection({
|
|
405
|
-
targetElement
|
|
406
|
+
targetElement: getSelectionTargetForEmbeddedFrameHit({
|
|
407
|
+
hitElement: targetElement,
|
|
408
|
+
clientX: event.clientX,
|
|
409
|
+
clientY: event.clientY
|
|
410
|
+
}),
|
|
406
411
|
pointerEvent: event,
|
|
407
412
|
candidates
|
|
408
413
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const FRAME_SHIELD_ATTRIBUTE = "data-select-mode-frame-shield";
|
|
2
|
+
export declare const FRAME_SHIELD_CSS = "iframe { pointer-events: none !important; }";
|
|
3
|
+
/**
|
|
4
|
+
* Select mode shields iframes from the pointer so presses over an embed reach
|
|
5
|
+
* this document instead of the embed's own (often cross-origin) one. The hit
|
|
6
|
+
* element is then whatever sits behind the frame, so lift the shield for one
|
|
7
|
+
* native hit test to find the frame the user actually pointed at. A frame
|
|
8
|
+
* injected as raw HTML has no React source, so prefer its nearest
|
|
9
|
+
* React-rendered ancestor, which the agent can edit. React 19 renders <body>
|
|
10
|
+
* too, so stop below it: a widget script appends its frame straight to the
|
|
11
|
+
* body, and selecting the frame beats selecting the whole page.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getSelectionTargetForEmbeddedFrameHit({ hitElement, clientX, clientY, }: {
|
|
14
|
+
hitElement: Element;
|
|
15
|
+
clientX: number;
|
|
16
|
+
clientY: number;
|
|
17
|
+
}): Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getFiberFromElement } from "../fiber";
|
|
2
|
+
const FRAME_SHIELD_ATTRIBUTE = "data-select-mode-frame-shield";
|
|
3
|
+
const FRAME_SHIELD_CSS = "iframe { pointer-events: none !important; }";
|
|
4
|
+
function getSelectionTargetForEmbeddedFrameHit({
|
|
5
|
+
hitElement,
|
|
6
|
+
clientX,
|
|
7
|
+
clientY
|
|
8
|
+
}) {
|
|
9
|
+
const shield = document.querySelector(
|
|
10
|
+
`style[${FRAME_SHIELD_ATTRIBUTE}]`
|
|
11
|
+
)?.sheet;
|
|
12
|
+
if (!shield || document.getElementsByTagName("iframe").length === 0) {
|
|
13
|
+
return hitElement;
|
|
14
|
+
}
|
|
15
|
+
shield.disabled = true;
|
|
16
|
+
const frame = document.elementFromPoint(clientX, clientY);
|
|
17
|
+
shield.disabled = false;
|
|
18
|
+
if (!(frame instanceof HTMLIFrameElement)) {
|
|
19
|
+
return hitElement;
|
|
20
|
+
}
|
|
21
|
+
for (let candidate = frame; candidate && candidate !== document.body; candidate = candidate.parentElement) {
|
|
22
|
+
if (getFiberFromElement(candidate)) {
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return frame;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
FRAME_SHIELD_ATTRIBUTE,
|
|
30
|
+
FRAME_SHIELD_CSS,
|
|
31
|
+
getSelectionTargetForEmbeddedFrameHit
|
|
32
|
+
};
|