diff --git a/src/dev-app/chips/chips-demo.html b/src/dev-app/chips/chips-demo.html index b5ebf78d29b4..178ed81cc080 100644 --- a/src/dev-app/chips/chips-demo.html +++ b/src/dev-app/chips/chips-demo.html @@ -124,6 +124,8 @@

Form Field

[matChipInputAddOnBlur]="addOnBlur" (matChipInputTokenEnd)="add($event)" /> + <mat-hint> is supported too! + <mat-error> is supported too! diff --git a/src/material-experimental/mdc-chips/chip-listbox.spec.ts b/src/material-experimental/mdc-chips/chip-listbox.spec.ts index 2161a0d43cb3..e8a9bbfd1f59 100644 --- a/src/material-experimental/mdc-chips/chip-listbox.spec.ts +++ b/src/material-experimental/mdc-chips/chip-listbox.spec.ts @@ -638,6 +638,12 @@ describe('MDC-based MatChipListbox', () => { expect(chipArray[4].focus).not.toHaveBeenCalled(); }); + + it('should support user binding to `aria-describedby`', fakeAsync(() => { + chipListboxInstance.userAriaDescribedBy = 'test'; + fixture.detectChanges(); + expect(chipListboxNativeElement.getAttribute('aria-describedby')).toBe('test'); + })); }); describe('multiple selection', () => { diff --git a/src/material-experimental/mdc-chips/chip-listbox.ts b/src/material-experimental/mdc-chips/chip-listbox.ts index 2f2effcee1f4..779dc8ef8260 100644 --- a/src/material-experimental/mdc-chips/chip-listbox.ts +++ b/src/material-experimental/mdc-chips/chip-listbox.ts @@ -66,8 +66,6 @@ export const MAT_CHIP_LISTBOX_CONTROL_VALUE_ACCESSOR: any = { 'class': 'mdc-evolution-chip-set mat-mdc-chip-listbox', '[attr.role]': 'role', '[tabIndex]': 'empty ? -1 : tabIndex', - // TODO: replace this binding with use of AriaDescriber - '[attr.aria-describedby]': '_ariaDescribedby || null', '[attr.aria-required]': 'role ? required : null', '[attr.aria-disabled]': 'disabled.toString()', '[attr.aria-multiselectable]': 'multiple', diff --git a/src/material-experimental/mdc-chips/chip-set.spec.ts b/src/material-experimental/mdc-chips/chip-set.spec.ts index 358a59a60f55..6fb657457911 100644 --- a/src/material-experimental/mdc-chips/chip-set.spec.ts +++ b/src/material-experimental/mdc-chips/chip-set.spec.ts @@ -75,6 +75,12 @@ describe('MDC-based MatChipSet', () => { fixture.detectChanges(); expect(chipSetNativeElement.getAttribute('role')).toBe('list'); }); + + it('should support user binding to `aria-describedby`', fakeAsync(() => { + chipSetInstance.userAriaDescribedBy = 'test'; + fixture.detectChanges(); + expect(chipSetNativeElement.getAttribute('aria-describedby')).toBe('test'); + })); }); }); diff --git a/src/material-experimental/mdc-chips/chip-set.ts b/src/material-experimental/mdc-chips/chip-set.ts index ede85c8e3ed2..0202cd7b6d32 100644 --- a/src/material-experimental/mdc-chips/chip-set.ts +++ b/src/material-experimental/mdc-chips/chip-set.ts @@ -55,6 +55,7 @@ const _MatChipSetMixinBase = mixinTabIndex(MatChipSetBase); 'class': 'mat-mdc-chip-set mdc-evolution-chip-set', '(keydown)': '_handleKeydown($event)', '[attr.role]': 'role', + '[attr.aria-describedby]': 'userAriaDescribedBy || null', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, @@ -85,6 +86,17 @@ export class MatChipSet return this._getChipStream(chip => chip.destroyed); } + @Input('aria-describedby') + get userAriaDescribedBy(): string { + return this._userAriaDescribedBy; + } + + set userAriaDescribedBy(userAriaDescribedBy: string) { + this._userAriaDescribedBy = userAriaDescribedBy; + } + + private _userAriaDescribedBy: string; + /** Whether the chip set is disabled. */ @Input() get disabled(): boolean { diff --git a/src/material/chips/chip-list.spec.ts b/src/material/chips/chip-list.spec.ts index 528657485d66..f14cea8285b8 100644 --- a/src/material/chips/chip-list.spec.ts +++ b/src/material/chips/chip-list.spec.ts @@ -749,6 +749,8 @@ describe('MatChipList', () => { let nativeChips: HTMLElement[]; describe('single selection', () => { + let chipListEl: HTMLElement; + beforeEach(() => { fixture = createComponent(BasicChipList); fixture.detectChanges(); @@ -757,6 +759,7 @@ describe('MatChipList', () => { .queryAll(By.css('mat-chip')) .map(chip => chip.nativeElement); chips = fixture.componentInstance.chips; + chipListEl = fixture.debugElement.query(By.css('mat-chip-list'))!.nativeElement; }); it('should take an initial view value with reactive forms', () => { @@ -949,6 +952,22 @@ describe('MatChipList', () => { expect(formField.classList).not.toContain('mat-focused'); })); + + it('should set `aria-describedby` to the id of the mat-hint', fakeAsync(() => { + expect(chipListEl.getAttribute('aria-describedby')).toBeNull(); + + fixture.componentInstance.hint = 'test'; + fixture.detectChanges(); + const hint = fixture.debugElement.query(By.css('.mat-hint')).nativeElement; + expect(chipListEl.getAttribute('aria-describedby')).toBe(hint.getAttribute('id')); + expect(chipListEl.getAttribute('aria-describedby')).toMatch(/^mat-hint-\d+$/); + })); + + it('should support user binding to `aria-describedby`', fakeAsync(() => { + fixture.componentInstance.ariaDescribedBy = 'test'; + fixture.detectChanges(); + expect(chipListEl.getAttribute('aria-describedby')).toBe('test'); + })); }); describe('multiple selection', () => { @@ -1586,11 +1605,13 @@ class FormFieldChipList { template: ` {{ food.viewValue }} + {{ hint }} `, }) @@ -1605,7 +1626,9 @@ class BasicChipList { {value: 'pasta-6', viewValue: 'Pasta'}, {value: 'sushi-7', viewValue: 'Sushi'}, ]; + ariaDescribedBy: string = ''; control = new FormControl(null); + hint: string; tabIndexOverride: number; selectable: boolean; diff --git a/src/material/chips/chip-list.ts b/src/material/chips/chip-list.ts index 9fbeab038666..92519de160ab 100644 --- a/src/material/chips/chip-list.ts +++ b/src/material/chips/chip-list.ts @@ -197,7 +197,16 @@ export class MatChipList * Implemented as part of MatFormFieldControl. * @docs-private */ - @Input('aria-describedby') userAriaDescribedBy: string; + @Input('aria-describedby') + get userAriaDescribedBy(): string { + return this._userAriaDescribedBy; + } + set userAriaDescribedBy(userAriaDescribedBy: string) { + this._userAriaDescribedBy = userAriaDescribedBy; + this.stateChanges.next(); + } + + private _userAriaDescribedBy: string; /** An object used to control when error messages are shown. */ @Input() override errorStateMatcher: ErrorStateMatcher; diff --git a/tools/public_api_guard/material/chips.md b/tools/public_api_guard/material/chips.md index 29bb7709427f..30f872f53f22 100644 --- a/tools/public_api_guard/material/chips.md +++ b/tools/public_api_guard/material/chips.md @@ -256,7 +256,8 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl _uid: string; protected _updateFocusForDestroyedChips(): void; protected _updateTabIndex(): void; - userAriaDescribedBy: string; + get userAriaDescribedBy(): string; + set userAriaDescribedBy(userAriaDescribedBy: string); _userTabIndex: number | null; get value(): any; set value(value: any);