@replohq/sandbox-runtime 1.7.10 → 1.7.11

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 (39) hide show
  1. package/_vendor/replo-utils/react/zustand.d.ts +13 -0
  2. package/_vendor/replo-utils/react/zustand.mjs +24 -0
  3. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +12 -2
  4. package/_vendor/schemas/selectModeIframeRpc.d.ts +66 -4
  5. package/_vendor/schemas/selectModeIframeRpc.mjs +16 -3
  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 +585 -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/hooks/useCrosshairCursor.d.ts +0 -10
  35. package/select-mode/hooks/useCrosshairCursor.js +0 -23
  36. package/select-mode/hooks/useEditModeStateMachine.d.ts +0 -16
  37. package/select-mode/hooks/useEditModeStateMachine.js +0 -228
  38. package/select-mode/hooks/useSelectModeController.d.ts +0 -11
  39. package/select-mode/hooks/useSelectModeController.js +0 -72
@@ -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;
@@ -19,18 +19,14 @@ import {
19
19
  isAdditiveSelectionClick,
20
20
  unionElementPageRects
21
21
  } from "../interactions/selectionGeometry";
22
- import {
23
- hideSelectionFrame,
24
- showAreaSelectionFrames,
25
- showElementSelectionFrames
26
- } from "../selectionFrame";
22
+ import { isTextEditableElement } from "../isTextEditableElement";
27
23
  import { isSelectionStillCurrent } from "../selectionPublish";
28
24
  import {
29
25
  getConnectedSelectedElement,
30
26
  IDLE_SESSION
31
27
  } from "../selectModeSession";
28
+ import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
32
29
  import { symbolicateSelectedElement } from "../symbolicate";
33
- import { isTextEditableElement } from "./useEditModeStateMachine";
34
30
  import { getRepetitionSiblingElements } from "./useSiblingResolution";
35
31
  const DOUBLE_CLICK_THRESHOLD_MS = 500;
36
32
  function getSelectedElementsFromSession(session) {
@@ -49,21 +45,35 @@ function getSelectedElementsFromSession(session) {
49
45
  async function maybeSymbolicate(data) {
50
46
  return data.kind === "react-element" ? symbolicateSelectedElement(data) : data;
51
47
  }
52
- function useSelectionInteractions({
53
- displayMode,
54
- editingElementRef,
55
- interactionSessionRef,
56
- onBeginEditing
57
- }) {
48
+ function useSelectionInteractions() {
49
+ const selectModeStore = useSelectModeStore();
50
+ const displayMode = useDisplayMode();
58
51
  useEffect(() => {
59
52
  if (displayMode !== "selection") {
60
53
  return;
61
54
  }
62
- let dragAnimationFrameId = null;
63
- const editingPointerIds = /* @__PURE__ */ new Set();
64
- let lastClick = null;
65
- let dragCandidates = [];
66
- let committedSelectionAtPointerDown = [];
55
+ const store = () => selectModeStore.getState();
56
+ const interactionSessionRef = {
57
+ get current() {
58
+ return store().interactionSession;
59
+ },
60
+ set current(next) {
61
+ store().actions.setInteractionSession(next);
62
+ }
63
+ };
64
+ const editingElementRef = {
65
+ get current() {
66
+ return store().editingElement;
67
+ }
68
+ };
69
+ const actions = () => store().actions;
70
+ const onBeginEditing = actions().beginEditing;
71
+ const hideSelectionFrame = () => {
72
+ actions().hideSelectionFrames();
73
+ };
74
+ const showAreaSelectionFrames = actions().showAreaSelectionFrames;
75
+ const showElementSelectionFrames = actions().showElementSelectionFrames;
76
+ const editingPointerIds = store().editingPointerIds;
67
77
  const clearCommittedSelection = ({
68
78
  rect
69
79
  }) => {
@@ -72,11 +82,12 @@ function useSelectionInteractions({
72
82
  getWebsiteBuilderRpc()?.areaSelected({ rect, items: [] });
73
83
  };
74
84
  const stopDragAnimation = () => {
85
+ const dragAnimationFrameId = store().dragAnimationFrameId;
75
86
  if (dragAnimationFrameId === null) {
76
87
  return;
77
88
  }
78
89
  cancelAnimationFrame(dragAnimationFrameId);
79
- dragAnimationFrameId = null;
90
+ actions().setDragAnimationFrameId(null);
80
91
  };
81
92
  const resetDragState = ({
82
93
  notifyParentDragCleared = true
@@ -84,7 +95,7 @@ function useSelectionInteractions({
84
95
  const currentSession = interactionSessionRef.current;
85
96
  stopDragAnimation();
86
97
  if (currentSession.type !== "drag") {
87
- dragCandidates = [];
98
+ actions().setDragCandidates([]);
88
99
  return;
89
100
  }
90
101
  if (notifyParentDragCleared && currentSession.hasExceededThreshold) {
@@ -93,7 +104,7 @@ function useSelectionInteractions({
93
104
  selectionRects: []
94
105
  });
95
106
  }
96
- dragCandidates = [];
107
+ actions().setDragCandidates([]);
97
108
  interactionSessionRef.current = IDLE_SESSION;
98
109
  };
99
110
  const postCurrentDragRect = () => {
@@ -120,7 +131,7 @@ function useSelectionInteractions({
120
131
  scrollOffset: session.scrollOffset
121
132
  });
122
133
  const selectionRects = getCandidatesInSelection({
123
- candidates: dragCandidates,
134
+ candidates: store().dragCandidates,
124
135
  selectionPageRect
125
136
  }).map((candidate) => ({
126
137
  id: candidate.id,
@@ -137,7 +148,7 @@ function useSelectionInteractions({
137
148
  const syncDragRectToViewport = () => {
138
149
  const session = interactionSessionRef.current;
139
150
  if (session.type !== "drag") {
140
- dragAnimationFrameId = null;
151
+ actions().setDragAnimationFrameId(null);
141
152
  return;
142
153
  }
143
154
  const nextScrollOffset = getDocumentScrollOffset();
@@ -159,7 +170,9 @@ function useSelectionInteractions({
159
170
  scrollOffset: nextScrollOffset
160
171
  };
161
172
  }
162
- dragAnimationFrameId = requestAnimationFrame(syncDragRectToViewport);
173
+ actions().setDragAnimationFrameId(
174
+ requestAnimationFrame(syncDragRectToViewport)
175
+ );
163
176
  };
164
177
  const handlePointerDown = (event) => {
165
178
  if (event.button !== 0) {
@@ -173,8 +186,8 @@ function useSelectionInteractions({
173
186
  event.preventDefault();
174
187
  event.stopPropagation();
175
188
  const scrollOffset = getDocumentScrollOffset();
176
- committedSelectionAtPointerDown = getSelectedElementsFromSession(
177
- interactionSessionRef.current
189
+ actions().setCommittedSelectionAtPointerDown(
190
+ getSelectedElementsFromSession(interactionSessionRef.current)
178
191
  );
179
192
  const dragStart = getDragPositionFromClient({
180
193
  clientX: event.clientX,
@@ -188,9 +201,11 @@ function useSelectionInteractions({
188
201
  scrollOffset,
189
202
  hasExceededThreshold: false
190
203
  };
191
- dragCandidates = collectDragCandidates(scrollOffset);
192
- if (dragAnimationFrameId === null) {
193
- dragAnimationFrameId = requestAnimationFrame(syncDragRectToViewport);
204
+ actions().setDragCandidates(collectDragCandidates(scrollOffset));
205
+ if (store().dragAnimationFrameId === null) {
206
+ actions().setDragAnimationFrameId(
207
+ requestAnimationFrame(syncDragRectToViewport)
208
+ );
194
209
  }
195
210
  };
196
211
  const handlePointerMove = (event) => {
@@ -210,10 +225,12 @@ function useSelectionInteractions({
210
225
  };
211
226
  const handleViewportChangeDuringDrag = () => {
212
227
  const session = interactionSessionRef.current;
213
- if (session.type !== "drag" || dragAnimationFrameId !== null) {
228
+ if (session.type !== "drag" || store().dragAnimationFrameId !== null) {
214
229
  return;
215
230
  }
216
- dragAnimationFrameId = requestAnimationFrame(syncDragRectToViewport);
231
+ actions().setDragAnimationFrameId(
232
+ requestAnimationFrame(syncDragRectToViewport)
233
+ );
217
234
  };
218
235
  const commitAreaSelection = ({
219
236
  selectedElements,
@@ -275,9 +292,9 @@ function useSelectionInteractions({
275
292
  scrollOffset
276
293
  });
277
294
  const now = pointerEvent.timeStamp;
278
- const previousClick = lastClick;
295
+ const previousClick = store().lastClick;
279
296
  if (previousClick && previousClick.target === selectionElement && now - previousClick.time <= DOUBLE_CLICK_THRESHOLD_MS && isTextEditableElement(selectionElement)) {
280
- lastClick = null;
297
+ actions().setLastClick(null);
281
298
  onBeginEditing({
282
299
  element: selectionElement,
283
300
  descriptorPromise: previousClick.descriptorPromise
@@ -289,11 +306,11 @@ function useSelectionInteractions({
289
306
  clickPosition,
290
307
  siblingSelectors: siblingElements.map(buildSelector)
291
308
  });
292
- lastClick = {
309
+ actions().setLastClick({
293
310
  descriptorPromise: Promise.resolve(data),
294
311
  target: selectionElement,
295
312
  time: now
296
- };
313
+ });
297
314
  if (!selectionElement.isConnected) {
298
315
  return;
299
316
  }
@@ -309,6 +326,7 @@ function useSelectionInteractions({
309
326
  };
310
327
  void maybeSymbolicate(data).then((symbolicated) => {
311
328
  const session = interactionSessionRef.current;
329
+ const committedSelectionAtPointerDown = store().committedSelectionAtPointerDown;
312
330
  const isPendingClickOnSelection = session.type === "drag" && !session.hasExceededThreshold && committedSelectionAtPointerDown.length === 1 && committedSelectionAtPointerDown[0] === selectionElement;
313
331
  if (!isPendingClickOnSelection && !isSelectionStillCurrent({
314
332
  session: interactionSessionRef.current,
@@ -329,14 +347,14 @@ function useSelectionInteractions({
329
347
  commitElementSelection({ selectionElement, pointerEvent });
330
348
  return;
331
349
  }
332
- lastClick = null;
350
+ actions().setLastClick(null);
333
351
  const pointerRect = getPointRect({
334
352
  x: pointerEvent.clientX,
335
353
  y: pointerEvent.clientY
336
354
  });
337
355
  const nextElements = getSelectionAfterAdditiveClick({
338
356
  candidates,
339
- currentSelectedElements: committedSelectionAtPointerDown,
357
+ currentSelectedElements: store().committedSelectionAtPointerDown,
340
358
  targetElement: selectionElement
341
359
  });
342
360
  const [firstElement] = nextElements;
@@ -349,7 +367,7 @@ function useSelectionInteractions({
349
367
  selectionElement: firstElement,
350
368
  pointerEvent
351
369
  });
352
- lastClick = null;
370
+ actions().setLastClick(null);
353
371
  return;
354
372
  }
355
373
  const scrollOffset = getDocumentScrollOffset();
@@ -382,7 +400,7 @@ function useSelectionInteractions({
382
400
  const dragStart = snapshot.type === "drag" ? snapshot.dragStart : null;
383
401
  const dragCurrent = snapshot.type === "drag" ? snapshot.dragCurrent : null;
384
402
  const scrollOffset = snapshot.type === "drag" ? snapshot.scrollOffset : getDocumentScrollOffset();
385
- const candidates = dragCandidates;
403
+ const candidates = store().dragCandidates;
386
404
  resetDragState({ notifyParentDragCleared: false });
387
405
  if (!dragStart || snapshot.type !== "drag") {
388
406
  const targetElement = event.target;
@@ -469,7 +487,7 @@ function useSelectionInteractions({
469
487
  window.removeEventListener("resize", handleViewportChangeDuringDrag);
470
488
  resetDragState();
471
489
  };
472
- }, [displayMode, editingElementRef, interactionSessionRef, onBeginEditing]);
490
+ }, [displayMode, selectModeStore]);
473
491
  }
474
492
  export {
475
493
  useSelectionInteractions
@@ -1,25 +1 @@
1
- import type { MutableRefObject } from "react";
2
- import type { DisplayMode } from "../../_vendor/schemas/generated/selectMode";
3
- import type { SelectModeInteractionSession } from "../selectModeSession";
4
- /**
5
- * After a selection (element or area) has been resolved, the parent's
6
- * highlight overlay is positioned in viewport coordinates. When the user
7
- * scrolls the iframe document the highlight would otherwise lag behind the
8
- * underlying element. This hook re-sends an updated rect on every scroll
9
- * (rAF-coalesced) so the parent overlay tracks the element/area through
10
- * scrolls without us having to mirror scroll state into the parent.
11
- *
12
- * - For an `element` session we send only `{ boundingRect, clickPosition }`
13
- * — the rest of the descriptor (selector, outerHTML, computed styles,
14
- * text metrics, source location) was captured at selection-commit time
15
- * and doesn't change while the user is just scrolling. Rebuilding the
16
- * full descriptor here would call `serializeStyledElement` recursively.
17
- * The in-iframe frames only remeasure the selected repetition group.
18
- * - For an `area` session we keep the original *page* rect (which doesn't
19
- * move) and convert it to viewport coordinates using the latest scroll
20
- * offset before sending.
21
- */
22
- export declare function useSelectionScrollSync({ displayMode, interactionSessionRef, }: {
23
- displayMode: DisplayMode;
24
- interactionSessionRef: MutableRefObject<SelectModeInteractionSession>;
25
- }): void;
1
+ export declare function useSelectionScrollSync(): void;
@@ -5,59 +5,67 @@ import {
5
5
  getElementBoundingRect,
6
6
  getViewportRectFromPageRect
7
7
  } from "../interactions/selectionGeometry";
8
- import { showElementSelectionFrames } from "../selectionFrame";
9
- function useSelectionScrollSync({
10
- displayMode,
11
- interactionSessionRef
12
- }) {
8
+ import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
9
+ function useSelectionScrollSync() {
10
+ const store = useSelectModeStore();
11
+ const displayMode = useDisplayMode();
13
12
  useEffect(() => {
14
13
  if (displayMode !== "selection") {
15
14
  return;
16
15
  }
17
- let animationFrameId = null;
18
16
  const handleScroll = () => {
19
- if (animationFrameId !== null) {
17
+ const { scrollSyncAnimationFrameId, actions } = store.getState();
18
+ if (scrollSyncAnimationFrameId !== null) {
20
19
  return;
21
20
  }
22
- animationFrameId = requestAnimationFrame(() => {
23
- animationFrameId = null;
24
- const session = interactionSessionRef.current;
25
- if (session.type === "element") {
26
- const boundingRect = getElementBoundingRect(session.element);
27
- const sessionClickPosition = session.clickPosition;
28
- const clickPosition = {
29
- x: sessionClickPosition !== null ? boundingRect.left + sessionClickPosition.x : boundingRect.right,
30
- y: sessionClickPosition !== null ? boundingRect.top + sessionClickPosition.y : boundingRect.bottom
31
- };
32
- const scrollOffset = getDocumentScrollOffset();
33
- showElementSelectionFrames({
34
- elements: [session.element, ...session.siblingElements],
35
- scrollOffset
36
- });
37
- getWebsiteBuilderRpc()?.selectedElementRectUpdate({
38
- rect: boundingRect,
39
- clickPosition
40
- });
41
- return;
42
- }
43
- if (session.type === "area") {
44
- getWebsiteBuilderRpc()?.selectedAreaRectUpdate({
45
- rect: getViewportRectFromPageRect({
46
- rect: session.pageRect,
47
- scrollOffset: getDocumentScrollOffset()
48
- })
49
- });
50
- }
51
- });
21
+ actions.setScrollSyncAnimationFrameId(
22
+ requestAnimationFrame(() => {
23
+ const { interactionSession, actions: storeActions } = store.getState();
24
+ storeActions.setScrollSyncAnimationFrameId(null);
25
+ if (interactionSession.type === "element") {
26
+ const boundingRect = getElementBoundingRect(
27
+ interactionSession.element
28
+ );
29
+ const sessionClickPosition = interactionSession.clickPosition;
30
+ const clickPosition = {
31
+ x: sessionClickPosition !== null ? boundingRect.left + sessionClickPosition.x : boundingRect.right,
32
+ y: sessionClickPosition !== null ? boundingRect.top + sessionClickPosition.y : boundingRect.bottom
33
+ };
34
+ const scrollOffset = getDocumentScrollOffset();
35
+ storeActions.showElementSelectionFrames({
36
+ elements: [
37
+ interactionSession.element,
38
+ ...interactionSession.siblingElements
39
+ ],
40
+ scrollOffset
41
+ });
42
+ getWebsiteBuilderRpc()?.selectedElementRectUpdate({
43
+ rect: boundingRect,
44
+ clickPosition
45
+ });
46
+ return;
47
+ }
48
+ if (interactionSession.type === "area") {
49
+ getWebsiteBuilderRpc()?.selectedAreaRectUpdate({
50
+ rect: getViewportRectFromPageRect({
51
+ rect: interactionSession.pageRect,
52
+ scrollOffset: getDocumentScrollOffset()
53
+ })
54
+ });
55
+ }
56
+ })
57
+ );
52
58
  };
53
59
  document.addEventListener("scroll", handleScroll, true);
54
60
  return () => {
55
61
  document.removeEventListener("scroll", handleScroll, true);
56
- if (animationFrameId !== null) {
57
- cancelAnimationFrame(animationFrameId);
62
+ const { scrollSyncAnimationFrameId, actions } = store.getState();
63
+ if (scrollSyncAnimationFrameId !== null) {
64
+ cancelAnimationFrame(scrollSyncAnimationFrameId);
65
+ actions.setScrollSyncAnimationFrameId(null);
58
66
  }
59
67
  };
60
- }, [displayMode, interactionSessionRef]);
68
+ }, [displayMode, store]);
61
69
  }
62
70
  export {
63
71
  useSelectionScrollSync