fix(overlay): don't dispatch key events to overlays that don't handle them (#11810)

This commit is contained in:
Kristiyan Kostadinov
2018-06-29 18:35:57 +02:00
committed by Joey Perrott
parent c59abc6c8f
commit c3fdc32445
4 changed files with 42 additions and 8 deletions
@@ -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();
});
});
@@ -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;
}
}
}
}
+13 -1
View File
@@ -31,10 +31,22 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
private _backdropClick: Subject<MouseEvent> = new Subject();
private _attachments = new Subject<void>();
private _detachments = new Subject<void>();
private _keydownEventsObservable: Observable<KeyboardEvent> = 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<KeyboardEvent>();
/** Amount of subscriptions to the keydown events. */
_keydownEventSubscriptions = 0;
constructor(
private _portalOutlet: PortalOutlet,
private _host: HTMLElement,
@@ -218,7 +230,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
/** Gets an observable of keydown events targeted to this overlay. */
keydownEvents(): Observable<KeyboardEvent> {
return this._keydownEvents.asObservable();
return this._keydownEventsObservable;
}
/** Gets the the current overlay configuration, which is immutable. */
+1 -1
View File
@@ -62,7 +62,7 @@ export class MatBottomSheetRef<T = any, R = any> {
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());
}
}