diff --git a/src/material/sidenav/BUILD.bazel b/src/material/sidenav/BUILD.bazel index 52b318181a63..29327a427d67 100644 --- a/src/material/sidenav/BUILD.bazel +++ b/src/material/sidenav/BUILD.bazel @@ -24,6 +24,7 @@ ng_module( "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/keycodes", + "//src/cdk/overlay", "//src/cdk/scrolling", "//src/material/core", "@npm//@angular/core", @@ -57,6 +58,7 @@ ng_test_library( "//src/cdk/a11y", "//src/cdk/bidi", "//src/cdk/keycodes", + "//src/cdk/overlay", "//src/cdk/platform", "//src/cdk/scrolling", "//src/cdk/testing", diff --git a/src/material/sidenav/drawer.spec.ts b/src/material/sidenav/drawer.spec.ts index 7e21171f5b97..7fd58b067661 100644 --- a/src/material/sidenav/drawer.spec.ts +++ b/src/material/sidenav/drawer.spec.ts @@ -2,6 +2,7 @@ import {A11yModule} from '@angular/cdk/a11y'; import {Direction} from '@angular/cdk/bidi'; import {ESCAPE} from '@angular/cdk/keycodes'; import {CdkScrollable} from '@angular/cdk/scrolling'; +import {OverlayKeyboardDispatcher, OverlayRef} from '@angular/cdk/overlay'; import { createKeyboardEvent, dispatchEvent, @@ -22,7 +23,13 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MatDrawer, MatDrawerContainer, MatSidenavModule} from './index'; describe('MatDrawer', () => { + let fakeKeyboardDispatcher: OverlayKeyboardDispatcher; + beforeEach(waitForAsync(() => { + fakeKeyboardDispatcher = { + _attachedOverlays: [] as OverlayRef[], + } as OverlayKeyboardDispatcher; + TestBed.configureTestingModule({ imports: [ MatSidenavModule, @@ -39,6 +46,12 @@ describe('MatDrawer', () => { IndirectDescendantDrawer, NestedDrawerContainers, ], + providers: [ + { + provide: OverlayKeyboardDispatcher, + useValue: fakeKeyboardDispatcher, + }, + ], }); })); @@ -237,6 +250,33 @@ describe('MatDrawer', () => { expect(event.defaultPrevented).toBe(false); })); + it('should not close when pressing escape while an overlay is open', fakeAsync(() => { + const fixture = TestBed.createComponent(BasicTestApp); + fixture.detectChanges(); + + const testComponent: BasicTestApp = fixture.debugElement.componentInstance; + const drawer = fixture.debugElement.query(By.directive(MatDrawer))!; + + drawer.componentInstance.open(); + fixture.detectChanges(); + tick(); + + expect(testComponent.closeCount).withContext('Expected no close events.').toBe(0); + expect(testComponent.closeStartCount).withContext('Expected no close start events.').toBe(0); + + fakeKeyboardDispatcher._attachedOverlays.push(null!); + const event = createKeyboardEvent('keydown', ESCAPE); + dispatchEvent(drawer.nativeElement, event); + fixture.detectChanges(); + flush(); + + expect(testComponent.closeCount).withContext('Expected still no close events.').toBe(0); + expect(testComponent.closeStartCount) + .withContext('Expected still no close start events.') + .toBe(0); + expect(event.defaultPrevented).toBe(false); + })); + it('should fire the open event when open on init', fakeAsync(() => { const fixture = TestBed.createComponent(DrawerSetToOpenedTrue); diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index efbb55507e75..1e51ce3c00dd 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -16,6 +16,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {ESCAPE, hasModifierKey} from '@angular/cdk/keycodes'; import {Platform} from '@angular/cdk/platform'; +import {OverlayKeyboardDispatcher} from '@angular/cdk/overlay'; import {CdkScrollable, ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling'; import {DOCUMENT} from '@angular/common'; import { @@ -179,6 +180,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy { private _renderer = inject(Renderer2); private readonly _interactivityChecker = inject(InteractivityChecker); private _doc = inject(DOCUMENT, {optional: true})!; + private _keyboardDispatcher = inject(OverlayKeyboardDispatcher, {optional: true}); _container? = inject(MAT_DRAWER_CONTAINER, {optional: true}); private _focusTrap: FocusTrap | null = null; @@ -360,19 +362,22 @@ export class MatDrawer implements AfterViewInit, OnDestroy { this._ngZone.runOutsideAngular(() => { const element = this._elementRef.nativeElement; (fromEvent(element, 'keydown') as Observable) - .pipe( - filter(event => { - return event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event); - }), - takeUntil(this._destroyed), - ) - .subscribe(event => - this._ngZone.run(() => { - this.close(); - event.stopPropagation(); - event.preventDefault(); - }), - ); + .pipe(takeUntil(this._destroyed)) + .subscribe(event => { + // Skip keyboard events if there are open overlays since they may be + // placed inside the sidenav and cause it to close unexpectedly. + if (this._keyboardDispatcher && this._keyboardDispatcher._attachedOverlays.length > 0) { + return; + } + + if (event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event)) { + this._ngZone.run(() => { + this.close(); + event.stopPropagation(); + event.preventDefault(); + }); + } + }); this._eventCleanups = [ this._renderer.listen(element, 'transitionrun', this._handleTransitionEvent),