diff --git a/src/lib/menu/_menu-theme.scss b/src/lib/menu/_menu-theme.scss index 667a4f5991ca..b6175bc31ad7 100644 --- a/src/lib/menu/_menu-theme.scss +++ b/src/lib/menu/_menu-theme.scss @@ -16,7 +16,9 @@ color: mat-color($foreground, 'text'); &[disabled] { - color: mat-color($foreground, 'disabled'); + &, &::after { + color: mat-color($foreground, 'disabled'); + } } } diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index d4062f9d4db9..5f2430c10743 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -47,7 +47,7 @@ export const _MatMenuItemMixinBase = mixinDisableRipple(mixinDisabled(MatMenuIte '[attr.aria-disabled]': 'disabled.toString()', '[attr.disabled]': 'disabled || null', '(click)': '_checkDisabled($event)', - '(mouseenter)': '_emitHoverEvent()', + '(mouseenter)': '_handleMouseEnter()', }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, @@ -121,10 +121,8 @@ export class MatMenuItem extends _MatMenuItemMixinBase } /** Emits to the hover stream. */ - _emitHoverEvent() { - if (!this.disabled) { - this._hovered.next(this); - } + _handleMouseEnter() { + this._hovered.next(this); } /** Gets the label to be used when determining whether the option should be focused. */ diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index 86a1a6a89da4..82f6c91ddb89 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -152,7 +152,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { if (this.triggersSubmenu()) { // Subscribe to changes in the hovered item in order to toggle the panel. this._hoverSubscription = this._parentMenu._hovered() - .pipe(filter(active => active === this._menuItemInstance)) + .pipe(filter(active => active === this._menuItemInstance && !active.disabled)) .subscribe(() => { this._openedByMouse = true; this.openMenu(); diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 5a0e01d1914c..b0067f03f72e 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -910,6 +910,57 @@ describe('MatMenu', () => { .toBe(1, 'Expected one open menu'); })); + it('should close submenu when hovering over disabled sibling item', fakeAsync(() => { + compileTestComponent(); + instance.rootTriggerEl.nativeElement.click(); + fixture.detectChanges(); + tick(500); + + const items = fixture.debugElement.queryAll(By.directive(MatMenuItem)); + + dispatchFakeEvent(items[0].nativeElement, 'mouseenter'); + fixture.detectChanges(); + tick(500); + + expect(overlay.querySelectorAll('.mat-menu-panel').length) + .toBe(2, 'Expected two open menus'); + + items[1].componentInstance.disabled = true; + fixture.detectChanges(); + + // Invoke the handler directly since the fake events are flaky on disabled elements. + items[1].componentInstance._handleMouseEnter(); + fixture.detectChanges(); + tick(500); + + expect(overlay.querySelectorAll('.mat-menu-panel').length) + .toBe(1, 'Expected one open menu'); + })); + + it('should not open submenu when hovering over disabled trigger', fakeAsync(() => { + compileTestComponent(); + instance.rootTriggerEl.nativeElement.click(); + fixture.detectChanges(); + tick(500); + + expect(overlay.querySelectorAll('.mat-menu-panel').length) + .toBe(1, 'Expected one open menu'); + + const item = fixture.debugElement.query(By.directive(MatMenuItem)); + + item.componentInstance.disabled = true; + fixture.detectChanges(); + + // Invoke the handler directly since the fake events are flaky on disabled elements. + item.componentInstance._handleMouseEnter(); + fixture.detectChanges(); + tick(500); + + expect(overlay.querySelectorAll('.mat-menu-panel').length) + .toBe(1, 'Expected to remain at one open menu'); + })); + + it('should open a nested menu when its trigger is clicked', () => { compileTestComponent(); instance.rootTriggerEl.nativeElement.click();