Skip to content

Commit ff0de11

Browse files
authored
fix(selection-list): incorrectly handling A key in some cases (#18513)
Fixes a couple of issues related to how the selection list handles the "A" key: 1. The typeahead doesn't work for the "A" key. It's because we were intercepting it earlier to support the "Select all" functionality. 2. Disables the ctrl + A shortcut in single selection mode, because it only makes sense during multi selection.
1 parent 4629e23 commit ff0de11

File tree

2 files changed

+61
-30
lines changed

2 files changed

+61
-30
lines changed

src/material/list/selection-list.spec.ts

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('MatSelectionList without forms', () => {
5757
}));
5858

5959
it('should be able to set a value on a list option', () => {
60-
const optionValues = ['inbox', 'starred', 'sent-mail', 'drafts'];
60+
const optionValues = ['inbox', 'starred', 'sent-mail', 'archive', 'drafts'];
6161

6262
optionValues.forEach((optionValue, index) => {
6363
expect(listOptions[index].componentInstance.value).toBe(optionValue);
@@ -337,35 +337,35 @@ describe('MatSelectionList without forms', () => {
337337
it('should restore focus if active option is destroyed', () => {
338338
const manager = selectionList.componentInstance._keyManager;
339339

340-
spyOn(listOptions[2].componentInstance, 'focus').and.callThrough();
341-
listOptions[3].componentInstance._handleFocus();
340+
spyOn(listOptions[3].componentInstance, 'focus').and.callThrough();
341+
listOptions[4].componentInstance._handleFocus();
342342

343-
expect(manager.activeItemIndex).toBe(3);
343+
expect(manager.activeItemIndex).toBe(4);
344344

345345
fixture.componentInstance.showLastOption = false;
346346
fixture.detectChanges();
347347

348-
expect(manager.activeItemIndex).toBe(2);
349-
expect(listOptions[2].componentInstance.focus).toHaveBeenCalled();
348+
expect(manager.activeItemIndex).toBe(3);
349+
expect(listOptions[3].componentInstance.focus).toHaveBeenCalled();
350350
});
351351

352352
it('should not attempt to focus the next option when the destroyed option was not focused',
353353
() => {
354354
const manager = selectionList.componentInstance._keyManager;
355355

356356
// Focus and blur the option to move the active item index.
357-
listOptions[3].componentInstance._handleFocus();
358-
listOptions[3].componentInstance._handleBlur();
357+
listOptions[4].componentInstance._handleFocus();
358+
listOptions[4].componentInstance._handleBlur();
359359

360-
spyOn(listOptions[2].componentInstance, 'focus').and.callThrough();
360+
spyOn(listOptions[3].componentInstance, 'focus').and.callThrough();
361361

362-
expect(manager.activeItemIndex).toBe(3);
362+
expect(manager.activeItemIndex).toBe(4);
363363

364364
fixture.componentInstance.showLastOption = false;
365365
fixture.detectChanges();
366366

367-
expect(manager.activeItemIndex).toBe(2);
368-
expect(listOptions[2].componentInstance.focus).not.toHaveBeenCalled();
367+
expect(manager.activeItemIndex).toBe(3);
368+
expect(listOptions[3].componentInstance.focus).not.toHaveBeenCalled();
369369
});
370370

371371
it('should focus previous item when press UP ARROW', () => {
@@ -475,7 +475,7 @@ describe('MatSelectionList without forms', () => {
475475
const event = dispatchKeyboardEvent(selectionList.nativeElement, 'keydown', END);
476476
fixture.detectChanges();
477477

478-
expect(manager.activeItemIndex).toBe(3);
478+
expect(manager.activeItemIndex).toBe(4);
479479
expect(event.defaultPrevented).toBe(true);
480480
});
481481

@@ -551,19 +551,33 @@ describe('MatSelectionList without forms', () => {
551551
fixture.detectChanges();
552552
tick(200);
553553

554-
expect(manager.activeItemIndex).toBe(3);
554+
expect(manager.activeItemIndex).toBe(4);
555555
}));
556556

557557
it('should be able to skip to an item by typing', fakeAsync(() => {
558558
const manager = selectionList.componentInstance._keyManager;
559559

560-
expect(manager.activeItemIndex).not.toBe(3);
560+
expect(manager.activeItemIndex).not.toBe(4);
561561

562562
const event = createKeyboardEvent('keydown', D, 'd');
563563
selectionList.componentInstance._keydown(event);
564564
fixture.detectChanges();
565565
tick(200);
566566

567+
expect(manager.activeItemIndex).toBe(4);
568+
}));
569+
570+
// Test for "A" specifically, because it's a special case that can be used to select all values.
571+
it('should be able to skip to an item by typing the letter "A"', fakeAsync(() => {
572+
const manager = selectionList.componentInstance._keyManager;
573+
574+
expect(manager.activeItemIndex).not.toBe(3);
575+
576+
const event = createKeyboardEvent('keydown', A, 'a');
577+
selectionList.componentInstance._keydown(event);
578+
fixture.detectChanges();
579+
tick(200);
580+
567581
expect(manager.activeItemIndex).toBe(3);
568582
}));
569583

@@ -588,7 +602,7 @@ describe('MatSelectionList without forms', () => {
588602
fixture.detectChanges();
589603
tick(100); // Tick the rest of the timeout.
590604

591-
expect(manager.activeItemIndex).toBe(3);
605+
expect(manager.activeItemIndex).toBe(4);
592606
expect(model.isEmpty()).toBe(true);
593607
}));
594608

@@ -902,7 +916,7 @@ describe('MatSelectionList without forms', () => {
902916

903917
describe('with single selection', () => {
904918
let fixture: ComponentFixture<SelectionListWithListOptions>;
905-
let listOption: DebugElement[];
919+
let listOptions: DebugElement[];
906920
let selectionList: DebugElement;
907921

908922
beforeEach(async(() => {
@@ -915,14 +929,14 @@ describe('MatSelectionList without forms', () => {
915929

916930
fixture = TestBed.createComponent(SelectionListWithListOptions);
917931
fixture.componentInstance.multiple = false;
918-
listOption = fixture.debugElement.queryAll(By.directive(MatListOption));
932+
listOptions = fixture.debugElement.queryAll(By.directive(MatListOption));
919933
selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!;
920934
fixture.detectChanges();
921935
}));
922936

923937
it('should select one option at a time', () => {
924-
const testListItem1 = listOption[1].injector.get<MatListOption>(MatListOption);
925-
const testListItem2 = listOption[2].injector.get<MatListOption>(MatListOption);
938+
const testListItem1 = listOptions[1].injector.get<MatListOption>(MatListOption);
939+
const testListItem2 = listOptions[2].injector.get<MatListOption>(MatListOption);
926940
const selectList =
927941
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
928942

@@ -932,16 +946,16 @@ describe('MatSelectionList without forms', () => {
932946
fixture.detectChanges();
933947

934948
expect(selectList.selected).toEqual([testListItem1]);
935-
expect(listOption[1].nativeElement.classList.contains('mat-list-single-selected-option'))
949+
expect(listOptions[1].nativeElement.classList.contains('mat-list-single-selected-option'))
936950
.toBe(true);
937951

938952
dispatchFakeEvent(testListItem2._getHostElement(), 'click');
939953
fixture.detectChanges();
940954

941955
expect(selectList.selected).toEqual([testListItem2]);
942-
expect(listOption[1].nativeElement.classList.contains('mat-list-single-selected-option'))
956+
expect(listOptions[1].nativeElement.classList.contains('mat-list-single-selected-option'))
943957
.toBe(false);
944-
expect(listOption[2].nativeElement.classList.contains('mat-list-single-selected-option'))
958+
expect(listOptions[2].nativeElement.classList.contains('mat-list-single-selected-option'))
945959
.toBe(true);
946960
});
947961

@@ -950,7 +964,7 @@ describe('MatSelectionList without forms', () => {
950964
});
951965

952966
it('should not deselect the target option on click', () => {
953-
const testListItem1 = listOption[1].injector.get<MatListOption>(MatListOption);
967+
const testListItem1 = listOptions[1].injector.get<MatListOption>(MatListOption);
954968
const selectList =
955969
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
956970

@@ -972,6 +986,19 @@ describe('MatSelectionList without forms', () => {
972986
expect(() => fixture.detectChanges()).toThrow(new Error(
973987
'Cannot change `multiple` mode of mat-selection-list after initialization.'));
974988
});
989+
990+
it('should do nothing when pressing ctrl + a', () => {
991+
const event = createKeyboardEvent('keydown', A, selectionList.nativeElement);
992+
Object.defineProperty(event, 'ctrlKey', {get: () => true});
993+
994+
expect(listOptions.every(option => !option.componentInstance.selected)).toBe(true);
995+
996+
dispatchEvent(selectionList.nativeElement, event);
997+
fixture.detectChanges();
998+
999+
expect(listOptions.every(option => !option.componentInstance.selected)).toBe(true);
1000+
});
1001+
9751002
});
9761003
});
9771004

@@ -1369,12 +1396,15 @@ describe('MatSelectionList with forms', () => {
13691396
<mat-list-option checkboxPosition="before" value="sent-mail">
13701397
Sent Mail
13711398
</mat-list-option>
1399+
<mat-list-option checkboxPosition="before" value="archive">
1400+
Archive
1401+
</mat-list-option>
13721402
<mat-list-option checkboxPosition="before" value="drafts" *ngIf="showLastOption">
13731403
Drafts
13741404
</mat-list-option>
13751405
</mat-selection-list>`})
13761406
class SelectionListWithListOptions {
1377-
showLastOption: boolean = true;
1407+
showLastOption = true;
13781408
listRippleDisabled = false;
13791409
multiple = true;
13801410
selectionListColor: ThemePalette;

src/material/list/selection-list.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,15 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements CanD
546546
event.preventDefault();
547547
}
548548
break;
549-
case A:
550-
if (hasModifierKey(event, 'ctrlKey') && !manager.isTyping()) {
549+
default:
550+
// The "A" key gets special treatment, because it's used for the "select all" functionality.
551+
if (keyCode === A && this.multiple && hasModifierKey(event, 'ctrlKey') &&
552+
!manager.isTyping()) {
551553
this.options.find(option => !option.selected) ? this.selectAll() : this.deselectAll();
552554
event.preventDefault();
555+
} else {
556+
manager.onKeydown(event);
553557
}
554-
break;
555-
default:
556-
manager.onKeydown(event);
557558
}
558559

559560
if ((keyCode === UP_ARROW || keyCode === DOWN_ARROW) && event.shiftKey &&

0 commit comments

Comments
 (0)