Skip to content

fix(material/dialog): dialog name on mac only using aria-label #29264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cdk/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
extends BasePortalOutlet
implements OnDestroy
{
private _platform = inject(Platform);
protected _platform = inject(Platform);
protected _document: Document;

/** The portal outlet inside of this container into which the dialog content will be loaded. */
Expand Down
7 changes: 7 additions & 0 deletions src/cdk/platform/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ export class Platform {
/** Whether the current browser is Safari. */
SAFARI: boolean = this.isBrowser && /safari/i.test(navigator.userAgent) && this.WEBKIT;

/** Whether the device is a Mac device/OS. */
MAC_OS: boolean =
!this.isBrowser && /(macintosh|macintel|macppc|mac68k|macos)/i.test(navigator.userAgent);

/** Whether the device is a Windows device/OS. */
WINDOWS: boolean = !this.isBrowser && /(win32|win64|windows|wince)/i.test(navigator.userAgent);

/** Backwards-compatible constructor. */
constructor(..._args: unknown[]);

Expand Down
7 changes: 4 additions & 3 deletions src/dev-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class DialogDemo {
minHeight: '',
maxWidth: '',
maxHeight: '',
ariaLabelledBy: 'jazz-title',
position: {
top: '',
bottom: '',
Expand Down Expand Up @@ -146,7 +147,7 @@ export class DialogDemo {
selector: 'demo-jazz-dialog',
template: `
<div cdkDrag cdkDragRootElement=".cdk-overlay-pane">
<p>Order printer ink refills.</p>
<p id="jazz-title">Order printer ink refills.</p>

<mat-form-field>
<mat-label>How many?</mat-label>
Expand Down Expand Up @@ -213,7 +214,7 @@ export class JazzDialog {
}
`,
template: `
<h2 mat-dialog-title>Neptune</h2>
<h2 id="jazz-title" mat-dialog-title>Neptune</h2>

<mat-dialog-content>
<p>
Expand Down Expand Up @@ -276,7 +277,7 @@ export class ContentElementDialog {
}
`,
template: `
<h2 mat-dialog-title>Neptune</h2>
<h2 id="jazz-title" mat-dialog-title>Neptune</h2>

<mat-dialog-content>
<iframe style="border: 0" src="https://en.wikipedia.org/wiki/Neptune"></iframe>
Expand Down
36 changes: 34 additions & 2 deletions src/material/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Inject,
NgZone,
OnDestroy,
OnInit,
Optional,
ViewEncapsulation,
ANIMATION_MODULE_TYPE,
Expand Down Expand Up @@ -72,9 +73,11 @@ export const CLOSE_ANIMATION_DURATION = 75;
'[class.mat-mdc-dialog-container-with-actions]': '_actionSectionCount > 0',
},
})
export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> implements OnDestroy {
export class MatDialogContainer
extends CdkDialogContainer<MatDialogConfig>
implements OnInit, OnDestroy
{
private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});

/** Emits when an animation state changes. */
_animationStateChanged = new EventEmitter<LegacyDialogAnimationEvent>();

Expand Down Expand Up @@ -120,6 +123,35 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
);
}

/** Get Dialog name from aria attributes */
private _getDialogName = (): string => {
// _ariaLabelledByQueue is created if ariaLabelledBy values are applied
// to the dialog config
const ariaLabelledByRefId = this._ariaLabelledByQueue[0];
// Get Element to get name/title from if ariaLabelledBy
const dialogNameElement = document.getElementById(ariaLabelledByRefId);
const dialogNameInnerText =
// If no ariaLabelledBy or ariaLabel, create default aria label
!dialogNameElement && !this._config.ariaLabel
? 'Dialog Modal'
: dialogNameElement?.innerText || dialogNameElement?.ariaLabel || this._config.ariaLabel;
return dialogNameInnerText || 'Dialog Modal';
};

private _setAriaLabel = (): void => {
/* Check for platform's operating system & provide
aria-labelledby value or default text as dialog
name if platform is MAC_OS or IOS. Fixes b/274674581 */
if (this._platform.MAC_OS || this._platform.IOS) {
this._config.ariaLabel = this._getDialogName();
}
return;
};

ngOnInit() {
this._setAriaLabel();
}

protected override _contentAttached(): void {
// Delegate to the original dialog-container initialization (i.e. saving the
// previous element, setting up the focus trap and moving focus to the container).
Expand Down
34 changes: 19 additions & 15 deletions src/material/dialog/dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class YourDialog {
```

### Specifying global configuration defaults

Default dialog options can be specified by providing an instance of `MatDialogConfig` for
MAT_DIALOG_DEFAULT_OPTIONS in your application's root module.

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

### Sharing data with the Dialog component.

If you want to share data with your dialog, you can use the `data`
option to pass information to the dialog component.

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

### Dialog content

Several directives are available to make it easier to structure your dialog content:

| Name | Description |
|------------------------|---------------------------------------------------------------------------------------------------------------|
| `mat-dialog-title` | \[Attr] Dialog title, applied to a heading element (e.g., `<h1>`, `<h2>`) |
| `<mat-dialog-content>` | Primary scrollable content of the dialog. |
| `<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`. |
| `mat-dialog-close` | \[Attr] Added to a `<button>`, makes the button close the dialog with an optional result from the bound value.|
| Name | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mat-dialog-title` | \[Attr] Dialog title, applied to a heading element (e.g., `<h1>`, `<h2>`) |
| `<mat-dialog-content>` | Primary scrollable content of the dialog. |
| `<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`. |
| `mat-dialog-close` | \[Attr] Added to a `<button>`, makes the button close the dialog with an optional result from the bound value. |

For example:

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

### Controlling the dialog animation

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

You should provide an accessible label to this root dialog element by setting the `ariaLabel` or
`ariaLabelledBy` properties of `MatDialogConfig`. You can additionally specify a description element
ID via the `ariaDescribedBy` property of `MatDialogConfig`.
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".

#### Keyboard interaction

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

| Value | Behavior |
|------------------|--------------------------------------------------------------------------|
| `first-tabbable` | Focus the first tabbable element. This is the default setting. |
| `first-header` | Focus the first header element (`role="heading"`, `h1` through `h6`) |
| `dialog` | Focus the root `role="dialog"` element. |
| Any CSS selector | Focus the first element matching the given selector. |
| Value | Behavior |
| ---------------- | -------------------------------------------------------------------- |
| `first-tabbable` | Focus the first tabbable element. This is the default setting. |
| `first-header` | Focus the first header element (`role="heading"`, `h1` through `h6`) |
| `dialog` | Focus the root `role="dialog"` element. |
| Any CSS selector | Focus the first element matching the given selector. |

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