Skip to content

Commit 9a160c5

Browse files
committed
fix(material/menu): Do not open the menu when trigger is disabled (properly handle disabledInteractive)
1 parent ef53e73 commit 9a160c5

File tree

4 files changed

+66
-13
lines changed

4 files changed

+66
-13
lines changed

src/dev-app/menu/menu-demo.html

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,7 @@
105105
</mat-menu>
106106
</div>
107107
<div class="demo-menu-section">
108-
<p>
109-
Position x: before
110-
</p>
108+
<p>Position x: before</p>
111109
<mat-toolbar class="demo-end-icon">
112110
<button matIconButton [matMenuTriggerFor]="posXMenu" aria-label="Open x-positioned menu">
113111
<mat-icon>more_vert</mat-icon>
@@ -124,9 +122,7 @@
124122
</mat-menu>
125123
</div>
126124
<div class="demo-menu-section">
127-
<p>
128-
Position y: above
129-
</p>
125+
<p>Position y: above</p>
130126
<mat-toolbar>
131127
<button matIconButton [matMenuTriggerFor]="posYMenu" aria-label="Open y-positioned menu">
132128
<mat-icon>more_vert</mat-icon>
@@ -158,9 +154,7 @@
158154
</mat-menu>
159155
</div>
160156
<div class="demo-menu-section">
161-
<p>
162-
Position x: before, overlapTrigger: true
163-
</p>
157+
<p>Position x: before, overlapTrigger: true</p>
164158
<mat-toolbar class="demo-end-icon">
165159
<button matIconButton [mat-menu-trigger-for]="posXMenuOverlay">
166160
<mat-icon>more_vert</mat-icon>
@@ -177,9 +171,7 @@
177171
</mat-menu>
178172
</div>
179173
<div class="demo-menu-section">
180-
<p>
181-
Position y: above, overlapTrigger: true
182-
</p>
174+
<p>Position y: above, overlapTrigger: true</p>
183175
<mat-toolbar>
184176
<button matIconButton [mat-menu-trigger-for]="posYMenuOverlay">
185177
<mat-icon>more_vert</mat-icon>
@@ -192,6 +184,25 @@
192184
}
193185
</mat-menu>
194186
</div>
187+
<div class="demo-menu-section">
188+
<p>disabledInteractive (should not open)</p>
189+
<mat-toolbar>
190+
<button
191+
disabled
192+
[disabledInteractive]="true"
193+
matIconButton
194+
[mat-menu-trigger-for]="disabledInteractiveMenu"
195+
>
196+
<mat-icon>more_vert</mat-icon>
197+
</button>
198+
</mat-toolbar>
199+
200+
<mat-menu #disabledInteractiveMenu="matMenu">
201+
@for (item of items; track item) {
202+
<button mat-menu-item [disabled]="item.disabled">{{ item.text }}</button>
203+
}
204+
</mat-menu>
205+
</div>
195206
</div>
196207

197208
<div class="demo-context-menu-area" [matContextMenuTriggerFor]="contextMenu">

src/material/menu/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ ng_project(
111111
"//src/cdk/overlay",
112112
"//src/cdk/scrolling",
113113
"//src/cdk/testing/private",
114+
"//src/material/button",
114115
"//src/material/core",
115116
],
116117
)

src/material/menu/menu-trigger-base.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import {
2222
} from '@angular/cdk/overlay';
2323
import {TemplatePortal} from '@angular/cdk/portal';
2424
import {
25+
booleanAttribute,
2526
ChangeDetectorRef,
2627
Directive,
2728
ElementRef,
2829
EventEmitter,
2930
inject,
3031
InjectionToken,
3132
Injector,
33+
input,
3234
NgZone,
3335
OnDestroy,
3436
ViewContainerRef,
@@ -139,6 +141,9 @@ export abstract class MatMenuTriggerBase implements OnDestroy {
139141
}
140142
private _menuInternal: MatMenuPanel | null = null;
141143

144+
/** Whether the host component is disabled. Needed to correctly handle disabledInteractive. */
145+
readonly triggerIsDisabled = input(false, {alias: 'disabled', transform: booleanAttribute});
146+
142147
/** Event emitted when the associated menu is opened. */
143148
abstract menuOpened: EventEmitter<void>;
144149

@@ -191,6 +196,10 @@ export abstract class MatMenuTriggerBase implements OnDestroy {
191196

192197
/** Internal method to open menu providing option to auto focus on first item. */
193198
protected _openMenu(autoFocus: boolean): void {
199+
if (this.triggerIsDisabled()) {
200+
return;
201+
}
202+
194203
const menu = this._menu;
195204

196205
if (this._menuOpen || !menu) {

src/material/menu/menu.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
provideFakeDirectionality,
4343
} from '../../cdk/testing/private';
4444
import {MATERIAL_ANIMATIONS, MatRipple} from '../core';
45+
import {MatButton} from '@angular/material/button';
4546
import {MatMenu, MatMenuItem} from './index';
4647
import {
4748
MAT_MENU_DEFAULT_OPTIONS,
@@ -1279,6 +1280,23 @@ describe('MatMenu', () => {
12791280
}));
12801281
});
12811282

1283+
it('does not open if the trigger element is disabled (including disabledInteractive)', fakeAsync(() => {
1284+
const fixture = TestBed.createComponent(DisabledMenu);
1285+
fixture.detectChanges();
1286+
1287+
const trigger = fixture.componentInstance.triggerEl.nativeElement;
1288+
trigger.click();
1289+
fixture.detectChanges();
1290+
tick(500);
1291+
expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull();
1292+
1293+
dispatchKeyboardEvent(trigger, 'keydown', ENTER);
1294+
trigger.click();
1295+
fixture.detectChanges();
1296+
tick(500);
1297+
expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull();
1298+
}));
1299+
12821300
describe('positions', () => {
12831301
let fixture: ComponentFixture<PositionedMenu>;
12841302
let trigger: HTMLElement;
@@ -2634,6 +2652,20 @@ class SimpleMenu {
26342652
})
26352653
class SimpleMenuOnPush extends SimpleMenu {}
26362654

2655+
@Component({
2656+
template: `
2657+
<button mat-button disabled [disabledInteractive]="true"
2658+
[matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
2659+
<mat-menu #menu="matMenu">
2660+
<button mat-menu-item> Action! </button>
2661+
</mat-menu>
2662+
`,
2663+
imports: [MatButton, MatMenuTrigger, MatMenu, MatMenuItem],
2664+
})
2665+
class DisabledMenu {
2666+
@ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef<HTMLElement>;
2667+
}
2668+
26372669
@Component({
26382670
template: `
26392671
<button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
@@ -2666,7 +2698,7 @@ interface TestableMenu {
26662698
class OverlapMenu implements TestableMenu {
26672699
@Input() overlapTrigger: boolean = false;
26682700
@ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger;
2669-
@ViewChild('triggerEl') triggerEl!: ElementRef<HTMLElement>;
2701+
@ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef<HTMLElement>;
26702702
}
26712703

26722704
@Component({

0 commit comments

Comments
 (0)