Skip to content

fix(material/chips): allow for role to be overwritten on chip list and chip #15794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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