Skip to content

Commit 195cbfa

Browse files
fix(material-experimental/mdc-chips): Mirror aria-describedby to matChipInput
Updates mat-chip-grid to associate any ids set for aria-describedby to the matChipInput instance within the grid, if one exists. Removes the aria-describedby attribute on the grid itself since it never receives focus. Fixes #24542
1 parent 9371606 commit 195cbfa

File tree

6 files changed

+47
-11
lines changed

6 files changed

+47
-11
lines changed

src/material-experimental/mdc-chips/chip-grid.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -853,12 +853,14 @@ describe('MDC-based MatChipGrid', () => {
853853
let errorTestComponent: ChipGridWithFormErrorMessages;
854854
let containerEl: HTMLElement;
855855
let chipGridEl: HTMLElement;
856+
let inputEl: HTMLElement;
856857

857858
beforeEach(() => {
858859
fixture = createComponent(ChipGridWithFormErrorMessages);
859860
errorTestComponent = fixture.componentInstance;
860861
containerEl = fixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
861862
chipGridEl = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement;
863+
inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
862864
});
863865

864866
it('should not show any errors if the user has not interacted', () => {
@@ -966,11 +968,11 @@ describe('MDC-based MatChipGrid', () => {
966968
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
967969
});
968970

969-
it('sets the aria-describedby to reference errors when in error state', () => {
971+
it('sets the aria-describedby on the input to reference errors when in error state', () => {
970972
let hintId = fixture.debugElement
971973
.query(By.css('.mat-mdc-form-field-hint'))!
972974
.nativeElement.getAttribute('id');
973-
let describedBy = chipGridEl.getAttribute('aria-describedby');
975+
let describedBy = inputEl.getAttribute('aria-describedby');
974976

975977
expect(hintId).withContext('hint should be shown').toBeTruthy();
976978
expect(describedBy).toBe(hintId);
@@ -982,7 +984,7 @@ describe('MDC-based MatChipGrid', () => {
982984
.queryAll(By.css('.mat-mdc-form-field-error'))
983985
.map(el => el.nativeElement.getAttribute('id'))
984986
.join(' ');
985-
describedBy = chipGridEl.getAttribute('aria-describedby');
987+
describedBy = inputEl.getAttribute('aria-describedby');
986988

987989
expect(errorIds).withContext('errors should be shown').toBeTruthy();
988990
expect(describedBy).toBe(errorIds);

src/material-experimental/mdc-chips/chip-grid.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ const _MatChipGridMixinBase = mixinErrorState(MatChipGridBase);
108108
'class': 'mat-mdc-chip-set mat-mdc-chip-grid mdc-evolution-chip-set',
109109
'[attr.role]': 'role',
110110
'[tabIndex]': '_chips && _chips.length === 0 ? -1 : tabIndex',
111-
// TODO: replace this binding with use of AriaDescriber
112-
'[attr.aria-describedby]': '_ariaDescribedby || null',
113111
'[attr.aria-disabled]': 'disabled.toString()',
114112
'[attr.aria-invalid]': 'errorState',
115113
'[class.mat-mdc-chip-list-disabled]': 'disabled',
@@ -145,6 +143,11 @@ export class MatChipGrid
145143

146144
protected override _defaultRole = 'grid';
147145

146+
/**
147+
* List of element ids to propagate to the chipInput's aria-describedby attribute.
148+
*/
149+
private _ariaDescribedbyIds: string[] = [];
150+
148151
/**
149152
* Function when touched. Set as part of ControlValueAccessor implementation.
150153
* @docs-private
@@ -337,6 +340,7 @@ export class MatChipGrid
337340
/** Associates an HTML input element with this chip grid. */
338341
registerInput(inputElement: MatChipTextControl): void {
339342
this._chipInput = inputElement;
343+
this._chipInput.setDescribedByIds(this._ariaDescribedbyIds);
340344
}
341345

342346
/**
@@ -378,7 +382,13 @@ export class MatChipGrid
378382
* @docs-private
379383
*/
380384
setDescribedByIds(ids: string[]) {
381-
this._ariaDescribedby = ids.join(' ');
385+
// We must keep this up to date to handle the case where ids are set
386+
// before the chip input is registered.
387+
this._ariaDescribedbyIds = ids;
388+
389+
if (this._chipInput) {
390+
this._chipInput.setDescribedByIds(ids);
391+
}
382392
}
383393

384394
/**

src/material-experimental/mdc-chips/chip-input.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,24 @@ describe('MDC-based MatChipInput', () => {
230230
dispatchKeyboardEvent(inputNativeElement, 'keydown', ENTER, undefined, {shift: true});
231231
expect(testChipInput.add).not.toHaveBeenCalled();
232232
});
233+
234+
it('should set aria-describedby correctly when a non-empty list of ids is passed to setDescribedByIds', () => {
235+
const ids = ['a', 'b', 'c'];
236+
237+
testChipInput.chipGridInstance.setDescribedByIds(ids);
238+
fixture.detectChanges();
239+
240+
expect(inputNativeElement.getAttribute('aria-describedby')).toEqual('a b c');
241+
});
242+
243+
it('should set aria-describedby correctly when an empty list of ids is passed to setDescribedByIds', () => {
244+
const ids: string[] = [];
245+
246+
testChipInput.chipGridInstance.setDescribedByIds(ids);
247+
fixture.detectChanges();
248+
249+
expect(inputNativeElement.getAttribute('aria-describedby')).toBeNull();
250+
});
233251
});
234252
});
235253

src/material-experimental/mdc-chips/chip-input.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ let nextUniqueId = 0;
6565
'[attr.disabled]': 'disabled || null',
6666
'[attr.placeholder]': 'placeholder || null',
6767
'[attr.aria-invalid]': '_chipGrid && _chipGrid.ngControl ? _chipGrid.ngControl.invalid : null',
68+
'[attr.aria-describedby]': '_ariaDescribedby || null',
6869
'[attr.aria-required]': '_chipGrid && _chipGrid.required || null',
6970
'[attr.required]': '_chipGrid && _chipGrid.required || null',
7071
},
@@ -73,6 +74,9 @@ export class MatChipInput implements MatChipTextControl, AfterContentInit, OnCha
7374
/** Used to prevent focus moving to chips while user is holding backspace */
7475
private _focusLastChipOnBackspace: boolean;
7576

77+
/** Value for ariaDescribedby property */
78+
_ariaDescribedby?: string;
79+
7680
/** Whether the control is focused. */
7781
focused: boolean = false;
7882
_chipGrid: MatChipGrid;
@@ -240,6 +244,10 @@ export class MatChipInput implements MatChipTextControl, AfterContentInit, OnCha
240244
this._focusLastChipOnBackspace = true;
241245
}
242246

247+
setDescribedByIds(ids: string[]): void {
248+
this._ariaDescribedby = ids.join(' ');
249+
}
250+
243251
/** Checks whether a keycode is one of the configured separators. */
244252
private _isSeparatorKey(event: KeyboardEvent) {
245253
return !hasModifierKey(event) && new Set(this.separatorKeyCodes).has(event.keyCode);

src/material-experimental/mdc-chips/chip-set.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ const _MatChipSetMixinBase = mixinTabIndex(MatChipSetBase);
6565
host: {
6666
'class': 'mat-mdc-chip-set mdc-evolution-chip-set',
6767
'[attr.role]': 'role',
68-
// TODO: replace this binding with use of AriaDescriber
69-
'[attr.aria-describedby]': '_ariaDescribedby || null',
7068
},
7169
encapsulation: ViewEncapsulation.None,
7270
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -138,9 +136,6 @@ export class MatChipSet
138136
},
139137
};
140138

141-
/** The aria-describedby attribute on the chip list for improved a11y. */
142-
_ariaDescribedby: string;
143-
144139
/**
145140
* Map from class to whether the class is enabled.
146141
* Enabled classes are set on the MDC chip-set div.

src/material-experimental/mdc-chips/chip-text-control.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ export interface MatChipTextControl {
2222

2323
/** Focuses the text control. */
2424
focus(): void;
25+
26+
/** Sets the list of ids the input is described by. */
27+
setDescribedByIds(ids: string[]): void;
2528
}

0 commit comments

Comments
 (0)