fix(dialog): escape key not working once element loses focus (#3082)

Fixes issue in the dialog that prevented the user from being able to close via the escape key, if any of the elements inside the dialog loses focus.

Fixes #3009.
This commit is contained in:
Kristiyan Kostadinov
2017-02-15 01:48:44 +01:00
committed by Andrew Seguin
parent 1547440a04
commit a08dc55802
5 changed files with 47 additions and 25 deletions
+10
View File
@@ -35,6 +35,16 @@ describe('dialog', () => {
});
});
it('should close by pressing escape when the first tabbable element has lost focus', () => {
element(by.id('default')).click();
waitForDialog().then(() => {
clickElementAtPoint('md-dialog-container', { x: 0, y: 0 });
pressKeys(Key.ESCAPE);
expectToExist('md-dialog-container', false);
});
});
it('should close by clicking on the "close" button', () => {
element(by.id('default')).click();
-11
View File
@@ -27,7 +27,6 @@ import 'rxjs/add/operator/first';
host: {
'[class.mat-dialog-container]': 'true',
'[attr.role]': 'dialogConfig?.role',
'(keydown.escape)': 'handleEscapeKey()',
},
encapsulation: ViewEncapsulation.None,
})
@@ -93,16 +92,6 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
});
}
/**
* Handles the user pressing the Escape key.
* @docs-private
*/
handleEscapeKey() {
if (!this.dialogConfig.disableClose) {
this.dialogRef.close();
}
}
ngOnDestroy() {
// When the dialog is destroyed, return focus to the element that originally had it before
// the dialog was opened. Wait for the DOM to finish settling before changing the focus so
+2 -1
View File
@@ -1,4 +1,5 @@
import {OverlayRef} from '../core';
import {MdDialogConfig} from './dialog-config';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -17,7 +18,7 @@ export class MdDialogRef<T> {
/** Subject for notifying the user that the dialog has finished closing. */
private _afterClosed: Subject<any> = new Subject();
constructor(private _overlayRef: OverlayRef) { }
constructor(private _overlayRef: OverlayRef, public config: MdDialogConfig) { }
/**
* Close the dialog.
+15 -12
View File
@@ -15,13 +15,12 @@ import {NgModule,
Injector,
Inject,
} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdDialogModule} from './index';
import {MdDialog} from './dialog';
import {OverlayContainer} from '../core';
import {MdDialogRef} from './dialog-ref';
import {MdDialogContainer} from './dialog-container';
import {MD_DIALOG_DATA} from './dialog-injector';
import {ESCAPE} from '../core/keyboard/keycodes';
describe('MdDialog', () => {
@@ -136,11 +135,7 @@ describe('MdDialog', () => {
viewContainerFixture.detectChanges();
let dialogContainer: MdDialogContainer =
viewContainerFixture.debugElement.query(By.directive(MdDialogContainer)).componentInstance;
// Fake the user pressing the escape key by calling the handler directly.
dialogContainer.handleEscapeKey();
dispatchKeydownEvent(document, ESCAPE);
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
});
@@ -324,11 +319,7 @@ describe('MdDialog', () => {
viewContainerFixture.detectChanges();
let dialogContainer: MdDialogContainer = viewContainerFixture.debugElement.query(
By.directive(MdDialogContainer)).componentInstance;
// Fake the user pressing the escape key by calling the handler directly.
dialogContainer.handleEscapeKey();
dispatchKeydownEvent(document, ESCAPE);
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy();
});
@@ -565,3 +556,15 @@ const TEST_DIRECTIVES = [
],
})
class DialogTestModule { }
// TODO(crisbeto): switch to using function from common testing utils once #2943 is merged.
function dispatchKeydownEvent(element: Node, keyCode: number) {
let event: any = document.createEvent('KeyboardEvent');
(event.initKeyEvent || event.initKeyboardEvent).bind(event)(
'keydown', true, true, window, 0, 0, 0, 0, 0, keyCode);
Object.defineProperty(event, 'keyCode', {
get: function() { return keyCode; }
});
element.dispatchEvent(event);
}
+20 -1
View File
@@ -3,6 +3,7 @@ import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core';
import {extendObject} from '../core/util/object-extend';
import {ESCAPE} from '../core/keyboard/keycodes';
import {DialogInjector} from './dialog-injector';
import {MdDialogConfig} from './dialog-config';
import {MdDialogRef} from './dialog-ref';
@@ -22,6 +23,7 @@ export class MdDialog {
private _openDialogsAtThisLevel: MdDialogRef<any>[] = [];
private _afterAllClosedAtThisLevel = new Subject<void>();
private _afterOpenAtThisLevel = new Subject<MdDialogRef<any>>();
private _boundKeydown = this._handleKeydown.bind(this);
/** Keeps track of the currently-open dialogs. */
get _openDialogs(): MdDialogRef<any>[] {
@@ -65,6 +67,10 @@ export class MdDialog {
let dialogRef =
this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config);
if (!this._openDialogs.length && !this._parentDialog) {
document.addEventListener('keydown', this._boundKeydown);
}
this._openDialogs.push(dialogRef);
dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
this._afterOpen.next(dialogRef);
@@ -129,7 +135,7 @@ export class MdDialog {
config?: MdDialogConfig): MdDialogRef<T> {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
let dialogRef = <MdDialogRef<T>> new MdDialogRef(overlayRef);
let dialogRef = <MdDialogRef<T>> new MdDialogRef(overlayRef, config);
if (!config.disableClose) {
// When the dialog backdrop is clicked, we want to close it.
@@ -199,9 +205,22 @@ export class MdDialog {
// no open dialogs are left, call next on afterAllClosed Subject
if (!this._openDialogs.length) {
this._afterAllClosed.next();
document.removeEventListener('keydown', this._boundKeydown);
}
}
}
/**
* Handles global key presses while there are open dialogs. Closes the
* top dialog when the user presses escape.
*/
private _handleKeydown(event: KeyboardEvent): void {
let topDialog = this._openDialogs[this._openDialogs.length - 1];
if (event.keyCode === ESCAPE && topDialog && !topDialog.config.disableClose) {
topDialog.close();
}
}
}
/**