Skip to content

Commit 2342929

Browse files
committed
fix(material/dialog): dialog name not read by screenreader on mac chrome or firefox
Fixes bug in Angular Components Dialog component where the aria-labelledby and aria-describedby values are not being read by Chrome or Firefox when using a mac. This checks if there is an html attribute of aria-labelledby or aria-describedby and grabs the innerText or aria-label value and uses that value as the aria-label for the dialog only on mac using chrome or firefox. Fixes b/274674581
1 parent db4c3c4 commit 2342929

File tree

3 files changed

+72
-31
lines changed

3 files changed

+72
-31
lines changed

src/dev-app/dialog/dialog-demo.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class DialogDemo {
7272
minHeight: '',
7373
maxWidth: '',
7474
maxHeight: '',
75+
ariaLabelledBy: 'jazz-title',
7576
position: {
7677
top: '',
7778
bottom: '',
@@ -145,7 +146,7 @@ export class DialogDemo {
145146
selector: 'demo-jazz-dialog',
146147
template: `
147148
<div cdkDrag cdkDragRootElement=".cdk-overlay-pane">
148-
<p>Order printer ink refills.</p>
149+
<p id="jazz-title">Order printer ink refills.</p>
149150
150151
<mat-form-field>
151152
<mat-label>How many?</mat-label>
@@ -214,7 +215,7 @@ export class JazzDialog {
214215
}
215216
`,
216217
template: `
217-
<h2 mat-dialog-title>Neptune</h2>
218+
<h2 id="jazz-title" mat-dialog-title>Neptune</h2>
218219
219220
<mat-dialog-content>
220221
<p>
@@ -277,7 +278,7 @@ export class ContentElementDialog {
277278
}
278279
`,
279280
template: `
280-
<h2 mat-dialog-title>Neptune</h2>
281+
<h2 id="jazz-title" mat-dialog-title>Neptune</h2>
281282
282283
<mat-dialog-content>
283284
<iframe style="border: 0" src="https://en.wikipedia.org/wiki/Neptune"></iframe>

src/material/dialog/dialog-container.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {FocusMonitor, FocusTrapFactory, InteractivityChecker} from '@angular/cdk
1010
import {OverlayRef} from '@angular/cdk/overlay';
1111
import {DOCUMENT} from '@angular/common';
1212
import {
13-
AfterViewInit,
1413
ChangeDetectionStrategy,
1514
Component,
1615
ComponentRef,
@@ -19,6 +18,7 @@ import {
1918
Inject,
2019
NgZone,
2120
OnDestroy,
21+
OnInit,
2222
Optional,
2323
ViewEncapsulation,
2424
ANIMATION_MODULE_TYPE,
@@ -74,7 +74,7 @@ export const CLOSE_ANIMATION_DURATION = 75;
7474
})
7575
export class MatDialogContainer
7676
extends CdkDialogContainer<MatDialogConfig>
77-
implements AfterViewInit, OnDestroy
77+
implements OnInit, OnDestroy
7878
{
7979
/** Emits when an animation state changes. */
8080
_animationStateChanged = new EventEmitter<LegacyDialogAnimationEvent>();
@@ -126,29 +126,65 @@ export class MatDialogContainer
126126
);
127127
}
128128

129+
/** Get userAgent to check for useragent operating system */
130+
private _getUserPlatform = (): string => {
131+
const window = this._getWindow();
132+
let userAgent = window.navigator.userAgent.toLowerCase(),
133+
macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i,
134+
windowsPlatforms = /(win32|win64|windows|wince)/i,
135+
iosPlatforms = /(iphone|ipad|ipod)/i,
136+
os = '';
137+
if (macosPlatforms.test(userAgent)) {
138+
os = 'macos';
139+
} else if (iosPlatforms.test(userAgent)) {
140+
os = 'ios';
141+
} else if (windowsPlatforms.test(userAgent)) {
142+
os = 'windows';
143+
} else if (/android/.test(userAgent)) {
144+
os = 'android';
145+
} else if (!os && /linux/.test(userAgent)) {
146+
os = 'linux';
147+
}
148+
console.log('userAgent:');
149+
console.log(userAgent);
150+
console.log(`os:`);
151+
console.log(os);
152+
// const platform = window.navigator.userAgent.platform
153+
return os;
154+
};
155+
129156
/** Get Dialog name from aria attributes */
130-
private _getDialogName = async (): Promise<string> => {
131-
const configData = this._config;
132-
/**_ariaLabelledByQueue and _ariaDescribedByQueue are created if ariaLabelledBy
133-
or ariaDescribedBy values are applied to the dialog config */
134-
const ariaLabelledByRefId = await this._ariaLabelledByQueue[0];
135-
const ariaDescribedByRefId = await this._ariaDescribedByQueue[0];
136-
/** Get Element to get name/title from if ariaLabelledBy or ariaDescribedBy */
157+
private _getDialogName = (): string => {
158+
// _ariaLabelledByQueue and _ariaDescribedByQueue are created if ariaLabelledBy
159+
// or ariaDescribedBy values are applied to the dialog config
160+
const ariaLabelledByRefId = this._ariaLabelledByQueue[0];
161+
const ariaDescribedByRefId = this._ariaDescribedByQueue[0];
162+
console.log(`ariaLabelledByRefId: ${ariaLabelledByRefId}`);
163+
console.log(`ariaDescribedByRefId: ${ariaDescribedByRefId}`);
164+
// Get Element to get name/title from if ariaLabelledBy or ariaDescribedBy
137165
const dialogNameElement =
138166
document.getElementById(ariaLabelledByRefId) || document.getElementById(ariaDescribedByRefId);
139167
const dialogNameInnerText =
140-
/** If no ariaLabelledBy, ariaDescribedBy, or ariaLabel, create default aria label */
168+
// If no ariaLabelledBy, ariaDescribedBy, or ariaLabel, create default aria label
141169
!dialogNameElement && !this._config.ariaLabel
142170
? 'Dialog Modal'
143-
: /** Otherwise prioritize use of ariaLabel */
171+
: // : Otherwise prioritize use of ariaLabel
144172
this._config.ariaLabel || dialogNameElement?.innerText || dialogNameElement?.ariaLabel;
145173
this._config.ariaLabel = dialogNameInnerText || 'Dialog Modal';
146174
console.log(`getDialogName this.config.ariaLabel: `);
147175
console.log(this._config.ariaLabel);
148176
return this._config.ariaLabel;
149177
};
150-
ngAfterViewInit() {
151-
this._getDialogName();
178+
179+
private _setAriaLabel = (): void => {
180+
const os = this._getUserPlatform();
181+
if (os === 'mac') {
182+
this._getDialogName();
183+
}
184+
return;
185+
};
186+
ngOnInit() {
187+
this._setAriaLabel();
152188
}
153189

154190
protected override _contentAttached(): void {

src/material/dialog/dialog.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export class YourDialog {
4040
```
4141

4242
### Specifying global configuration defaults
43+
4344
Default dialog options can be specified by providing an instance of `MatDialogConfig` for
4445
MAT_DIALOG_DEFAULT_OPTIONS in your application's root module.
4546

@@ -52,6 +53,7 @@ MAT_DIALOG_DEFAULT_OPTIONS in your application's root module.
5253
```
5354

5455
### Sharing data with the Dialog component.
56+
5557
If you want to share data with your dialog, you can use the `data`
5658
option to pass information to the dialog component.
5759

@@ -88,16 +90,18 @@ will be available implicitly in the template:
8890
<!-- example(dialog-data) -->
8991

9092
### Dialog content
93+
9194
Several directives are available to make it easier to structure your dialog content:
9295

93-
| Name | Description |
94-
|------------------------|---------------------------------------------------------------------------------------------------------------|
95-
| `mat-dialog-title` | \[Attr] Dialog title, applied to a heading element (e.g., `<h1>`, `<h2>`) |
96-
| `<mat-dialog-content>` | Primary scrollable content of the dialog. |
97-
| `<mat-dialog-actions>` | Container for action buttons at the bottom of the dialog. Button alignment can be controlled via the `align` attribute which can be set to `end` and `center`. |
98-
| `mat-dialog-close` | \[Attr] Added to a `<button>`, makes the button close the dialog with an optional result from the bound value.|
96+
| Name | Description |
97+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
98+
| `mat-dialog-title` | \[Attr] Dialog title, applied to a heading element (e.g., `<h1>`, `<h2>`) |
99+
| `<mat-dialog-content>` | Primary scrollable content of the dialog. |
100+
| `<mat-dialog-actions>` | Container for action buttons at the bottom of the dialog. Button alignment can be controlled via the `align` attribute which can be set to `end` and `center`. |
101+
| `mat-dialog-close` | \[Attr] Added to a `<button>`, makes the button close the dialog with an optional result from the bound value. |
99102

100103
For example:
104+
101105
```html
102106
<h2 mat-dialog-title>Delete all elements?</h2>
103107
<mat-dialog-content>This will delete all elements that are currently on this page and cannot be undone.</mat-dialog-content>
@@ -119,6 +123,7 @@ You can control which elements are tab stops with the `tabindex` attribute
119123
<!-- example(dialog-content) -->
120124

121125
### Controlling the dialog animation
126+
122127
You can control the duration of the dialog's enter and exit animations using the
123128
`enterAnimationDuration` and `exitAnimationDuration` options. If you want to disable the dialog's
124129
animation completely, you can do so by setting the properties to `0ms`.
@@ -130,11 +135,10 @@ animation completely, you can do so by setting the properties to `0ms`.
130135
`MatDialog` creates modal dialogs that implements the ARIA `role="dialog"` pattern by default.
131136
You can change the dialog's role to `alertdialog` via `MatDialogConfig`.
132137

133-
You should provide an accessible label to this root dialog element by setting the `ariaLabel` or
134-
`ariaLabelledBy` properties of `MatDialogConfig`. You can additionally specify a description element
135-
ID via the `ariaDescribedBy` property of `MatDialogConfig`.
138+
In order to make your dialog title/name known and read by all screenreaders regardless of OS or browser, you should provide an accessible label to this root dialog element. You can do so either by setting the dialog name/title as a value to the `ariaLabel` property of `MatDialogConfig` or providing the id of the respective element with the dialog name as `ariaLabelledBy` property of `MatDialogConfig`. You can additionally specify a description element ID via the `ariaDescribedBy` property of `MatDialogConfig`. If none of these properties (`ariaLabel`,`ariaLabelledBy`, or `ariaDescribedBy`) are applied to `MatDialogConfig` the default aria-label value will be "Dialog Modal".
136139

137140
#### Keyboard interaction
141+
138142
By default, the escape key closes `MatDialog`. While you can disable this behavior via
139143
the `disableClose` property of `MatDialogConfig`, doing this breaks the expected interaction
140144
pattern for the ARIA `role="dialog"` pattern.
@@ -146,12 +150,12 @@ When opened, `MatDialog` traps browser focus such that it cannot escape the root
146150
You can customize which element receives focus with the `autoFocus` property of
147151
`MatDialogConfig`, which supports the following values.
148152

149-
| Value | Behavior |
150-
|------------------|--------------------------------------------------------------------------|
151-
| `first-tabbable` | Focus the first tabbable element. This is the default setting. |
152-
| `first-header` | Focus the first header element (`role="heading"`, `h1` through `h6`) |
153-
| `dialog` | Focus the root `role="dialog"` element. |
154-
| Any CSS selector | Focus the first element matching the given selector. |
153+
| Value | Behavior |
154+
| ---------------- | -------------------------------------------------------------------- |
155+
| `first-tabbable` | Focus the first tabbable element. This is the default setting. |
156+
| `first-header` | Focus the first header element (`role="heading"`, `h1` through `h6`) |
157+
| `dialog` | Focus the root `role="dialog"` element. |
158+
| Any CSS selector | Focus the first element matching the given selector. |
155159

156160
While the default setting applies the best behavior for most applications, special cases may benefit
157161
from these alternatives. Always test your application to verify the behavior that works best for

0 commit comments

Comments
 (0)