From c1ea3e205f4fd03c20dc3abbf50801117822557e Mon Sep 17 00:00:00 2001 From: seongminn Date: Thu, 31 Jul 2025 15:27:37 +0900 Subject: [PATCH 1/2] refactor: propagate arrow-up keyboard event to menu-primitive --- .../react/dropdown-menu/src/dropdown-menu.tsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/react/dropdown-menu/src/dropdown-menu.tsx b/packages/react/dropdown-menu/src/dropdown-menu.tsx index 94ea815f2..570a57b47 100644 --- a/packages/react/dropdown-menu/src/dropdown-menu.tsx +++ b/packages/react/dropdown-menu/src/dropdown-menu.tsx @@ -33,6 +33,8 @@ type DropdownMenuContextValue = { onOpenChange(open: boolean): void; onOpenToggle(): void; modal: boolean; + wasOpenedWithArrowUp: boolean; + setWasOpenedWithArrowUp(value: boolean): void; }; const [DropdownMenuProvider, useDropdownMenuContext] = @@ -65,6 +67,14 @@ const DropdownMenu: React.FC = (props: ScopedProps { + if (!open) { + setWasOpenedWithArrowUp(false); + } + }, [open]); return ( = (props: ScopedProps setOpen((prevOpen) => !prevOpen), [setOpen])} modal={modal} + wasOpenedWithArrowUp={wasOpenedWithArrowUp} + setWasOpenedWithArrowUp={setWasOpenedWithArrowUp} > {children} @@ -130,7 +142,11 @@ const DropdownMenuTrigger = React.forwardRef @@ -167,7 +183,7 @@ const CONTENT_NAME = 'DropdownMenuContent'; type DropdownMenuContentElement = React.ComponentRef; type MenuContentProps = React.ComponentPropsWithoutRef; -interface DropdownMenuContentProps extends Omit {} +interface DropdownMenuContentProps extends MenuContentProps {} const DropdownMenuContent = React.forwardRef( (props: ScopedProps, forwardedRef) => { @@ -183,6 +199,21 @@ const DropdownMenuContent = React.forwardRef { + // If opened with ArrowUp, focus last item instead of first + if (context.wasOpenedWithArrowUp) { + event.preventDefault(); + // Focus the last item + const target = event.target as HTMLElement; + const items = target.querySelectorAll('[role="menuitem"]:not([data-disabled])'); + const lastItem = items[items.length - 1] as HTMLElement; + if (lastItem) { + setTimeout(() => lastItem.focus(), 0); + } + } + // Call the original onEntryFocus if provided + contentProps.onEntryFocus?.(event); + }} onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => { if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus(); hasInteractedOutsideRef.current = false; From 1ad83e1dd8bf2a81a08d882483248571197e8658 Mon Sep 17 00:00:00 2001 From: seongminn Date: Thu, 31 Jul 2025 22:31:40 +0900 Subject: [PATCH 2/2] chore: add changeset --- .changeset/gold-otters-judge.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gold-otters-judge.md diff --git a/.changeset/gold-otters-judge.md b/.changeset/gold-otters-judge.md new file mode 100644 index 000000000..86ed5f707 --- /dev/null +++ b/.changeset/gold-otters-judge.md @@ -0,0 +1,5 @@ +--- +'@radix-ui/react-dropdown-menu': minor +--- + +move focus to the last item when opening with ArrowUp