@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.
Files changed (41) hide show
  1. package/_vendor/replo-utils/react/zustand.d.ts +11 -0
  2. package/_vendor/replo-utils/react/zustand.mjs +13 -0
  3. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +11 -8
  4. package/_vendor/schemas/selectModeIframeRpc.d.ts +66 -130
  5. package/_vendor/schemas/selectModeIframeRpc.mjs +16 -11
  6. package/buildMetadata.js +3 -3
  7. package/package.json +6 -5
  8. package/select-mode/EditorSelection.d.ts +1 -22
  9. package/select-mode/EditorSelection.js +61 -100
  10. package/select-mode/SelectionFrames.d.ts +1 -0
  11. package/select-mode/SelectionFrames.js +58 -0
  12. package/select-mode/annotationFrames.d.ts +13 -0
  13. package/select-mode/annotationFrames.js +31 -1
  14. package/select-mode/displayModeStore.d.ts +0 -2
  15. package/select-mode/displayModeStore.js +11 -30
  16. package/select-mode/hooks/useParentMessaging.js +3 -0
  17. package/select-mode/hooks/useSelectionHover.d.ts +1 -19
  18. package/select-mode/hooks/useSelectionHover.js +37 -49
  19. package/select-mode/hooks/useSelectionInteractions.d.ts +1 -12
  20. package/select-mode/hooks/useSelectionInteractions.js +57 -39
  21. package/select-mode/hooks/useSelectionScrollSync.d.ts +1 -25
  22. package/select-mode/hooks/useSelectionScrollSync.js +48 -40
  23. package/select-mode/iframeRpcChannel.d.ts +24 -1
  24. package/select-mode/inlineEditFeedback.d.ts +6 -0
  25. package/select-mode/inlineEditFeedback.js +47 -0
  26. package/select-mode/isTextEditableElement.d.ts +1 -0
  27. package/select-mode/isTextEditableElement.js +15 -0
  28. package/select-mode/selectModeStore.d.ts +160 -0
  29. package/select-mode/selectModeStore.js +586 -0
  30. package/select-mode/selectionFrame.d.ts +10 -59
  31. package/select-mode/selectionFrame.js +26 -150
  32. package/select-mode/symbolicate.js +4 -0
  33. package/select-mode/tiptapInlineEditor.js +16 -1
  34. package/select-mode/useShallowStore.d.ts +2 -0
  35. package/select-mode/useShallowStore.js +9 -0
  36. package/select-mode/hooks/useCrosshairCursor.d.ts +0 -10
  37. package/select-mode/hooks/useCrosshairCursor.js +0 -23
  38. package/select-mode/hooks/useEditModeStateMachine.d.ts +0 -16
  39. package/select-mode/hooks/useEditModeStateMachine.js +0 -228
  40. package/select-mode/hooks/useSelectModeController.d.ts +0 -11
  41. package/select-mode/hooks/useSelectModeController.js +0 -72
@@ -1,39 +1,29 @@
1
1
  "use client";
2
- import { useCallback, useEffect } from "react";
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 { showElementSelectionFrames } from "./selectionFrame";
21
- import { getConnectedSelectedElement } from "./selectModeSession";
11
+ import { SelectionFrames } from "./SelectionFrames";
22
12
  import {
23
- isUserSourceLocation,
24
- symbolicateSelectedElement
25
- } from "./symbolicate";
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
- displayMode,
29
- interactionSessionRef,
21
+ formatCommand,
22
+ postSelectedElementSnapshot,
23
+ revertInlineEdit,
30
24
  setDisplayMode,
31
25
  setSelectionType
32
- } = useSelectModeController();
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 (editingElementRef.current) return;
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 = interactionSessionRef.current;
46
+ const missingSession = store.getState().interactionSession;
107
47
  missingTimeout = setTimeout(() => {
108
48
  missingTimeout = void 0;
109
- if (interactionSessionRef.current === missingSession) {
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 = interactionSessionRef.current;
118
- if (editingElementRef.current || session.type !== "element") return;
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 (editingElementRef.current || interactionSessionRef.current.type !== "element")
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: editingElementRef.current });
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
- editingElementRef,
151
- interactionSessionRef,
152
- postSelectedElementSnapshot,
153
- reapplyOverrides
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: revert,
121
+ onExitEditMode: revertInlineEdit,
159
122
  onFormatCommand: formatCommand,
160
123
  resolveSessionOverrides,
161
124
  revertOverride,
162
125
  setDisplayMode,
163
126
  setSelectionType
164
127
  });
165
- useCrosshairCursor({ displayMode });
166
- useSelectionInteractions({
167
- displayMode,
168
- editingElementRef,
169
- interactionSessionRef,
170
- onBeginEditing: beginEditing
171
- });
172
- useSelectionHover({ displayMode, interactionSessionRef });
173
- useSelectionScrollSync({
174
- displayMode,
175
- interactionSessionRef
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 { useSyncExternalStore } from "react";
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 SERVER_SNAPSHOT;
8
+ return "browse";
17
9
  },
18
10
  subscribe(listener) {
19
- listeners.add(listener);
20
- return () => {
21
- listeners.delete(listener);
22
- };
11
+ return selectModeStore.subscribe((state, previous) => {
12
+ if (state.displayMode !== previous.displayMode) {
13
+ listener();
14
+ }
15
+ });
23
16
  },
24
17
  setDisplayMode(nextDisplayMode) {
25
- if (nextDisplayMode === displayMode) {
26
- return;
27
- }
28
- displayMode = nextDisplayMode;
29
- notify();
18
+ selectModeStore.getState().actions.setDisplayMode(nextDisplayMode);
30
19
  },
31
20
  reset() {
32
- displayMode = "browse";
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
  };
@@ -41,6 +41,9 @@ function useParentMessaging({
41
41
  setDisplayMode(displayMode);
42
42
  },
43
43
  setSelectionType: (input) => {
44
+ if (input.selectionType === "none") {
45
+ onExitEditMode?.();
46
+ }
44
47
  setSelectionType(input);
45
48
  },
46
49
  applyOptimisticUpdate: (message) => {
@@ -1,19 +1 @@
1
- import type { MutableRefObject } from "react";
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
- let activeHoverDedupReset = null;
6
- function resetHoverRectDedup() {
7
- activeHoverDedupReset?.();
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 = lastPostedRect !== void 0 && (rect === lastPostedRect || rect !== null && lastPostedRect !== null && rect.x === lastPostedRect.x && rect.y === lastPostedRect.y && rect.width === lastPostedRect.width && rect.height === lastPostedRect.height);
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
- lastPostedRect = rect;
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
- if (interactionSessionRef.current.type === "drag" || hoverSyncAnimationFrameId !== null) {
43
+ const { interactionSession, hoverSyncAnimationFrameId, actions } = store.getState();
44
+ if (interactionSession.type === "drag" || hoverSyncAnimationFrameId !== null) {
53
45
  return;
54
46
  }
55
- hoverSyncAnimationFrameId = requestAnimationFrame(() => {
56
- hoverSyncAnimationFrameId = null;
57
- syncCurrentHover();
58
- });
47
+ actions.setHoverSyncAnimationFrameId(
48
+ requestAnimationFrame(() => {
49
+ store.getState().actions.setHoverSyncAnimationFrameId(null);
50
+ syncCurrentHover();
51
+ })
52
+ );
59
53
  };
60
54
  const handlePointerMove = (event) => {
61
- lastPointerClientPosition = {
62
- x: event.clientX,
63
- y: event.clientY
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(lastPointerClientPosition);
60
+ syncCurrentHover(position);
69
61
  };
70
62
  const handlePointerOver = (event) => {
71
- lastPointerClientPosition = {
72
- x: event.clientX,
73
- y: event.clientY
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(lastPointerClientPosition);
68
+ syncCurrentHover(position);
79
69
  };
80
70
  const handlePointerOut = (event) => {
81
- if (interactionSessionRef.current.type === "drag") {
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
- lastPointerClientPosition = null;
78
+ const { hoverSyncAnimationFrameId, actions } = store.getState();
79
+ actions.setLastPointerClientPosition(null);
89
80
  if (hoverSyncAnimationFrameId !== null) {
90
81
  cancelAnimationFrame(hoverSyncAnimationFrameId);
91
- hoverSyncAnimationFrameId = null;
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", handleViewportChange, true);
103
- document.addEventListener("wheel", handleViewportChange, {
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", handleViewportChange);
95
+ window.addEventListener("resize", scheduleHoverSync);
108
96
  return () => {
109
- activeHoverDedupReset = null;
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", handleViewportChange, true);
117
- document.removeEventListener("wheel", handleViewportChange, {
105
+ document.removeEventListener("scroll", scheduleHoverSync, true);
106
+ document.removeEventListener("wheel", scheduleHoverSync, {
118
107
  capture: true
119
108
  });
120
- window.removeEventListener("resize", handleViewportChange);
109
+ window.removeEventListener("resize", scheduleHoverSync);
121
110
  };
122
- }, [displayMode, interactionSessionRef]);
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({ displayMode, editingElementRef, interactionSessionRef, onBeginEditing, }: {
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;