diff --git a/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.spec.ts b/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.spec.ts index 0f89c8c1b545..3f8ddd4dca8b 100644 --- a/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.spec.ts +++ b/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.spec.ts @@ -53,7 +53,7 @@ describe('OverlayKeyboardDispatcher', () => { const overlayOne = overlay.create(); const overlayTwo = overlay.create(); const overlayOneSpy = jasmine.createSpy('overlayOne keyboard event spy'); - const overlayTwoSpy = jasmine.createSpy('overlayOne keyboard event spy'); + const overlayTwoSpy = jasmine.createSpy('overlayTwo keyboard event spy'); overlayOne.keydownEvents().subscribe(overlayOneSpy); overlayTwo.keydownEvents().subscribe(overlayTwoSpy); @@ -143,6 +143,20 @@ describe('OverlayKeyboardDispatcher', () => { expect(body.removeEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function), true); }); + it('should skip overlays that do not have keydown event subscriptions', () => { + const overlayOne = overlay.create(); + const overlayTwo = overlay.create(); + const overlayOneSpy = jasmine.createSpy('overlayOne keyboard event spy'); + + overlayOne.keydownEvents().subscribe(overlayOneSpy); + keyboardDispatcher.add(overlayOne); + keyboardDispatcher.add(overlayTwo); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + + expect(overlayOneSpy).toHaveBeenCalled(); + }); + }); diff --git a/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts b/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts index 89d5ab345d93..a0886b218a88 100644 --- a/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts +++ b/src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts @@ -75,11 +75,19 @@ export class OverlayKeyboardDispatcher implements OnDestroy { /** Keyboard event listener that will be attached to the body. */ private _keydownListener = (event: KeyboardEvent) => { - if (this._attachedOverlays.length) { - // Dispatch the keydown event to the top overlay. We want to target the most recent overlay, - // rather than trying to match where the event came from, because some components might open - // an overlay, but keep focus on a trigger element (e.g. for select and autocomplete). - this._attachedOverlays[this._attachedOverlays.length - 1]._keydownEvents.next(event); + const overlays = this._attachedOverlays; + + for (let i = overlays.length - 1; i > -1; i--) { + // Dispatch the keydown event to the top overlay which has subscribers to its keydown events. + // We want to target the most recent overlay, rather than trying to match where the event came + // from, because some components might open an overlay, but keep focus on a trigger element + // (e.g. for select and autocomplete). We skip overlays without keydown event subscriptions, + // because we don't want overlays that don't handle keyboard events to block the ones below + // them that do. + if (overlays[i]._keydownEventSubscriptions > 0) { + overlays[i]._keydownEvents.next(event); + break; + } } } } diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 6db556d858b7..27ff70f27455 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -30,10 +30,22 @@ export class OverlayRef implements PortalOutlet { private _backdropClick: Subject = new Subject(); private _attachments = new Subject(); private _detachments = new Subject(); + private _keydownEventsObservable: Observable = Observable.create(observer => { + const subscription = this._keydownEvents.subscribe(observer); + this._keydownEventSubscriptions++; + + return () => { + subscription.unsubscribe(); + this._keydownEventSubscriptions--; + }; + }); /** Stream of keydown events dispatched to this overlay. */ _keydownEvents = new Subject(); + /** Amount of subscriptions to the keydown events. */ + _keydownEventSubscriptions = 0; + constructor( private _portalOutlet: PortalOutlet, private _host: HTMLElement, @@ -217,7 +229,7 @@ export class OverlayRef implements PortalOutlet { /** Gets an observable of keydown events targeted to this overlay. */ keydownEvents(): Observable { - return this._keydownEvents.asObservable(); + return this._keydownEventsObservable; } /** Gets the the current overlay configuration, which is immutable. */ diff --git a/src/lib/bottom-sheet/bottom-sheet-ref.ts b/src/lib/bottom-sheet/bottom-sheet-ref.ts index 3c964abe9df4..9b7765884a31 100644 --- a/src/lib/bottom-sheet/bottom-sheet-ref.ts +++ b/src/lib/bottom-sheet/bottom-sheet-ref.ts @@ -62,7 +62,7 @@ export class MatBottomSheetRef { if (!containerInstance.bottomSheetConfig.disableClose) { merge( _overlayRef.backdropClick(), - _overlayRef._keydownEvents.pipe(filter(event => event.keyCode === ESCAPE)) + _overlayRef.keydownEvents().pipe(filter(event => event.keyCode === ESCAPE)) ).subscribe(() => this.dismiss()); } }