@replohq/sandbox-runtime 1.7.10 → 1.7.12
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/react/zustand.d.ts +11 -0
- package/_vendor/replo-utils/react/zustand.mjs +13 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +11 -8
- package/_vendor/schemas/selectModeIframeRpc.d.ts +66 -130
- package/_vendor/schemas/selectModeIframeRpc.mjs +16 -11
- package/buildMetadata.js +3 -3
- package/package.json +6 -5
- package/select-mode/EditorSelection.d.ts +1 -22
- package/select-mode/EditorSelection.js +61 -100
- package/select-mode/SelectionFrames.d.ts +1 -0
- package/select-mode/SelectionFrames.js +58 -0
- package/select-mode/annotationFrames.d.ts +13 -0
- package/select-mode/annotationFrames.js +31 -1
- package/select-mode/displayModeStore.d.ts +0 -2
- package/select-mode/displayModeStore.js +11 -30
- package/select-mode/hooks/useParentMessaging.js +3 -0
- package/select-mode/hooks/useSelectionHover.d.ts +1 -19
- package/select-mode/hooks/useSelectionHover.js +37 -49
- package/select-mode/hooks/useSelectionInteractions.d.ts +1 -12
- package/select-mode/hooks/useSelectionInteractions.js +57 -39
- package/select-mode/hooks/useSelectionScrollSync.d.ts +1 -25
- package/select-mode/hooks/useSelectionScrollSync.js +48 -40
- package/select-mode/iframeRpcChannel.d.ts +24 -1
- package/select-mode/inlineEditFeedback.d.ts +6 -0
- package/select-mode/inlineEditFeedback.js +47 -0
- package/select-mode/isTextEditableElement.d.ts +1 -0
- package/select-mode/isTextEditableElement.js +15 -0
- package/select-mode/selectModeStore.d.ts +160 -0
- package/select-mode/selectModeStore.js +586 -0
- package/select-mode/selectionFrame.d.ts +10 -59
- package/select-mode/selectionFrame.js +26 -150
- package/select-mode/symbolicate.js +4 -0
- package/select-mode/tiptapInlineEditor.js +16 -1
- package/select-mode/useShallowStore.d.ts +2 -0
- package/select-mode/useShallowStore.js +9 -0
- package/select-mode/hooks/useCrosshairCursor.d.ts +0 -10
- package/select-mode/hooks/useCrosshairCursor.js +0 -23
- package/select-mode/hooks/useEditModeStateMachine.d.ts +0 -16
- package/select-mode/hooks/useEditModeStateMachine.js +0 -228
- package/select-mode/hooks/useSelectModeController.d.ts +0 -11
- package/select-mode/hooks/useSelectModeController.js +0 -72
|
@@ -1,39 +1,29 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
buildElementDescriptor,
|
|
5
|
-
buildSelector
|
|
6
|
-
} from "./descriptor/elementDescriptor";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
7
4
|
import { installFastRefreshRelay } from "./fast-refresh-relay";
|
|
8
|
-
import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
|
|
9
|
-
import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
|
|
10
5
|
import { useOptimisticOverrides } from "./hooks/useOptimisticOverrides";
|
|
11
6
|
import { useParentMessaging } from "./hooks/useParentMessaging";
|
|
12
7
|
import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
13
8
|
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
14
9
|
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
15
|
-
import { useSelectModeController } from "./hooks/useSelectModeController";
|
|
16
|
-
import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
|
|
17
|
-
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
18
|
-
import { getDocumentScrollOffset } from "./interactions/selectionGeometry";
|
|
19
10
|
import { PREVIEW_RENDERED_EVENT } from "./previewRendered";
|
|
20
|
-
import {
|
|
21
|
-
import { getConnectedSelectedElement } from "./selectModeSession";
|
|
11
|
+
import { SelectionFrames } from "./SelectionFrames";
|
|
22
12
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
useDisplayMode,
|
|
14
|
+
useSelectModeStore,
|
|
15
|
+
useSelectModeStoreActions
|
|
16
|
+
} from "./selectModeStore";
|
|
26
17
|
function EditorSelection() {
|
|
18
|
+
const store = useSelectModeStore();
|
|
19
|
+
const displayMode = useDisplayMode();
|
|
27
20
|
const {
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
formatCommand,
|
|
22
|
+
postSelectedElementSnapshot,
|
|
23
|
+
revertInlineEdit,
|
|
30
24
|
setDisplayMode,
|
|
31
25
|
setSelectionType
|
|
32
|
-
} =
|
|
33
|
-
const { beginEditing, editingElementRef, formatCommand, revert } = useEditModeStateMachine({
|
|
34
|
-
displayMode,
|
|
35
|
-
interactionSessionRef
|
|
36
|
-
});
|
|
26
|
+
} = useSelectModeStoreActions();
|
|
37
27
|
const {
|
|
38
28
|
applyOptimisticUpdate,
|
|
39
29
|
markCommitted,
|
|
@@ -41,56 +31,6 @@ function EditorSelection() {
|
|
|
41
31
|
resolveSessionOverrides,
|
|
42
32
|
revertOverride
|
|
43
33
|
} = useOptimisticOverrides();
|
|
44
|
-
const postSelectedElementSnapshot = useCallback(
|
|
45
|
-
({ clearMissing = false } = {}) => {
|
|
46
|
-
const session = interactionSessionRef.current;
|
|
47
|
-
if (session.type !== "element") {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
const element = getConnectedSelectedElement(session);
|
|
51
|
-
if (!element) {
|
|
52
|
-
if (!clearMissing) return false;
|
|
53
|
-
setSelectionType({ selectionType: "none" });
|
|
54
|
-
getWebsiteBuilderRpc()?.areaSelected({
|
|
55
|
-
rect: session.data.rect,
|
|
56
|
-
items: []
|
|
57
|
-
});
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const siblingElements = getRepetitionSiblingElements({ element });
|
|
61
|
-
const descriptor = buildElementDescriptor({
|
|
62
|
-
element,
|
|
63
|
-
clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
|
|
64
|
-
siblingSelectors: siblingElements.map(buildSelector)
|
|
65
|
-
});
|
|
66
|
-
if (descriptor.kind !== "react-element") {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
const refreshedSession = {
|
|
70
|
-
...session,
|
|
71
|
-
element,
|
|
72
|
-
siblingElements,
|
|
73
|
-
data: session.data
|
|
74
|
-
};
|
|
75
|
-
interactionSessionRef.current = refreshedSession;
|
|
76
|
-
showElementSelectionFrames({
|
|
77
|
-
elements: [element, ...siblingElements],
|
|
78
|
-
scrollOffset: getDocumentScrollOffset()
|
|
79
|
-
});
|
|
80
|
-
void symbolicateSelectedElement(descriptor).then((symbolicated) => {
|
|
81
|
-
if (interactionSessionRef.current !== refreshedSession) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (symbolicated.sourceLocation && !isUserSourceLocation(symbolicated.sourceLocation)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
refreshedSession.data = symbolicated;
|
|
88
|
-
getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
|
|
89
|
-
});
|
|
90
|
-
return true;
|
|
91
|
-
},
|
|
92
|
-
[interactionSessionRef, setSelectionType]
|
|
93
|
-
);
|
|
94
34
|
useEffect(() => {
|
|
95
35
|
let refreshTimeout;
|
|
96
36
|
let missingTimeout;
|
|
@@ -98,15 +38,15 @@ function EditorSelection() {
|
|
|
98
38
|
const scheduleSnapshot = () => {
|
|
99
39
|
clearTimeout(refreshTimeout);
|
|
100
40
|
refreshTimeout = setTimeout(() => {
|
|
101
|
-
if (
|
|
41
|
+
if (store.getState().editingElement) return;
|
|
102
42
|
if (postSelectedElementSnapshot()) {
|
|
103
43
|
clearTimeout(missingTimeout);
|
|
104
44
|
missingTimeout = void 0;
|
|
105
45
|
} else if (!missingTimeout) {
|
|
106
|
-
const missingSession =
|
|
46
|
+
const missingSession = store.getState().interactionSession;
|
|
107
47
|
missingTimeout = setTimeout(() => {
|
|
108
48
|
missingTimeout = void 0;
|
|
109
|
-
if (
|
|
49
|
+
if (store.getState().interactionSession === missingSession) {
|
|
110
50
|
postSelectedElementSnapshot({ clearMissing: true });
|
|
111
51
|
}
|
|
112
52
|
}, 5e3);
|
|
@@ -114,14 +54,14 @@ function EditorSelection() {
|
|
|
114
54
|
}, 250);
|
|
115
55
|
};
|
|
116
56
|
const observer = new MutationObserver((records) => {
|
|
117
|
-
const session =
|
|
118
|
-
if (
|
|
57
|
+
const session = store.getState().interactionSession;
|
|
58
|
+
if (store.getState().editingElement || session.type !== "element") return;
|
|
119
59
|
if (!session.element.isConnected || records.some((record) => session.element.contains(record.target))) {
|
|
120
60
|
scheduleSnapshot();
|
|
121
61
|
}
|
|
122
62
|
});
|
|
123
63
|
const watchRender = () => {
|
|
124
|
-
if (
|
|
64
|
+
if (store.getState().editingElement || store.getState().interactionSession.type !== "element")
|
|
125
65
|
return;
|
|
126
66
|
clearTimeout(observationTimeout);
|
|
127
67
|
observer.observe(document.body, {
|
|
@@ -133,7 +73,7 @@ function EditorSelection() {
|
|
|
133
73
|
};
|
|
134
74
|
installFastRefreshRelay({
|
|
135
75
|
onComplete: () => {
|
|
136
|
-
reapplyOverrides({ skipElement:
|
|
76
|
+
reapplyOverrides({ skipElement: store.getState().editingElement });
|
|
137
77
|
watchRender();
|
|
138
78
|
}
|
|
139
79
|
});
|
|
@@ -146,35 +86,56 @@ function EditorSelection() {
|
|
|
146
86
|
window.removeEventListener(PREVIEW_RENDERED_EVENT, watchRender);
|
|
147
87
|
installFastRefreshRelay();
|
|
148
88
|
};
|
|
149
|
-
}, [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
89
|
+
}, [store, postSelectedElementSnapshot, reapplyOverrides]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const cancelOnPointerDown = (event) => {
|
|
92
|
+
const { editingElement, inlineEditRequest, actions } = store.getState();
|
|
93
|
+
if (event.button === 0 && editingElement && event.target instanceof Node && !editingElement.contains(event.target)) {
|
|
94
|
+
actions.commitInlineEdit();
|
|
95
|
+
}
|
|
96
|
+
if (inlineEditRequest && event.target instanceof Node && !inlineEditRequest.element.contains(event.target)) {
|
|
97
|
+
actions.dismissInlineEditRequest();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const cancelOnEscape = (event) => {
|
|
101
|
+
const { inlineEditRequest, actions } = store.getState();
|
|
102
|
+
if (!inlineEditRequest || event.key !== "Escape") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (inlineEditRequest.status === "pending") {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
}
|
|
108
|
+
actions.dismissInlineEditRequest();
|
|
109
|
+
};
|
|
110
|
+
document.addEventListener("pointerdown", cancelOnPointerDown, true);
|
|
111
|
+
document.addEventListener("keydown", cancelOnEscape, true);
|
|
112
|
+
return () => {
|
|
113
|
+
document.removeEventListener("pointerdown", cancelOnPointerDown, true);
|
|
114
|
+
document.removeEventListener("keydown", cancelOnEscape, true);
|
|
115
|
+
store.getState().actions.revertInlineEdit();
|
|
116
|
+
};
|
|
117
|
+
}, [store]);
|
|
155
118
|
useParentMessaging({
|
|
156
119
|
applyOptimisticUpdate,
|
|
157
120
|
markCommitted,
|
|
158
|
-
onExitEditMode:
|
|
121
|
+
onExitEditMode: revertInlineEdit,
|
|
159
122
|
onFormatCommand: formatCommand,
|
|
160
123
|
resolveSessionOverrides,
|
|
161
124
|
revertOverride,
|
|
162
125
|
setDisplayMode,
|
|
163
126
|
setSelectionType
|
|
164
127
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
177
|
-
return null;
|
|
128
|
+
useSelectionInteractions();
|
|
129
|
+
useSelectionHover();
|
|
130
|
+
useSelectionScrollSync();
|
|
131
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
132
|
+
displayMode === "selection" && /* @__PURE__ */ jsx("style", { children: `
|
|
133
|
+
* { cursor: crosshair !important; user-select: none !important; -webkit-user-drag: none !important; }
|
|
134
|
+
[data-select-mode-inline-editor][contenteditable="true"],
|
|
135
|
+
[data-select-mode-inline-editor][contenteditable="true"] * { cursor: text !important; }
|
|
136
|
+
` }),
|
|
137
|
+
/* @__PURE__ */ jsx(SelectionFrames, {})
|
|
138
|
+
] });
|
|
178
139
|
}
|
|
179
140
|
export {
|
|
180
141
|
EditorSelection
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SelectionFrames(): import("react").ReactPortal | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import {
|
|
5
|
+
SELECTION_FRAME_BORDER_HEX,
|
|
6
|
+
SELECTION_FRAME_EDITING_BORDER_HEX,
|
|
7
|
+
SELECTION_FRAMES_CONTAINER_ID
|
|
8
|
+
} from "./selectionFrame";
|
|
9
|
+
import {
|
|
10
|
+
useIsSelectionFrameEditing,
|
|
11
|
+
useSelectionFrames
|
|
12
|
+
} from "./selectModeStore";
|
|
13
|
+
const CONTAINER_STYLE = {
|
|
14
|
+
position: "absolute",
|
|
15
|
+
top: 0,
|
|
16
|
+
left: 0,
|
|
17
|
+
width: 0,
|
|
18
|
+
height: 0,
|
|
19
|
+
margin: 0,
|
|
20
|
+
padding: 0,
|
|
21
|
+
pointerEvents: "none",
|
|
22
|
+
zIndex: 2147483646
|
|
23
|
+
};
|
|
24
|
+
function SelectionFrames() {
|
|
25
|
+
const frames = useSelectionFrames();
|
|
26
|
+
const isEditing = useIsSelectionFrameEditing();
|
|
27
|
+
if (typeof document === "undefined" || !document.body || frames.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const borderColor = isEditing ? SELECTION_FRAME_EDITING_BORDER_HEX : SELECTION_FRAME_BORDER_HEX;
|
|
31
|
+
return createPortal(
|
|
32
|
+
/* @__PURE__ */ jsx("div", { id: SELECTION_FRAMES_CONTAINER_ID, "aria-hidden": true, style: CONTAINER_STYLE, children: frames.map((frame, index) => /* @__PURE__ */ jsx(
|
|
33
|
+
"div",
|
|
34
|
+
{
|
|
35
|
+
"aria-hidden": true,
|
|
36
|
+
style: {
|
|
37
|
+
position: "absolute",
|
|
38
|
+
boxSizing: "border-box",
|
|
39
|
+
borderRadius: 2,
|
|
40
|
+
pointerEvents: "none",
|
|
41
|
+
margin: 0,
|
|
42
|
+
padding: 0,
|
|
43
|
+
left: frame.pageRect.x,
|
|
44
|
+
top: frame.pageRect.y,
|
|
45
|
+
width: frame.pageRect.width,
|
|
46
|
+
height: frame.pageRect.height,
|
|
47
|
+
border: `2px solid ${borderColor}`,
|
|
48
|
+
background: frame.variant === "area" ? "rgba(192, 38, 211, 0.08)" : "transparent"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
`${frame.variant}-${index}`
|
|
52
|
+
)) }),
|
|
53
|
+
document.body
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
SelectionFrames
|
|
58
|
+
};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-iframe annotation frame renderer — `selectionFrame`'s sibling surface.
|
|
3
|
+
*
|
|
4
|
+
* Same architecture and reasons as selection frames: page-coordinate
|
|
5
|
+
* `position: absolute` nodes in a body-level container, so document scroll
|
|
6
|
+
* translates them with zero JavaScript per scroll frame, and styling is
|
|
7
|
+
* vanilla inline CSS because customer pages have no tailwind.
|
|
8
|
+
*
|
|
9
|
+
* An annotation frame is an invisible positioning box over the annotated
|
|
10
|
+
* element carrying a numbered badge dot — the dot is the entire visual.
|
|
11
|
+
* Unlike selection frames these rebuild wholesale on each call: re-renders
|
|
12
|
+
* happen on set/re-measure (rare), never per scroll, so pooling buys nothing.
|
|
13
|
+
*/
|
|
1
14
|
export declare const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
2
15
|
interface AnnotationRect {
|
|
3
16
|
x: number;
|
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
import { getOrCreateFramesLayerContainer } from "./selectionFrame";
|
|
2
1
|
const ANNOTATION_CONTAINER_ID = "replo-annotation-frames";
|
|
2
|
+
function getOrCreateFramesLayerContainer({
|
|
3
|
+
id,
|
|
4
|
+
zIndex
|
|
5
|
+
}) {
|
|
6
|
+
if (typeof document === "undefined") {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const existing = document.getElementById(id);
|
|
10
|
+
if (existing instanceof HTMLDivElement) {
|
|
11
|
+
return existing;
|
|
12
|
+
}
|
|
13
|
+
if (!document.body) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const container = document.createElement("div");
|
|
17
|
+
container.id = id;
|
|
18
|
+
container.setAttribute("aria-hidden", "true");
|
|
19
|
+
container.style.cssText = [
|
|
20
|
+
"position: absolute",
|
|
21
|
+
"top: 0",
|
|
22
|
+
"left: 0",
|
|
23
|
+
"width: 0",
|
|
24
|
+
"height: 0",
|
|
25
|
+
"margin: 0",
|
|
26
|
+
"padding: 0",
|
|
27
|
+
"pointer-events: none",
|
|
28
|
+
`z-index: ${zIndex}`
|
|
29
|
+
].join(";");
|
|
30
|
+
document.body.append(container);
|
|
31
|
+
return container;
|
|
32
|
+
}
|
|
3
33
|
const ANNOTATION_FONT_STYLE_ID = "replo-annotation-font";
|
|
4
34
|
const BADGE_SIZE_PX = 30;
|
|
5
35
|
const EDITORIAL_ITALIC_FONT_URL = "https://assets.replocdn.com/PPEditorialNew-Italic.woff2";
|
|
@@ -4,7 +4,5 @@ export declare const displayModeStore: {
|
|
|
4
4
|
getServerSnapshot(): DisplayMode;
|
|
5
5
|
subscribe(listener: () => void): () => void;
|
|
6
6
|
setDisplayMode(nextDisplayMode: DisplayMode): void;
|
|
7
|
-
/** Test-only: reset back to the default so specs don't leak state. */
|
|
8
7
|
reset(): void;
|
|
9
8
|
};
|
|
10
|
-
export declare function useDisplayMode(): DisplayMode;
|
|
@@ -1,45 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
4
|
-
let displayMode = "browse";
|
|
5
|
-
const SERVER_SNAPSHOT = "browse";
|
|
6
|
-
function notify() {
|
|
7
|
-
for (const listener of listeners) {
|
|
8
|
-
listener();
|
|
9
|
-
}
|
|
10
|
-
}
|
|
2
|
+
import { selectModeStore } from "./selectModeStore";
|
|
11
3
|
const displayModeStore = {
|
|
12
4
|
getSnapshot() {
|
|
13
|
-
return displayMode;
|
|
5
|
+
return selectModeStore.getState().displayMode;
|
|
14
6
|
},
|
|
15
7
|
getServerSnapshot() {
|
|
16
|
-
return
|
|
8
|
+
return "browse";
|
|
17
9
|
},
|
|
18
10
|
subscribe(listener) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
return selectModeStore.subscribe((state, previous) => {
|
|
12
|
+
if (state.displayMode !== previous.displayMode) {
|
|
13
|
+
listener();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
23
16
|
},
|
|
24
17
|
setDisplayMode(nextDisplayMode) {
|
|
25
|
-
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
displayMode = nextDisplayMode;
|
|
29
|
-
notify();
|
|
18
|
+
selectModeStore.getState().actions.setDisplayMode(nextDisplayMode);
|
|
30
19
|
},
|
|
31
20
|
reset() {
|
|
32
|
-
|
|
21
|
+
selectModeStore.getState().actions.reset();
|
|
33
22
|
}
|
|
34
23
|
};
|
|
35
|
-
function useDisplayMode() {
|
|
36
|
-
return useSyncExternalStore(
|
|
37
|
-
displayModeStore.subscribe,
|
|
38
|
-
displayModeStore.getSnapshot,
|
|
39
|
-
displayModeStore.getServerSnapshot
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
24
|
export {
|
|
43
|
-
displayModeStore
|
|
44
|
-
useDisplayMode
|
|
25
|
+
displayModeStore
|
|
45
26
|
};
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { DisplayMode } from "../../_vendor/schemas/generated/selectMode";
|
|
3
|
-
import type { SelectModeInteractionSession } from "../selectModeSession";
|
|
4
|
-
export declare function resetHoverRectDedup(): void;
|
|
5
|
-
/**
|
|
6
|
-
* Tracks whichever element is under the user's pointer while in `selection`
|
|
7
|
-
* mode and reports its bounding rect to the parent so the parent can render
|
|
8
|
-
* the hover overlay.
|
|
9
|
-
*
|
|
10
|
-
* Two behaviors worth knowing about:
|
|
11
|
-
* - During an active drag we suppress hover updates; the drag overlay is
|
|
12
|
-
* the only highlight we want visible.
|
|
13
|
-
* - Unchanged rectangles do not cross the iframe bridge. Viewport updates
|
|
14
|
-
* are coalesced because scroll and wheel can fire in the same frame.
|
|
15
|
-
*/
|
|
16
|
-
export declare function useSelectionHover({ displayMode, interactionSessionRef, }: {
|
|
17
|
-
displayMode: DisplayMode;
|
|
18
|
-
interactionSessionRef: MutableRefObject<SelectModeInteractionSession>;
|
|
19
|
-
}): void;
|
|
1
|
+
export declare function useSelectionHover(): void;
|
|
@@ -2,34 +2,25 @@ import { useEffect } from "react";
|
|
|
2
2
|
import { getNearestSelectableElement } from "../descriptor/elementDescriptor";
|
|
3
3
|
import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
|
|
4
4
|
import { getElementBoundingRect } from "../interactions/selectionGeometry";
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function useSelectionHover({
|
|
10
|
-
displayMode,
|
|
11
|
-
interactionSessionRef
|
|
12
|
-
}) {
|
|
5
|
+
import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
|
|
6
|
+
function useSelectionHover() {
|
|
7
|
+
const store = useSelectModeStore();
|
|
8
|
+
const displayMode = useDisplayMode();
|
|
13
9
|
useEffect(() => {
|
|
14
10
|
if (displayMode !== "selection") {
|
|
15
11
|
return;
|
|
16
12
|
}
|
|
17
|
-
let hoverSyncAnimationFrameId = null;
|
|
18
|
-
let lastPointerClientPosition = null;
|
|
19
|
-
let lastPostedRect;
|
|
20
|
-
activeHoverDedupReset = () => {
|
|
21
|
-
lastPostedRect = void 0;
|
|
22
|
-
};
|
|
23
13
|
const postHoveredElement = (element) => {
|
|
14
|
+
const { lastPostedHoverRect, actions } = store.getState();
|
|
24
15
|
const rect = element ? getElementBoundingRect(element) : null;
|
|
25
|
-
const isUnchanged =
|
|
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);
|
|
26
17
|
if (isUnchanged) {
|
|
27
18
|
return;
|
|
28
19
|
}
|
|
29
|
-
|
|
20
|
+
actions.setLastPostedHoverRect(rect);
|
|
30
21
|
getWebsiteBuilderRpc()?.hoverElement({ rect });
|
|
31
22
|
};
|
|
32
|
-
const syncCurrentHover = (pointerClientPosition = lastPointerClientPosition) => {
|
|
23
|
+
const syncCurrentHover = (pointerClientPosition = store.getState().lastPointerClientPosition) => {
|
|
33
24
|
let hoveredElement = null;
|
|
34
25
|
if (pointerClientPosition) {
|
|
35
26
|
const isPointerWithinViewport = pointerClientPosition.x >= 0 && pointerClientPosition.y >= 0 && pointerClientPosition.x <= window.innerWidth && pointerClientPosition.y <= window.innerHeight;
|
|
@@ -49,79 +40,76 @@ function useSelectionHover({
|
|
|
49
40
|
);
|
|
50
41
|
};
|
|
51
42
|
const scheduleHoverSync = () => {
|
|
52
|
-
|
|
43
|
+
const { interactionSession, hoverSyncAnimationFrameId, actions } = store.getState();
|
|
44
|
+
if (interactionSession.type === "drag" || hoverSyncAnimationFrameId !== null) {
|
|
53
45
|
return;
|
|
54
46
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
actions.setHoverSyncAnimationFrameId(
|
|
48
|
+
requestAnimationFrame(() => {
|
|
49
|
+
store.getState().actions.setHoverSyncAnimationFrameId(null);
|
|
50
|
+
syncCurrentHover();
|
|
51
|
+
})
|
|
52
|
+
);
|
|
59
53
|
};
|
|
60
54
|
const handlePointerMove = (event) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
65
|
-
if (interactionSessionRef.current.type === "drag") {
|
|
55
|
+
const position = { x: event.clientX, y: event.clientY };
|
|
56
|
+
store.getState().actions.setLastPointerClientPosition(position);
|
|
57
|
+
if (store.getState().interactionSession.type === "drag") {
|
|
66
58
|
return;
|
|
67
59
|
}
|
|
68
|
-
syncCurrentHover(
|
|
60
|
+
syncCurrentHover(position);
|
|
69
61
|
};
|
|
70
62
|
const handlePointerOver = (event) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
if (interactionSessionRef.current.type === "drag") {
|
|
63
|
+
const position = { x: event.clientX, y: event.clientY };
|
|
64
|
+
store.getState().actions.setLastPointerClientPosition(position);
|
|
65
|
+
if (store.getState().interactionSession.type === "drag") {
|
|
76
66
|
return;
|
|
77
67
|
}
|
|
78
|
-
syncCurrentHover(
|
|
68
|
+
syncCurrentHover(position);
|
|
79
69
|
};
|
|
80
70
|
const handlePointerOut = (event) => {
|
|
81
|
-
if (
|
|
71
|
+
if (store.getState().interactionSession.type === "drag") {
|
|
82
72
|
return;
|
|
83
73
|
}
|
|
84
74
|
const nextRelatedTarget = event.relatedTarget;
|
|
85
75
|
if (nextRelatedTarget instanceof Node && document.contains(nextRelatedTarget)) {
|
|
86
76
|
return;
|
|
87
77
|
}
|
|
88
|
-
|
|
78
|
+
const { hoverSyncAnimationFrameId, actions } = store.getState();
|
|
79
|
+
actions.setLastPointerClientPosition(null);
|
|
89
80
|
if (hoverSyncAnimationFrameId !== null) {
|
|
90
81
|
cancelAnimationFrame(hoverSyncAnimationFrameId);
|
|
91
|
-
|
|
82
|
+
actions.setHoverSyncAnimationFrameId(null);
|
|
92
83
|
}
|
|
93
84
|
postHoveredElement(null);
|
|
94
85
|
};
|
|
95
|
-
const handleViewportChange = () => {
|
|
96
|
-
scheduleHoverSync();
|
|
97
|
-
};
|
|
98
86
|
syncCurrentHover();
|
|
99
87
|
document.addEventListener("pointermove", handlePointerMove, true);
|
|
100
88
|
document.addEventListener("pointerover", handlePointerOver, true);
|
|
101
89
|
document.addEventListener("pointerout", handlePointerOut, true);
|
|
102
|
-
document.addEventListener("scroll",
|
|
103
|
-
document.addEventListener("wheel",
|
|
90
|
+
document.addEventListener("scroll", scheduleHoverSync, true);
|
|
91
|
+
document.addEventListener("wheel", scheduleHoverSync, {
|
|
104
92
|
capture: true,
|
|
105
93
|
passive: true
|
|
106
94
|
});
|
|
107
|
-
window.addEventListener("resize",
|
|
95
|
+
window.addEventListener("resize", scheduleHoverSync);
|
|
108
96
|
return () => {
|
|
109
|
-
|
|
97
|
+
const { hoverSyncAnimationFrameId } = store.getState();
|
|
110
98
|
if (hoverSyncAnimationFrameId !== null) {
|
|
111
99
|
cancelAnimationFrame(hoverSyncAnimationFrameId);
|
|
100
|
+
store.getState().actions.setHoverSyncAnimationFrameId(null);
|
|
112
101
|
}
|
|
113
102
|
document.removeEventListener("pointermove", handlePointerMove, true);
|
|
114
103
|
document.removeEventListener("pointerover", handlePointerOver, true);
|
|
115
104
|
document.removeEventListener("pointerout", handlePointerOut, true);
|
|
116
|
-
document.removeEventListener("scroll",
|
|
117
|
-
document.removeEventListener("wheel",
|
|
105
|
+
document.removeEventListener("scroll", scheduleHoverSync, true);
|
|
106
|
+
document.removeEventListener("wheel", scheduleHoverSync, {
|
|
118
107
|
capture: true
|
|
119
108
|
});
|
|
120
|
-
window.removeEventListener("resize",
|
|
109
|
+
window.removeEventListener("resize", scheduleHoverSync);
|
|
121
110
|
};
|
|
122
|
-
}, [displayMode,
|
|
111
|
+
}, [displayMode, store]);
|
|
123
112
|
}
|
|
124
113
|
export {
|
|
125
|
-
resetHoverRectDedup,
|
|
126
114
|
useSelectionHover
|
|
127
115
|
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { MutableRefObject } from "react";
|
|
2
|
-
import type { DisplayMode, SelectableData } from "../../_vendor/schemas/generated/selectMode";
|
|
3
|
-
import type { SelectModeInteractionSession } from "../selectModeSession";
|
|
4
1
|
/**
|
|
5
2
|
* Owns the iframe-side pointer interactions that drive selection:
|
|
6
3
|
*
|
|
@@ -24,12 +21,4 @@ import type { SelectModeInteractionSession } from "../selectModeSession";
|
|
|
24
21
|
* underlying page can't navigate or trigger handlers while the user is
|
|
25
22
|
* working in selection mode.
|
|
26
23
|
*/
|
|
27
|
-
export declare function useSelectionInteractions(
|
|
28
|
-
displayMode: DisplayMode;
|
|
29
|
-
editingElementRef?: MutableRefObject<HTMLElement | null>;
|
|
30
|
-
interactionSessionRef: MutableRefObject<SelectModeInteractionSession>;
|
|
31
|
-
onBeginEditing: (args: {
|
|
32
|
-
element: Element;
|
|
33
|
-
descriptorPromise: Promise<SelectableData>;
|
|
34
|
-
}) => void;
|
|
35
|
-
}): void;
|
|
24
|
+
export declare function useSelectionInteractions(): void;
|