Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions src/dev-app/menu/menu-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@
</mat-menu>
</div>
<div class="demo-menu-section">
<p>
Position x: before
</p>
<p>Position x: before</p>
<mat-toolbar class="demo-end-icon">
<button matIconButton [matMenuTriggerFor]="posXMenu" aria-label="Open x-positioned menu">
<mat-icon>more_vert</mat-icon>
Expand All @@ -124,9 +122,7 @@
</mat-menu>
</div>
<div class="demo-menu-section">
<p>
Position y: above
</p>
<p>Position y: above</p>
<mat-toolbar>
<button matIconButton [matMenuTriggerFor]="posYMenu" aria-label="Open y-positioned menu">
<mat-icon>more_vert</mat-icon>
Expand Down Expand Up @@ -158,9 +154,7 @@
</mat-menu>
</div>
<div class="demo-menu-section">
<p>
Position x: before, overlapTrigger: true
</p>
<p>Position x: before, overlapTrigger: true</p>
<mat-toolbar class="demo-end-icon">
<button matIconButton [mat-menu-trigger-for]="posXMenuOverlay">
<mat-icon>more_vert</mat-icon>
Expand All @@ -177,9 +171,7 @@
</mat-menu>
</div>
<div class="demo-menu-section">
<p>
Position y: above, overlapTrigger: true
</p>
<p>Position y: above, overlapTrigger: true</p>
<mat-toolbar>
<button matIconButton [mat-menu-trigger-for]="posYMenuOverlay">
<mat-icon>more_vert</mat-icon>
Expand All @@ -192,6 +184,39 @@
}
</mat-menu>
</div>
<div class="demo-menu-section">
<p>disabledInteractive (should not open)</p>
<mat-toolbar>
<button
disabled
[disabledInteractive]="true"
matIconButton
[mat-menu-trigger-for]="disabledInteractiveMenu"
>
<mat-icon>more_vert</mat-icon>
</button>
</mat-toolbar>

<mat-menu #disabledInteractiveMenu="matMenu">
@for (item of items; track item) {
<button mat-menu-item [disabled]="item.disabled">{{ item.text }}</button>
}
</mat-menu>
</div>
<div class="demo-menu-section">
<p>vanilla button disabled binding</p>
<mat-toolbar>
<button [disabled]="true" [mat-menu-trigger-for]="disabledInteractiveMenu">
<mat-icon>more_vert</mat-icon>
</button>
</mat-toolbar>

<mat-menu #disabledInteractiveMenu="matMenu">
@for (item of items; track item) {
<button mat-menu-item [disabled]="item.disabled">{{ item.text }}</button>
}
</mat-menu>
</div>
</div>

<div class="demo-context-menu-area" [matContextMenuTriggerFor]="contextMenu">
Expand Down
1 change: 1 addition & 0 deletions src/material/menu/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ ng_project(
"//src/cdk/overlay",
"//src/cdk/scrolling",
"//src/cdk/testing/private",
"//src/material/button",
"//src/material/core",
],
)
Expand Down
13 changes: 13 additions & 0 deletions src/material/menu/menu-trigger-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {
booleanAttribute,
ChangeDetectorRef,
Directive,
ElementRef,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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'));
}
}
34 changes: 33 additions & 1 deletion src/material/menu/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<PositionedMenu>;
let trigger: HTMLElement;
Expand Down Expand Up @@ -2634,6 +2652,20 @@ class SimpleMenu {
})
class SimpleMenuOnPush extends SimpleMenu {}

@Component({
template: `
<button mat-button disabled [disabledInteractive]="true"
[matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
<mat-menu #menu="matMenu">
<button mat-menu-item> Action! </button>
</mat-menu>
`,
imports: [MatButton, MatMenuTrigger, MatMenu, MatMenuItem],
})
class DisabledMenu {
@ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef<HTMLElement>;
}

@Component({
template: `
<button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
Expand Down Expand Up @@ -2666,7 +2698,7 @@ interface TestableMenu {
class OverlapMenu implements TestableMenu {
@Input() overlapTrigger: boolean = false;
@ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger;
@ViewChild('triggerEl') triggerEl!: ElementRef<HTMLElement>;
@ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef<HTMLElement>;
}

@Component({
Expand Down
Loading