From 4adba9cd46e4ef62ad2791d3dbbc9d65af5e3d2b Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 18 Dec 2017 20:44:37 +0100 Subject: [PATCH] fix(select): support using shift + arrow key to toggle items in a multi-select Based on the accessibility guidelines for a multi-selection listbox (https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox), potentially we can support toggling the previous/next item if the user presses shift + up/down arrow. --- src/lib/select/select.spec.ts | 62 +++++++++++++++++++++++++++++++++++ src/lib/select/select.ts | 8 +++++ 2 files changed, 70 insertions(+) diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 83038234601e..55f27f8b682c 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -435,6 +435,68 @@ describe('MatSelect', () => { .toBe(false, 'Expected panel to stay closed.'); })); + it('should toggle the next option when pressing shift + DOWN_ARROW on a multi-select', + fakeAsync(() => { + fixture.destroy(); + + const multiFixture = TestBed.createComponent(MultiSelect); + const event = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + + multiFixture.detectChanges(); + select = multiFixture.debugElement.query(By.css('mat-select')).nativeElement; + + multiFixture.componentInstance.select.open(); + multiFixture.detectChanges(); + flush(); + + expect(multiFixture.componentInstance.select.value).toBeFalsy(); + + dispatchEvent(select, event); + multiFixture.detectChanges(); + + expect(multiFixture.componentInstance.select.value).toEqual(['pizza-1']); + + dispatchEvent(select, event); + multiFixture.detectChanges(); + + expect(multiFixture.componentInstance.select.value).toEqual(['pizza-1', 'tacos-2']); + })); + + it('should toggle the previous option when pressing shift + UP_ARROW on a multi-select', + fakeAsync(() => { + fixture.destroy(); + + const multiFixture = TestBed.createComponent(MultiSelect); + const event = createKeyboardEvent('keydown', UP_ARROW); + Object.defineProperty(event, 'shiftKey', {get: () => true}); + + multiFixture.detectChanges(); + select = multiFixture.debugElement.query(By.css('mat-select')).nativeElement; + + multiFixture.componentInstance.select.open(); + multiFixture.detectChanges(); + flush(); + + // Move focus down first. + for (let i = 0; i < 5; i++) { + dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + multiFixture.detectChanges(); + } + + expect(multiFixture.componentInstance.select.value).toBeFalsy(); + + dispatchEvent(select, event); + multiFixture.detectChanges(); + + expect(multiFixture.componentInstance.select.value).toEqual(['chips-4']); + + dispatchEvent(select, event); + multiFixture.detectChanges(); + + expect(multiFixture.componentInstance.select.value).toEqual(['sandwich-3', 'chips-4']); + })); + it('should prevent the default action when pressing space', fakeAsync(() => { const event = dispatchKeyboardEvent(select, 'keydown', SPACE); expect(event.defaultPrevented).toBe(true); diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 778f16bc8764..07344f5d3b75 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -665,7 +665,15 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, event.preventDefault(); this._keyManager.activeItem._selectViaInteraction(); } else { + const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW; + const previouslyFocusedIndex = this._keyManager.activeItemIndex; + this._keyManager.onKeydown(event); + + if (this._multiple && isArrowKey && event.shiftKey && this._keyManager.activeItem && + this._keyManager.activeItemIndex !== previouslyFocusedIndex) { + this._keyManager.activeItem._selectViaInteraction(); + } } }