From 9122335b25b28a5532159ab87c36aab3be9c3716 Mon Sep 17 00:00:00 2001 From: Jullierme Barros Date: Tue, 10 Sep 2024 08:09:18 -0300 Subject: [PATCH] feat(material/checkbox): add new aria properties to MatCheckbox (#29457) * feat(material/checkbox): add new aria properties to MatCheckbox Added three new aria properties to MatCheckbox: `aria-expanded`: Indicates whether the checkbox controls the visibility of another element. This should be a boolean value (true or false). `aria-controls`: Specifies the ID of the element that the checkbox controls. `aria-owns`: Specifies the ID of the element that the checkbox visually owns. These attributes will be added to the generated checkbox element if they are specified and won't be present in the HTML if not provided. Also added a small paragraph at the end of the checkbox.md file. Fixes #28761 * feat(material/checkbox): add new aria properties to MatCheckbox Added three new aria properties to MatCheckbox: `aria-expanded`: Indicates whether the checkbox controls the visibility of another element. This should be a boolean value (true or false). `aria-controls`: Specifies the ID of the element that the checkbox controls. `aria-owns`: Specifies the ID of the element that the checkbox visually owns. These attributes will be added to the generated checkbox element if they are specified and won't be present in the HTML if not provided. Also added a small paragraph at the end of the checkbox.md file. Fixes #28761 * feat(material/checkbox): add new aria properties to MatCheckbox Added three new aria properties to MatCheckbox: `aria-expanded`: Indicates whether the checkbox controls the visibility of another element. This should be a boolean value (true or false). `aria-controls`: Specifies the ID of the element that the checkbox controls. `aria-owns`: Specifies the ID of the element that the checkbox visually owns. These attributes will be added to the generated checkbox element if they are specified and won't be present in the HTML if not provided. Also added a small paragraph at the end of the checkbox.md file. Fixes #28761 --- src/material/checkbox/checkbox.html | 3 + src/material/checkbox/checkbox.md | 6 + src/material/checkbox/checkbox.spec.ts | 120 ++++++++++++++++++++ src/material/checkbox/checkbox.ts | 13 +++ tools/public_api_guard/material/checkbox.md | 7 +- 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/material/checkbox/checkbox.html b/src/material/checkbox/checkbox.html index 9cac64e875b7..759f09f64bed 100644 --- a/src/material/checkbox/checkbox.html +++ b/src/material/checkbox/checkbox.html @@ -10,7 +10,10 @@ [attr.aria-labelledby]="ariaLabelledby" [attr.aria-describedby]="ariaDescribedby" [attr.aria-checked]="indeterminate ? 'mixed' : null" + [attr.aria-controls]="ariaControls" [attr.aria-disabled]="disabled && disabledInteractive ? true : null" + [attr.aria-expanded]="ariaExpanded" + [attr.aria-owns]="ariaOwns" [attr.name]="name" [attr.value]="value" [checked]="checked" diff --git a/src/material/checkbox/checkbox.md b/src/material/checkbox/checkbox.md index 0260fa3cf6dd..0cfb43e1bd12 100644 --- a/src/material/checkbox/checkbox.md +++ b/src/material/checkbox/checkbox.md @@ -74,3 +74,9 @@ binding these properties, as demonstrated below. ``` + +Additionally, `MatCheckbox` now supports the following accessibility properties: + +- **`aria-expanded`**: Indicates whether the checkbox controls the visibility of another element. This should be a boolean value (`true` or `false`). +- **`aria-controls`**: Specifies the ID of the element that the checkbox controls. +- **`aria-owns`**: Specifies the ID of the element that the checkbox visually owns. diff --git a/src/material/checkbox/checkbox.spec.ts b/src/material/checkbox/checkbox.spec.ts index 9c029d2bc8e2..35fb4441d91e 100644 --- a/src/material/checkbox/checkbox.spec.ts +++ b/src/material/checkbox/checkbox.spec.ts @@ -807,6 +807,94 @@ describe('MatCheckbox', () => { }); }); + describe('with provided aria-expanded', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided postive aria-expanded', () => { + fixture = createComponent(CheckboxWithPositiveAriaExpanded); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + }); + + it('should use the provided negative aria-expanded', () => { + fixture = createComponent(CheckboxWithNegativeAriaExpanded); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); + + it('should not assign aria-expanded if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe(null); + }); + }); + + describe('with provided aria-controls', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided aria-controls', () => { + fixture = createComponent(CheckboxWithAriaControls); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-controls')).toBe('some-id'); + }); + + it('should not assign aria-controls if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-controls')).toBe(null); + }); + }); + + describe('with provided aria-owns', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided aria-owns', () => { + fixture = createComponent(CheckboxWithAriaOwns); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-owns')).toBe('some-id'); + }); + + it('should not assign aria-owns if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-owns')).toBe(null); + }); + }); + describe('with provided tabIndex', () => { let checkboxDebugElement: DebugElement; let checkboxNativeElement: HTMLElement; @@ -1237,6 +1325,38 @@ class CheckboxWithAriaLabelledby {} }) class CheckboxWithAriaDescribedby {} +/** Simple test component with an aria-expanded set with true. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithPositiveAriaExpanded {} + +/** Simple test component with an aria-expanded set with false. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithNegativeAriaExpanded {} + +/** Simple test component with an aria-controls set. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithAriaControls {} + +/** Simple test component with an aria-owns set. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithAriaOwns {} + /** Simple test component with name attribute */ @Component({ template: ``, diff --git a/src/material/checkbox/checkbox.ts b/src/material/checkbox/checkbox.ts index 19a09c881c29..b4627499dae5 100644 --- a/src/material/checkbox/checkbox.ts +++ b/src/material/checkbox/checkbox.ts @@ -160,6 +160,19 @@ export class MatCheckbox /** The 'aria-describedby' attribute is read after the element's label and field type. */ @Input('aria-describedby') ariaDescribedby: string; + /** + * Users can specify the `aria-expanded` attribute which will be forwarded to the input element + */ + @Input({alias: 'aria-expanded', transform: booleanAttribute}) ariaExpanded: boolean; + + /** + * Users can specify the `aria-controls` attribute which will be forwarded to the input element + */ + @Input('aria-controls') ariaControls: string; + + /** Users can specify the `aria-owns` attribute which will be forwarded to the input element */ + @Input('aria-owns') ariaOwns: string; + private _uniqueId: string; /** A unique id for the checkbox input. If none is supplied, it will be auto-generated. */ diff --git a/tools/public_api_guard/material/checkbox.md b/tools/public_api_guard/material/checkbox.md index bc59c890df69..e1b0acd3cf25 100644 --- a/tools/public_api_guard/material/checkbox.md +++ b/tools/public_api_guard/material/checkbox.md @@ -48,9 +48,12 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess }; // (undocumented) _animationMode?: string | undefined; + ariaControls: string; ariaDescribedby: string; + ariaExpanded: boolean; ariaLabel: string; ariaLabelledby: string | null; + ariaOwns: string; readonly change: EventEmitter; get checked(): boolean; set checked(value: boolean); @@ -78,6 +81,8 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess labelPosition: 'before' | 'after'; name: string | null; // (undocumented) + static ngAcceptInputType_ariaExpanded: unknown; + // (undocumented) static ngAcceptInputType_checked: unknown; // (undocumented) static ngAcceptInputType_disabled: unknown; @@ -123,7 +128,7 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess // (undocumented) writeValue(value: any): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }