Skip to content

Commit

Permalink
fix(material/chips): implement ariaDescription with aria-describedby (#…
Browse files Browse the repository at this point in the history
…26105)

For the `ariaDescription` Input, implement with aria-describedby rather
than aria-description. aria-description is still in W3C Editor's Draft for ARIA
1.3.
  • Loading branch information
zarend authored Dec 9, 2022
1 parent 03264d8 commit dcf2fac
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 7 deletions.
4 changes: 3 additions & 1 deletion src/material/chips/chip-option.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[_allowFocusWhenDisabled]="true"
[attr.aria-selected]="ariaSelected"
[attr.aria-label]="ariaLabel"
[attr.aria-description]="ariaDescription"
[attr.aria-describedby]="_ariaDescriptionId"
role="option">
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic">
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
Expand All @@ -34,3 +34,5 @@
*ngIf="_hasTrailingIcon()">
<ng-content select="mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"></ng-content>
</span>

<span class="cdk-visually-hidden" [id]="_ariaDescriptionId">{{ariaDescription}}</span>
24 changes: 22 additions & 2 deletions src/material/chips/chip-option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,28 @@ describe('MDC-based Option Chips', () => {
.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');
expect(optionElement.getAttribute('aria-label')).toMatch(/option name/i);

const optionElementDescribedBy = optionElement!.getAttribute('aria-describedby');
expect(optionElementDescribedBy)
.withContext('expected primary grid cell to have a non-empty aria-describedby attribute')
.toBeTruthy();

const optionElementDescriptions = Array.from(
(fixture.nativeElement as HTMLElement).querySelectorAll(
optionElementDescribedBy!
.split(/\s+/g)
.map(x => `#${x}`)
.join(','),
),
);

const optionElementDescription = optionElementDescriptions
.map(x => x.textContent?.trim())
.join(' ')
.trim();

expect(optionElementDescription).toMatch(/option description/i);
});
});

Expand Down
4 changes: 3 additions & 1 deletion src/material/chips/chip-row.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
[tabIndex]="tabIndex"
[disabled]="disabled"
[attr.aria-label]="ariaLabel"
[attr.aria-description]="ariaDescription">
[attr.aria-describedby]="_ariaDescriptionId">
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic" *ngIf="leadingIcon">
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
</span>
Expand All @@ -38,3 +38,5 @@
*ngIf="_hasTrailingIcon()">
<ng-content select="mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"></ng-content>
</span>

<span class="cdk-visually-hidden" [id]="_ariaDescriptionId">{{ariaDescription}}</span>
26 changes: 23 additions & 3 deletions src/material/chips/chip-row.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,15 +340,35 @@ describe('MDC-based Row Chips', () => {

fixture.detectChanges();

const primaryGridCell = fixture.nativeElement.querySelector(
const primaryGridCell = (fixture.nativeElement as HTMLElement).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');
expect(primaryGridCell!.getAttribute('aria-label')).toMatch(/chip name/i);

const primaryGridCellDescribedBy = primaryGridCell!.getAttribute('aria-describedby');
expect(primaryGridCellDescribedBy)
.withContext('expected primary grid cell to have a non-empty aria-describedby attribute')
.toBeTruthy();

const primaryGridCellDescriptions = Array.from(
(fixture.nativeElement as HTMLElement).querySelectorAll(
primaryGridCellDescribedBy!
.split(/\s+/g)
.map(x => `#${x}`)
.join(','),
),
);

const primaryGridCellDescription = primaryGridCellDescriptions
.map(x => x.textContent?.trim())
.join(' ')
.trim();

expect(primaryGridCellDescription).toMatch(/chip description/i);
});
});
});
Expand Down
9 changes: 9 additions & 0 deletions src/material/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,21 @@ export class MatChip
/** A unique id for the chip. If none is supplied, it will be auto-generated. */
@Input() id: string = `mat-mdc-chip-${uid++}`;

// TODO(#26104): Consider deprecating and using `_computeAriaAccessibleName` instead.
// `ariaLabel` may be unnecessary, and `_computeAriaAccessibleName` only supports
// datepicker's use case.
/** ARIA label for the content of the chip. */
@Input('aria-label') ariaLabel: string | null = null;

// TODO(#26104): Consider deprecating and using `_computeAriaAccessibleName` instead.
// `ariaDescription` may be unnecessary, and `_computeAriaAccessibleName` only supports
// datepicker's use case.
/** ARIA description for the content of the chip. */
@Input('aria-description') ariaDescription: string | null = null;

/** Id of a span that contains this chip's aria description. */
_ariaDescriptionId = `${this.id}-aria-description`;

private _textElement!: HTMLElement;

/**
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/material/chips.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColo
constructor(_changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>, _ngZone: NgZone, _focusMonitor: FocusMonitor, _document: any, animationMode?: string, _globalRippleOptions?: RippleGlobalOptions | undefined, tabIndex?: string);
_animationsDisabled: boolean;
ariaDescription: string | null;
_ariaDescriptionId: string;
ariaLabel: string | null;
protected basicChipAttrName: string;
// (undocumented)
Expand Down

0 comments on commit dcf2fac

Please sign in to comment.