diff --git a/e2e/components/dialog/dialog.e2e.ts b/e2e/components/dialog/dialog.e2e.ts index 7c160f62b561..7a9d862384c0 100644 --- a/e2e/components/dialog/dialog.e2e.ts +++ b/e2e/components/dialog/dialog.e2e.ts @@ -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(); diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 06576496cbde..c9948e53b7b4 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -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 diff --git a/src/lib/dialog/dialog-ref.ts b/src/lib/dialog/dialog-ref.ts index bd9a45df84ac..bbbd31664792 100644 --- a/src/lib/dialog/dialog-ref.ts +++ b/src/lib/dialog/dialog-ref.ts @@ -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 { /** Subject for notifying the user that the dialog has finished closing. */ private _afterClosed: Subject = new Subject(); - constructor(private _overlayRef: OverlayRef) { } + constructor(private _overlayRef: OverlayRef, public config: MdDialogConfig) { } /** * Close the dialog. diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index 103d0474a7e3..dbb02ac81aed 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -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); +} diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index a4bd05585fe1..90cba743464a 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -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[] = []; private _afterAllClosedAtThisLevel = new Subject(); private _afterOpenAtThisLevel = new Subject>(); + private _boundKeydown = this._handleKeydown.bind(this); /** Keeps track of the currently-open dialogs. */ get _openDialogs(): MdDialogRef[] { @@ -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 { // Create a reference to the dialog we're creating in order to give the user a handle // to modify and close it. - let dialogRef = > new MdDialogRef(overlayRef); + let dialogRef = > 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(); + } + } } /**