@replohq/sandbox-runtime 1.9.0 → 1.10.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/fonts.mjs +18 -6
- package/_vendor/schemas/generated/selectMode.d.ts +15 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +12 -2
- package/_vendor/schemas/selectMode.d.ts +28 -0
- package/_vendor/schemas/selectMode.mjs +1 -1
- package/_vendor/schemas/selectModeIframeRpc.d.ts +57 -0
- package/_vendor/schemas/selectModeIframeRpc.mjs +18 -2
- package/_vendor/schemas/selectModeImage.mjs +6 -2
- package/buildMetadata.js +3 -3
- package/{chunk-XWNBKE63.mjs → chunk-NBVMONRN.mjs} +7 -1
- package/design-library-preview/DesignLibraryPreview.js +18 -6
- package/package.json +4 -4
- package/select-mode/EditorSelection.js +21 -4
- package/select-mode/SelectionFrames.js +56 -23
- package/select-mode/descriptor/elementDescriptor.d.ts +2 -1
- package/select-mode/descriptor/elementDescriptor.js +61 -14
- package/select-mode/hooks/useOptimisticOverrides.js +18 -6
- package/select-mode/hooks/useParentMessaging.d.ts +7 -1
- package/select-mode/hooks/useParentMessaging.js +6 -0
- package/select-mode/hooks/useSelectionHover.js +38 -15
- package/select-mode/hooks/useSelectionInteractions.js +11 -37
- package/select-mode/hooks/useSelectionScrollSync.js +5 -1
- package/select-mode/iframeRpcChannel.d.ts +13 -0
- package/select-mode/iframeRpcChannel.js +2 -0
- package/select-mode/imageOverridePreview.js +14 -5
- package/select-mode/imageSources.js +2 -1
- package/select-mode/inlineEditFeedback.js +9 -3
- package/select-mode/interactions/sectionGeometry.d.ts +3 -0
- package/select-mode/interactions/sectionGeometry.js +200 -0
- package/select-mode/selectModeStore.d.ts +36 -8
- package/select-mode/selectModeStore.js +141 -21
- package/select-mode/selectionFrame.d.ts +3 -1
- package/select-mode/selectionFrame.js +4 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
|
+
import { getDocumentScrollOffset } from "./interactions/selectionGeometry";
|
|
4
5
|
import {
|
|
6
|
+
SECTION_SELECTION_FRAME_BACKGROUND,
|
|
7
|
+
SECTION_SELECTION_FRAME_BORDER_HEX,
|
|
5
8
|
SELECTION_FRAME_BORDER_HEX,
|
|
6
9
|
SELECTION_FRAME_EDITING_BORDER_HEX,
|
|
7
10
|
SELECTION_FRAMES_CONTAINER_ID
|
|
8
11
|
} from "./selectionFrame";
|
|
9
12
|
import {
|
|
10
13
|
useIsSelectionFrameEditing,
|
|
14
|
+
useSectionHoverRect,
|
|
11
15
|
useSelectionFrames
|
|
12
16
|
} from "./selectModeStore";
|
|
13
17
|
const CONTAINER_STYLE = {
|
|
@@ -24,32 +28,61 @@ const CONTAINER_STYLE = {
|
|
|
24
28
|
function SelectionFrames() {
|
|
25
29
|
const frames = useSelectionFrames();
|
|
26
30
|
const isEditing = useIsSelectionFrameEditing();
|
|
27
|
-
|
|
31
|
+
const sectionHoverRect = useSectionHoverRect();
|
|
32
|
+
if (typeof document === "undefined" || !document.body || frames.length === 0 && !sectionHoverRect) {
|
|
28
33
|
return null;
|
|
29
34
|
}
|
|
30
|
-
const
|
|
35
|
+
const elementBorderColor = isEditing ? SELECTION_FRAME_EDITING_BORDER_HEX : SELECTION_FRAME_BORDER_HEX;
|
|
36
|
+
const scrollOffset = getDocumentScrollOffset();
|
|
37
|
+
const sectionLabelPosition = sectionHoverRect ? {
|
|
38
|
+
x: sectionHoverRect.x + scrollOffset.left,
|
|
39
|
+
y: sectionHoverRect.y + scrollOffset.top
|
|
40
|
+
} : frames.find((frame) => frame.variant === "section")?.pageRect;
|
|
31
41
|
return createPortal(
|
|
32
|
-
/* @__PURE__ */
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { id: SELECTION_FRAMES_CONTAINER_ID, "aria-hidden": true, style: CONTAINER_STYLE, children: [
|
|
43
|
+
frames.map((frame, index) => /* @__PURE__ */ jsx(
|
|
44
|
+
"div",
|
|
45
|
+
{
|
|
46
|
+
"aria-hidden": true,
|
|
47
|
+
"data-variant": frame.variant,
|
|
48
|
+
style: {
|
|
49
|
+
position: "absolute",
|
|
50
|
+
boxSizing: "border-box",
|
|
51
|
+
borderRadius: 2,
|
|
52
|
+
pointerEvents: "none",
|
|
53
|
+
margin: 0,
|
|
54
|
+
padding: 0,
|
|
55
|
+
left: frame.pageRect.x,
|
|
56
|
+
top: frame.pageRect.y,
|
|
57
|
+
width: frame.pageRect.width,
|
|
58
|
+
height: frame.pageRect.height,
|
|
59
|
+
border: `2px solid ${frame.variant === "section" ? SECTION_SELECTION_FRAME_BORDER_HEX : elementBorderColor}`,
|
|
60
|
+
background: {
|
|
61
|
+
area: "rgba(192, 38, 211, 0.08)",
|
|
62
|
+
element: "transparent",
|
|
63
|
+
section: SECTION_SELECTION_FRAME_BACKGROUND
|
|
64
|
+
}[frame.variant]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
`${frame.variant}-${index}`
|
|
68
|
+
)),
|
|
69
|
+
sectionLabelPosition && /* @__PURE__ */ jsx(
|
|
70
|
+
"span",
|
|
71
|
+
{
|
|
72
|
+
style: {
|
|
73
|
+
position: "absolute",
|
|
74
|
+
top: sectionLabelPosition.y + 6,
|
|
75
|
+
left: sectionLabelPosition.x + 6,
|
|
76
|
+
padding: "3px 6px",
|
|
77
|
+
borderRadius: 4,
|
|
78
|
+
background: "#2563eb",
|
|
79
|
+
color: "white",
|
|
80
|
+
font: "500 12px/16px system-ui"
|
|
81
|
+
},
|
|
82
|
+
children: "Section"
|
|
49
83
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)) }),
|
|
84
|
+
)
|
|
85
|
+
] }),
|
|
53
86
|
document.body
|
|
54
87
|
);
|
|
55
88
|
}
|
|
@@ -17,8 +17,9 @@ export declare function buildSelector(element: Element): string;
|
|
|
17
17
|
* about a selection: identification, geometry, source location, and a
|
|
18
18
|
* style-inlined HTML snippet.
|
|
19
19
|
*/
|
|
20
|
-
export declare function buildElementDescriptor({ element, clickPosition, siblingSelectors, }: {
|
|
20
|
+
export declare function buildElementDescriptor({ element, clickPosition, siblingSelectors, section, }: {
|
|
21
21
|
element: Element;
|
|
22
22
|
clickPosition: SelectModeClickPosition;
|
|
23
23
|
siblingSelectors?: string[];
|
|
24
|
+
section?: Element | null;
|
|
24
25
|
}): SelectableData;
|
|
@@ -8,6 +8,43 @@ import { getImageDescriptor } from "../imageSources";
|
|
|
8
8
|
import { skipInlineTextMarkElements } from "../inlineTextMarks";
|
|
9
9
|
import { getElementBoundingRect } from "../interactions/selectionGeometry";
|
|
10
10
|
import { getDefinedStyles, getInheritedStyles } from "./styleExtraction";
|
|
11
|
+
const SECTION_SELECTOR = 'section, article, aside, [role="region"]';
|
|
12
|
+
const SECTION_LABEL_LENGTH = 60;
|
|
13
|
+
function normalizeSectionLabel(value) {
|
|
14
|
+
return value?.replace(/\s+/g, " ").trim().slice(0, SECTION_LABEL_LENGTH) || void 0;
|
|
15
|
+
}
|
|
16
|
+
function findSectionLabel(section) {
|
|
17
|
+
if (!section) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
const ariaLabel = normalizeSectionLabel(section.getAttribute("aria-label"));
|
|
21
|
+
if (ariaLabel) {
|
|
22
|
+
return ariaLabel;
|
|
23
|
+
}
|
|
24
|
+
const labelledBy = section.getAttribute("aria-labelledby");
|
|
25
|
+
const labelledByText = normalizeSectionLabel(
|
|
26
|
+
labelledBy?.split(/\s+/).map((id) => section.ownerDocument.getElementById(id)?.textContent).filter(Boolean).join(" ")
|
|
27
|
+
);
|
|
28
|
+
if (labelledByText) {
|
|
29
|
+
return labelledByText;
|
|
30
|
+
}
|
|
31
|
+
const headings = Array.from(
|
|
32
|
+
section.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
|
33
|
+
).filter((candidate) => {
|
|
34
|
+
if (candidate.checkVisibility?.({ visibilityProperty: true }) === false) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const semanticSection = candidate.closest(SECTION_SELECTOR);
|
|
38
|
+
return !semanticSection || semanticSection === section || !section.contains(semanticSection);
|
|
39
|
+
});
|
|
40
|
+
const highestHeadingLevel = Math.min(
|
|
41
|
+
...headings.map((heading2) => Number(heading2.tagName[1]))
|
|
42
|
+
);
|
|
43
|
+
const [heading, competingHeading] = headings.filter(
|
|
44
|
+
(candidate) => Number(candidate.tagName[1]) === highestHeadingLevel
|
|
45
|
+
);
|
|
46
|
+
return competingHeading ? void 0 : normalizeSectionLabel(heading ? getRenderedText(heading) : void 0);
|
|
47
|
+
}
|
|
11
48
|
function getNearestSelectableElement(element) {
|
|
12
49
|
const host = skipInlineTextMarkElements(element);
|
|
13
50
|
return host.closest(`[${SELECT_MODE_SELECTABLE_ATTRIBUTE}]`) ?? host;
|
|
@@ -30,12 +67,20 @@ function getSelectableDataFromElement(element) {
|
|
|
30
67
|
rect: getElementBoundingRect(selectableElement)
|
|
31
68
|
};
|
|
32
69
|
}
|
|
33
|
-
function
|
|
70
|
+
function getRenderedText(element) {
|
|
71
|
+
if (element instanceof HTMLElement) {
|
|
72
|
+
return (element.innerText ?? element.textContent)?.trim() ?? null;
|
|
73
|
+
}
|
|
74
|
+
return element.textContent?.trim() ?? null;
|
|
75
|
+
}
|
|
76
|
+
function identifyElement({
|
|
77
|
+
element,
|
|
78
|
+
trimmedText
|
|
79
|
+
}) {
|
|
34
80
|
const tagName = element.tagName.toLowerCase();
|
|
35
81
|
if (tagName === "button" || element.getAttribute("role") === "button" || tagName === "input") {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return `${tagName}: "${trimmedText2.slice(0, 60)}"`;
|
|
82
|
+
if (trimmedText) {
|
|
83
|
+
return `${tagName}: "${trimmedText.slice(0, 60)}"`;
|
|
39
84
|
}
|
|
40
85
|
const ariaLabel = element.getAttribute("aria-label");
|
|
41
86
|
if (ariaLabel) {
|
|
@@ -49,9 +94,8 @@ function identifyElement(element) {
|
|
|
49
94
|
return tagName;
|
|
50
95
|
}
|
|
51
96
|
if (tagName === "a") {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return `link: "${trimmedText2.slice(0, 60)}"`;
|
|
97
|
+
if (trimmedText) {
|
|
98
|
+
return `link: "${trimmedText.slice(0, 60)}"`;
|
|
55
99
|
}
|
|
56
100
|
const href = element.getAttribute("href");
|
|
57
101
|
if (href) {
|
|
@@ -72,8 +116,7 @@ function identifyElement(element) {
|
|
|
72
116
|
return "image";
|
|
73
117
|
}
|
|
74
118
|
if (/^h[1-6]$/.test(tagName)) {
|
|
75
|
-
|
|
76
|
-
return trimmedText2 ? `heading(${tagName}): "${trimmedText2.slice(0, 60)}"` : tagName;
|
|
119
|
+
return trimmedText ? `heading(${tagName}): "${trimmedText.slice(0, 60)}"` : tagName;
|
|
77
120
|
}
|
|
78
121
|
if (["div", "main", "section", "article", "header", "footer", "nav"].includes(
|
|
79
122
|
tagName
|
|
@@ -83,7 +126,6 @@ function identifyElement(element) {
|
|
|
83
126
|
return role2;
|
|
84
127
|
}
|
|
85
128
|
}
|
|
86
|
-
const trimmedText = element.textContent?.trim();
|
|
87
129
|
if (trimmedText && trimmedText.length < 120) {
|
|
88
130
|
const role2 = element.getAttribute("role");
|
|
89
131
|
const label = role2 ?? tagName;
|
|
@@ -168,7 +210,8 @@ function getTextMetrics(element) {
|
|
|
168
210
|
function buildElementDescriptor({
|
|
169
211
|
element,
|
|
170
212
|
clickPosition,
|
|
171
|
-
siblingSelectors = []
|
|
213
|
+
siblingSelectors = [],
|
|
214
|
+
section
|
|
172
215
|
}) {
|
|
173
216
|
const selectableData = getSelectableDataFromElement(element);
|
|
174
217
|
if (selectableData) {
|
|
@@ -177,12 +220,15 @@ function buildElementDescriptor({
|
|
|
177
220
|
const textMetrics = getTextMetrics(element);
|
|
178
221
|
const fiberChain = element instanceof HTMLElement ? getFiberChain(element) : [];
|
|
179
222
|
const sourceLocation = fiberChain[0]?.sourceLocation;
|
|
223
|
+
const trimmedText = getRenderedText(element);
|
|
224
|
+
const sectionLabel = findSectionLabel(section);
|
|
180
225
|
return {
|
|
181
226
|
kind: "react-element",
|
|
182
227
|
selector: buildSelector(element),
|
|
183
228
|
tagName: element.tagName.toLowerCase(),
|
|
184
|
-
elementType: identifyElement(element),
|
|
185
|
-
textContent:
|
|
229
|
+
elementType: identifyElement({ element, trimmedText }),
|
|
230
|
+
textContent: trimmedText?.slice(0, 200) ?? null,
|
|
231
|
+
...sectionLabel ? { sectionLabel } : {},
|
|
186
232
|
rect: getElementBoundingRect(element),
|
|
187
233
|
textMetrics,
|
|
188
234
|
outerHTML: element.outerHTML.slice(0, 4e3),
|
|
@@ -192,7 +238,8 @@ function buildElementDescriptor({
|
|
|
192
238
|
clickPosition,
|
|
193
239
|
sourceLocation,
|
|
194
240
|
fiberChain,
|
|
195
|
-
siblingSelectors
|
|
241
|
+
siblingSelectors,
|
|
242
|
+
...section ? { section: { selector: buildSelector(section) } } : {}
|
|
196
243
|
};
|
|
197
244
|
}
|
|
198
245
|
export {
|
|
@@ -91,7 +91,9 @@ function applyPayload({
|
|
|
91
91
|
payload,
|
|
92
92
|
token
|
|
93
93
|
}) {
|
|
94
|
-
if (payload.kind === "image")
|
|
94
|
+
if (payload.kind === "image") {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
95
97
|
if (payload.kind === "css") {
|
|
96
98
|
addToken(element, token);
|
|
97
99
|
return;
|
|
@@ -201,7 +203,9 @@ function reapplyOverride({
|
|
|
201
203
|
override,
|
|
202
204
|
skipElement
|
|
203
205
|
}) {
|
|
204
|
-
if (override.payload.kind === "image")
|
|
206
|
+
if (override.payload.kind === "image") {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
205
209
|
for (const target of override.targets) {
|
|
206
210
|
const element = resolveTarget(target);
|
|
207
211
|
if (!element || skipElement && element.contains(skipElement)) {
|
|
@@ -220,10 +224,14 @@ function reapplyOverride({
|
|
|
220
224
|
function syncImagePreviews() {
|
|
221
225
|
const configurations = /* @__PURE__ */ new Map();
|
|
222
226
|
for (const override of getOverrideList()) {
|
|
223
|
-
if (override.payload.kind !== "image")
|
|
227
|
+
if (override.payload.kind !== "image") {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
224
230
|
for (const target of override.targets) {
|
|
225
231
|
const element = resolveTarget(target);
|
|
226
|
-
if (!(element instanceof HTMLImageElement))
|
|
232
|
+
if (!(element instanceof HTMLImageElement)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
227
235
|
const previous = configurations.get(element);
|
|
228
236
|
configurations.set(element, {
|
|
229
237
|
image: override.payload.image,
|
|
@@ -232,7 +240,9 @@ function syncImagePreviews() {
|
|
|
232
240
|
}
|
|
233
241
|
}
|
|
234
242
|
for (const [element, preview] of imagePreviews) {
|
|
235
|
-
if (configurations.has(element))
|
|
243
|
+
if (configurations.has(element)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
236
246
|
preview.dispose();
|
|
237
247
|
imagePreviews.delete(element);
|
|
238
248
|
}
|
|
@@ -253,7 +263,9 @@ function syncImagePreviews() {
|
|
|
253
263
|
}
|
|
254
264
|
setOverrideList(
|
|
255
265
|
getOverrideList().filter((entry) => {
|
|
256
|
-
if (entry.payload.kind !== "image")
|
|
266
|
+
if (entry.payload.kind !== "image") {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
257
269
|
entry.targets = entry.targets.filter((target) => {
|
|
258
270
|
const element = resolveTarget(target);
|
|
259
271
|
return !element || !acknowledged.has(element);
|
|
@@ -16,7 +16,7 @@ import type { DisplayMode, SelectModeFormatCommand, SelectModeOptimisticUpdatePa
|
|
|
16
16
|
* the initial `setDisplayMode` — without this handshake the parent would
|
|
17
17
|
* race the iframe's mount and the first message could be lost.
|
|
18
18
|
*/
|
|
19
|
-
export declare function useParentMessaging({ applyOptimisticUpdate, markCommitted, resolveSessionOverrides, onEnterEditMode, onExitEditMode, onFormatCommand, revertOverride, setDisplayMode, setSelectionType, }: {
|
|
19
|
+
export declare function useParentMessaging({ applyOptimisticUpdate, markCommitted, resolveSessionOverrides, onExpandToSection, onSetSectionPreview, onEnterEditMode, onExitEditMode, onFormatCommand, revertOverride, setDisplayMode, setSelectionType, }: {
|
|
20
20
|
applyOptimisticUpdate?: (input: {
|
|
21
21
|
editId: string;
|
|
22
22
|
payload: SelectModeOptimisticUpdatePayload;
|
|
@@ -25,6 +25,12 @@ export declare function useParentMessaging({ applyOptimisticUpdate, markCommitte
|
|
|
25
25
|
}) => void;
|
|
26
26
|
markCommitted?: (editId: string, sessionId: string) => void;
|
|
27
27
|
resolveSessionOverrides?: (sessionId: string, outcome: "persisted" | "failed") => void;
|
|
28
|
+
onExpandToSection?: (input: {
|
|
29
|
+
targetSelector: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
onSetSectionPreview?: (input: {
|
|
32
|
+
targetSelector: string | null;
|
|
33
|
+
}) => void;
|
|
28
34
|
onEnterEditMode?: () => void;
|
|
29
35
|
onExitEditMode?: () => void;
|
|
30
36
|
onFormatCommand?: (command: SelectModeFormatCommand) => void;
|
|
@@ -27,6 +27,8 @@ function useParentMessaging({
|
|
|
27
27
|
applyOptimisticUpdate,
|
|
28
28
|
markCommitted,
|
|
29
29
|
resolveSessionOverrides,
|
|
30
|
+
onExpandToSection,
|
|
31
|
+
onSetSectionPreview,
|
|
30
32
|
onEnterEditMode,
|
|
31
33
|
onExitEditMode,
|
|
32
34
|
onFormatCommand,
|
|
@@ -37,6 +39,8 @@ function useParentMessaging({
|
|
|
37
39
|
useIframeRpcChannel(useMemo(() => getSelectModeIframeRpcChannel(), []));
|
|
38
40
|
useEffect(() => {
|
|
39
41
|
setSelectModeParentHandlers({
|
|
42
|
+
expandToSection: (input) => onExpandToSection?.(input),
|
|
43
|
+
setSectionPreview: (input) => onSetSectionPreview?.(input),
|
|
40
44
|
setDisplayMode: ({ displayMode }) => {
|
|
41
45
|
setDisplayMode(displayMode);
|
|
42
46
|
},
|
|
@@ -83,6 +87,8 @@ function useParentMessaging({
|
|
|
83
87
|
applyOptimisticUpdate,
|
|
84
88
|
markCommitted,
|
|
85
89
|
resolveSessionOverrides,
|
|
90
|
+
onExpandToSection,
|
|
91
|
+
onSetSectionPreview,
|
|
86
92
|
onEnterEditMode,
|
|
87
93
|
onExitEditMode,
|
|
88
94
|
onFormatCommand,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import { getNearestSelectableElement } from "../descriptor/elementDescriptor";
|
|
3
3
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
4
|
+
import {
|
|
5
|
+
CONTAINER_TAGS,
|
|
6
|
+
findContainingSection
|
|
7
|
+
} from "../interactions/sectionGeometry";
|
|
4
8
|
import { getElementBoundingRect } from "../interactions/selectionGeometry";
|
|
5
9
|
import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
|
|
6
10
|
function useSelectionHover() {
|
|
@@ -11,16 +15,30 @@ function useSelectionHover() {
|
|
|
11
15
|
return;
|
|
12
16
|
}
|
|
13
17
|
const postHoveredElement = (element) => {
|
|
14
|
-
const {
|
|
18
|
+
const { lastPostedHover, actions } = store.getState();
|
|
19
|
+
const lastPostedHoverRect = lastPostedHover?.rect;
|
|
15
20
|
const rect = element ? getElementBoundingRect(element) : null;
|
|
16
|
-
const isUnchanged = lastPostedHoverRect !== void 0 && (rect === lastPostedHoverRect || rect !== null && lastPostedHoverRect !== null && rect.x === lastPostedHoverRect.x && rect.y === lastPostedHoverRect.y && rect.width === lastPostedHoverRect.width && rect.height === lastPostedHoverRect.height);
|
|
21
|
+
const isUnchanged = lastPostedHover?.element === element && lastPostedHoverRect !== void 0 && (rect === lastPostedHoverRect || rect !== null && lastPostedHoverRect !== null && rect.x === lastPostedHoverRect.x && rect.y === lastPostedHoverRect.y && rect.width === lastPostedHoverRect.width && rect.height === lastPostedHoverRect.height);
|
|
17
22
|
if (isUnchanged) {
|
|
18
23
|
return;
|
|
19
24
|
}
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
const hoverRect = rect && element ? {
|
|
26
|
+
...rect,
|
|
27
|
+
isSection: element === store.getState().sectionPreviewElement || CONTAINER_TAGS.has(element.tagName) && findContainingSection(element) === element
|
|
28
|
+
} : null;
|
|
29
|
+
actions.setLastPostedHover({ element, rect: hoverRect });
|
|
30
|
+
getWebsiteBuilderRpc()?.hoverElement({ rect: hoverRect });
|
|
22
31
|
};
|
|
23
32
|
const syncCurrentHover = (pointerClientPosition = store.getState().lastPointerClientPosition) => {
|
|
33
|
+
const previewSection = store.getState().sectionPreviewElement;
|
|
34
|
+
if (previewSection) {
|
|
35
|
+
if (previewSection.isConnected) {
|
|
36
|
+
postHoveredElement(previewSection);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
store.getState().actions.setSectionPreview({ targetSelector: null });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
24
42
|
let hoveredElement = null;
|
|
25
43
|
if (pointerClientPosition) {
|
|
26
44
|
const isPointerWithinViewport = pointerClientPosition.x >= 0 && pointerClientPosition.y >= 0 && pointerClientPosition.x <= window.innerWidth && pointerClientPosition.y <= window.innerHeight;
|
|
@@ -59,14 +77,6 @@ function useSelectionHover() {
|
|
|
59
77
|
}
|
|
60
78
|
syncCurrentHover(position);
|
|
61
79
|
};
|
|
62
|
-
const handlePointerOver = (event) => {
|
|
63
|
-
const position = { x: event.clientX, y: event.clientY };
|
|
64
|
-
store.getState().actions.setLastPointerClientPosition(position);
|
|
65
|
-
if (store.getState().interactionSession.type === "drag") {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
syncCurrentHover(position);
|
|
69
|
-
};
|
|
70
80
|
const handlePointerOut = (event) => {
|
|
71
81
|
if (store.getState().interactionSession.type === "drag") {
|
|
72
82
|
return;
|
|
@@ -81,11 +91,20 @@ function useSelectionHover() {
|
|
|
81
91
|
cancelAnimationFrame(hoverSyncAnimationFrameId);
|
|
82
92
|
actions.setHoverSyncAnimationFrameId(null);
|
|
83
93
|
}
|
|
84
|
-
|
|
94
|
+
if (store.getState().sectionPreviewElement) {
|
|
95
|
+
syncCurrentHover(null);
|
|
96
|
+
} else {
|
|
97
|
+
postHoveredElement(null);
|
|
98
|
+
}
|
|
85
99
|
};
|
|
100
|
+
const stopPreviewObservation = store.subscribe((state, previous) => {
|
|
101
|
+
if (state.sectionPreviewElement !== previous.sectionPreviewElement) {
|
|
102
|
+
syncCurrentHover();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
86
105
|
syncCurrentHover();
|
|
87
106
|
document.addEventListener("pointermove", handlePointerMove, true);
|
|
88
|
-
document.addEventListener("pointerover",
|
|
107
|
+
document.addEventListener("pointerover", handlePointerMove, true);
|
|
89
108
|
document.addEventListener("pointerout", handlePointerOut, true);
|
|
90
109
|
document.addEventListener("scroll", scheduleHoverSync, true);
|
|
91
110
|
document.addEventListener("wheel", scheduleHoverSync, {
|
|
@@ -94,13 +113,17 @@ function useSelectionHover() {
|
|
|
94
113
|
});
|
|
95
114
|
window.addEventListener("resize", scheduleHoverSync);
|
|
96
115
|
return () => {
|
|
116
|
+
stopPreviewObservation();
|
|
117
|
+
store.getState().actions.setSectionPreview({ targetSelector: null });
|
|
118
|
+
postHoveredElement(null);
|
|
119
|
+
store.getState().actions.setLastPostedHover(void 0);
|
|
97
120
|
const { hoverSyncAnimationFrameId } = store.getState();
|
|
98
121
|
if (hoverSyncAnimationFrameId !== null) {
|
|
99
122
|
cancelAnimationFrame(hoverSyncAnimationFrameId);
|
|
100
123
|
store.getState().actions.setHoverSyncAnimationFrameId(null);
|
|
101
124
|
}
|
|
102
125
|
document.removeEventListener("pointermove", handlePointerMove, true);
|
|
103
|
-
document.removeEventListener("pointerover",
|
|
126
|
+
document.removeEventListener("pointerover", handlePointerMove, true);
|
|
104
127
|
document.removeEventListener("pointerout", handlePointerOut, true);
|
|
105
128
|
document.removeEventListener("scroll", scheduleHoverSync, true);
|
|
106
129
|
document.removeEventListener("wheel", scheduleHoverSync, {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import {
|
|
3
3
|
buildElementDescriptor,
|
|
4
|
-
buildSelector,
|
|
5
4
|
getNearestSelectableElement
|
|
6
5
|
} from "../descriptor/elementDescriptor";
|
|
7
6
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
7
|
+
import { findContainingSection } from "../interactions/sectionGeometry";
|
|
8
8
|
import {
|
|
9
9
|
collectDragCandidates,
|
|
10
10
|
getCandidatesInSelection,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from "../selectModeSession";
|
|
28
28
|
import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
|
|
29
29
|
import { symbolicateSelectedElement } from "../symbolicate";
|
|
30
|
-
import { getRepetitionSiblingElements } from "./useSiblingResolution";
|
|
31
30
|
const DOUBLE_CLICK_THRESHOLD_MS = 500;
|
|
32
31
|
function getSelectedElementsFromSession(session) {
|
|
33
32
|
if (session.type === "element") {
|
|
@@ -72,8 +71,8 @@ function useSelectionInteractions() {
|
|
|
72
71
|
actions().hideSelectionFrames();
|
|
73
72
|
};
|
|
74
73
|
const showAreaSelectionFrames = actions().showAreaSelectionFrames;
|
|
75
|
-
const showElementSelectionFrames = actions().showElementSelectionFrames;
|
|
76
74
|
const editingPointerIds = store().editingPointerIds;
|
|
75
|
+
let committedDataAtPointerDown = null;
|
|
77
76
|
const clearCommittedSelection = ({
|
|
78
77
|
rect
|
|
79
78
|
}) => {
|
|
@@ -186,6 +185,8 @@ function useSelectionInteractions() {
|
|
|
186
185
|
event.preventDefault();
|
|
187
186
|
event.stopPropagation();
|
|
188
187
|
const scrollOffset = getDocumentScrollOffset();
|
|
188
|
+
const session = interactionSessionRef.current;
|
|
189
|
+
committedDataAtPointerDown = session.type === "element" ? session.data : null;
|
|
189
190
|
actions().setCommittedSelectionAtPointerDown(
|
|
190
191
|
getSelectedElementsFromSession(interactionSessionRef.current)
|
|
191
192
|
);
|
|
@@ -242,6 +243,7 @@ function useSelectionInteractions() {
|
|
|
242
243
|
element,
|
|
243
244
|
data: buildElementDescriptor({
|
|
244
245
|
element,
|
|
246
|
+
section: findContainingSection(element),
|
|
245
247
|
clickPosition: getElementAnchor({
|
|
246
248
|
element,
|
|
247
249
|
selectionRect: anchorRect
|
|
@@ -277,7 +279,6 @@ function useSelectionInteractions() {
|
|
|
277
279
|
selectionElement,
|
|
278
280
|
pointerEvent
|
|
279
281
|
}) => {
|
|
280
|
-
const targetRect = selectionElement.getBoundingClientRect();
|
|
281
282
|
const clickPosition = getElementAnchor({
|
|
282
283
|
element: selectionElement,
|
|
283
284
|
selectionRect: getPointRect({
|
|
@@ -285,12 +286,6 @@ function useSelectionInteractions() {
|
|
|
285
286
|
y: pointerEvent.clientY
|
|
286
287
|
})
|
|
287
288
|
});
|
|
288
|
-
const scrollOffset = getDocumentScrollOffset();
|
|
289
|
-
const siblingElements = selectionElement instanceof HTMLElement ? getRepetitionSiblingElements({ element: selectionElement }) : [];
|
|
290
|
-
showElementSelectionFrames({
|
|
291
|
-
elements: [selectionElement, ...siblingElements],
|
|
292
|
-
scrollOffset
|
|
293
|
-
});
|
|
294
289
|
const now = pointerEvent.timeStamp;
|
|
295
290
|
const previousClick = store().lastClick;
|
|
296
291
|
if (previousClick && previousClick.target === selectionElement && now - previousClick.time <= DOUBLE_CLICK_THRESHOLD_MS && isTextEditableElement(selectionElement)) {
|
|
@@ -301,41 +296,20 @@ function useSelectionInteractions() {
|
|
|
301
296
|
});
|
|
302
297
|
return;
|
|
303
298
|
}
|
|
304
|
-
const data =
|
|
299
|
+
const data = actions().commitElementSelection({
|
|
305
300
|
element: selectionElement,
|
|
306
301
|
clickPosition,
|
|
307
|
-
|
|
302
|
+
isPendingClickOnSelection: (descriptor) => {
|
|
303
|
+
const session = interactionSessionRef.current;
|
|
304
|
+
const committedSelectionAtPointerDown = store().committedSelectionAtPointerDown;
|
|
305
|
+
return session.type === "drag" && !session.hasExceededThreshold && committedDataAtPointerDown === descriptor && committedSelectionAtPointerDown.length === 1 && committedSelectionAtPointerDown[0] === selectionElement;
|
|
306
|
+
}
|
|
308
307
|
});
|
|
309
308
|
actions().setLastClick({
|
|
310
309
|
descriptorPromise: Promise.resolve(data),
|
|
311
310
|
target: selectionElement,
|
|
312
311
|
time: now
|
|
313
312
|
});
|
|
314
|
-
if (!selectionElement.isConnected) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
interactionSessionRef.current = {
|
|
318
|
-
type: "element",
|
|
319
|
-
element: selectionElement,
|
|
320
|
-
siblingElements,
|
|
321
|
-
clickPosition: {
|
|
322
|
-
x: clickPosition.x - targetRect.left,
|
|
323
|
-
y: clickPosition.y - targetRect.top
|
|
324
|
-
},
|
|
325
|
-
data
|
|
326
|
-
};
|
|
327
|
-
void maybeSymbolicate(data).then((symbolicated) => {
|
|
328
|
-
const session = interactionSessionRef.current;
|
|
329
|
-
const committedSelectionAtPointerDown = store().committedSelectionAtPointerDown;
|
|
330
|
-
const isPendingClickOnSelection = session.type === "drag" && !session.hasExceededThreshold && committedSelectionAtPointerDown.length === 1 && committedSelectionAtPointerDown[0] === selectionElement;
|
|
331
|
-
if (!isPendingClickOnSelection && !isSelectionStillCurrent({
|
|
332
|
-
session: interactionSessionRef.current,
|
|
333
|
-
selection: { type: "element", element: selectionElement }
|
|
334
|
-
})) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
|
|
338
|
-
});
|
|
339
313
|
};
|
|
340
314
|
const commitClickSelection = ({
|
|
341
315
|
targetElement,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
+
import { isSelectedSection } from "../../_vendor/schemas/selectMode.mjs";
|
|
2
3
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
3
4
|
import {
|
|
4
5
|
getDocumentScrollOffset,
|
|
@@ -37,7 +38,8 @@ function useSelectionScrollSync() {
|
|
|
37
38
|
interactionSession.element,
|
|
38
39
|
...interactionSession.siblingElements
|
|
39
40
|
],
|
|
40
|
-
scrollOffset
|
|
41
|
+
scrollOffset,
|
|
42
|
+
isSection: isSelectedSection(interactionSession.data)
|
|
41
43
|
});
|
|
42
44
|
getWebsiteBuilderRpc()?.selectedElementRectUpdate({
|
|
43
45
|
rect: boundingRect,
|
|
@@ -57,8 +59,10 @@ function useSelectionScrollSync() {
|
|
|
57
59
|
);
|
|
58
60
|
};
|
|
59
61
|
document.addEventListener("scroll", handleScroll, true);
|
|
62
|
+
window.addEventListener("resize", handleScroll);
|
|
60
63
|
return () => {
|
|
61
64
|
document.removeEventListener("scroll", handleScroll, true);
|
|
65
|
+
window.removeEventListener("resize", handleScroll);
|
|
62
66
|
const { scrollSyncAnimationFrameId, actions } = store.getState();
|
|
63
67
|
if (scrollSyncAnimationFrameId !== null) {
|
|
64
68
|
cancelAnimationFrame(scrollSyncAnimationFrameId);
|
|
@@ -13,9 +13,13 @@ export declare function getSelectModeIframeRpcChannel(): import("../_vendor/sche
|
|
|
13
13
|
element: import("zod").ZodObject<{
|
|
14
14
|
kind: import("zod").ZodLiteral<"react-element">;
|
|
15
15
|
selector: import("zod").ZodString;
|
|
16
|
+
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
17
|
+
selector: import("zod").ZodString;
|
|
18
|
+
}, import("zod/v4/core").$strip>>;
|
|
16
19
|
tagName: import("zod").ZodString;
|
|
17
20
|
elementType: import("zod").ZodString;
|
|
18
21
|
textContent: import("zod").ZodNullable<import("zod").ZodString>;
|
|
22
|
+
sectionLabel: import("zod").ZodOptional<import("zod").ZodString>;
|
|
19
23
|
rect: import("zod").ZodObject<{
|
|
20
24
|
x: import("zod").ZodNumber;
|
|
21
25
|
y: import("zod").ZodNumber;
|
|
@@ -104,9 +108,13 @@ export declare function getSelectModeIframeRpcChannel(): import("../_vendor/sche
|
|
|
104
108
|
data: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
105
109
|
kind: import("zod").ZodLiteral<"react-element">;
|
|
106
110
|
selector: import("zod").ZodString;
|
|
111
|
+
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
112
|
+
selector: import("zod").ZodString;
|
|
113
|
+
}, import("zod/v4/core").$strip>>;
|
|
107
114
|
tagName: import("zod").ZodString;
|
|
108
115
|
elementType: import("zod").ZodString;
|
|
109
116
|
textContent: import("zod").ZodNullable<import("zod").ZodString>;
|
|
117
|
+
sectionLabel: import("zod").ZodOptional<import("zod").ZodString>;
|
|
110
118
|
rect: import("zod").ZodObject<{
|
|
111
119
|
x: import("zod").ZodNumber;
|
|
112
120
|
y: import("zod").ZodNumber;
|
|
@@ -202,6 +210,7 @@ export declare function getSelectModeIframeRpcChannel(): import("../_vendor/sche
|
|
|
202
210
|
bottom: import("zod").ZodNumber;
|
|
203
211
|
width: import("zod").ZodNumber;
|
|
204
212
|
height: import("zod").ZodNumber;
|
|
213
|
+
isSection: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
205
214
|
}, import("zod/v4/core").$strip>>;
|
|
206
215
|
}, import("zod/v4/core").$strip>;
|
|
207
216
|
outputSchema: import("zod").ZodVoid;
|
|
@@ -283,9 +292,13 @@ export declare function getSelectModeIframeRpcChannel(): import("../_vendor/sche
|
|
|
283
292
|
items: import("zod").ZodArray<import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
284
293
|
kind: import("zod").ZodLiteral<"react-element">;
|
|
285
294
|
selector: import("zod").ZodString;
|
|
295
|
+
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
296
|
+
selector: import("zod").ZodString;
|
|
297
|
+
}, import("zod/v4/core").$strip>>;
|
|
286
298
|
tagName: import("zod").ZodString;
|
|
287
299
|
elementType: import("zod").ZodString;
|
|
288
300
|
textContent: import("zod").ZodNullable<import("zod").ZodString>;
|
|
301
|
+
sectionLabel: import("zod").ZodOptional<import("zod").ZodString>;
|
|
289
302
|
rect: import("zod").ZodObject<{
|
|
290
303
|
x: import("zod").ZodNumber;
|
|
291
304
|
y: import("zod").ZodNumber;
|
|
@@ -25,6 +25,8 @@ function createSelectModeIframeRpcChannel() {
|
|
|
25
25
|
outgoingMethods: previewIframeToWebsiteBuilderMethodConfig,
|
|
26
26
|
incomingMethods: websiteBuilderToPreviewIframeMethodConfig,
|
|
27
27
|
handlers: {
|
|
28
|
+
expandToSection: (input) => messagingHandlers?.expandToSection(input),
|
|
29
|
+
setSectionPreview: (input) => messagingHandlers?.setSectionPreview(input),
|
|
28
30
|
setDisplayMode: (input) => {
|
|
29
31
|
messagingHandlers?.setDisplayMode(input);
|
|
30
32
|
},
|