diff --git a/apps/desktop/src/preview/PickPreload.ts b/apps/desktop/src/preview/PickPreload.ts index 4cb5987cdeb85..044226e15ec69 100644 --- a/apps/desktop/src/preview/PickPreload.ts +++ b/apps/desktop/src/preview/PickPreload.ts @@ -43,6 +43,16 @@ const MAX_MARQUEE_ELEMENTS = 20; const ELEMENT_CONTEXT_TIMEOUT_MS = 5_000; const CONTENT_LAYER_Z_INDEX = 1; const CHROME_LAYER_Z_INDEX = 10; +// Page dialogs (Radix, react-remove-scroll) dismiss, refocus or block scrolling on document events +// that shadow retargeting attributes to the overlay host. +const PAGE_ISOLATED_EVENTS = [ + "pointerdown", + "focusin", + "focusout", + "wheel", + "touchstart", + "touchmove", +] as const; let recordingCursor: ReturnType | null = null; ipcRenderer.on( @@ -1314,6 +1324,13 @@ function startAnnotation(sendEnabled: boolean): void { event.stopPropagation(); }; + const stopAtOverlay = (event: Event): void => event.stopPropagation(); + // Focus moving from the page into the overlay targets the page element, so the host never sees it. + const onFocusOutToOverlay = (event: FocusEvent): void => { + if (event.relatedTarget instanceof Element && isAnnotationNode(event.relatedTarget)) + event.stopPropagation(); + }; + const onPointerOut = (event: PointerEvent): void => { if (event.relatedTarget === null) clearHoverOutline(); }; @@ -1351,6 +1368,8 @@ function startAnnotation(sendEnabled: boolean): void { dragHandle.removeEventListener("pointermove", onEditorPointerMove); dragHandle.removeEventListener("pointerup", onEditorPointerUp); dragHandle.removeEventListener("pointercancel", onEditorPointerUp); + for (const type of PAGE_ISOLATED_EVENTS) host.removeEventListener(type, stopAtOverlay); + document.documentElement.removeEventListener("focusout", onFocusOutToOverlay); if (editorLayoutFrame !== null) window.cancelAnimationFrame(editorLayoutFrame); ipcRenderer.off(CANCEL_PICK_CHANNEL, onCancel); ipcRenderer.off(ANNOTATION_CAPTURED_CHANNEL, onCaptured); @@ -1463,6 +1482,10 @@ function startAnnotation(sendEnabled: boolean): void { window.addEventListener("keydown", onKeyDown, { capture: true }); window.addEventListener("scroll", repaint, { capture: true, passive: true }); window.addEventListener("resize", repaint, { passive: true }); + // Bubble phase, so the overlay's own handlers still see these events. + for (const type of PAGE_ISOLATED_EVENTS) + host.addEventListener(type, stopAtOverlay, { passive: true }); + document.documentElement.addEventListener("focusout", onFocusOutToOverlay); ipcRenderer.on(CANCEL_PICK_CHANNEL, onCancel); ipcRenderer.on(ANNOTATION_CAPTURED_CHANNEL, onCaptured); if (inputLayer) {