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
35 changes: 23 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,25 @@
}
</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>

<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
9 changes: 9 additions & 0 deletions src/material/menu/menu-trigger-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import {
} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {
booleanAttribute,
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
inject,
InjectionToken,
Injector,
input,
NgZone,
OnDestroy,
ViewContainerRef,
Expand Down Expand Up @@ -139,6 +141,9 @@ export abstract class MatMenuTriggerBase implements OnDestroy {
}
private _menuInternal: MatMenuPanel | null = null;

/** Whether the host component is disabled. Needed to correctly handle disabledInteractive. */
readonly triggerIsDisabled = input(false, {alias: 'disabled', transform: booleanAttribute});
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't we just call this disabled?


/** Event emitted when the associated menu is opened. */
abstract menuOpened: EventEmitter<void>;

Expand Down Expand Up @@ -191,6 +196,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.triggerIsDisabled()) {
return;
}

const menu = this._menu;

if (this._menuOpen || !menu) {
Expand Down
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