From 2be0afcfb54b16dd426e5ad65f6aa52301dc2d45 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 3 Jan 2025 08:47:11 +0100 Subject: [PATCH] fix(cdk/menu): avoid resetting the scroll position when using the mouse (#30249) The CDK menu has some logic that forwards focus to the first item when the host is focused. The problem is that every time the user clicks on the scrollbar, they blur the current item and focus the menu which then forwards focus back to the first item which in turn causes the scroll position to jump to the top. These changes add some logic to not forward focus when focus comes from a mouse interaction. Fixes #30130. --- src/cdk/menu/menu-base.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cdk/menu/menu-base.ts b/src/cdk/menu/menu-base.ts index 36a4a357b181..7bf538e8b040 100644 --- a/src/cdk/menu/menu-base.ts +++ b/src/cdk/menu/menu-base.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {_IdGenerator, FocusKeyManager, FocusOrigin} from '@angular/cdk/a11y'; +import {_IdGenerator, FocusKeyManager, FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import { AfterContentInit, @@ -43,7 +43,6 @@ import {PointerFocusTracker} from './pointer-focus-tracker'; '[id]': 'id', '[attr.aria-orientation]': 'orientation', '[attr.data-cdk-menu-stack-id]': 'menuStack.id', - '(focus)': 'focusFirstItem()', '(focusin)': 'menuStack.setHasFocus(true)', '(focusout)': 'menuStack.setHasFocus(false)', }, @@ -52,6 +51,7 @@ export abstract class CdkMenuBase extends CdkMenuGroup implements Menu, AfterContentInit, OnDestroy { + private _focusMonitor = inject(FocusMonitor); protected ngZone = inject(NgZone); private _renderer = inject(Renderer2); @@ -108,6 +108,7 @@ export abstract class CdkMenuBase this.menuStack.push(this); } this._setKeyManager(); + this._handleFocus(); this._subscribeToMenuStackHasFocus(); this._subscribeToMenuOpen(); this._subscribeToMenuStackClosed(); @@ -115,6 +116,7 @@ export abstract class CdkMenuBase } ngOnDestroy() { + this._focusMonitor.stopMonitoring(this.nativeElement); this.keyManager?.destroy(); this.destroyed.next(); this.destroyed.complete(); @@ -231,4 +233,18 @@ export abstract class CdkMenuBase this.menuAim.initialize(this, this.pointerTracker!); } } + + /** Handles focus landing on the host element of the menu. */ + private _handleFocus() { + this._focusMonitor + .monitor(this.nativeElement, false) + .pipe(takeUntil(this.destroyed)) + .subscribe(origin => { + // Don't forward focus on mouse interactions, because it can + // mess with the user's scroll position. See #30130. + if (origin !== null && origin !== 'mouse') { + this.focusFirstItem(origin); + } + }); + } }