diff --git a/src/lib/dialog/dialog-config.ts b/src/lib/dialog/dialog-config.ts index 71ff36ad6621..64318cb7ea31 100644 --- a/src/lib/dialog/dialog-config.ts +++ b/src/lib/dialog/dialog-config.ts @@ -81,6 +81,11 @@ export class MatDialogConfig { /** ID of the element that describes the dialog. */ ariaDescribedBy?: string | null = null; + /** Aria label to assign to the dialog element */ + ariaLabel?: string | null = null; + + /** Whether the dialog should focus the first focusable element on open. */ + autoFocus?: boolean = true; // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling. } diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 5753f2d57169..b4a89a87120e 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -71,7 +71,8 @@ export function throwMatDialogContentAlreadyAttachedError() { 'class': 'mat-dialog-container', 'tabindex': '-1', '[attr.role]': '_config?.role', - '[attr.aria-labelledby]': '_ariaLabelledBy', + '[attr.aria-labelledby]': '_config?.ariaLabel ? null : _ariaLabelledBy', + '[attr.aria-label]': '_config?.ariaLabel', '[attr.aria-describedby]': '_config?.ariaDescribedBy || null', '[@slideDialog]': '_state', '(@slideDialog.start)': '_onAnimationStart($event)', @@ -144,7 +145,9 @@ export class MatDialogContainer extends BasePortalOutlet { // 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(); + if (this._config.autoFocus) { + this._focusTrap.focusInitialElementWhenReady(); + } } /** Restores focus to the element that was focused before the dialog opened. */ diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index 66f6a86004e2..3670d44f61ce 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -788,6 +788,18 @@ describe('MatDialog', () => { .toBe('INPUT', 'Expected first tabbable element (input) in the dialog to be focused.'); })); + it('should allow disabling focus of the first tabbable element', fakeAsync(() => { + dialog.open(PizzaMsg, { + viewContainerRef: testViewContainerRef, + autoFocus: false + }); + + viewContainerFixture.detectChanges(); + flushMicrotasks(); + + expect(document.activeElement.tagName).not.toBe('INPUT'); + })); + it('should re-focus trigger element when dialog closes', fakeAsync(() => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); @@ -930,6 +942,32 @@ describe('MatDialog', () => { })); }); + + describe('aria-label', () => { + it('should be able to set a custom aria-label', () => { + dialog.open(PizzaMsg, { + ariaLabel: 'Hello there', + viewContainerRef: testViewContainerRef + }); + viewContainerFixture.detectChanges(); + + const container = overlayContainerElement.querySelector('mat-dialog-container')!; + expect(container.getAttribute('aria-label')).toBe('Hello there'); + }); + + it('should not set the aria-labelledby automatically if it has an aria-label', fakeAsync(() => { + dialog.open(ContentElementDialog, { + ariaLabel: 'Hello there', + viewContainerRef: testViewContainerRef + }); + viewContainerFixture.detectChanges(); + tick(); + viewContainerFixture.detectChanges(); + + const container = overlayContainerElement.querySelector('mat-dialog-container')!; + expect(container.hasAttribute('aria-labelledby')).toBe(false); + })); + }); }); describe('MatDialog with a parent MatDialog', () => {