Skip to content

fix(material/chips): add support for aria-description #25935

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
Nov 18, 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<mat-chip-row *ngFor="let fruit of fruits"
(removed)="remove(fruit)"
[editable]="true"
(edited)="edit(fruit, $event)">
(edited)="edit(fruit, $event)"
[aria-description]="'press enter to edit ' + fruit.name">
{{fruit.name}}
<button matChipRemove [attr.aria-label]="'remove ' + fruit">
<button matChipRemove [attr.aria-label]="'remove ' + fruit.name">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ChipsInputExample {

// Edit existing fruit
const index = this.fruits.indexOf(fruit);
if (index > 0) {
if (index >= 0) {
this.fruits[index].name = value;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/material/chips/chip-option.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[_allowFocusWhenDisabled]="true"
[attr.aria-selected]="ariaSelected"
[attr.aria-label]="ariaLabel"
[attr.aria-description]="ariaDescription"
role="option">
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic" *ngIf="_hasLeadingGraphic()">
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
Expand Down
22 changes: 21 additions & 1 deletion src/material/chips/chip-option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,23 @@ describe('MDC-based Option Chips', () => {
});
});

describe('a11y', () => {
it('should apply `ariaLabel` and `ariaDesciption` to the element with option role', () => {
testComponent.ariaLabel = 'option name';
testComponent.ariaDescription = 'option description';

fixture.detectChanges();

const optionElement = fixture.nativeElement.querySelector('[role="option"]') as HTMLElement;
expect(optionElement)
.withContext('expected to find an element with option role')
.toBeTruthy();

expect(optionElement.getAttribute('aria-label')).toBe('option name');
expect(optionElement.getAttribute('aria-description')).toBe('option description');
});
});

it('should contain a focus indicator inside the text label', () => {
const label = chipNativeElement.querySelector('.mdc-evolution-chip__text-label');
expect(label?.querySelector('.mat-mdc-focus-indicator')).toBeTruthy();
Expand All @@ -310,7 +327,8 @@ describe('MDC-based Option Chips', () => {
<mat-chip-option [selectable]="selectable"
[color]="color" [selected]="selected" [disabled]="disabled"
(destroyed)="chipDestroy($event)"
(selectionChange)="chipSelectionChange($event)">
(selectionChange)="chipSelectionChange($event)"
[aria-label]="ariaLabel" [aria-description]="ariaDescription">
<span class="avatar" matChipAvatar></span>
{{name}}
</mat-chip-option>
Expand All @@ -325,6 +343,8 @@ class SingleChip {
selected: boolean = false;
selectable: boolean = true;
shouldShow: boolean = true;
ariaLabel: string | null = null;
ariaDescription: string | null = null;

chipDestroy: (event?: MatChipEvent) => void = () => {};
chipSelectionChange: (event?: MatChipSelectionChange) => void = () => {};
Expand Down
1 change: 1 addition & 0 deletions src/material/chips/chip-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class MatChipSelectionChange {
'[class.mat-mdc-chip-with-trailing-icon]': '_hasTrailingIcon()',
'[attr.tabindex]': 'null',
'[attr.aria-label]': 'null',
'[attr.aria-description]': 'null',
'[attr.role]': 'role',
'[id]': 'id',
},
Expand Down
3 changes: 2 additions & 1 deletion src/material/chips/chip-row.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
[attr.role]="editable ? 'button' : null"
[tabIndex]="tabIndex"
[disabled]="disabled"
[attr.aria-label]="ariaLabel">
[attr.aria-label]="ariaLabel"
[attr.aria-description]="ariaDescription">
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic" *ngIf="leadingIcon">
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
</span>
Expand Down
24 changes: 23 additions & 1 deletion src/material/chips/chip-row.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,25 @@ describe('MDC-based Row Chips', () => {
expect(document.activeElement).not.toBe(primaryAction);
}));
});

describe('a11y', () => {
it('should apply `ariaLabel` and `ariaDesciption` to the primary gridcell', () => {
fixture.componentInstance.ariaLabel = 'chip name';
fixture.componentInstance.ariaDescription = 'chip description';

fixture.detectChanges();

const primaryGridCell = fixture.nativeElement.querySelector(
'[role="gridcell"].mdc-evolution-chip__cell--primary .mat-mdc-chip-action',
);
expect(primaryGridCell)
.withContext('expected to find the grid cell for the primary chip action')
.toBeTruthy();

expect(primaryGridCell.getAttribute('aria-label')).toBe('chip name');
expect(primaryGridCell.getAttribute('aria-description')).toBe('chip description');
});
});
});
});

Expand All @@ -342,7 +361,8 @@ describe('MDC-based Row Chips', () => {
<mat-chip-row [removable]="removable"
[color]="color" [disabled]="disabled" [editable]="editable"
(destroyed)="chipDestroy($event)"
(removed)="chipRemove($event)" (edited)="chipEdit($event)">
(removed)="chipRemove($event)" (edited)="chipEdit($event)"
[aria-label]="ariaLabel" [aria-description]="ariaDescription">
{{name}}
<button matChipRemove>x</button>
<span *ngIf="useCustomEditInput" class="projected-edit-input" matChipEditInput></span>
Expand All @@ -361,6 +381,8 @@ class SingleChip {
shouldShow: boolean = true;
editable: boolean = false;
useCustomEditInput: boolean = true;
ariaLabel: string | null = null;
ariaDescription: string | null = null;

chipDestroy: (event?: MatChipEvent) => void = () => {};
chipRemove: (event?: MatChipEvent) => void = () => {};
Expand Down
1 change: 1 addition & 0 deletions src/material/chips/chip-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface MatChipEditedEvent extends MatChipEvent {
'[id]': 'id',
'[attr.tabindex]': 'null',
'[attr.aria-label]': 'null',
'[attr.aria-description]': 'null',
'[attr.role]': 'role',
'(mousedown)': '_mousedown($event)',
'(dblclick)': '_doubleclick($event)',
Expand Down
3 changes: 3 additions & 0 deletions src/material/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export class MatChip
/** ARIA label for the content of the chip. */
@Input('aria-label') ariaLabel: string | null = null;

/** ARIA description for the content of the chip. */
@Input('aria-description') ariaDescription: string | null = null;

private _textElement!: HTMLElement;

/**
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/chips.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const MAT_CHIPS_DEFAULT_OPTIONS: InjectionToken<MatChipsDefaultOptions>;
export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColor, CanDisableRipple, CanDisable, HasTabIndex, OnDestroy {
constructor(_changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>, _ngZone: NgZone, _focusMonitor: FocusMonitor, _document: any, animationMode?: string, _globalRippleOptions?: RippleGlobalOptions | undefined, tabIndex?: string);
_animationsDisabled: boolean;
ariaDescription: string | null;
ariaLabel: string | null;
protected basicChipAttrName: string;
// (undocumented)
Expand Down Expand Up @@ -113,7 +114,7 @@ export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColo
// (undocumented)
protected _value: any;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatChip, "mat-basic-chip, mat-chip", ["matChip"], { "color": "color"; "disabled": "disabled"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "role": "role"; "id": "id"; "ariaLabel": "aria-label"; "value": "value"; "removable": "removable"; "highlighted": "highlighted"; }, { "removed": "removed"; "destroyed": "destroyed"; }, ["leadingIcon", "trailingIcon", "removeIcon"], ["mat-chip-avatar, [matChipAvatar]", "*", "mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"], false, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MatChip, "mat-basic-chip, mat-chip", ["matChip"], { "color": "color"; "disabled": "disabled"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "role": "role"; "id": "id"; "ariaLabel": "aria-label"; "ariaDescription": "aria-description"; "value": "value"; "removable": "removable"; "highlighted": "highlighted"; }, { "removed": "removed"; "destroyed": "destroyed"; }, ["leadingIcon", "trailingIcon", "removeIcon"], ["mat-chip-avatar, [matChipAvatar]", "*", "mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"], false, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatChip, [null, null, null, null, null, { optional: true; }, { optional: true; }, { attribute: "tabindex"; }]>;
}
Expand Down