@replohq/sandbox-runtime 1.18.1 → 1.19.1
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/_vendor/replo-utils/lib/crypto.d.ts +1 -0
- package/_vendor/replo-utils/lib/crypto.mjs +1 -1
- package/_vendor/replo-utils/lib/fonts.mjs +2 -2
- package/_vendor/replo-utils/lib/math.mjs +3 -3
- package/_vendor/replo-utils/lib/misc.mjs +4 -4
- package/_vendor/replo-utils/lib/pageCapture.mjs +1 -1
- package/_vendor/schemas/errors.d.ts +0 -1
- package/_vendor/schemas/generated/pageHeatmap.d.ts +57 -0
- package/_vendor/schemas/generated/pageHeatmap.mjs +1 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +6 -6
- package/_vendor/schemas/pageHeatmap.d.ts +132 -0
- package/_vendor/schemas/pageHeatmap.mjs +72 -0
- package/_vendor/schemas/previewHealthIframeRpc.mjs +6 -6
- package/_vendor/schemas/selectMode.mjs +5 -5
- package/_vendor/schemas/selectModeIframeRpc.mjs +5 -5
- package/buildMetadata.js +3 -3
- package/{chunk-KRA4LUIR.mjs → chunk-36IPO47A.mjs} +2 -2
- package/{chunk-TP4ZXK4R.mjs → chunk-QTGDSYSU.mjs} +1 -1
- package/{chunk-FLYEIRFK.mjs → chunk-SKVPFN7M.mjs} +1 -1
- package/{chunk-PH3S6LJI.mjs → chunk-THOBCJJA.mjs} +2 -2
- package/{chunk-LIPH4O4F.mjs → chunk-ZTT7SNYN.mjs} +5 -2
- package/heatmaps/HeatLayerMount.d.ts +1 -0
- package/heatmaps/HeatLayerMount.js +12 -0
- package/heatmaps/heatBadges.d.ts +25 -0
- package/heatmaps/heatBadges.js +98 -0
- package/heatmaps/heatCore.d.ts +34 -0
- package/heatmaps/heatCore.js +465 -0
- package/heatmaps/heatLayer.d.ts +1 -0
- package/heatmaps/heatLayer.js +85 -0
- package/heatmaps/heatRender.d.ts +36 -0
- package/heatmaps/heatRender.js +198 -0
- package/package.json +4 -4
- package/sandbox-runtime.js +2 -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/{chunk-TESFJQID.mjs → chunk-4ITJNSXZ.mjs} +0 -0
- /package/{chunk-G3S6ZNPF.mjs → chunk-XYS6G2ZC.mjs} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const STOPS = [
|
|
2
|
+
{ offset: 0, color: "rgba(40, 60, 255, 0)" },
|
|
3
|
+
{ offset: 0.18, color: "rgba(40, 90, 255, 0.55)" },
|
|
4
|
+
{ offset: 0.38, color: "rgba(0, 200, 220, 0.7)" },
|
|
5
|
+
{ offset: 0.55, color: "rgba(60, 220, 90, 0.78)" },
|
|
6
|
+
{ offset: 0.72, color: "rgba(255, 225, 40, 0.86)" },
|
|
7
|
+
{ offset: 0.86, color: "rgba(255, 140, 20, 0.92)" },
|
|
8
|
+
{ offset: 1, color: "rgba(240, 30, 40, 0.95)" }
|
|
9
|
+
];
|
|
10
|
+
const SCRIM_COLOR = "rgba(20, 18, 40, 0.24)";
|
|
11
|
+
const MARK_LINE_COLOR = "rgba(255, 255, 255, 0.95)";
|
|
12
|
+
const MARK_LABEL_BACKGROUND = "rgba(23, 23, 23, 0.85)";
|
|
13
|
+
const REACH_MARKS = [0.75, 0.5, 0.25];
|
|
14
|
+
const LABEL_FONT_PX = 13;
|
|
15
|
+
let paletteCache = null;
|
|
16
|
+
function getPalette() {
|
|
17
|
+
if (paletteCache) {
|
|
18
|
+
return paletteCache;
|
|
19
|
+
}
|
|
20
|
+
const canvas = document.createElement("canvas");
|
|
21
|
+
canvas.width = 256;
|
|
22
|
+
canvas.height = 1;
|
|
23
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
24
|
+
if (!context) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const gradient = context.createLinearGradient(0, 0, 256, 0);
|
|
28
|
+
for (const stop of STOPS) {
|
|
29
|
+
gradient.addColorStop(stop.offset, stop.color);
|
|
30
|
+
}
|
|
31
|
+
context.fillStyle = gradient;
|
|
32
|
+
context.fillRect(0, 0, 256, 1);
|
|
33
|
+
paletteCache = context.getImageData(0, 0, 256, 1).data;
|
|
34
|
+
return paletteCache;
|
|
35
|
+
}
|
|
36
|
+
const stampCache = /* @__PURE__ */ new Map();
|
|
37
|
+
function getDiscStamp(radius) {
|
|
38
|
+
const cached = stampCache.get(radius);
|
|
39
|
+
if (cached) {
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
const size = Math.max(1, Math.ceil(radius * 4));
|
|
43
|
+
const stamp = document.createElement("canvas");
|
|
44
|
+
stamp.width = size;
|
|
45
|
+
stamp.height = size;
|
|
46
|
+
const context = stamp.getContext("2d");
|
|
47
|
+
if (!context) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const gradient = context.createRadialGradient(
|
|
51
|
+
size / 2,
|
|
52
|
+
size / 2,
|
|
53
|
+
0,
|
|
54
|
+
size / 2,
|
|
55
|
+
size / 2,
|
|
56
|
+
radius * 2
|
|
57
|
+
);
|
|
58
|
+
gradient.addColorStop(0, "rgba(0, 0, 0, 1)");
|
|
59
|
+
gradient.addColorStop(0.45, "rgba(0, 0, 0, 0.45)");
|
|
60
|
+
gradient.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
61
|
+
context.fillStyle = gradient;
|
|
62
|
+
context.fillRect(0, 0, size, size);
|
|
63
|
+
stampCache.set(radius, stamp);
|
|
64
|
+
return stamp;
|
|
65
|
+
}
|
|
66
|
+
function withScrimBehind(context) {
|
|
67
|
+
context.globalCompositeOperation = "destination-over";
|
|
68
|
+
context.fillStyle = SCRIM_COLOR;
|
|
69
|
+
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
|
|
70
|
+
context.globalCompositeOperation = "source-over";
|
|
71
|
+
}
|
|
72
|
+
function drawHeatPoints({
|
|
73
|
+
context,
|
|
74
|
+
points,
|
|
75
|
+
radius
|
|
76
|
+
}) {
|
|
77
|
+
const { width, height } = context.canvas;
|
|
78
|
+
context.clearRect(0, 0, width, height);
|
|
79
|
+
const stamp = getDiscStamp(radius);
|
|
80
|
+
const palette = getPalette();
|
|
81
|
+
if (points.length > 0 && stamp && palette) {
|
|
82
|
+
const cells = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const point of points) {
|
|
84
|
+
const key = `${Math.round(point.x / radius)},${Math.round(point.y / radius)}`;
|
|
85
|
+
cells.set(key, (cells.get(key) ?? 0) + point.weight);
|
|
86
|
+
}
|
|
87
|
+
const sums = [...cells.values()].sort((first, second) => first - second);
|
|
88
|
+
const max = Math.max(1, sums[Math.floor(sums.length * 0.97)] ?? 1);
|
|
89
|
+
for (const point of points) {
|
|
90
|
+
context.globalAlpha = Math.min(1, Math.max(0.04, point.weight / max));
|
|
91
|
+
context.drawImage(stamp, point.x - radius * 2, point.y - radius * 2);
|
|
92
|
+
}
|
|
93
|
+
context.globalAlpha = 1;
|
|
94
|
+
const image = context.getImageData(0, 0, width, height);
|
|
95
|
+
const pixels = image.data;
|
|
96
|
+
for (let index = 0; index < pixels.length; index += 4) {
|
|
97
|
+
const alpha = pixels[index + 3] ?? 0;
|
|
98
|
+
if (alpha === 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const offset = alpha * 4;
|
|
102
|
+
pixels[index] = palette[offset] ?? 0;
|
|
103
|
+
pixels[index + 1] = palette[offset + 1] ?? 0;
|
|
104
|
+
pixels[index + 2] = palette[offset + 2] ?? 0;
|
|
105
|
+
pixels[index + 3] = palette[offset + 3] ?? 0;
|
|
106
|
+
}
|
|
107
|
+
context.putImageData(image, 0, 0);
|
|
108
|
+
}
|
|
109
|
+
withScrimBehind(context);
|
|
110
|
+
}
|
|
111
|
+
function getScrollReach(buckets) {
|
|
112
|
+
const total = buckets.reduce((sum, bucket) => sum + bucket.views, 0);
|
|
113
|
+
const reach = Array.from({ length: 101 }, () => 0);
|
|
114
|
+
if (total === 0) {
|
|
115
|
+
return reach;
|
|
116
|
+
}
|
|
117
|
+
const viewsByPct = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const bucket of buckets) {
|
|
119
|
+
viewsByPct.set(
|
|
120
|
+
bucket.pct,
|
|
121
|
+
(viewsByPct.get(bucket.pct) ?? 0) + bucket.views
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
let remaining = total;
|
|
125
|
+
for (let pct = 0; pct <= 100; pct++) {
|
|
126
|
+
reach[pct] = remaining / total;
|
|
127
|
+
remaining -= viewsByPct.get(pct) ?? 0;
|
|
128
|
+
}
|
|
129
|
+
return reach;
|
|
130
|
+
}
|
|
131
|
+
function drawScrollBands({
|
|
132
|
+
context,
|
|
133
|
+
reach
|
|
134
|
+
}) {
|
|
135
|
+
const { width, height } = context.canvas;
|
|
136
|
+
context.clearRect(0, 0, width, height);
|
|
137
|
+
const palette = getPalette();
|
|
138
|
+
if (!palette) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (let pct = 0; pct < 100; pct++) {
|
|
142
|
+
const share = Math.min(1, Math.max(0, reach[pct] ?? 0));
|
|
143
|
+
const offset = Math.round(share * 255) * 4;
|
|
144
|
+
const alpha = Math.min(0.62, (palette[offset + 3] ?? 0) / 255 * 0.7);
|
|
145
|
+
context.fillStyle = `rgba(${palette[offset] ?? 0}, ${palette[offset + 1] ?? 0}, ${palette[offset + 2] ?? 0}, ${alpha})`;
|
|
146
|
+
const top = Math.floor(pct / 100 * height);
|
|
147
|
+
const bottom = Math.ceil((pct + 1) / 100 * height);
|
|
148
|
+
context.fillRect(0, top, width, bottom - top);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function getReachMarks({
|
|
152
|
+
reach,
|
|
153
|
+
height
|
|
154
|
+
}) {
|
|
155
|
+
return REACH_MARKS.flatMap((share) => {
|
|
156
|
+
const pct = reach.findIndex((value) => value < share);
|
|
157
|
+
return pct > 0 ? [{ share, y: pct / 100 * height }] : [];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function drawMarkLine({
|
|
161
|
+
context,
|
|
162
|
+
y,
|
|
163
|
+
label,
|
|
164
|
+
dashed,
|
|
165
|
+
scale
|
|
166
|
+
}) {
|
|
167
|
+
const { width } = context.canvas;
|
|
168
|
+
context.save();
|
|
169
|
+
context.strokeStyle = MARK_LINE_COLOR;
|
|
170
|
+
context.lineWidth = Math.max(1, 2 * scale);
|
|
171
|
+
context.setLineDash(dashed ? [8 * scale, 6 * scale] : []);
|
|
172
|
+
context.beginPath();
|
|
173
|
+
context.moveTo(0, y);
|
|
174
|
+
context.lineTo(width, y);
|
|
175
|
+
context.stroke();
|
|
176
|
+
const fontPx = LABEL_FONT_PX * scale;
|
|
177
|
+
context.font = `500 ${fontPx}px system-ui, -apple-system, sans-serif`;
|
|
178
|
+
const paddingX = 10 * scale;
|
|
179
|
+
const labelHeight = fontPx + 10 * scale;
|
|
180
|
+
const labelWidth = context.measureText(label).width + paddingX * 2;
|
|
181
|
+
const left = 24 * scale;
|
|
182
|
+
const top = Math.max(0, y - labelHeight - 6 * scale);
|
|
183
|
+
context.fillStyle = MARK_LABEL_BACKGROUND;
|
|
184
|
+
context.beginPath();
|
|
185
|
+
context.roundRect(left, top, labelWidth, labelHeight, labelHeight / 2);
|
|
186
|
+
context.fill();
|
|
187
|
+
context.fillStyle = "#fff";
|
|
188
|
+
context.textBaseline = "middle";
|
|
189
|
+
context.fillText(label, left + paddingX, top + labelHeight / 2);
|
|
190
|
+
context.restore();
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
drawHeatPoints,
|
|
194
|
+
drawMarkLine,
|
|
195
|
+
drawScrollBands,
|
|
196
|
+
getReachMarks,
|
|
197
|
+
getScrollReach
|
|
198
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.19.1",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-10-
|
|
8
|
+
"commit": "0ccd8b79a0c0654b1b2e215b83c1efed676128fe",
|
|
9
|
+
"builtAt": "2026-10-24T00:25:38.939Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
package/sandbox-runtime.js
CHANGED
|
@@ -5,6 +5,7 @@ import { AnnotationLayerMount } from "./annotations/AnnotationLayerMount";
|
|
|
5
5
|
import { ClientErrorBoundary } from "./ClientErrorBoundary";
|
|
6
6
|
import { DesignLibraryPreview } from "./design-library-preview/DesignLibraryPreview";
|
|
7
7
|
import { DocumentReadyBeacon } from "./DocumentReadyBeacon";
|
|
8
|
+
import { HeatLayerMount } from "./heatmaps/HeatLayerMount";
|
|
8
9
|
import LinkInterceptor from "./LinkInterceptor";
|
|
9
10
|
import { NavigationReceiver } from "./NavigationReceiver";
|
|
10
11
|
import { PageCaptureResponder } from "./PageCaptureResponder";
|
|
@@ -26,6 +27,7 @@ function SandboxRuntime({ children }) {
|
|
|
26
27
|
/* @__PURE__ */ jsx(PageCaptureResponder, {}),
|
|
27
28
|
/* @__PURE__ */ jsx(EditorSelection, {}),
|
|
28
29
|
/* @__PURE__ */ jsx(AnnotationLayerMount, {}),
|
|
30
|
+
/* @__PURE__ */ jsx(HeatLayerMount, {}),
|
|
29
31
|
/* @__PURE__ */ jsx(DesignLibraryPreview, {}),
|
|
30
32
|
children
|
|
31
33
|
] })
|
|
@@ -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
|
+
};
|
|
File without changes
|
|
File without changes
|