From 4e120723eb8f281a23696e177d698dea26e2a0ac Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 22 Aug 2017 20:26:19 +0200 Subject: [PATCH] fix(dialog): move focus into container if no focusable elements are found (#6524) Fixes #6513. --- src/lib/dialog/dialog-container.ts | 9 ++++++++- src/lib/dialog/dialog.scss | 1 + src/lib/dialog/dialog.spec.ts | 20 ++++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 6ef4ddaf6c89..83bc4a1d8c73 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -65,6 +65,7 @@ export function throwMdDialogContentAlreadyAttachedError() { ], host: { 'class': 'mat-dialog-container', + 'tabindex': '-1', '[attr.role]': '_config?.role', '[attr.aria-labelledby]': '_ariaLabelledBy', '[attr.aria-describedby]': '_config?.ariaDescribedBy || null', @@ -143,7 +144,13 @@ export class MdDialogContainer extends BasePortalHost { // If were to attempt to focus immediately, then the content of the dialog would not yet be // ready in instances where change detection has to run first. To deal with this, we simply // wait for the microtask queue to be empty. - this._focusTrap.focusInitialElementWhenReady(); + this._focusTrap.focusInitialElementWhenReady().then(hasMovedFocus => { + // If we didn't find any focusable elements inside the dialog, focus the + // container so the user can't tab into other elements behind it. + if (!hasMovedFocus) { + this._elementRef.nativeElement.focus(); + } + }); } /** Restores focus to the element that was focused before the dialog opened. */ diff --git a/src/lib/dialog/dialog.scss b/src/lib/dialog/dialog.scss index dd6d21c965a0..ab09312d4aeb 100644 --- a/src/lib/dialog/dialog.scss +++ b/src/lib/dialog/dialog.scss @@ -17,6 +17,7 @@ $mat-dialog-button-margin: 8px !default; box-sizing: border-box; overflow: auto; max-width: $mat-dialog-max-width; + outline: 0; // The dialog container should completely fill its parent overlay element. width: 100%; diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index ca09dc13883c..93beb70a5161 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -721,6 +721,17 @@ describe('MdDialog', () => { document.body.removeChild(input); })); + it('should move focus to the container if there are no focusable elements in the dialog', + fakeAsync(() => { + dialog.open(DialogWithoutFocusableElements); + + viewContainerFixture.detectChanges(); + flushMicrotasks(); + + expect(document.activeElement.tagName) + .toBe('MD-DIALOG-CONTAINER', 'Expected dialog container to be focused.'); + })); + }); describe('dialog content elements', () => { @@ -952,6 +963,9 @@ class DialogWithInjectedData { constructor(@Inject(MD_DIALOG_DATA) public data: any) { } } +@Component({template: '

Pasta

'}) +class DialogWithoutFocusableElements {} + // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_DIRECTIVES = [ @@ -961,7 +975,8 @@ const TEST_DIRECTIVES = [ DirectiveWithViewContainer, ComponentWithOnPushViewContainer, ContentElementDialog, - DialogWithInjectedData + DialogWithInjectedData, + DialogWithoutFocusableElements ]; @NgModule({ @@ -973,7 +988,8 @@ const TEST_DIRECTIVES = [ ComponentWithTemplateRef, PizzaMsg, ContentElementDialog, - DialogWithInjectedData + DialogWithInjectedData, + DialogWithoutFocusableElements, ], }) class DialogTestModule { }