Skip to content

Commit

Permalink
fix(material/chips): allow for role to be overwritten on chip list an…
Browse files Browse the repository at this point in the history
…d chip (#15794)

Allows for the ARIA `role` of the `mat-chip-list` and `mat-chip` to be overwritten.

Fixes #15787.
  • Loading branch information
crisbeto authored Mar 2, 2022
1 parent 175937e commit f05e65a
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 23 deletions.
10 changes: 9 additions & 1 deletion src/material-experimental/mdc-chips/chip-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ describe('MDC-based MatChipGrid', () => {

expect(chipGridNativeElement.hasAttribute('role')).toBe(false);
});

it('should be able to set a custom role', () => {
testComponent.role = 'listbox';
fixture.detectChanges();

expect(chipGridNativeElement.getAttribute('role')).toBe('listbox');
});
});

describe('focus behaviors', () => {
Expand Down Expand Up @@ -1028,7 +1035,7 @@ describe('MDC-based MatChipGrid', () => {

@Component({
template: `
<mat-chip-grid [tabIndex]="tabIndex" #chipGrid>
<mat-chip-grid [tabIndex]="tabIndex" [role]="role" #chipGrid>
<mat-chip-row *ngFor="let i of chips"
[editable]="editable">
{{name}} {{i + 1}}
Expand All @@ -1041,6 +1048,7 @@ class StandardChipGrid {
tabIndex: number = 0;
chips = [0, 1, 2, 3, 4];
editable = false;
role: string | null = null;
}

@Component({
Expand Down
7 changes: 2 additions & 5 deletions src/material-experimental/mdc-chips/chip-grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export class MatChipGrid
/** The chip input to add more chips */
protected _chipInput: MatChipTextControl;

protected override _defaultRole = 'grid';

/**
* Function when touched. Set as part of ControlValueAccessor implementation.
* @docs-private
Expand Down Expand Up @@ -186,11 +188,6 @@ export class MatChipGrid
);
}

/** The ARIA role applied to the chip grid. */
override get role(): string | null {
return this.empty ? null : 'grid';
}

/**
* Implemented as part of MatFormFieldControl.
* @docs-private
Expand Down
10 changes: 9 additions & 1 deletion src/material-experimental/mdc-chips/chip-listbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ describe('MDC-based MatChipListbox', () => {
expect(chipListboxNativeElement.hasAttribute('role')).toBe(false);
});

it('should be able to set a custom role', () => {
testComponent.role = 'grid';
fixture.detectChanges();

expect(chipListboxNativeElement.getAttribute('role')).toBe('grid');
});

it('should not set aria-required when it does not have a role', () => {
testComponent.chips = [];
fixture.detectChanges();
Expand Down Expand Up @@ -745,7 +752,7 @@ describe('MDC-based MatChipListbox', () => {

@Component({
template: `
<mat-chip-listbox [tabIndex]="tabIndex" [selectable]="selectable">
<mat-chip-listbox [tabIndex]="tabIndex" [selectable]="selectable" [role]="role">
<mat-chip-option *ngFor="let i of chips" (select)="chipSelect(i)"
(deselect)="chipDeselect(i)">
{{name}} {{i + 1}}
Expand All @@ -759,6 +766,7 @@ class StandardChipListbox {
chipDeselect: (index?: number) => void = () => {};
tabIndex: number = 0;
chips = [0, 1, 2, 3, 4];
role: string | null = null;
}

@Component({
Expand Down
5 changes: 1 addition & 4 deletions src/material-experimental/mdc-chips/chip-listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,8 @@ export class MatChipListbox
*/
_onChange: (value: any) => void = () => {};

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

/** Whether the user should be allowed to select multiple chips. */
@Input()
Expand Down
11 changes: 11 additions & 0 deletions src/material-experimental/mdc-chips/chip-option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ describe('MDC-based Option Chips', () => {
.withContext('Expected chip ripples to be disabled.')
.toBe(true);
});

it('should have the correct role', () => {
expect(chipNativeElement.getAttribute('role')).toBe('presentation');
});

it('should be able to set a custom role', () => {
chipInstance.role = 'button';
fixture.detectChanges();

expect(chipNativeElement.getAttribute('role')).toBe('button');
});
});

describe('keyboard behavior', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/material-experimental/mdc-chips/chip-row.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ describe('MDC-based Row Chips', () => {

expect(event.defaultPrevented).toBe(true);
});

it('should have the correct role', () => {
expect(chipNativeElement.getAttribute('role')).toBe('row');
});

it('should be able to set a custom role', () => {
chipInstance.role = 'button';
fixture.detectChanges();

expect(chipNativeElement.getAttribute('role')).toBe('button');
});
});

describe('keyboard behavior', () => {
Expand Down
15 changes: 9 additions & 6 deletions src/material-experimental/mdc-chips/chip-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export class MatChipSet
/** Subject that emits when the component has been destroyed. */
protected _destroyed = new Subject<void>();

/** Role to use if it hasn't been overwritten by the user. */
protected _defaultRole = 'presentation';

/** Combined stream of all of the child chips' remove events. */
get chipDestroyedChanges(): Observable<MatChipEvent> {
return this._getChipStream(chip => chip.destroyed);
Expand Down Expand Up @@ -163,17 +166,17 @@ export class MatChipSet
/** The ARIA role applied to the chip set. */
@Input()
get role(): string | null {
if (this._role) {
return this._role;
} else {
return this.empty ? null : 'presentation';
if (this._explicitRole) {
return this._explicitRole;
}

return this.empty ? null : this._defaultRole;
}

set role(value: string | null) {
this._role = value;
this._explicitRole = value;
}
private _role: string | null = null;
private _explicitRole: string | null = null;

/** Whether any of the chips inside of this chip-set has focus. */
get focused(): boolean {
Expand Down
14 changes: 11 additions & 3 deletions src/material/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ describe('MatChipList', () => {
expect(chipListNativeElement.hasAttribute('role')).toBe(false);
expect(chipListNativeElement.hasAttribute('aria-required')).toBe(false);
});

it('should be able to set a custom role', () => {
fixture.componentInstance.chipList.role = 'grid';
fixture.detectChanges();

expect(chipListNativeElement.getAttribute('role')).toBe('grid');
});
});

describe('focus behaviors', () => {
Expand Down Expand Up @@ -1725,9 +1732,9 @@ class FalsyValueChipList {
@Component({
template: `
<mat-chip-list>
<mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
{{ food.viewValue }}
</mat-chip>
<mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
{{ food.viewValue }}
</mat-chip>
</mat-chip-list>
`,
})
Expand All @@ -1738,6 +1745,7 @@ class SelectedChipList {
{value: 2, viewValue: 'Pasta', selected: true},
];
@ViewChildren(MatChip) chips: QueryList<MatChip>;
@ViewChild(MatChipList, {static: false}) chipList: MatChipList;
}

@Component({
Expand Down
9 changes: 9 additions & 0 deletions src/material/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,18 @@ export class MatChipList
}

/** The ARIA role applied to the chip list. */
@Input()
get role(): string | null {
if (this._explicitRole) {
return this._explicitRole;
}

return this.empty ? null : 'listbox';
}
set role(role: string | null) {
this._explicitRole = role;
}
private _explicitRole?: string | null;

/**
* Implemented as part of MatFormFieldControl.
Expand Down
22 changes: 22 additions & 0 deletions src/material/chips/chip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ describe('MatChip', () => {

expect(chip.getAttribute('tabindex')).toBe('15');
});

it('should have the correct role', () => {
fixture = TestBed.createComponent(BasicChip);
fixture.detectChanges();
chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!;
chipNativeElement = chipDebugElement.nativeElement;

expect(chipNativeElement.getAttribute('role')).toBe('option');
});

it('should be able to set a custom role', () => {
fixture = TestBed.createComponent(BasicChip);
fixture.detectChanges();
chipDebugElement = fixture.debugElement.query(By.directive(MatChip))!;
chipInstance = chipDebugElement.injector.get<MatChip>(MatChip);
chipNativeElement = chipDebugElement.nativeElement;

chipInstance.role = 'gridcell';
fixture.detectChanges();

expect(chipNativeElement.getAttribute('role')).toBe('gridcell');
});
});

describe('MatChip', () => {
Expand Down
5 changes: 4 additions & 1 deletion src/material/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class MatChipTrailingIcon {}
host: {
'class': 'mat-chip mat-focus-indicator',
'[attr.tabindex]': 'disabled ? null : tabIndex',
'role': 'option',
'[attr.role]': 'role',
'[class.mat-chip-selected]': 'selected',
'[class.mat-chip-with-avatar]': 'avatar',
'[class.mat-chip-with-trailing-icon]': 'trailingIcon || removeIcon',
Expand Down Expand Up @@ -207,6 +207,9 @@ export class MatChip
/** The chip's remove toggler. */
@ContentChild(MAT_CHIP_REMOVE) removeIcon: MatChipRemove;

/** ARIA role that should be applied to the chip. */
@Input() role: string = 'option';

/** Whether the chip is selected. */
@Input()
get selected(): boolean {
Expand Down
6 changes: 4 additions & 2 deletions tools/public_api_guard/material/chips.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
removeIcon: MatChipRemove;
rippleConfig: RippleConfig & RippleGlobalOptions;
get rippleDisabled(): boolean;
role: string;
select(): void;
get selectable(): boolean;
set selectable(value: BooleanInput);
Expand All @@ -108,7 +109,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
// (undocumented)
protected _value: any;
// (undocumented)
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"]>;
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"]>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatChip, [null, null, null, { optional: true; }, null, null, { optional: true; }, { attribute: "tabindex"; }]>;
}
Expand Down Expand Up @@ -235,6 +236,7 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl
// (undocumented)
protected _required: boolean | undefined;
get role(): string | null;
set role(role: string | null);
get selectable(): boolean;
set selectable(value: BooleanInput);
// (undocumented)
Expand Down Expand Up @@ -264,7 +266,7 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl
// (undocumented)
writeValue(value: any): void;
// (undocumented)
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"], ["*"]>;
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"], ["*"]>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatChipList, [null, null, { optional: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }]>;
}
Expand Down

0 comments on commit f05e65a

Please sign in to comment.