Skip to content

fix(cdk/menu): don't close menu until outside press is released - #34097

Open
prath47 wants to merge 1 commit into
angular:mainfrom
prath47:cdk-menu-close-on-pointer-up
Open

prath47 wants to merge 1 commit into
angular:mainfrom
prath47:cdk-menu-close-on-pointer-up

Conversation

@prath47

@prath47 prath47 commented Nov 7, 2026

Copy link
Copy Markdown

Fixes #27878

With a standalone cdkMenuTriggerFor, pressing down anywhere outside the open menu closes it immediately, before the button is released. As noted in the issue, the outside click listener never gets a chance: the mousedown blurs the menu, menuStack.hasFocus emits false and CdkMenuTrigger calls closeAll(). That means the close can't be cancelled by moving the pointer away before releasing (WCAG 2.5.2 Pointer Cancellation), and it's different from the menu bar and mat-menu.

I didn't remove the hasFocus listener, because it's also what closes the menu when focus leaves it from the keyboard (Tab, or focus moving somewhere else programmatically). Instead, while a root menu is open the trigger now tracks pointer presses on the document:

  • pointerdown outside of the trigger and the menu stack marks that a press is in progress.
  • If focus leaves the menu during that press, closing is deferred until pointerup.
  • On pointerup the menu closes if the pointer was released outside, and stays open if it was moved back onto the menu.
  • pointercancel and closing the menu reset the state, and the listeners are only attached while the menu is open.

I listen on the document rather than relying on the overlay's outside click events. A press on the empty area below a short page lands on <html>, which the outside click dispatcher (attached to body) doesn't see, and the menu would stay open after the release. Focus loss that isn't caused by a press closes the menu right away, the same as before.

Tests

New specs in menu-trigger.spec.ts (standalone trigger):

  • focus leaving the menu without a press still closes it
  • press outside → focus leaves → menu stays open → pointerup outside closes it
  • press outside → focus leaves → pointerup on the menu keeps it open
  • a press that was already released doesn't stop focus loss from closing the menu
bazel test //src/cdk/menu:unit_tests

//src/cdk/menu:unit_tests_chromium      PASSED
//src/cdk/menu:unit_tests_firefox       PASSED

Without the change in menu-trigger.ts, the two press specs fail (Expected 1 to be 2.).

Manual check

Standalone trigger + cdkMenu with a nested submenu, CDK 22.2.6 and then the same build with this change. I drove it with Playwright using real mouse events, holding the button for 500ms on the page background:

Chromium 156 Firefox 157 WebKit 27.2
menu open while the press is held (before → after) no → yes no → yes no → yes
closed after releasing outside yes → yes yes → yes yes → yes
press outside, drag back onto the menu, release closed → stays open closed → stays open closed → stays open
Tab out of the menu closes → closes closes → closes closes → closes

Before, while the mouse button is still held down outside the menu:

before

After:

after

Pressing down outside of a menu opened by a standalone trigger moves focus
away from the menu, which closed it right away on mousedown. This doesn't
allow users to cancel the action by moving the pointer back before
releasing it (WCAG 2.5.2 Pointer Cancellation).

When focus leaves the menu because of a press outside of it, wait for the
press to be released and only close the menu if it's released outside as
well. Focus moving away by other means (e.g. the keyboard) still closes
the menu immediately.

Fixes angular#27878

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(cdk/menu): menu is closed on mousedown event

1 participant