diff --git a/src/dev-app/menu/menu-demo.html b/src/dev-app/menu/menu-demo.html index d4cc3801ed01..6f42f60caa10 100644 --- a/src/dev-app/menu/menu-demo.html +++ b/src/dev-app/menu/menu-demo.html @@ -105,9 +105,7 @@
-

- Position x: before -

+

Position x: before

-

- Position y: above -

+

Position y: above

-

- Position x: before, overlapTrigger: true -

+

Position x: before, overlapTrigger: true

-

- Position y: above, overlapTrigger: true -

+

Position y: above, overlapTrigger: true

+
+

disabledInteractive (should not open)

+ + + + + + @for (item of items; track item) { + + } + +
+
+

vanilla button disabled binding

+ + + + + + @for (item of items; track item) { + + } + +
diff --git a/src/material/menu/BUILD.bazel b/src/material/menu/BUILD.bazel index 4fb81fba46b5..9b8ab63420f1 100644 --- a/src/material/menu/BUILD.bazel +++ b/src/material/menu/BUILD.bazel @@ -111,6 +111,7 @@ ng_project( "//src/cdk/overlay", "//src/cdk/scrolling", "//src/cdk/testing/private", + "//src/material/button", "//src/material/core", ], ) diff --git a/src/material/menu/menu-trigger-base.ts b/src/material/menu/menu-trigger-base.ts index efdf997a3b88..1519ceeccd4d 100644 --- a/src/material/menu/menu-trigger-base.ts +++ b/src/material/menu/menu-trigger-base.ts @@ -22,6 +22,7 @@ import { } from '@angular/cdk/overlay'; import {TemplatePortal} from '@angular/cdk/portal'; import { + booleanAttribute, ChangeDetectorRef, Directive, ElementRef, @@ -191,6 +192,10 @@ export abstract class MatMenuTriggerBase implements OnDestroy { /** Internal method to open menu providing option to auto focus on first item. */ protected _openMenu(autoFocus: boolean): void { + if (this._triggerIsAriaDisabled()) { + return; + } + const menu = this._menu; if (this._menuOpen || !menu) { @@ -477,4 +482,12 @@ export abstract class MatMenuTriggerBase implements OnDestroy { private _ownsMenu(menu: MatMenuPanel): boolean { return PANELS_TO_TRIGGERS.get(menu) === this; } + + /** + * Detect if the trigger element is aria-disabled, indicating it should behave as + * disabled and not open the menu. + */ + private _triggerIsAriaDisabled() { + return booleanAttribute(this._element.nativeElement.getAttribute('aria-disabled')); + } } diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts index 28a0d50fd10e..14c1ecb99d97 100644 --- a/src/material/menu/menu.spec.ts +++ b/src/material/menu/menu.spec.ts @@ -42,6 +42,7 @@ import { provideFakeDirectionality, } from '../../cdk/testing/private'; import {MATERIAL_ANIMATIONS, MatRipple} from '../core'; +import {MatButton} from '@angular/material/button'; import {MatMenu, MatMenuItem} from './index'; import { MAT_MENU_DEFAULT_OPTIONS, @@ -1279,6 +1280,23 @@ describe('MatMenu', () => { })); }); + it('does not open if the trigger element is disabled (including disabledInteractive)', fakeAsync(() => { + const fixture = TestBed.createComponent(DisabledMenu); + fixture.detectChanges(); + + const trigger = fixture.componentInstance.triggerEl.nativeElement; + trigger.click(); + fixture.detectChanges(); + tick(500); + expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull(); + + dispatchKeyboardEvent(trigger, 'keydown', ENTER); + trigger.click(); + fixture.detectChanges(); + tick(500); + expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull(); + })); + describe('positions', () => { let fixture: ComponentFixture; let trigger: HTMLElement; @@ -2634,6 +2652,20 @@ class SimpleMenu { }) class SimpleMenuOnPush extends SimpleMenu {} +@Component({ + template: ` + + + + + `, + imports: [MatButton, MatMenuTrigger, MatMenu, MatMenuItem], +}) +class DisabledMenu { + @ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef; +} + @Component({ template: ` @@ -2666,7 +2698,7 @@ interface TestableMenu { class OverlapMenu implements TestableMenu { @Input() overlapTrigger: boolean = false; @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; - @ViewChild('triggerEl') triggerEl!: ElementRef; + @ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef; } @Component({