Prevent enter/space keydown events from bubbling up from menu button

This commit is contained in:
Harrison Healey 2025-07-21 12:00:44 -04:00
parent c6a37b60b2
commit aefe3792d3
No known key found for this signature in database
GPG key ID: 645A033311E2725E
3 changed files with 11 additions and 9 deletions

View file

@ -192,6 +192,13 @@ export function Menu(props: Props) {
}
}
function handleMenuButtonKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
// Prevent the event from bubbling up to a parent's keydown handler so that it can trigger a click event
event.stopPropagation();
}
}
// We construct the menu button so we can set onClick correctly here to support both web and mobile view
function renderMenuButton() {
const MenuButtonComponent = props.menuButton?.as ?? 'button';
@ -208,6 +215,7 @@ export function Menu(props: Props) {
aria-describedby={props.menuButton?.['aria-describedby']}
className={props.menuButton?.class ?? ''}
onClick={handleMenuButtonClick}
onKeyDown={handleMenuButtonKeyDown}
>
{props.menuButton.children}
</MenuButtonComponent>

View file

@ -149,12 +149,12 @@ export function MenuItem(props: Props) {
function handleClick(event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) {
if (isCorrectKeyPressedOnMenuItem(event)) {
event.stopPropagation();
// If the menu item is a checkbox or radio button, we don't want to close the menu when it is clicked.
// unless forceCloseOnSelect is set to true.
// see https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
if (isRoleCheckboxOrRadio(role) && !forceCloseOnSelect) {
event.stopPropagation();
} else {
if (!isRoleCheckboxOrRadio(role) || forceCloseOnSelect) {
// close submenu first if it is open
if (subMenuContext.close) {
subMenuContext.close();

View file

@ -131,12 +131,6 @@ function ThreadItem({
}
}
// Don't select the thread when a menu is focused
if (e.target instanceof HTMLElement &&
(e.target.hasAttribute('aria-haspopup') || e.target.role === 'menuitem')) {
return;
}
if (e.altKey) {
const hasUnreads = thread ? Boolean(thread.unread_replies) : false;
const lastViewedAt = hasUnreads ? Date.now() : unreadTimestamp;