From d8b0e731ef6879d5db9b94e638b574b810518c0a Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 5 Mar 2026 17:44:48 -0500 Subject: [PATCH] fix: ESC for back in `SharePopover` not working (#11662) * fix: ESC for back in SharePopover not working closes #11656 * Normalize prevent default callbacks --- app/components/Menu/ContextMenu.tsx | 8 ++------ app/components/Menu/DropdownMenu.tsx | 8 ++------ app/components/SearchPopover.tsx | 3 ++- app/hooks/useKeyDown.ts | 9 +++++++-- app/scenes/Collection/components/ShareButton.tsx | 2 ++ app/scenes/Document/components/ShareButton.tsx | 2 ++ app/scenes/Search/Search.tsx | 7 ++----- app/utils/events.ts | 8 ++++++++ 8 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 app/utils/events.ts diff --git a/app/components/Menu/ContextMenu.tsx b/app/components/Menu/ContextMenu.tsx index 559edc7118..933f5f2f7e 100644 --- a/app/components/Menu/ContextMenu.tsx +++ b/app/components/Menu/ContextMenu.tsx @@ -3,6 +3,7 @@ import { actionToMenuItem } from "~/actions"; import useActionContext from "~/hooks/useActionContext"; import useMobile from "~/hooks/useMobile"; import type { ActionVariant, ActionWithChildren } from "~/types"; +import { preventDefault } from "~/utils/events"; import { toMenuItems } from "./transformer"; import { observer } from "mobx-react"; import { useComputed } from "~/hooks/useComputed"; @@ -61,11 +62,6 @@ export const ContextMenu = observer( } }, []); - const handleCloseAutoFocus = React.useCallback( - (e: Event) => e.preventDefault(), - [] - ); - if (isMobile || !action || menuItems.length === 0) { return <>{children}; } @@ -80,7 +76,7 @@ export const ContextMenu = observer( aria-label={ariaLabel} onAnimationStart={disablePointerEvents} onAnimationEnd={enablePointerEvents} - onCloseAutoFocus={handleCloseAutoFocus} + onCloseAutoFocus={preventDefault} > {content} diff --git a/app/components/Menu/DropdownMenu.tsx b/app/components/Menu/DropdownMenu.tsx index 50402e290e..11278004e9 100644 --- a/app/components/Menu/DropdownMenu.tsx +++ b/app/components/Menu/DropdownMenu.tsx @@ -13,6 +13,7 @@ import { MenuProvider } from "~/components/primitives/Menu/MenuContext"; import { actionToMenuItem } from "~/actions"; import useActionContext from "~/hooks/useActionContext"; import useMobile from "~/hooks/useMobile"; +import { preventDefault } from "~/utils/events"; import type { ActionVariant, ActionWithChildren, @@ -98,11 +99,6 @@ export const DropdownMenu = observer( } }, []); - const handleCloseAutoFocus = React.useCallback( - (e: Event) => e.preventDefault(), - [] - ); - if (isMobile) { return ( {content} {append} diff --git a/app/components/SearchPopover.tsx b/app/components/SearchPopover.tsx index 65d6520e08..592518878c 100644 --- a/app/components/SearchPopover.tsx +++ b/app/components/SearchPopover.tsx @@ -16,6 +16,7 @@ import { import { id as bodyContentId } from "~/components/SkipNavContent"; import useKeyDown from "~/hooks/useKeyDown"; import useStores from "~/hooks/useStores"; +import { preventDefault } from "~/utils/events"; import type { SearchResult } from "~/types"; import SearchListItem from "./SearchListItem"; @@ -230,7 +231,7 @@ function SearchPopover({ shareId, className }: Props) { align="start" shrink onEscapeKeyDown={handleEscapeList} - onOpenAutoFocus={(e) => e.preventDefault()} + onOpenAutoFocus={preventDefault} onInteractOutside={(event) => { const target = event.target as Element | null; if (target === searchInputRef.current) { diff --git a/app/hooks/useKeyDown.ts b/app/hooks/useKeyDown.ts index 9a6ef7059a..5ccaa2f7bf 100644 --- a/app/hooks/useKeyDown.ts +++ b/app/hooks/useKeyDown.ts @@ -63,9 +63,14 @@ window.addEventListener("keydown", (event) => { return; } + // Track whether defaultPrevented was already set by an external handler (e.g. + // Radix UI's DismissableLayer) so we only break on preventDefault calls made + // by our own callbacks. + const wasDefaultPrevented = event.defaultPrevented; + // reverse so that the last registered callbacks get executed first - for (const registered of callbacks.reverse()) { - if (event.defaultPrevented === true) { + for (const registered of [...callbacks].reverse()) { + if (!wasDefaultPrevented && event.defaultPrevented) { break; } diff --git a/app/scenes/Collection/components/ShareButton.tsx b/app/scenes/Collection/components/ShareButton.tsx index 0b2d843618..6f25eabb5a 100644 --- a/app/scenes/Collection/components/ShareButton.tsx +++ b/app/scenes/Collection/components/ShareButton.tsx @@ -12,6 +12,7 @@ import { import useCurrentTeam from "~/hooks/useCurrentTeam"; import useMobile from "~/hooks/useMobile"; import useStores from "~/hooks/useStores"; +import { preventDefault } from "~/utils/events"; import lazyWithRetry from "~/utils/lazyWithRetry"; const SharePopover = lazyWithRetry( @@ -64,6 +65,7 @@ function ShareButton({ collection }: Props) { minHeight={175} side="bottom" align="end" + onEscapeKeyDown={preventDefault} > {loading && } -
ev.preventDefault()} - > + void }) => { + event.preventDefault(); +};