diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index f0fb04b6da6c..f8efe8da2d8e 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -66,6 +66,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', @@ -155,7 +156,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 398fd269caa1..02064c04fc7d 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -679,6 +679,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', () => { @@ -893,6 +904,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 = [ @@ -901,7 +915,8 @@ const TEST_DIRECTIVES = [ DirectiveWithViewContainer, ComponentWithOnPushViewContainer, ContentElementDialog, - DialogWithInjectedData + DialogWithInjectedData, + DialogWithoutFocusableElements ]; @NgModule({ @@ -912,7 +927,8 @@ const TEST_DIRECTIVES = [ ComponentWithChildViewContainer, PizzaMsg, ContentElementDialog, - DialogWithInjectedData + DialogWithInjectedData, + DialogWithoutFocusableElements, ], }) class DialogTestModule { }