diff --git a/src/material-experimental/mdc-menu/menu.html b/src/material-experimental/mdc-menu/menu.html index c2c2c285c999..90d8bcc58f64 100644 --- a/src/material-experimental/mdc-menu/menu.html +++ b/src/material-experimental/mdc-menu/menu.html @@ -9,7 +9,10 @@ (@transformMenu.start)="_onAnimationStart($event)" (@transformMenu.done)="_onAnimationDone($event)" tabindex="-1" - role="menu"> + role="menu" + [attr.aria-label]="ariaLabel || null" + [attr.aria-labelledby]="ariaLabelledby || null" + [attr.aria-describedby]="ariaDescribedby || null">
diff --git a/src/material-experimental/mdc-menu/menu.spec.ts b/src/material-experimental/mdc-menu/menu.spec.ts index 7d983727a6db..83dfee6e0bbb 100644 --- a/src/material-experimental/mdc-menu/menu.spec.ts +++ b/src/material-experimental/mdc-menu/menu.spec.ts @@ -503,6 +503,38 @@ describe('MDC-based MatMenu', () => { expect(role).toBe('menu', 'Expected panel to have the "menu" role.'); }); + it('should forward ARIA attributes to the menu panel', () => { + const fixture = createComponent(SimpleMenu, [], [FakeIcon]); + const instance = fixture.componentInstance; + fixture.detectChanges(); + instance.trigger.openMenu(); + fixture.detectChanges(); + + const menuPanel = overlayContainerElement.querySelector('.mat-mdc-menu-panel')!; + expect(menuPanel.hasAttribute('aria-label')).toBe(false); + expect(menuPanel.hasAttribute('aria-labelledby')).toBe(false); + expect(menuPanel.hasAttribute('aria-describedby')).toBe(false); + + // Note that setting all of these at the same time is invalid, + // but it's up to the consumer to handle it correctly. + instance.ariaLabel = 'Custom aria-label'; + instance.ariaLabelledby = 'custom-labelled-by'; + instance.ariaDescribedby = 'custom-described-by'; + fixture.detectChanges(); + + expect(menuPanel.getAttribute('aria-label')).toBe('Custom aria-label'); + expect(menuPanel.getAttribute('aria-labelledby')).toBe('custom-labelled-by'); + expect(menuPanel.getAttribute('aria-describedby')).toBe('custom-described-by'); + + // Change these to empty strings to make sure that we don't preserve empty attributes. + instance.ariaLabel = instance.ariaLabelledby = instance.ariaDescribedby = ''; + fixture.detectChanges(); + + expect(menuPanel.hasAttribute('aria-label')).toBe(false); + expect(menuPanel.hasAttribute('aria-labelledby')).toBe(false); + expect(menuPanel.hasAttribute('aria-describedby')).toBe(false); + }); + it('should set the "menuitem" role on the items by default', () => { const fixture = createComponent(SimpleMenu, [], [FakeIcon]); fixture.detectChanges(); @@ -2163,7 +2195,10 @@ describe('MatMenu default overrides', () => { #menu="matMenu" [class]="panelClass" (closed)="closeCallback($event)" - [backdropClass]="backdropClass"> + [backdropClass]="backdropClass" + [aria-label]="ariaLabel" + [aria-labelledby]="ariaLabelledby" + [aria-describedby]="ariaDescribedby"> @@ -2185,6 +2220,9 @@ class SimpleMenu { backdropClass: string; panelClass: string; restoreFocus = true; + ariaLabel: string; + ariaLabelledby: string; + ariaDescribedby: string; } @Component({ diff --git a/src/material/menu/menu.html b/src/material/menu/menu.html index acd80470095e..8bd298ce6417 100644 --- a/src/material/menu/menu.html +++ b/src/material/menu/menu.html @@ -9,7 +9,10 @@ (@transformMenu.start)="_onAnimationStart($event)" (@transformMenu.done)="_onAnimationDone($event)" tabindex="-1" - role="menu"> + role="menu" + [attr.aria-label]="ariaLabel || null" + [attr.aria-labelledby]="ariaLabelledby || null" + [attr.aria-describedby]="ariaDescribedby || null"> diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts index 1b0af4d27f80..2b459397f09e 100644 --- a/src/material/menu/menu.spec.ts +++ b/src/material/menu/menu.spec.ts @@ -503,6 +503,38 @@ describe('MatMenu', () => { expect(role).toBe('menu', 'Expected panel to have the "menu" role.'); }); + it('should forward ARIA attributes to the menu panel', () => { + const fixture = createComponent(SimpleMenu, [], [FakeIcon]); + const instance = fixture.componentInstance; + fixture.detectChanges(); + instance.trigger.openMenu(); + fixture.detectChanges(); + + const menuPanel = overlayContainerElement.querySelector('.mat-menu-panel')!; + expect(menuPanel.hasAttribute('aria-label')).toBe(false); + expect(menuPanel.hasAttribute('aria-labelledby')).toBe(false); + expect(menuPanel.hasAttribute('aria-describedby')).toBe(false); + + // Note that setting all of these at the same time is invalid, + // but it's up to the consumer to handle it correctly. + instance.ariaLabel = 'Custom aria-label'; + instance.ariaLabelledby = 'custom-labelled-by'; + instance.ariaDescribedby = 'custom-described-by'; + fixture.detectChanges(); + + expect(menuPanel.getAttribute('aria-label')).toBe('Custom aria-label'); + expect(menuPanel.getAttribute('aria-labelledby')).toBe('custom-labelled-by'); + expect(menuPanel.getAttribute('aria-describedby')).toBe('custom-described-by'); + + // Change these to empty strings to make sure that we don't preserve empty attributes. + instance.ariaLabel = instance.ariaLabelledby = instance.ariaDescribedby = ''; + fixture.detectChanges(); + + expect(menuPanel.hasAttribute('aria-label')).toBe(false); + expect(menuPanel.hasAttribute('aria-labelledby')).toBe(false); + expect(menuPanel.hasAttribute('aria-describedby')).toBe(false); + }); + it('should set the "menuitem" role on the items by default', () => { const fixture = createComponent(SimpleMenu, [], [FakeIcon]); fixture.detectChanges(); @@ -2151,7 +2183,10 @@ describe('MatMenu default overrides', () => { #menu="matMenu" [class]="panelClass" (closed)="closeCallback($event)" - [backdropClass]="backdropClass"> + [backdropClass]="backdropClass" + [aria-label]="ariaLabel" + [aria-labelledby]="ariaLabelledby" + [aria-describedby]="ariaDescribedby"> @@ -2173,6 +2208,9 @@ class SimpleMenu { backdropClass: string; panelClass: string; restoreFocus = true; + ariaLabel: string; + ariaLabelledby: string; + ariaDescribedby: string; } @Component({ diff --git a/src/material/menu/menu.ts b/src/material/menu/menu.ts index ec48f2a2d2e1..4333955bac47 100644 --- a/src/material/menu/menu.ts +++ b/src/material/menu/menu.ts @@ -132,6 +132,15 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel