Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Menubar] Adds keyboard behaviors to the menubar, improves accessibility #3320

Draft
wants to merge 55 commits into
base: develop
Choose a base branch
from

Conversation

tespin
Copy link
Contributor

@tespin tespin commented Jan 17, 2025

Progresses #2933

Just setting this up to track next steps for the component! This PR focuses on adding proper keyboard behaviors to the recently refactored Menubar. Currently in progress

from wai aria menubar pattern

Keyboard Interactions

  • Tab and Shift + Tab
    • Move focus into a menubar:
      • If focus is moving into the menubar for the first time, focus is set on the first menuitem.
      • If the menubar has previously contained focus, focus is optionally set on the menuitem that last had focus. Otherwise, it is set on the first menuitem that is not disabled.
    • When focus is on a menuitem in a menu or menubar, move focus out of the menu or menubar, and close all menus and submenus.
  • Enter
    • When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.
    • Otherwise, activates the item and closes the menu.
  • Space
    • When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.
    • When focus is on a menuitem that does not have a submenu, activates the menuitem and closes the menu.
  • Down Arrow
    • When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the first item in the submenu.
    • When focus is in a menu, moves focus to the next item, optionally wrapping from the last to the first.
  • Up Arrow
    • When focus is in a menu, moves focus to the previous item, optionally wrapping from the first to the last.
    • When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the last item in the submenu.
  • Right Arrow
    • When focus is in a menubar, moves focus to the next item, optionally wrapping from the last to the first.
      - [ ] When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.
    • When focus is in a menu and on an item that does not have a submenu, performs the following 3 actions:
      • Closes the submenu and any parent menus.
      • Moves focus to the next item in the menubar.
      • If focus is now on a menuitem with a submenu, either: (Recommended) opens the submenu of that menuitem without moving focus into the submenu, or opens the submenu of that menuitem and places focus on the first item in the submenu.
  • Left Arrow
    • When focus is in a menubar, moves focus to the previous item, optionally wrapping from the first to the last.
      - [ ] When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem
    • When focus is in a submenu of an item in a menubar, performs the following 3 actions:
      • Closes the submenu.
      • Moves focus to the previous item in the menubar.
      • If focus is now on a menuitem with a submenu, either: (Recommended) opens the submenu of that menuitem without moving focus into the submenu, or opens the submenu of that menuitem and places focus on the first item in the submenu.
  • Home
    • If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.
  • End
    • If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.
  • Escape
    • Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened.
  • Any key that corresponds to a printable character
    • (Optional): Move focus to the next item in the current menu whose label begins with that printable character.
  • Conditionally disable mouse behaviors when navigating with keyboard.

Mouse Interactions

  • Clicks
    • Clicking on a menuitem with a submenu opens the submenu and focuses that menuitem.
    • Clicking on a menuitem within a submenu activates that item and closes the submenu.
    • Clicking outside of a menuitem with a submenu closes that submenu.
    • Clicking outside of the menubar closes all submenus.
  • Hover
    • After a parent menu item in the menubar has been activated and the user hovers over a different parent item in the menubar, focus will follow hover.
    • When a submenu is open and the user hovers over an item in the submenu, focus follows hover.

JS Docs

  • contexts.jsx
  • Menubar.jsx
  • MenubarItem.jsx
  • MenubarSubmenu.jsx
  • usePrevious.js

edit: this pr is getting rather large, I'm going to move the tests and other enhancements into a separate PR.

I have verified that this pull request:

  • has no linting errors (npm run lint)
  • has no test errors (npm run test)
  • is from a uniquely-named feature branch and is up to date with the develop branch.
  • is descriptively named and links to an issue number, i.e. Fixes #123

@tespin tespin mentioned this pull request Jan 17, 2025
10 tasks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Extract logo to header level for better semantic structure
  • Ensure arrow keys only cycle through menu items, excluding the logo

@@ -200,7 +205,20 @@ function Menubar({ children, className }) {

return (
<MenubarContext.Provider value={contextValue}>
<div className={className} ref={nodeRef} onFocus={handleFocus}>
<div
Copy link
Contributor Author

@tespin tespin Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues:

  • originally we were using useKeyDownHandlers to handle the Menubar keyboard events. This hook captured events globally, which conflicted with keyboard behaviors for the rest of the page, including the editor itself.

Changes:

  • Added e.stopPropagation() to all handlers
  • Attached handlers directly to the Menubar container with role='menubar', aria-orientation='horizontal', and tabIndex='0'
  • removed use of useKeyDownHandlers
  • added checks for menu and focus state

New issues:

  • role='menubar' is now in two places: within the div in Menubar.jsx and in a ul within Nav.jsx

These changes should make it so that Menubar keyboard events are only handled when the actual component or related elements are focused, while preserving the keyboard behaviors for the rest of the page. Instead of using the useKeyDownHandlers hook, events are now handled directly by the Menubar element with checks for focus and menu state.

We might need to take a second look at the way we compose the Menubar component in Nav.jsx to resolve the new issues.

const [activeIndex, setActiveIndex] = useState(0);
const prevIndex = usePrevious(activeIndex);

// old state variables
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version of the Menubar did not keep track of its menu items. However, we needed a way to manage this collection to implement keyboard navigation. Arrays were useful for basic keyboard features but sets allow for more predictable registration / cleanup operations and guarantee uniqueness.

Changes:

  • array-based management of state renamed to maintain functionality during migration
  • added new state variables for managing collections using sets

Keyboard navigation is eventually reimplemented with the new set data structure. The old state variables are cleaned up in a future commit.

@tespin
Copy link
Contributor Author

tespin commented Jan 26, 2025

Adding a video here to see the changes so far --- I'm still testing focus management and evaluating state variables against certain race conditions, but most keyboard behaviors have been implemented. Will slowly work on adding tests

edit: since this pr is getting long, I'll open a new one to add the tests

Menubar Keyboard Patterns

menubar_test.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant