From 30103fd2c82212e0c89424819bc0a729309da297 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 10 Nov 2025 12:44:10 +0100 Subject: [PATCH] Fix dropdown menu not focusing first item when opened via keyboard (#36804) --- .../mastodon/components/dropdown_menu.tsx | 78 +++++++------------ .../components/edited_timestamp/index.tsx | 14 +--- .../components/status/boost_button.tsx | 29 ++----- 3 files changed, 35 insertions(+), 86 deletions(-) diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx index afcd4176e..2a97313f5 100644 --- a/app/javascript/mastodon/components/dropdown_menu.tsx +++ b/app/javascript/mastodon/components/dropdown_menu.tsx @@ -42,16 +42,10 @@ import { IconButton } from './icon_button'; let id = 0; -export interface RenderItemFnHandlers { - onClick: React.MouseEventHandler; - onKeyUp: React.KeyboardEventHandler; -} - export type RenderItemFn = ( item: Item, index: number, - handlers: RenderItemFnHandlers, - focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void, + onClick: React.MouseEventHandler, ) => React.ReactNode; type ItemClickFn = (item: Item, index: number) => void; @@ -101,7 +95,6 @@ export const DropdownMenu = ({ onItemClick, }: DropdownMenuProps) => { const nodeRef = useRef(null); - const focusedItemRef = useRef(null); useEffect(() => { const handleDocumentClick = (e: MouseEvent) => { @@ -163,8 +156,11 @@ export const DropdownMenu = ({ document.addEventListener('click', handleDocumentClick, { capture: true }); document.addEventListener('keydown', handleKeyDown, { capture: true }); - if (focusedItemRef.current && openedViaKeyboard) { - focusedItemRef.current.focus({ preventScroll: true }); + if (openedViaKeyboard) { + const firstMenuItem = nodeRef.current?.querySelector< + HTMLAnchorElement | HTMLButtonElement + >('li:first-child > :is(a, button)'); + firstMenuItem?.focus({ preventScroll: true }); } return () => { @@ -175,13 +171,6 @@ export const DropdownMenu = ({ }; }, [onClose, openedViaKeyboard]); - const handleFocusedItemRef = useCallback( - (c: HTMLAnchorElement | HTMLButtonElement | null) => { - focusedItemRef.current = c as HTMLElement; - }, - [], - ); - const handleItemClick = useCallback( (e: React.MouseEvent | React.KeyboardEvent) => { const i = Number(e.currentTarget.getAttribute('data-index')); @@ -207,15 +196,6 @@ export const DropdownMenu = ({ [onClose, onItemClick, items], ); - const handleItemKeyUp = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - handleItemClick(e); - } - }, - [handleItemClick], - ); - const nativeRenderItem = (option: Item, i: number) => { if (!isMenuItem(option)) { return null; @@ -232,9 +212,7 @@ export const DropdownMenu = ({ if (isActionItem(option)) { element = ( diff --git a/app/javascript/mastodon/components/status/boost_button.tsx b/app/javascript/mastodon/components/status/boost_button.tsx index cdbe24228..21c210b9e 100644 --- a/app/javascript/mastodon/components/status/boost_button.tsx +++ b/app/javascript/mastodon/components/status/boost_button.tsx @@ -14,7 +14,7 @@ import type { Status } from '@/mastodon/models/status'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import type { SomeRequired } from '@/mastodon/utils/types'; -import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu'; +import type { RenderItemFn } from '../dropdown_menu'; import { Dropdown, DropdownMenuItemContent } from '../dropdown_menu'; import { IconButton } from '../icon_button'; @@ -74,18 +74,12 @@ const StandaloneBoostButton: FC = ({ status, counters }) => { ); }; -const renderMenuItem: RenderItemFn = ( - item, - index, - handlers, - focusRefCallback, -) => ( +const renderMenuItem: RenderItemFn = (item, index, onClick) => ( ); @@ -208,16 +202,10 @@ const BoostOrQuoteMenu: FC = ({ status, counters }) => { interface ReblogMenuItemProps { item: ActionMenuItem; index: number; - handlers: RenderItemFnHandlers; - focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void; + onClick: React.MouseEventHandler; } -const ReblogMenuItem: FC = ({ - index, - item, - handlers, - focusRefCallback, -}) => { +const ReblogMenuItem: FC = ({ index, item, onClick }) => { const { text, highlighted, disabled } = item; return ( @@ -227,12 +215,7 @@ const ReblogMenuItem: FC = ({ })} key={`${text}-${index}`} > -