From ab077d3baf1be714ef6224f5802153f91c8a3cea Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 12 Apr 2018 23:54:16 +0200 Subject: [PATCH] fix(selection-list): toggle newly-focused item when pressing arrow key + shift [Based on the accessibility guidelines](https://www.w3.org/TR/wai-aria-practices-1.1/), multi-select listboxes can support toggling items by pressing the arrow keys + shift. These changes implement the behavior in the selection list. --- src/lib/list/selection-list.spec.ts | 48 +++++++++++++++++++++++++++++ src/lib/list/selection-list.ts | 18 ++++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts index 90335550e3e6..2315e69e03f1 100644 --- a/src/lib/list/selection-list.spec.ts +++ b/src/lib/list/selection-list.spec.ts @@ -233,6 +233,30 @@ describe('MatSelectionList without forms', () => { expect(manager.activeItemIndex).toEqual(1); }); + it('should focus and toggle the next item when pressing SHIFT + UP_ARROW', () => { + const manager = selectionList.componentInstance._keyManager; + const upKeyEvent = createKeyboardEvent('keydown', UP_ARROW); + Object.defineProperty(upKeyEvent, 'shiftKey', {get: () => true}); + + dispatchFakeEvent(listOptions[3].nativeElement, 'focus'); + expect(manager.activeItemIndex).toBe(3); + + expect(listOptions[1].componentInstance.selected).toBe(false); + expect(listOptions[2].componentInstance.selected).toBe(false); + + selectionList.componentInstance._keydown(upKeyEvent); + fixture.detectChanges(); + + expect(listOptions[1].componentInstance.selected).toBe(false); + expect(listOptions[2].componentInstance.selected).toBe(true); + + selectionList.componentInstance._keydown(upKeyEvent); + fixture.detectChanges(); + + expect(listOptions[1].componentInstance.selected).toBe(true); + expect(listOptions[2].componentInstance.selected).toBe(true); + }); + it('should focus next item when press DOWN ARROW', () => { const manager = selectionList.componentInstance._keyManager; @@ -245,6 +269,30 @@ describe('MatSelectionList without forms', () => { expect(manager.activeItemIndex).toEqual(3); }); + it('should focus and toggle the next item when pressing SHIFT + DOWN_ARROW', () => { + const manager = selectionList.componentInstance._keyManager; + const downKeyEvent = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(downKeyEvent, 'shiftKey', {get: () => true}); + + dispatchFakeEvent(listOptions[0].nativeElement, 'focus'); + expect(manager.activeItemIndex).toBe(0); + + expect(listOptions[1].componentInstance.selected).toBe(false); + expect(listOptions[2].componentInstance.selected).toBe(false); + + selectionList.componentInstance._keydown(downKeyEvent); + fixture.detectChanges(); + + expect(listOptions[1].componentInstance.selected).toBe(true); + expect(listOptions[2].componentInstance.selected).toBe(false); + + selectionList.componentInstance._keydown(downKeyEvent); + fixture.detectChanges(); + + expect(listOptions[1].componentInstance.selected).toBe(true); + expect(listOptions[2].componentInstance.selected).toBe(true); + }); + it('should be able to focus the first item when pressing HOME', () => { const manager = selectionList.componentInstance._keyManager; expect(manager.activeItemIndex).toBe(-1); diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index e46895f219f2..934fc41fe54b 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -9,7 +9,7 @@ import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {SelectionModel} from '@angular/cdk/collections'; -import {SPACE, ENTER, HOME, END} from '@angular/cdk/keycodes'; +import {SPACE, ENTER, HOME, END, UP_ARROW, DOWN_ARROW} from '@angular/cdk/keycodes'; import { AfterContentInit, Attribute, @@ -370,7 +370,11 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu /** Passes relevant key presses to our key manager. */ _keydown(event: KeyboardEvent) { - switch (event.keyCode) { + const keyCode = event.keyCode; + const manager = this._keyManager; + const previousFocusIndex = manager.activeItemIndex; + + switch (keyCode) { case SPACE: case ENTER: if (!this.disabled) { @@ -382,12 +386,16 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu break; case HOME: case END: - event.keyCode === HOME ? this._keyManager.setFirstItemActive() : - this._keyManager.setLastItemActive(); + keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive(); event.preventDefault(); break; default: - this._keyManager.onKeydown(event); + manager.onKeydown(event); + } + + if ((keyCode === UP_ARROW || keyCode === DOWN_ARROW) && event.shiftKey && + manager.activeItemIndex !== previousFocusIndex) { + this._toggleSelectOnFocusedOption(); } }