Skip to content

Commit 4413893

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 f31b2f4 commit 4413893

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';
@@ -3869,6 +3870,96 @@ describe('MatSelect', () => {
38693870
expect(testInstance.control.value).toEqual([null, 'pizza-1', null]);
38703871
}));
38713872

3873+
it('should select all options when pressing ctrl + a', () => {
3874+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3875+
const options = fixture.componentInstance.options.toArray();
3876+
3877+
expect(testInstance.control.value).toBeFalsy();
3878+
expect(options.every(option => option.selected)).toBe(false);
3879+
3880+
fixture.componentInstance.select.open();
3881+
fixture.detectChanges();
3882+
3883+
const event = createKeyboardEvent('keydown', A, selectElement);
3884+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3885+
dispatchEvent(selectElement, event);
3886+
fixture.detectChanges();
3887+
3888+
expect(options.every(option => option.selected)).toBe(true);
3889+
expect(testInstance.control.value).toEqual([
3890+
'steak-0',
3891+
'pizza-1',
3892+
'tacos-2',
3893+
'sandwich-3',
3894+
'chips-4',
3895+
'eggs-5',
3896+
'pasta-6',
3897+
'sushi-7'
3898+
]);
3899+
});
3900+
3901+
it('should select all options when pressing ctrl + a when some options are selected', () => {
3902+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3903+
const options = fixture.componentInstance.options.toArray();
3904+
3905+
options[0].select();
3906+
fixture.detectChanges();
3907+
3908+
expect(testInstance.control.value).toEqual(['steak-0']);
3909+
expect(options.some(option => option.selected)).toBe(true);
3910+
3911+
fixture.componentInstance.select.open();
3912+
fixture.detectChanges();
3913+
3914+
const event = createKeyboardEvent('keydown', A, selectElement);
3915+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3916+
dispatchEvent(selectElement, event);
3917+
fixture.detectChanges();
3918+
3919+
expect(options.every(option => option.selected)).toBe(true);
3920+
expect(testInstance.control.value).toEqual([
3921+
'steak-0',
3922+
'pizza-1',
3923+
'tacos-2',
3924+
'sandwich-3',
3925+
'chips-4',
3926+
'eggs-5',
3927+
'pasta-6',
3928+
'sushi-7'
3929+
]);
3930+
});
3931+
3932+
it('should deselect all options with ctrl + a if all options are selected', () => {
3933+
const selectElement = fixture.nativeElement.querySelector('mat-select');
3934+
const options = fixture.componentInstance.options.toArray();
3935+
3936+
options.forEach(option => option.select());
3937+
fixture.detectChanges();
3938+
3939+
expect(testInstance.control.value).toEqual([
3940+
'steak-0',
3941+
'pizza-1',
3942+
'tacos-2',
3943+
'sandwich-3',
3944+
'chips-4',
3945+
'eggs-5',
3946+
'pasta-6',
3947+
'sushi-7'
3948+
]);
3949+
expect(options.every(option => option.selected)).toBe(true);
3950+
3951+
fixture.componentInstance.select.open();
3952+
fixture.detectChanges();
3953+
3954+
const event = createKeyboardEvent('keydown', A, selectElement);
3955+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
3956+
dispatchEvent(selectElement, event);
3957+
fixture.detectChanges();
3958+
3959+
expect(options.some(option => option.selected)).toBe(false);
3960+
expect(testInstance.control.value).toEqual([]);
3961+
});
3962+
38723963
});
38733964
});
38743965

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)