diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index 8e66bc1ccfeb..4ca28b2b946b 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -175,6 +175,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo get multiple(): boolean { return this._multiple; } set multiple(value: boolean) { this._multiple = coerceBooleanProperty(value); + this._syncChipsState(); } private _multiple: boolean = false; @@ -267,7 +268,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo get disabled(): boolean { return this.ngControl ? !!this.ngControl.disabled : this._disabled; } set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); - this._syncChipsDisabledState(); + this._syncChipsState(); } protected _disabled: boolean = false; @@ -371,7 +372,7 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo // Since this happens after the content has been // checked, we need to defer it to the next tick. Promise.resolve().then(() => { - this._syncChipsDisabledState(); + this._syncChipsState(); }); } @@ -781,11 +782,12 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo return this.chips.some(chip => chip._hasFocus); } - /** Syncs the list's disabled state with the individual chips. */ - private _syncChipsDisabledState() { + /** Syncs the list's state with the individual chips. */ + private _syncChipsState() { if (this.chips) { this.chips.forEach(chip => { chip.disabled = this._disabled; + chip._chipListMultiple = this.multiple; }); } } diff --git a/src/lib/chips/chip.spec.ts b/src/lib/chips/chip.spec.ts index c08144603d50..2875266932ac 100644 --- a/src/lib/chips/chip.spec.ts +++ b/src/lib/chips/chip.spec.ts @@ -1,12 +1,12 @@ import {Directionality} from '@angular/cdk/bidi'; import {BACKSPACE, DELETE, SPACE} from '@angular/cdk/keycodes'; import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing'; -import {Component, DebugElement} from '@angular/core'; +import {Component, DebugElement, ViewChild} from '@angular/core'; import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; -import {MatChip, MatChipEvent, MatChipSelectionChange, MatChipsModule} from './index'; +import {MatChip, MatChipEvent, MatChipSelectionChange, MatChipsModule, MatChipList} from './index'; describe('Chips', () => { @@ -257,7 +257,19 @@ describe('Chips', () => { expect(testComponent.chipSelectionChange).toHaveBeenCalledWith(CHIP_DESELECTED_EVENT); }); - it('should have correct aria-selected', () => { + it('should have correct aria-selected in single selection mode', () => { + expect(chipNativeElement.hasAttribute('aria-selected')).toBe(false); + + testComponent.selected = true; + fixture.detectChanges(); + + expect(chipNativeElement.getAttribute('aria-selected')).toBe('true'); + }); + + it('should have the correct aria-selected in multi-selection mode', () => { + testComponent.chipList.multiple = true; + fixture.detectChanges(); + expect(chipNativeElement.getAttribute('aria-selected')).toBe('false'); testComponent.selected = true; @@ -265,6 +277,7 @@ describe('Chips', () => { expect(chipNativeElement.getAttribute('aria-selected')).toBe('true'); }); + }); describe('when selectable is false', () => { @@ -390,6 +403,7 @@ describe('Chips', () => { ` }) class SingleChip { + @ViewChild(MatChipList) chipList: MatChipList; disabled: boolean = false; name: string = 'Test'; color: string = 'primary'; diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 715e246aa750..10fe4bb79787 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -145,6 +145,9 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes /** Whether the chip list is selectable */ chipListSelectable: boolean = true; + /** Whether the chip list is in multi-selection mode. */ + _chipListMultiple: boolean = false; + /** The chip avatar */ @ContentChild(MatChipAvatar) avatar: MatChipAvatar; @@ -218,7 +221,10 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes /** The ARIA selected applied to the chip. */ get ariaSelected(): string | null { - return this.selectable ? this.selected.toString() : null; + // Remove the `aria-selected` when the chip is deselected in single-selection mode, because + // it adds noise to NVDA users where "not selected" will be read out for each chip. + return this.selectable && (this._chipListMultiple || this.selected) ? + this.selected.toString() : null; } constructor(public _elementRef: ElementRef, diff --git a/tools/public_api_guard/lib/chips.d.ts b/tools/public_api_guard/lib/chips.d.ts index cc7433a965d9..e0670faeb05e 100644 --- a/tools/public_api_guard/lib/chips.d.ts +++ b/tools/public_api_guard/lib/chips.d.ts @@ -5,6 +5,7 @@ export declare const _MatChipMixinBase: CanColorCtor & CanDisableRippleCtor & Ca export declare const MAT_CHIPS_DEFAULT_OPTIONS: InjectionToken; export declare class MatChip extends _MatChipMixinBase implements FocusableOption, OnDestroy, CanColor, CanDisable, CanDisableRipple, RippleTarget { + _chipListMultiple: boolean; _elementRef: ElementRef; _hasFocus: boolean; readonly _onBlur: Subject;