@replohq/sandbox-runtime 0.1.0
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/ClientErrorBoundary.d.ts +10 -0
- package/ClientErrorBoundary.js +61 -0
- package/ClientErrorBoundary.js.map +7 -0
- package/DocumentReadyBeacon.d.ts +1 -0
- package/DocumentReadyBeacon.js +9 -0
- package/DocumentReadyBeacon.js.map +7 -0
- package/LICENSE +11 -0
- package/LinkInterceptor.d.ts +1 -0
- package/LinkInterceptor.js +128 -0
- package/LinkInterceptor.js.map +7 -0
- package/NextErrorReporter.d.ts +1 -0
- package/NextErrorReporter.js +92 -0
- package/NextErrorReporter.js.map +7 -0
- package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
- package/_vendor/replo-utils/lib/errors.d.ts +83 -0
- package/_vendor/replo-utils/lib/fonts.d.ts +49 -0
- package/_vendor/replo-utils/lib/fonts.mjs +221 -0
- package/_vendor/replo-utils/lib/geometry.d.ts +18 -0
- package/_vendor/replo-utils/lib/geometry.mjs +9 -0
- package/_vendor/replo-utils/lib/json.d.ts +6 -0
- package/_vendor/replo-utils/lib/log.d.ts +11 -0
- package/_vendor/replo-utils/lib/misc.d.ts +145 -0
- package/_vendor/replo-utils/lib/misc.mjs +2 -0
- package/_vendor/replo-utils/lib/object.d.ts +31 -0
- package/_vendor/replo-utils/lib/parse.d.ts +15 -0
- package/_vendor/replo-utils/lib/posthogReplay.d.ts +43 -0
- package/_vendor/replo-utils/lib/posthogReplay.mjs +121 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.d.ts +14 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.mjs +14 -0
- package/_vendor/schemas/designLibrary.d.ts +361 -0
- package/_vendor/schemas/designLibrary.mjs +126 -0
- package/_vendor/schemas/errors.d.ts +459 -0
- package/_vendor/schemas/generated/errors.d.ts +61 -0
- package/_vendor/schemas/generated/pageAnnotations.d.ts +48 -0
- package/_vendor/schemas/generated/pageAnnotations.mjs +1 -0
- package/_vendor/schemas/generated/selectMode.d.ts +119 -0
- package/_vendor/schemas/generated/selectMode.mjs +1 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +100 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.mjs +1 -0
- package/_vendor/schemas/iframeRpcChannel.d.ts +82 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +520 -0
- package/_vendor/schemas/pageAnnotations.d.ts +134 -0
- package/_vendor/schemas/pageAnnotations.mjs +61 -0
- package/_vendor/schemas/savedStyles.d.ts +140 -0
- package/_vendor/schemas/selectMode.d.ts +522 -0
- package/_vendor/schemas/selectMode.mjs +3 -0
- package/_vendor/schemas/selectModeIframeRpc.d.ts +900 -0
- package/_vendor/schemas/selectModeIframeRpc.mjs +223 -0
- package/annotations/AnnotationLayerMount.d.ts +1 -0
- package/annotations/AnnotationLayerMount.js +13 -0
- package/annotations/AnnotationLayerMount.js.map +7 -0
- package/annotations/annotationController.d.ts +1 -0
- package/annotations/annotationController.js +384 -0
- package/annotations/annotationController.js.map +7 -0
- package/annotations/bridgeGate.d.ts +7 -0
- package/annotations/bridgeGate.js +22 -0
- package/annotations/bridgeGate.js.map +7 -0
- package/chunk-3UFG7XTQ.mjs +205 -0
- package/chunk-FNTGY7I5.mjs +68 -0
- package/chunk-N5EI2MOZ.mjs +1535 -0
- package/design-library-preview/DesignLibraryPreview.d.ts +1 -0
- package/design-library-preview/DesignLibraryPreview.js +93 -0
- package/design-library-preview/DesignLibraryPreview.js.map +7 -0
- package/package.json +41 -0
- package/posthog-relay.d.ts +11 -0
- package/posthog-relay.js +72 -0
- package/posthog-relay.js.map +7 -0
- package/sandbox-runtime.d.ts +1 -0
- package/sandbox-runtime.js +29 -0
- package/sandbox-runtime.js.map +7 -0
- package/select-mode/EditorSelection.d.ts +24 -0
- package/select-mode/EditorSelection.js +95 -0
- package/select-mode/EditorSelection.js.map +7 -0
- package/select-mode/annotationFrames.d.ts +33 -0
- package/select-mode/annotationFrames.js +119 -0
- package/select-mode/annotationFrames.js.map +7 -0
- package/select-mode/descriptor/elementDescriptor.d.ts +26 -0
- package/select-mode/descriptor/elementDescriptor.js +201 -0
- package/select-mode/descriptor/elementDescriptor.js.map +7 -0
- package/select-mode/descriptor/styleExtraction.d.ts +3 -0
- package/select-mode/descriptor/styleExtraction.js +271 -0
- package/select-mode/descriptor/styleExtraction.js.map +7 -0
- package/select-mode/displayModeStore.d.ts +10 -0
- package/select-mode/displayModeStore.js +47 -0
- package/select-mode/displayModeStore.js.map +7 -0
- package/select-mode/fast-refresh-relay.d.ts +3 -0
- package/select-mode/fast-refresh-relay.js +33 -0
- package/select-mode/fast-refresh-relay.js.map +7 -0
- package/select-mode/fiber.d.ts +49 -0
- package/select-mode/fiber.js +306 -0
- package/select-mode/fiber.js.map +7 -0
- package/select-mode/hooks/useCrosshairCursor.d.ts +12 -0
- package/select-mode/hooks/useCrosshairCursor.js +20 -0
- package/select-mode/hooks/useCrosshairCursor.js.map +7 -0
- package/select-mode/hooks/useEditModeStateMachine.d.ts +16 -0
- package/select-mode/hooks/useEditModeStateMachine.js +235 -0
- package/select-mode/hooks/useEditModeStateMachine.js.map +7 -0
- package/select-mode/hooks/useOptimisticOverrides.d.ts +17 -0
- package/select-mode/hooks/useOptimisticOverrides.js +283 -0
- package/select-mode/hooks/useOptimisticOverrides.js.map +7 -0
- package/select-mode/hooks/useParentMessaging.d.ts +36 -0
- package/select-mode/hooks/useParentMessaging.js +74 -0
- package/select-mode/hooks/useParentMessaging.js.map +7 -0
- package/select-mode/hooks/useSelectModeController.d.ts +8 -0
- package/select-mode/hooks/useSelectModeController.js +46 -0
- package/select-mode/hooks/useSelectModeController.js.map +7 -0
- package/select-mode/hooks/useSelectionHover.d.ts +22 -0
- package/select-mode/hooks/useSelectionHover.js +110 -0
- package/select-mode/hooks/useSelectionHover.js.map +7 -0
- package/select-mode/hooks/useSelectionInteractions.d.ts +37 -0
- package/select-mode/hooks/useSelectionInteractions.js +353 -0
- package/select-mode/hooks/useSelectionInteractions.js.map +7 -0
- package/select-mode/hooks/useSelectionScrollSync.d.ts +28 -0
- package/select-mode/hooks/useSelectionScrollSync.js +70 -0
- package/select-mode/hooks/useSelectionScrollSync.js.map +7 -0
- package/select-mode/hooks/useSiblingResolution.d.ts +6 -0
- package/select-mode/hooks/useSiblingResolution.js +59 -0
- package/select-mode/hooks/useSiblingResolution.js.map +7 -0
- package/select-mode/iframeRpcChannel.d.ts +356 -0
- package/select-mode/iframeRpcChannel.js +92 -0
- package/select-mode/iframeRpcChannel.js.map +7 -0
- package/select-mode/inlineTextMarks.d.ts +6 -0
- package/select-mode/inlineTextMarks.js +25 -0
- package/select-mode/inlineTextMarks.js.map +7 -0
- package/select-mode/interactions/selectionGeometry.d.ts +105 -0
- package/select-mode/interactions/selectionGeometry.js +192 -0
- package/select-mode/interactions/selectionGeometry.js.map +7 -0
- package/select-mode/selectModeSession.d.ts +21 -0
- package/select-mode/selectModeSession.js +7 -0
- package/select-mode/selectModeSession.js.map +7 -0
- package/select-mode/selectionFrame.d.ts +81 -0
- package/select-mode/selectionFrame.js +132 -0
- package/select-mode/selectionFrame.js.map +7 -0
- package/select-mode/symbolicate.d.ts +2 -0
- package/select-mode/symbolicate.js +200 -0
- package/select-mode/symbolicate.js.map +7 -0
- package/select-mode/tiptapInlineEditor.d.ts +23 -0
- package/select-mode/tiptapInlineEditor.js +120 -0
- package/select-mode/tiptapInlineEditor.js.map +7 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { isIntersecting, isRectEnclosing } from "../../_vendor/replo-utils/lib/geometry.mjs";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { isInlineTextMarkElement } from "../inlineTextMarks";
|
|
4
|
+
const SELECTION_TOLERANCE_PX = 4;
|
|
5
|
+
function normalizeSelectionRect({
|
|
6
|
+
startX,
|
|
7
|
+
startY,
|
|
8
|
+
endX,
|
|
9
|
+
endY
|
|
10
|
+
}) {
|
|
11
|
+
const left = Math.min(startX, endX);
|
|
12
|
+
const right = Math.max(startX, endX);
|
|
13
|
+
const top = Math.min(startY, endY);
|
|
14
|
+
const bottom = Math.max(startY, endY);
|
|
15
|
+
return {
|
|
16
|
+
x: left,
|
|
17
|
+
y: top,
|
|
18
|
+
width: right - left,
|
|
19
|
+
height: bottom - top,
|
|
20
|
+
top,
|
|
21
|
+
right,
|
|
22
|
+
bottom,
|
|
23
|
+
left
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function getDragPositionFromClient({
|
|
27
|
+
clientX,
|
|
28
|
+
clientY,
|
|
29
|
+
scrollOffset
|
|
30
|
+
}) {
|
|
31
|
+
return {
|
|
32
|
+
clientX,
|
|
33
|
+
clientY,
|
|
34
|
+
pageX: clientX + scrollOffset.left,
|
|
35
|
+
pageY: clientY + scrollOffset.top
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function getDocumentScrollOffset() {
|
|
39
|
+
const scrollingElement = document.scrollingElement;
|
|
40
|
+
return {
|
|
41
|
+
left: scrollingElement?.scrollLeft ?? window.scrollX,
|
|
42
|
+
top: scrollingElement?.scrollTop ?? window.scrollY
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function getViewportSelectionRect({
|
|
46
|
+
dragStart,
|
|
47
|
+
dragCurrent,
|
|
48
|
+
scrollOffset
|
|
49
|
+
}) {
|
|
50
|
+
return normalizeSelectionRect({
|
|
51
|
+
startX: dragStart.pageX - scrollOffset.left,
|
|
52
|
+
startY: dragStart.pageY - scrollOffset.top,
|
|
53
|
+
endX: dragCurrent.pageX - scrollOffset.left,
|
|
54
|
+
endY: dragCurrent.pageY - scrollOffset.top
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function getPageRectFromViewportRect({
|
|
58
|
+
rect,
|
|
59
|
+
scrollOffset
|
|
60
|
+
}) {
|
|
61
|
+
return {
|
|
62
|
+
x: rect.x + scrollOffset.left,
|
|
63
|
+
y: rect.y + scrollOffset.top,
|
|
64
|
+
width: rect.width,
|
|
65
|
+
height: rect.height,
|
|
66
|
+
left: rect.left + scrollOffset.left,
|
|
67
|
+
top: rect.top + scrollOffset.top,
|
|
68
|
+
right: rect.right + scrollOffset.left,
|
|
69
|
+
bottom: rect.bottom + scrollOffset.top
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function getViewportRectFromPageRect({
|
|
73
|
+
rect,
|
|
74
|
+
scrollOffset
|
|
75
|
+
}) {
|
|
76
|
+
return {
|
|
77
|
+
x: rect.x - scrollOffset.left,
|
|
78
|
+
y: rect.y - scrollOffset.top,
|
|
79
|
+
width: rect.width,
|
|
80
|
+
height: rect.height,
|
|
81
|
+
left: rect.left - scrollOffset.left,
|
|
82
|
+
top: rect.top - scrollOffset.top,
|
|
83
|
+
right: rect.right - scrollOffset.left,
|
|
84
|
+
bottom: rect.bottom - scrollOffset.top
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function isMeaningfulElement(element, elementRect) {
|
|
88
|
+
const tagName = element.tagName.toLowerCase();
|
|
89
|
+
if (["html", "body", "script", "style", "meta", "link"].includes(tagName)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (isInlineTextMarkElement(element)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (elementRect.width < 4 || elementRect.height < 4) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function doesRectIntersectSelection({
|
|
101
|
+
elementRect,
|
|
102
|
+
selectionRect,
|
|
103
|
+
tolerancePx
|
|
104
|
+
}) {
|
|
105
|
+
return isIntersecting(elementRect, selectionRect, { tolerance: tolerancePx }) && !isRectEnclosing(elementRect, selectionRect);
|
|
106
|
+
}
|
|
107
|
+
function collectDragCandidates(scrollOffset) {
|
|
108
|
+
const candidates = [];
|
|
109
|
+
for (const element of Array.from(document.querySelectorAll("*"))) {
|
|
110
|
+
const viewportRect = getElementBoundingRect(element);
|
|
111
|
+
if (!isMeaningfulElement(element, viewportRect)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
candidates.push({
|
|
115
|
+
id: uuidv4(),
|
|
116
|
+
element,
|
|
117
|
+
pageRect: getPageRectFromViewportRect({
|
|
118
|
+
rect: viewportRect,
|
|
119
|
+
scrollOffset
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return candidates;
|
|
124
|
+
}
|
|
125
|
+
function collapseFullyCoveredChildren({
|
|
126
|
+
candidates,
|
|
127
|
+
selected
|
|
128
|
+
}) {
|
|
129
|
+
const candidateElements = new Set(
|
|
130
|
+
candidates.map((candidate) => candidate.element)
|
|
131
|
+
);
|
|
132
|
+
const selectedElements = new Set(
|
|
133
|
+
selected.map((candidate) => candidate.element)
|
|
134
|
+
);
|
|
135
|
+
return selected.filter((candidate) => {
|
|
136
|
+
const parent = candidate.element.parentElement;
|
|
137
|
+
if (!parent || !selectedElements.has(parent)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const everyCandidateChildSelected = Array.from(parent.children).filter((child) => candidateElements.has(child)).every((child) => selectedElements.has(child));
|
|
141
|
+
return !everyCandidateChildSelected;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function getCandidatesInSelection({
|
|
145
|
+
candidates,
|
|
146
|
+
selectionPageRect
|
|
147
|
+
}) {
|
|
148
|
+
const intersecting = candidates.filter((candidate) => {
|
|
149
|
+
return doesRectIntersectSelection({
|
|
150
|
+
elementRect: candidate.pageRect,
|
|
151
|
+
selectionRect: selectionPageRect,
|
|
152
|
+
tolerancePx: SELECTION_TOLERANCE_PX
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
return collapseFullyCoveredChildren({ candidates, selected: intersecting });
|
|
156
|
+
}
|
|
157
|
+
function getElementAnchor({
|
|
158
|
+
element,
|
|
159
|
+
selectionRect
|
|
160
|
+
}) {
|
|
161
|
+
const rect = element.getBoundingClientRect();
|
|
162
|
+
return {
|
|
163
|
+
x: Math.min(Math.max(selectionRect.left, rect.left), rect.right),
|
|
164
|
+
y: Math.min(Math.max(selectionRect.top, rect.top), rect.bottom)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function getElementBoundingRect(element) {
|
|
168
|
+
const rect = element.getBoundingClientRect();
|
|
169
|
+
return {
|
|
170
|
+
x: rect.x,
|
|
171
|
+
y: rect.y,
|
|
172
|
+
width: rect.width,
|
|
173
|
+
height: rect.height,
|
|
174
|
+
top: rect.top,
|
|
175
|
+
right: rect.right,
|
|
176
|
+
bottom: rect.bottom,
|
|
177
|
+
left: rect.left
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
collectDragCandidates,
|
|
182
|
+
doesRectIntersectSelection,
|
|
183
|
+
getCandidatesInSelection,
|
|
184
|
+
getDocumentScrollOffset,
|
|
185
|
+
getDragPositionFromClient,
|
|
186
|
+
getElementAnchor,
|
|
187
|
+
getElementBoundingRect,
|
|
188
|
+
getPageRectFromViewportRect,
|
|
189
|
+
getViewportRectFromPageRect,
|
|
190
|
+
getViewportSelectionRect
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=selectionGeometry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../select-mode/interactions/selectionGeometry.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n SelectModeBoundingRect,\n SelectModeClickPosition,\n} from \"schemas/generated/selectMode\";\n\nimport { isIntersecting, isRectEnclosing } from \"replo-utils/lib/geometry\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { isInlineTextMarkElement } from \"../inlineTextMarks\";\n\nexport type DragPosition = {\n clientX: number;\n clientY: number;\n pageX: number;\n pageY: number;\n};\n\nexport type ScrollOffset = {\n left: number;\n top: number;\n};\n\nexport type SelectedAreaState = {\n pageRect: SelectModeBoundingRect;\n};\n\n// NOTE (Noah, 2026-04-21): A small fudge factor so elements\n// that visually appear to touch the selection rect (but whose bounding\n// box is a pixel or two off due to borders/sub-pixel rendering) still get\n// included.\nconst SELECTION_TOLERANCE_PX = 4;\n\nfunction normalizeSelectionRect({\n startX,\n startY,\n endX,\n endY,\n}: {\n startX: number;\n startY: number;\n endX: number;\n endY: number;\n}): SelectModeBoundingRect {\n const left = Math.min(startX, endX);\n const right = Math.max(startX, endX);\n const top = Math.min(startY, endY);\n const bottom = Math.max(startY, endY);\n\n return {\n x: left,\n y: top,\n width: right - left,\n height: bottom - top,\n top,\n right,\n bottom,\n left,\n };\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getDragPositionFromClient({\n clientX,\n clientY,\n scrollOffset,\n}: {\n clientX: number;\n clientY: number;\n scrollOffset: ScrollOffset;\n}): DragPosition {\n return {\n clientX,\n clientY,\n pageX: clientX + scrollOffset.left,\n pageY: clientY + scrollOffset.top,\n };\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getDocumentScrollOffset(): ScrollOffset {\n const scrollingElement = document.scrollingElement;\n return {\n left: scrollingElement?.scrollLeft ?? window.scrollX,\n top: scrollingElement?.scrollTop ?? window.scrollY,\n };\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getViewportSelectionRect({\n dragStart,\n dragCurrent,\n scrollOffset,\n}: {\n dragStart: DragPosition;\n dragCurrent: DragPosition;\n scrollOffset: ScrollOffset;\n}): SelectModeBoundingRect {\n return normalizeSelectionRect({\n startX: dragStart.pageX - scrollOffset.left,\n startY: dragStart.pageY - scrollOffset.top,\n endX: dragCurrent.pageX - scrollOffset.left,\n endY: dragCurrent.pageY - scrollOffset.top,\n });\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getPageRectFromViewportRect({\n rect,\n scrollOffset,\n}: {\n rect: SelectModeBoundingRect;\n scrollOffset: ScrollOffset;\n}): SelectModeBoundingRect {\n return {\n x: rect.x + scrollOffset.left,\n y: rect.y + scrollOffset.top,\n width: rect.width,\n height: rect.height,\n left: rect.left + scrollOffset.left,\n top: rect.top + scrollOffset.top,\n right: rect.right + scrollOffset.left,\n bottom: rect.bottom + scrollOffset.top,\n };\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getViewportRectFromPageRect({\n rect,\n scrollOffset,\n}: {\n rect: SelectModeBoundingRect;\n scrollOffset: ScrollOffset;\n}): SelectModeBoundingRect {\n return {\n x: rect.x - scrollOffset.left,\n y: rect.y - scrollOffset.top,\n width: rect.width,\n height: rect.height,\n left: rect.left - scrollOffset.left,\n top: rect.top - scrollOffset.top,\n right: rect.right - scrollOffset.left,\n bottom: rect.bottom - scrollOffset.top,\n };\n}\n\nfunction isMeaningfulElement(\n element: Element,\n elementRect: SelectModeBoundingRect,\n): boolean {\n const tagName = element.tagName.toLowerCase();\n if ([\"html\", \"body\", \"script\", \"style\", \"meta\", \"link\"].includes(tagName)) {\n return false;\n }\n\n if (isInlineTextMarkElement(element)) {\n return false;\n }\n\n if (elementRect.width < 4 || elementRect.height < 4) {\n return false;\n }\n\n return true;\n}\n\n/**\n * A drag captures an element when their boxes overlap (within\n * `tolerancePx`) \u2014 but NOT when the drag sits entirely inside the\n * element. The latter case is the user dragging *within* a large\n * container (a section, `<main>`, `<body>`) to lasso its children; the\n * container itself isn't a thing they're trying to select, so we drop it.\n */\nexport function doesRectIntersectSelection({\n elementRect,\n selectionRect,\n tolerancePx,\n}: {\n elementRect: SelectModeBoundingRect;\n selectionRect: SelectModeBoundingRect;\n tolerancePx: number;\n}) {\n return (\n isIntersecting(elementRect, selectionRect, { tolerance: tolerancePx }) &&\n !isRectEnclosing(elementRect, selectionRect)\n );\n}\n\n// A meaningful element plus its page-coordinate rect and a stable id,\n// snapshotted once when a drag begins. Page coordinates are scroll-\n// invariant, so per-move hit-testing never needs to re-measure.\nexport type DragCandidate = {\n id: string;\n element: Element;\n pageRect: SelectModeBoundingRect;\n};\n\n/**\n * Snapshots every meaningful element and its page rect when a drag begins.\n * Each candidate gets a stable id so the parent overlay can key its\n * live highlights without remounting them as the drag moves.\n */\nexport function collectDragCandidates(\n scrollOffset: ScrollOffset,\n): DragCandidate[] {\n const candidates: DragCandidate[] = [];\n for (const element of Array.from(document.querySelectorAll(\"*\"))) {\n const viewportRect = getElementBoundingRect(element);\n if (!isMeaningfulElement(element, viewportRect)) {\n continue;\n }\n candidates.push({\n id: uuidv4(),\n element,\n pageRect: getPageRectFromViewportRect({\n rect: viewportRect,\n scrollOffset,\n }),\n });\n }\n return candidates;\n}\n\n/**\n * Drop a child whose parent is also selected and every one of that\n * parent's *candidate* children is selected: the parent already represents\n * the whole group, so the children are redundant. Returns only the\n * candidates that survive the collapse.\n *\n * Only children that are themselves drag candidates count toward \"fully\n * covered\". A parent's DOM children can include decorative / non-selectable\n * elements (and `.children` excludes text nodes); those can never be\n * selected, so counting them would wedge the check permanently false and\n * the collapse would never fire.\n *\n * Membership is read once up front, so the collapse cascades up the tree\n * in a single pass \u2014 a parent kept this round is itself dropped when *its*\n * parent is likewise fully covered, leaving only the topmost ancestor of\n * any fully-enclosed subtree.\n */\nfunction collapseFullyCoveredChildren({\n candidates,\n selected,\n}: {\n candidates: DragCandidate[];\n selected: DragCandidate[];\n}): DragCandidate[] {\n const candidateElements = new Set(\n candidates.map((candidate) => candidate.element),\n );\n const selectedElements = new Set(\n selected.map((candidate) => candidate.element),\n );\n return selected.filter((candidate) => {\n const parent = candidate.element.parentElement;\n if (!parent || !selectedElements.has(parent)) {\n return true;\n }\n const everyCandidateChildSelected = Array.from(parent.children)\n .filter((child) => candidateElements.has(child))\n .every((child) => selectedElements.has(child));\n return !everyCandidateChildSelected;\n });\n}\n\n/**\n * Cheap per-move hit-test against the pre-collected candidates: keep the\n * ones whose page rect overlaps the selection (excluding any that wholly\n * enclose it), then collapse any fully-covered parent/children group down\n * to just the parent (see `collapseFullyCoveredChildren`). Does no DOM\n * queries or re-measures of geometry \u2014 the only DOM reads are cheap\n * parent/child structural lookups.\n *\n * Because the collapse depends on which siblings are currently captured,\n * elements legitimately move in and out of the result as the drag grows or\n * shrinks: a child drops out the moment the drag swallows its last sibling\n * (the parent takes over), and reappears if the drag retreats.\n */\nexport function getCandidatesInSelection({\n candidates,\n selectionPageRect,\n}: {\n candidates: DragCandidate[];\n selectionPageRect: SelectModeBoundingRect;\n}): DragCandidate[] {\n const intersecting = candidates.filter((candidate) => {\n return doesRectIntersectSelection({\n elementRect: candidate.pageRect,\n selectionRect: selectionPageRect,\n tolerancePx: SELECTION_TOLERANCE_PX,\n });\n });\n\n return collapseFullyCoveredChildren({ candidates, selected: intersecting });\n}\n\n/**\n * Picks a \"click position\" for an element that fell inside an area\n * selection. We use the corner of the element nearest the top-left of the\n * selection rect, clamped to the element's own bounds. This anchor is what\n * lets the selected-element overlay continue to track the element when the\n * user scrolls (`useSelectionScrollSync` re-projects relative to it).\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getElementAnchor({\n element,\n selectionRect,\n}: {\n element: Element;\n selectionRect: SelectModeBoundingRect;\n}): SelectModeClickPosition {\n const rect = element.getBoundingClientRect();\n\n return {\n x: Math.min(Math.max(selectionRect.left, rect.left), rect.right),\n y: Math.min(Math.max(selectionRect.top, rect.top), rect.bottom),\n };\n}\n\n/**\n * @author Kevin O'Connell 2026-04-17\n */\nexport function getElementBoundingRect(\n element: Element,\n): SelectModeBoundingRect {\n const rect = element.getBoundingClientRect();\n\n return {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SelectableData, SelectModeBoundingRect, SelectModeClickPosition, SelectModeSelectionType } from "../_vendor/schemas/generated/selectMode";
|
|
2
|
+
import type { DragPosition, ScrollOffset } from "./interactions/selectionGeometry";
|
|
3
|
+
export type SelectModeInteractionSession = {
|
|
4
|
+
type: Extract<SelectModeSelectionType, "none">;
|
|
5
|
+
} | {
|
|
6
|
+
type: Extract<SelectModeSelectionType, "drag">;
|
|
7
|
+
dragStart: DragPosition;
|
|
8
|
+
dragCurrent: DragPosition;
|
|
9
|
+
scrollOffset: ScrollOffset;
|
|
10
|
+
hasExceededThreshold: boolean;
|
|
11
|
+
} | {
|
|
12
|
+
type: Extract<SelectModeSelectionType, "element">;
|
|
13
|
+
element: Element;
|
|
14
|
+
clickPosition: SelectModeClickPosition | null;
|
|
15
|
+
data: SelectableData;
|
|
16
|
+
} | {
|
|
17
|
+
type: Extract<SelectModeSelectionType, "area">;
|
|
18
|
+
pageRect: SelectModeBoundingRect;
|
|
19
|
+
items: SelectableData[];
|
|
20
|
+
};
|
|
21
|
+
export declare const IDLE_SESSION: SelectModeInteractionSession;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../select-mode/selectModeSession.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n SelectableData,\n SelectModeBoundingRect,\n SelectModeClickPosition,\n SelectModeSelectionType,\n} from \"schemas/generated/selectMode\";\nimport type {\n DragPosition,\n ScrollOffset,\n} from \"./interactions/selectionGeometry\";\n\n// NOTE (Kevin O'Connell, 2026-04-22, REPL-26014): SDK-internal liveness\n// state for the active drag/select interaction. Looks like a near-\n// duplicate of agent-ui's `SelectModeSelection` (now in\n// `schemas/selectMode.ts`), but deliberately not lifted into\n// `packages/schemas` for two reasons:\n//\n// 1. The `element` variant carries `element: Element` \u2014 a live DOM\n// ref. Zod has no way to model that; `z.custom<Element>()` would\n// regress to `any` in the generated `.ts` (see the `ZodCustom`\n// branch in `packages/schemas/zod/generateTypeDefinition.ts`),\n// which would lose the strong typing the rest of the SDK relies\n// on when it reads `session.element.getBoundingClientRect()`,\n// etc.\n// 2. There is no second consumer. This type is used only inside\n// `packages/sandbox-runtime/select-mode/`; the parent side\n// (agent-ui) consumes the serialized `SelectModeSelection`\n// instead. Lifting it would move a single-package-internal type\n// into a shared package without any cross-package reader.\n//\n// What we *do* share with the schema is the discriminator vocabulary:\n// the literals come from `SelectModeSelectionType` (generated from\n// `selectModeSelectionTypeSchema` in `schemas/selectMode.ts`), so\n// adding or renaming a selection variant is a TS error here, in\n// `SelectModeSelection`, and in `SelectModeEditSelectionMetadata`.\nexport type SelectModeInteractionSession =\n | { type: Extract<SelectModeSelectionType, \"none\"> }\n | {\n type: Extract<SelectModeSelectionType, \"drag\">;\n dragStart: DragPosition;\n dragCurrent: DragPosition;\n scrollOffset: ScrollOffset;\n hasExceededThreshold: boolean;\n }\n | {\n type: Extract<SelectModeSelectionType, \"element\">;\n element: Element;\n clickPosition: SelectModeClickPosition | null;\n data: SelectableData;\n }\n | {\n type: Extract<SelectModeSelectionType, \"area\">;\n pageRect: SelectModeBoundingRect;\n items: SelectableData[];\n };\n\nexport const IDLE_SESSION: SelectModeInteractionSession = {\n type: \"none\",\n};\n"],
|
|
5
|
+
"mappings": "AAwDO,MAAM,eAA6C;AAAA,EACxD,MAAM;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-iframe selection frame renderer.
|
|
3
|
+
*
|
|
4
|
+
* Lives in the canopy iframe (not in agent-ui) because the previous
|
|
5
|
+
* parent-side overlay had to be re-anchored on every scroll frame via
|
|
6
|
+
* postMessage hop -> Zustand update -> React reconciliation, which
|
|
7
|
+
* shows up as visible lag when the user scrolls quickly.
|
|
8
|
+
*
|
|
9
|
+
* By rendering directly inside the canopy iframe's document we get two
|
|
10
|
+
* properties for free:
|
|
11
|
+
*
|
|
12
|
+
* 1. `position: absolute` + page coordinates means the document's own
|
|
13
|
+
* scroll naturally translates the frames — zero JavaScript per
|
|
14
|
+
* scroll frame, the browser composites them with the underlying page
|
|
15
|
+
* content.
|
|
16
|
+
* 2. No bridge serialization: the only cost when selection commits is
|
|
17
|
+
* a handful of style writes on the frame nodes.
|
|
18
|
+
*
|
|
19
|
+
* A single click resolves to one frame; an area drag resolves to one
|
|
20
|
+
* frame per captured element, so the renderer keeps a pool of nodes
|
|
21
|
+
* under a shared container and reconciles it on each call (reusing
|
|
22
|
+
* existing nodes when the count is stable, which keeps the single-frame
|
|
23
|
+
* scroll-sync hot path a couple of style writes rather than a DOM churn).
|
|
24
|
+
*
|
|
25
|
+
* The canopy iframe loads a customer page and doesn't include lattice /
|
|
26
|
+
* tailwind, so styling is intentionally vanilla inline CSS rather than
|
|
27
|
+
* Tailwind classes.
|
|
28
|
+
*
|
|
29
|
+
* @author Kevin O'Connell 2026-05-26
|
|
30
|
+
*/
|
|
31
|
+
type SelectionFrameVariant = "element" | "area";
|
|
32
|
+
type SelectionFramePageRect = {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
type SelectionFrameSpec = {
|
|
39
|
+
pageRect: SelectionFramePageRect;
|
|
40
|
+
variant: SelectionFrameVariant;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Toggle the brighter inline-edit border and re-color any frames already on
|
|
44
|
+
* screen so the change is immediate, not deferred to the next redraw.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setSelectionFrameEditing(editing: boolean): void;
|
|
47
|
+
/**
|
|
48
|
+
* Shared body-level layer container for in-iframe editor overlays (selection
|
|
49
|
+
* frames, annotation frames). Zero-size and pointer-transparent so it never
|
|
50
|
+
* affects the page; children opt into pointer events individually.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getOrCreateFramesLayerContainer({ id, zIndex, }: {
|
|
53
|
+
id: string;
|
|
54
|
+
zIndex: number;
|
|
55
|
+
}): HTMLDivElement | null;
|
|
56
|
+
/**
|
|
57
|
+
* Position selection frames at the given *page* coordinates, one node per
|
|
58
|
+
* spec. Page coords don't shift during scroll, so the caller is free to
|
|
59
|
+
* call this once at commit time and let the browser handle the rest of the
|
|
60
|
+
* scroll lifetime. Reconciles the node pool against `frames.length` so a
|
|
61
|
+
* repeated single-frame call (scroll sync) just repositions one node.
|
|
62
|
+
*
|
|
63
|
+
* @author Kevin O'Connell 2026-05-26
|
|
64
|
+
*/
|
|
65
|
+
export declare function showSelectionFrames({ frames, }: {
|
|
66
|
+
frames: SelectionFrameSpec[];
|
|
67
|
+
}): void;
|
|
68
|
+
/**
|
|
69
|
+
* Convenience wrapper for the single-frame (element click) case.
|
|
70
|
+
*
|
|
71
|
+
* @author Kevin O'Connell 2026-05-26
|
|
72
|
+
*/
|
|
73
|
+
export declare function showSelectionFrame({ pageRect, variant, }: {
|
|
74
|
+
pageRect: SelectionFramePageRect;
|
|
75
|
+
variant: SelectionFrameVariant;
|
|
76
|
+
}): void;
|
|
77
|
+
/**
|
|
78
|
+
* @author Kevin O'Connell 2026-05-26
|
|
79
|
+
*/
|
|
80
|
+
export declare function hideSelectionFrame(): void;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const FUCHSIA_HEX = "#c026d3";
|
|
2
|
+
const EDITING_FUCHSIA_HEX = "#e879f9";
|
|
3
|
+
const FRAMES_CONTAINER_ID = "replo-select-mode-selection-frames";
|
|
4
|
+
let isEditing = false;
|
|
5
|
+
function getFrameBorderHex() {
|
|
6
|
+
return isEditing ? EDITING_FUCHSIA_HEX : FUCHSIA_HEX;
|
|
7
|
+
}
|
|
8
|
+
function applyVariantStyle({
|
|
9
|
+
frame,
|
|
10
|
+
variant
|
|
11
|
+
}) {
|
|
12
|
+
frame.style.border = `2px solid ${getFrameBorderHex()}`;
|
|
13
|
+
frame.style.background = variant === "area" ? "rgba(192, 38, 211, 0.08)" : "transparent";
|
|
14
|
+
}
|
|
15
|
+
function setSelectionFrameEditing(editing) {
|
|
16
|
+
if (isEditing === editing) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
isEditing = editing;
|
|
20
|
+
if (typeof document === "undefined") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const container = document.getElementById(FRAMES_CONTAINER_ID);
|
|
24
|
+
if (!(container instanceof HTMLDivElement)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const child of container.children) {
|
|
28
|
+
if (child instanceof HTMLDivElement) {
|
|
29
|
+
child.style.borderColor = getFrameBorderHex();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getOrCreateFramesLayerContainer({
|
|
34
|
+
id,
|
|
35
|
+
zIndex
|
|
36
|
+
}) {
|
|
37
|
+
if (typeof document === "undefined") {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const existing = document.getElementById(id);
|
|
41
|
+
if (existing instanceof HTMLDivElement) {
|
|
42
|
+
return existing;
|
|
43
|
+
}
|
|
44
|
+
if (!document.body) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const container = document.createElement("div");
|
|
48
|
+
container.id = id;
|
|
49
|
+
container.setAttribute("aria-hidden", "true");
|
|
50
|
+
container.style.cssText = [
|
|
51
|
+
"position: absolute",
|
|
52
|
+
"top: 0",
|
|
53
|
+
"left: 0",
|
|
54
|
+
"width: 0",
|
|
55
|
+
"height: 0",
|
|
56
|
+
"margin: 0",
|
|
57
|
+
"padding: 0",
|
|
58
|
+
"pointer-events: none",
|
|
59
|
+
`z-index: ${zIndex}`
|
|
60
|
+
].join(";");
|
|
61
|
+
document.body.append(container);
|
|
62
|
+
return container;
|
|
63
|
+
}
|
|
64
|
+
function getOrCreateContainer() {
|
|
65
|
+
return getOrCreateFramesLayerContainer({
|
|
66
|
+
id: FRAMES_CONTAINER_ID,
|
|
67
|
+
zIndex: 2147483646
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function createFrameNode() {
|
|
71
|
+
const frame = document.createElement("div");
|
|
72
|
+
frame.setAttribute("aria-hidden", "true");
|
|
73
|
+
frame.style.cssText = [
|
|
74
|
+
"position: absolute",
|
|
75
|
+
"top: 0",
|
|
76
|
+
"left: 0",
|
|
77
|
+
"box-sizing: border-box",
|
|
78
|
+
"border-radius: 2px",
|
|
79
|
+
"pointer-events: none",
|
|
80
|
+
"margin: 0",
|
|
81
|
+
"padding: 0"
|
|
82
|
+
].join(";");
|
|
83
|
+
return frame;
|
|
84
|
+
}
|
|
85
|
+
function showSelectionFrames({
|
|
86
|
+
frames
|
|
87
|
+
}) {
|
|
88
|
+
const container = getOrCreateContainer();
|
|
89
|
+
if (!container) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
while (container.childElementCount > frames.length) {
|
|
93
|
+
container.lastElementChild?.remove();
|
|
94
|
+
}
|
|
95
|
+
while (container.childElementCount < frames.length) {
|
|
96
|
+
container.append(createFrameNode());
|
|
97
|
+
}
|
|
98
|
+
frames.forEach((spec, index) => {
|
|
99
|
+
const frame = container.children[index];
|
|
100
|
+
if (!(frame instanceof HTMLDivElement)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
applyVariantStyle({ frame, variant: spec.variant });
|
|
104
|
+
frame.style.left = `${spec.pageRect.x}px`;
|
|
105
|
+
frame.style.top = `${spec.pageRect.y}px`;
|
|
106
|
+
frame.style.width = `${spec.pageRect.width}px`;
|
|
107
|
+
frame.style.height = `${spec.pageRect.height}px`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function showSelectionFrame({
|
|
111
|
+
pageRect,
|
|
112
|
+
variant
|
|
113
|
+
}) {
|
|
114
|
+
showSelectionFrames({ frames: [{ pageRect, variant }] });
|
|
115
|
+
}
|
|
116
|
+
function hideSelectionFrame() {
|
|
117
|
+
if (typeof document === "undefined") {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const container = document.getElementById(FRAMES_CONTAINER_ID);
|
|
121
|
+
if (container instanceof HTMLDivElement) {
|
|
122
|
+
container.replaceChildren();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
getOrCreateFramesLayerContainer,
|
|
127
|
+
hideSelectionFrame,
|
|
128
|
+
setSelectionFrameEditing,
|
|
129
|
+
showSelectionFrame,
|
|
130
|
+
showSelectionFrames
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=selectionFrame.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../select-mode/selectionFrame.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * In-iframe selection frame renderer.\n *\n * Lives in the canopy iframe (not in agent-ui) because the previous\n * parent-side overlay had to be re-anchored on every scroll frame via\n * postMessage hop -> Zustand update -> React reconciliation, which\n * shows up as visible lag when the user scrolls quickly.\n *\n * By rendering directly inside the canopy iframe's document we get two\n * properties for free:\n *\n * 1. `position: absolute` + page coordinates means the document's own\n * scroll naturally translates the frames \u2014 zero JavaScript per\n * scroll frame, the browser composites them with the underlying page\n * content.\n * 2. No bridge serialization: the only cost when selection commits is\n * a handful of style writes on the frame nodes.\n *\n * A single click resolves to one frame; an area drag resolves to one\n * frame per captured element, so the renderer keeps a pool of nodes\n * under a shared container and reconciles it on each call (reusing\n * existing nodes when the count is stable, which keeps the single-frame\n * scroll-sync hot path a couple of style writes rather than a DOM churn).\n *\n * The canopy iframe loads a customer page and doesn't include lattice /\n * tailwind, so styling is intentionally vanilla inline CSS rather than\n * Tailwind classes.\n *\n * @author Kevin O'Connell 2026-05-26\n */\n\n// Matches `--color-fuchsia` (Tailwind v4 `fuchsia-600`) used by agent-ui\n// so the in-iframe frame is visually consistent with the rest of the\n// select-mode UI.\nconst FUCHSIA_HEX = \"#c026d3\";\n\n// Brighter fuchsia (`fuchsia-400`) used while a text block is being inline\n// edited, so the active edit target stands out from a plain selection.\nconst EDITING_FUCHSIA_HEX = \"#e879f9\";\n\nconst FRAMES_CONTAINER_ID = \"replo-select-mode-selection-frames\";\n\n// Whether an inline text edit is in progress. Module-level so every frame\n// redraw (including the scroll-sync hot path) picks up the brighter color\n// without threading the flag through each call.\nlet isEditing = false;\n\nfunction getFrameBorderHex(): string {\n return isEditing ? EDITING_FUCHSIA_HEX : FUCHSIA_HEX;\n}\n\ntype SelectionFrameVariant = \"element\" | \"area\";\n\ntype SelectionFramePageRect = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\ntype SelectionFrameSpec = {\n pageRect: SelectionFramePageRect;\n variant: SelectionFrameVariant;\n};\n\nfunction applyVariantStyle({\n frame,\n variant,\n}: {\n frame: HTMLDivElement;\n variant: SelectionFrameVariant;\n}) {\n // Both variants use the same bright fuchsia border so a single click and\n // each member of an area selection read as equally \"selected\"; the area\n // members carry a faint fill so a cluster still reads as a group. While\n // inline editing, the border brightens to mark the active edit target.\n frame.style.border = `2px solid ${getFrameBorderHex()}`;\n frame.style.background =\n variant === \"area\" ? \"rgba(192, 38, 211, 0.08)\" : \"transparent\";\n}\n\n/**\n * Toggle the brighter inline-edit border and re-color any frames already on\n * screen so the change is immediate, not deferred to the next redraw.\n */\nexport function setSelectionFrameEditing(editing: boolean) {\n if (isEditing === editing) {\n return;\n }\n isEditing = editing;\n if (typeof document === \"undefined\") {\n return;\n }\n const container = document.getElementById(FRAMES_CONTAINER_ID);\n if (!(container instanceof HTMLDivElement)) {\n return;\n }\n for (const child of container.children) {\n if (child instanceof HTMLDivElement) {\n child.style.borderColor = getFrameBorderHex();\n }\n }\n}\n\n/**\n * Shared body-level layer container for in-iframe editor overlays (selection\n * frames, annotation frames). Zero-size and pointer-transparent so it never\n * affects the page; children opt into pointer events individually.\n */\nexport function getOrCreateFramesLayerContainer({\n id,\n zIndex,\n}: {\n id: string;\n zIndex: number;\n}): HTMLDivElement | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const existing = document.getElementById(id);\n if (existing instanceof HTMLDivElement) {\n return existing;\n }\n if (!document.body) {\n return null;\n }\n const container = document.createElement(\"div\");\n container.id = id;\n container.setAttribute(\"aria-hidden\", \"true\");\n container.style.cssText = [\n \"position: absolute\",\n \"top: 0\",\n \"left: 0\",\n \"width: 0\",\n \"height: 0\",\n \"margin: 0\",\n \"padding: 0\",\n \"pointer-events: none\",\n `z-index: ${zIndex}`,\n ].join(\";\");\n document.body.append(container);\n return container;\n}\n\nfunction getOrCreateContainer(): HTMLDivElement | null {\n return getOrCreateFramesLayerContainer({\n id: FRAMES_CONTAINER_ID,\n zIndex: 2147483646,\n });\n}\n\nfunction createFrameNode(): HTMLDivElement {\n const frame = document.createElement(\"div\");\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.style.cssText = [\n \"position: absolute\",\n \"top: 0\",\n \"left: 0\",\n \"box-sizing: border-box\",\n \"border-radius: 2px\",\n \"pointer-events: none\",\n \"margin: 0\",\n \"padding: 0\",\n ].join(\";\");\n return frame;\n}\n\n/**\n * Position selection frames at the given *page* coordinates, one node per\n * spec. Page coords don't shift during scroll, so the caller is free to\n * call this once at commit time and let the browser handle the rest of the\n * scroll lifetime. Reconciles the node pool against `frames.length` so a\n * repeated single-frame call (scroll sync) just repositions one node.\n *\n * @author Kevin O'Connell 2026-05-26\n */\nexport function showSelectionFrames({\n frames,\n}: {\n frames: SelectionFrameSpec[];\n}) {\n const container = getOrCreateContainer();\n if (!container) {\n return;\n }\n\n while (container.childElementCount > frames.length) {\n container.lastElementChild?.remove();\n }\n while (container.childElementCount < frames.length) {\n container.append(createFrameNode());\n }\n\n frames.forEach((spec, index) => {\n const frame = container.children[index];\n if (!(frame instanceof HTMLDivElement)) {\n return;\n }\n applyVariantStyle({ frame, variant: spec.variant });\n frame.style.left = `${spec.pageRect.x}px`;\n frame.style.top = `${spec.pageRect.y}px`;\n frame.style.width = `${spec.pageRect.width}px`;\n frame.style.height = `${spec.pageRect.height}px`;\n });\n}\n\n/**\n * Convenience wrapper for the single-frame (element click) case.\n *\n * @author Kevin O'Connell 2026-05-26\n */\nexport function showSelectionFrame({\n pageRect,\n variant,\n}: {\n pageRect: SelectionFramePageRect;\n variant: SelectionFrameVariant;\n}) {\n showSelectionFrames({ frames: [{ pageRect, variant }] });\n}\n\n/**\n * @author Kevin O'Connell 2026-05-26\n */\nexport function hideSelectionFrame() {\n if (typeof document === \"undefined\") {\n return;\n }\n const container = document.getElementById(FRAMES_CONTAINER_ID);\n if (container instanceof HTMLDivElement) {\n container.replaceChildren();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|