Skip to content

Commit f05e65a

Browse files
authored
fix(material/chips): allow for role to be overwritten on chip list and chip (#15794)
Allows for the ARIA `role` of the `mat-chip-list` and `mat-chip` to be overwritten. Fixes #15787.
1 parent 175937e commit f05e65a

File tree

12 files changed

+102
-23
lines changed

12 files changed

+102
-23
lines changed

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ describe('MDC-based MatChipGrid', () => {
103103

104104
expect(chipGridNativeElement.hasAttribute('role')).toBe(false);
105105
});
106+
107+
it('should be able to set a custom role', () => {
108+
testComponent.role = 'listbox';
109+
fixture.detectChanges();
110+
111+
expect(chipGridNativeElement.getAttribute('role')).toBe('listbox');
112+
});
106113
});
107114

108115
describe('focus behaviors', () => {
@@ -1028,7 +1035,7 @@ describe('MDC-based MatChipGrid', () => {
10281035

10291036
@Component({
10301037
template: `
1031-
<mat-chip-grid [tabIndex]="tabIndex" #chipGrid>
1038+
<mat-chip-grid [tabIndex]="tabIndex" [role]="role" #chipGrid>
10321039
<mat-chip-row *ngFor="let i of chips"
10331040
[editable]="editable">
10341041
{{name}} {{i + 1}}
@@ -1041,6 +1048,7 @@ class StandardChipGrid {
10411048
tabIndex: number = 0;
10421049
chips = [0, 1, 2, 3, 4];
10431050
editable = false;
1051+
role: string | null = null;
10441052
}
10451053

10461054
@Component({

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

+2-5
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ export class MatChipGrid
143143
/** The chip input to add more chips */
144144
protected _chipInput: MatChipTextControl;
145145

146+
protected override _defaultRole = 'grid';
147+
146148
/**
147149
* Function when touched. Set as part of ControlValueAccessor implementation.
148150
* @docs-private
@@ -186,11 +188,6 @@ export class MatChipGrid
186188
);
187189
}
188190

189-
/** The ARIA role applied to the chip grid. */
190-
override get role(): string | null {
191-
return this.empty ? null : 'grid';
192-
}
193-
194191
/**
195192
* Implemented as part of MatFormFieldControl.
196193
* @docs-private

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ describe('MDC-based MatChipListbox', () => {
8989
expect(chipListboxNativeElement.hasAttribute('role')).toBe(false);
9090
});
9191

92+
it('should be able to set a custom role', () => {
93+
testComponent.role = 'grid';
94+
fixture.detectChanges();
95+
96+
expect(chipListboxNativeElement.getAttribute('role')).toBe('grid');
97+
});
98+
9299
it('should not set aria-required when it does not have a role', () => {
93100
testComponent.chips = [];
94101
fixture.detectChanges();
@@ -745,7 +752,7 @@ describe('MDC-based MatChipListbox', () => {
745752

746753
@Component({
747754
template: `
748-
<mat-chip-listbox [tabIndex]="tabIndex" [selectable]="selectable">
755+
<mat-chip-listbox [tabIndex]="tabIndex" [selectable]="selectable" [role]="role">
749756
<mat-chip-option *ngFor="let i of chips" (select)="chipSelect(i)"
750757
(deselect)="chipDeselect(i)">
751758
{{name}} {{i + 1}}
@@ -759,6 +766,7 @@ class StandardChipListbox {
759766
chipDeselect: (index?: number) => void = () => {};
760767
tabIndex: number = 0;
761768
chips = [0, 1, 2, 3, 4];
769+
role: string | null = null;
762770
}
763771

764772
@Component({

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,8 @@ export class MatChipListbox
9999
*/
100100
_onChange: (value: any) => void = () => {};
101101

102-
/** The ARIA role applied to the chip listbox. */
103102
// TODO: MDC uses `grid` here
104-
override get role(): string | null {
105-
return this.empty ? null : 'listbox';
106-
}
103+
protected override _defaultRole = 'listbox';
107104

108105
/** Whether the user should be allowed to select multiple chips. */
109106
@Input()

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

+11
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ describe('MDC-based Option Chips', () => {
185185
.withContext('Expected chip ripples to be disabled.')
186186
.toBe(true);
187187
});
188+
189+
it('should have the correct role', () => {
190+
expect(chipNativeElement.getAttribute('role')).toBe('presentation');
191+
});
192+
193+
it('should be able to set a custom role', () => {
194+
chipInstance.role = 'button';
195+
fixture.detectChanges();
196+
197+
expect(chipNativeElement.getAttribute('role')).toBe('button');
198+
});
188199
});
189200

190201
describe('keyboard behavior', () => {

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

+11
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ describe('MDC-based Row Chips', () => {
104104

105105
expect(event.defaultPrevented).toBe(true);
106106
});
107+
108+
it('should have the correct role', () => {
109+
expect(chipNativeElement.getAttribute('role')).toBe('row');
110+
});
111+
112+
it('should be able to set a custom role', () => {
113+
chipInstance.role = 'button';
114+
fixture.detectChanges();
115+
116+
expect(chipNativeElement.getAttribute('role')).toBe('button');
117+
});
107118
});
108119

109120
describe('keyboard behavior', () => {

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ export class MatChipSet
8888
/** Subject that emits when the component has been destroyed. */
8989
protected _destroyed = new Subject<void>();
9090

91+
/** Role to use if it hasn't been overwritten by the user. */
92+
protected _defaultRole = 'presentation';
93+
9194
/** Combined stream of all of the child chips' remove events. */
9295
get chipDestroyedChanges(): Observable<MatChipEvent> {
9396
return this._getChipStream(chip => chip.destroyed);
@@ -163,17 +166,17 @@ export class MatChipSet
163166
/** The ARIA role applied to the chip set. */
164167
@Input()
165168
get role(): string | null {
166-
if (this._role) {
167-
return this._role;
168-
} else {
169-
return this.empty ? null : 'presentation';
169+
if (this._explicitRole) {
170+
return this._explicitRole;
170171
}
172+
173+
return this.empty ? null : this._defaultRole;
171174
}
172175

173176
set role(value: string | null) {
174-
this._role = value;
177+
this._explicitRole = value;
175178
}
176-
private _role: string | null = null;
179+
private _explicitRole: string | null = null;
177180

178181
/** Whether any of the chips inside of this chip-set has focus. */
179182
get focused(): boolean {

src/material/chips/chip-list.spec.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ describe('MatChipList', () => {
191191
expect(chipListNativeElement.hasAttribute('role')).toBe(false);
192192
expect(chipListNativeElement.hasAttribute('aria-required')).toBe(false);
193193
});
194+
195+
it('should be able to set a custom role', () => {
196+
fixture.componentInstance.chipList.role = 'grid';
197+
fixture.detectChanges();
198+
199+
expect(chipListNativeElement.getAttribute('role')).toBe('grid');
200+
});
194201
});
195202

196203
describe('focus behaviors', () => {
@@ -1725,9 +1732,9 @@ class FalsyValueChipList {
17251732
@Component({
17261733
template: `
17271734
<mat-chip-list>
1728-
<mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
1729-
{{ food.viewValue }}
1730-
</mat-chip>
1735+
<mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
1736+
{{ food.viewValue }}
1737+
</mat-chip>
17311738
</mat-chip-list>
17321739
`,
17331740
})
@@ -1738,6 +1745,7 @@ class SelectedChipList {
17381745
{value: 2, viewValue: 'Pasta', selected: true},
17391746
];
17401747
@ViewChildren(MatChip) chips: QueryList<MatChip>;
1748+
@ViewChild(MatChipList, {static: false}) chipList: MatChipList;
17411749
}
17421750

17431751
@Component({

src/material/chips/chip-list.ts

+9
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,18 @@ export class MatChipList
180180
}
181181

182182
/** The ARIA role applied to the chip list. */
183+
@Input()
183184
get role(): string | null {
185+
if (this._explicitRole) {
186+
return this._explicitRole;
187+
}
188+
184189
return this.empty ? null : 'listbox';
185190
}
191+
set role(role: string | null) {
192+
this._explicitRole = role;
193+
}
194+
private _explicitRole?: string | null;
186195

187196
/**
188197
* Implemented as part of MatFormFieldControl.

src/material/chips/chip.spec.ts

+22
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ describe('MatChip', () => {
7373

7474
expect(chip.getAttribute('tabindex')).toBe('15');
7575
});
76+
77+
it('should have the correct role', () => {
78+
fixture = TestBed.createComponent(BasicChip);
79+
fixture.detectChanges();
80+
chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!;
81+
chipNativeElement = chipDebugElement.nativeElement;
82+
83+
expect(chipNativeElement.getAttribute('role')).toBe('option');
84+
});
85+
86+
it('should be able to set a custom role', () => {
87+
fixture = TestBed.createComponent(BasicChip);
88+
fixture.detectChanges();
89+
chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!;
90+
chipInstance = chipDebugElement.injector.get<MatChip>(MatChip);
91+
chipNativeElement = chipDebugElement.nativeElement;
92+
93+
chipInstance.role = 'gridcell';
94+
fixture.detectChanges();
95+
96+
expect(chipNativeElement.getAttribute('role')).toBe('gridcell');
97+
});
7698
});
7799

78100
describe('MatChip', () => {

src/material/chips/chip.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class MatChipTrailingIcon {}
126126
host: {
127127
'class': 'mat-chip mat-focus-indicator',
128128
'[attr.tabindex]': 'disabled ? null : tabIndex',
129-
'role': 'option',
129+
'[attr.role]': 'role',
130130
'[class.mat-chip-selected]': 'selected',
131131
'[class.mat-chip-with-avatar]': 'avatar',
132132
'[class.mat-chip-with-trailing-icon]': 'trailingIcon || removeIcon',
@@ -207,6 +207,9 @@ export class MatChip
207207
/** The chip's remove toggler. */
208208
@ContentChild(MAT_CHIP_REMOVE) removeIcon: MatChipRemove;
209209

210+
/** ARIA role that should be applied to the chip. */
211+
@Input() role: string = 'option';
212+
210213
/** Whether the chip is selected. */
211214
@Input()
212215
get selected(): boolean {

tools/public_api_guard/material/chips.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
9090
removeIcon: MatChipRemove;
9191
rippleConfig: RippleConfig & RippleGlobalOptions;
9292
get rippleDisabled(): boolean;
93+
role: string;
9394
select(): void;
9495
get selectable(): boolean;
9596
set selectable(value: BooleanInput);
@@ -108,7 +109,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
108109
// (undocumented)
109110
protected _value: any;
110111
// (undocumented)
111-
static ɵdir: i0.ɵɵDirectiveDeclaration<MatChip, "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", ["matChip"], { "color": "color"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "selected": "selected"; "value": "value"; "selectable": "selectable"; "disabled": "disabled"; "removable": "removable"; }, { "selectionChange": "selectionChange"; "destroyed": "destroyed"; "removed": "removed"; }, ["avatar", "trailingIcon", "removeIcon"]>;
112+
static ɵdir: i0.ɵɵDirectiveDeclaration<MatChip, "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", ["matChip"], { "color": "color"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "role": "role"; "selected": "selected"; "value": "value"; "selectable": "selectable"; "disabled": "disabled"; "removable": "removable"; }, { "selectionChange": "selectionChange"; "destroyed": "destroyed"; "removed": "removed"; }, ["avatar", "trailingIcon", "removeIcon"]>;
112113
// (undocumented)
113114
static ɵfac: i0.ɵɵFactoryDeclaration<MatChip, [null, null, null, { optional: true; }, null, null, { optional: true; }, { attribute: "tabindex"; }]>;
114115
}
@@ -235,6 +236,7 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl
235236
// (undocumented)
236237
protected _required: boolean | undefined;
237238
get role(): string | null;
239+
set role(role: string | null);
238240
get selectable(): boolean;
239241
set selectable(value: BooleanInput);
240242
// (undocumented)
@@ -264,7 +266,7 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl
264266
// (undocumented)
265267
writeValue(value: any): void;
266268
// (undocumented)
267-
static ɵcmp: i0.ɵɵComponentDeclaration<MatChipList, "mat-chip-list", ["matChipList"], { "userAriaDescribedBy": "aria-describedby"; "errorStateMatcher": "errorStateMatcher"; "multiple": "multiple"; "compareWith": "compareWith"; "value": "value"; "required": "required"; "placeholder": "placeholder"; "disabled": "disabled"; "ariaOrientation": "aria-orientation"; "selectable": "selectable"; "tabIndex": "tabIndex"; }, { "change": "change"; "valueChange": "valueChange"; }, ["chips"], ["*"]>;
269+
static ɵcmp: i0.ɵɵComponentDeclaration<MatChipList, "mat-chip-list", ["matChipList"], { "role": "role"; "userAriaDescribedBy": "aria-describedby"; "errorStateMatcher": "errorStateMatcher"; "multiple": "multiple"; "compareWith": "compareWith"; "value": "value"; "required": "required"; "placeholder": "placeholder"; "disabled": "disabled"; "ariaOrientation": "aria-orientation"; "selectable": "selectable"; "tabIndex": "tabIndex"; }, { "change": "change"; "valueChange": "valueChange"; }, ["chips"], ["*"]>;
268270
// (undocumented)
269271
static ɵfac: i0.ɵɵFactoryDeclaration<MatChipList, [null, null, { optional: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }]>;
270272
}

0 commit comments

Comments
 (0)