From 8353dc596f97738b13eb37ad99c2773b5f7b7da9 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 2 Mar 2022 09:41:12 +0100 Subject: [PATCH] fix(material/chips): allow for role to be overwritten on chip list and chip Allows for the ARIA `role` of the `mat-chip-list` and `mat-chip` to be overwritten. Fixes #15787. --- .../mdc-chips/chip-grid.spec.ts | 10 ++++++++- .../mdc-chips/chip-grid.ts | 7 ++---- .../mdc-chips/chip-listbox.spec.ts | 10 ++++++++- .../mdc-chips/chip-listbox.ts | 5 +---- .../mdc-chips/chip-option.spec.ts | 11 ++++++++++ .../mdc-chips/chip-row.spec.ts | 11 ++++++++++ .../mdc-chips/chip-set.ts | 15 ++++++++----- src/material/chips/chip-list.spec.ts | 14 +++++++++--- src/material/chips/chip-list.ts | 9 ++++++++ src/material/chips/chip.spec.ts | 22 +++++++++++++++++++ src/material/chips/chip.ts | 5 ++++- tools/public_api_guard/material/chips.md | 6 +++-- 12 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/material-experimental/mdc-chips/chip-grid.spec.ts b/src/material-experimental/mdc-chips/chip-grid.spec.ts index 96e44bcb7d25..3208ac3e8a96 100644 --- a/src/material-experimental/mdc-chips/chip-grid.spec.ts +++ b/src/material-experimental/mdc-chips/chip-grid.spec.ts @@ -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', () => { @@ -1028,7 +1035,7 @@ describe('MDC-based MatChipGrid', () => { @Component({ template: ` - + {{name}} {{i + 1}} @@ -1041,6 +1048,7 @@ class StandardChipGrid { tabIndex: number = 0; chips = [0, 1, 2, 3, 4]; editable = false; + role: string | null = null; } @Component({ diff --git a/src/material-experimental/mdc-chips/chip-grid.ts b/src/material-experimental/mdc-chips/chip-grid.ts index 119d184d49b5..b1370f6bcc69 100644 --- a/src/material-experimental/mdc-chips/chip-grid.ts +++ b/src/material-experimental/mdc-chips/chip-grid.ts @@ -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 @@ -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 diff --git a/src/material-experimental/mdc-chips/chip-listbox.spec.ts b/src/material-experimental/mdc-chips/chip-listbox.spec.ts index 105e2e45cbe4..6b42be0408cd 100644 --- a/src/material-experimental/mdc-chips/chip-listbox.spec.ts +++ b/src/material-experimental/mdc-chips/chip-listbox.spec.ts @@ -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(); @@ -745,7 +752,7 @@ describe('MDC-based MatChipListbox', () => { @Component({ template: ` - + {{name}} {{i + 1}} @@ -759,6 +766,7 @@ class StandardChipListbox { chipDeselect: (index?: number) => void = () => {}; tabIndex: number = 0; chips = [0, 1, 2, 3, 4]; + role: string | null = null; } @Component({ diff --git a/src/material-experimental/mdc-chips/chip-listbox.ts b/src/material-experimental/mdc-chips/chip-listbox.ts index c3ee1d4ae724..35fdea92b1bb 100644 --- a/src/material-experimental/mdc-chips/chip-listbox.ts +++ b/src/material-experimental/mdc-chips/chip-listbox.ts @@ -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() diff --git a/src/material-experimental/mdc-chips/chip-option.spec.ts b/src/material-experimental/mdc-chips/chip-option.spec.ts index 626f8f58494e..58d6a1db9b1c 100644 --- a/src/material-experimental/mdc-chips/chip-option.spec.ts +++ b/src/material-experimental/mdc-chips/chip-option.spec.ts @@ -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', () => { diff --git a/src/material-experimental/mdc-chips/chip-row.spec.ts b/src/material-experimental/mdc-chips/chip-row.spec.ts index c130cda43d36..7fa36e2ce0e0 100644 --- a/src/material-experimental/mdc-chips/chip-row.spec.ts +++ b/src/material-experimental/mdc-chips/chip-row.spec.ts @@ -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', () => { diff --git a/src/material-experimental/mdc-chips/chip-set.ts b/src/material-experimental/mdc-chips/chip-set.ts index 1b8787de063a..381bcbfa6361 100644 --- a/src/material-experimental/mdc-chips/chip-set.ts +++ b/src/material-experimental/mdc-chips/chip-set.ts @@ -88,6 +88,9 @@ export class MatChipSet /** Subject that emits when the component has been destroyed. */ protected _destroyed = new Subject(); + /** 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 { return this._getChipStream(chip => chip.destroyed); @@ -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 { diff --git a/src/material/chips/chip-list.spec.ts b/src/material/chips/chip-list.spec.ts index f1784412638c..74ad017a8b92 100644 --- a/src/material/chips/chip-list.spec.ts +++ b/src/material/chips/chip-list.spec.ts @@ -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', () => { @@ -1725,9 +1732,9 @@ class FalsyValueChipList { @Component({ template: ` - - {{ food.viewValue }} - + + {{ food.viewValue }} + `, }) @@ -1738,6 +1745,7 @@ class SelectedChipList { {value: 2, viewValue: 'Pasta', selected: true}, ]; @ViewChildren(MatChip) chips: QueryList; + @ViewChild(MatChipList, {static: false}) chipList: MatChipList; } @Component({ diff --git a/src/material/chips/chip-list.ts b/src/material/chips/chip-list.ts index ef040ace66a7..f61678cdcaa8 100644 --- a/src/material/chips/chip-list.ts +++ b/src/material/chips/chip-list.ts @@ -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. diff --git a/src/material/chips/chip.spec.ts b/src/material/chips/chip.spec.ts index d74f241b1756..17c82fe4640c 100644 --- a/src/material/chips/chip.spec.ts +++ b/src/material/chips/chip.spec.ts @@ -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); + chipNativeElement = chipDebugElement.nativeElement; + + chipInstance.role = 'gridcell'; + fixture.detectChanges(); + + expect(chipNativeElement.getAttribute('role')).toBe('gridcell'); + }); }); describe('MatChip', () => { diff --git a/src/material/chips/chip.ts b/src/material/chips/chip.ts index 1307926cba3b..fb1108ebb0ea 100644 --- a/src/material/chips/chip.ts +++ b/src/material/chips/chip.ts @@ -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', @@ -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 { diff --git a/tools/public_api_guard/material/chips.md b/tools/public_api_guard/material/chips.md index a8f8bada3b3c..c1641c19127b 100644 --- a/tools/public_api_guard/material/chips.md +++ b/tools/public_api_guard/material/chips.md @@ -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); @@ -108,7 +109,7 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes // (undocumented) protected _value: any; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -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) @@ -264,7 +266,7 @@ export class MatChipList extends _MatChipListBase implements MatFormFieldControl // (undocumented) writeValue(value: any): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }