Skip to content

Commit 512ef76

Browse files
committed
fix(select): support ctrl+a shortcut for multi-select
[Based on the accessibility guidelines](https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox), multi-selection lists should support selecting/deselecting all using ctrl+a.
1 parent 69629ad commit 512ef76

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

src/lib/select/select.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SPACE,
1010
TAB,
1111
UP_ARROW,
12+
A,
1213
} from '@angular/cdk/keycodes';
1314
import {OverlayContainer} from '@angular/cdk/overlay';
1415
import {Platform} from '@angular/cdk/platform';
@@ -3823,6 +3824,96 @@ describe('MatSelect', () => {
38233824
expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(2);
38243825
}));
38253826

3827+
it('should select all options when pressing ctrl + a', () => {
3828+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3829+
const options = fixture.componentInstance.options.toArray();
3830+
3831+
expect(testInstance.control.value).toBeFalsy();
3832+
expect(options.every(option => option.selected)).toBe(false);
3833+
3834+
fixture.componentInstance.select.open();
3835+
fixture.detectChanges();
3836+
3837+
const event = createKeyboardEvent('keydown', A, selectElement);
3838+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3839+
dispatchEvent(selectElement, event);
3840+
fixture.detectChanges();
3841+
3842+
expect(options.every(option => option.selected)).toBe(true);
3843+
expect(testInstance.control.value).toEqual([
3844+
'steak-0',
3845+
'pizza-1',
3846+
'tacos-2',
3847+
'sandwich-3',
3848+
'chips-4',
3849+
'eggs-5',
3850+
'pasta-6',
3851+
'sushi-7'
3852+
]);
3853+
});
3854+
3855+
it('should select all options when pressing ctrl + a when some options are selected', () => {
3856+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3857+
const options = fixture.componentInstance.options.toArray();
3858+
3859+
options[0].select();
3860+
fixture.detectChanges();
3861+
3862+
expect(testInstance.control.value).toEqual(['steak-0']);
3863+
expect(options.some(option => option.selected)).toBe(true);
3864+
3865+
fixture.componentInstance.select.open();
3866+
fixture.detectChanges();
3867+
3868+
const event = createKeyboardEvent('keydown', A, selectElement);
3869+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3870+
dispatchEvent(selectElement, event);
3871+
fixture.detectChanges();
3872+
3873+
expect(options.every(option => option.selected)).toBe(true);
3874+
expect(testInstance.control.value).toEqual([
3875+
'steak-0',
3876+
'pizza-1',
3877+
'tacos-2',
3878+
'sandwich-3',
3879+
'chips-4',
3880+
'eggs-5',
3881+
'pasta-6',
3882+
'sushi-7'
3883+
]);
3884+
});
3885+
3886+
it('should deselect all options with ctrl + a if all options are selected', () => {
3887+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3888+
const options = fixture.componentInstance.options.toArray();
3889+
3890+
options.forEach(option => option.select());
3891+
fixture.detectChanges();
3892+
3893+
expect(testInstance.control.value).toEqual([
3894+
'steak-0',
3895+
'pizza-1',
3896+
'tacos-2',
3897+
'sandwich-3',
3898+
'chips-4',
3899+
'eggs-5',
3900+
'pasta-6',
3901+
'sushi-7'
3902+
]);
3903+
expect(options.every(option => option.selected)).toBe(true);
3904+
3905+
fixture.componentInstance.select.open();
3906+
fixture.detectChanges();
3907+
3908+
const event = createKeyboardEvent('keydown', A, selectElement);
3909+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3910+
dispatchEvent(selectElement, event);
3911+
fixture.detectChanges();
3912+
3913+
expect(options.some(option => option.selected)).toBe(false);
3914+
expect(testInstance.control.value).toEqual([]);
3915+
});
3916+
38263917
});
38273918
});
38283919

src/lib/select/select.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
RIGHT_ARROW,
2020
SPACE,
2121
UP_ARROW,
22+
A,
2223
} from '@angular/cdk/keycodes';
2324
import {
2425
CdkConnectedOverlay,
@@ -695,6 +696,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
695696
} else if ((keyCode === ENTER || keyCode === SPACE) && manager.activeItem) {
696697
event.preventDefault();
697698
manager.activeItem._selectViaInteraction();
699+
} else if (this._multiple && keyCode === A && event.ctrlKey) {
700+
event.preventDefault();
701+
const hasDeselectedOptions = this.options.some(option => !option.selected);
702+
this.options.forEach(option => hasDeselectedOptions ? option.select() : option.deselect());
698703
} else {
699704
const previouslyFocusedIndex = manager.activeItemIndex;
700705

0 commit comments

Comments
 (0)