Skip to content

Commit c571da6

Browse files
authored
fix(material/list): set initial focus on first selected option in selection list (#23092)
Currently when a selection list receives focus, it forwards it either to the option that the user interacted with last or the first option in the list. This goes against the a11y best practices and is weird when there the user lands on the list for the first time and it has preselected options. These changes switch to focusing the first selected option and falling back to the first option. This is consistent with the MDC implementation as well. Fixes #22675.
1 parent 3e08c8d commit c571da6

File tree

3 files changed

+16
-20
lines changed

3 files changed

+16
-20
lines changed

scripts/check-mdc-tests-config.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,6 @@ export const config = {
8282
'should work in a step'
8383
],
8484
'mdc-list': [
85-
// MDC does focus previously focused options, but rather always selects the first selected
86-
// option. We have different test in the MDC-based list that captures this behavior.
87-
'should focus the previously focused option when the list takes focus a second time',
88-
8985
// TODO: these tests need to be double-checked for missing functionality.
9086
'should not apply any additional class to a list without lines',
9187
'should not add the mat-list-single-selected-option class (in multiple mode)',

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,22 +326,23 @@ describe('MatSelectionList without forms', () => {
326326
expect(listOptions[0].componentInstance.focus).not.toHaveBeenCalled();
327327
})));
328328

329-
it('should focus the previously focused option when the list takes focus a second time', () => {
330-
spyOn(listOptions[1].componentInstance, 'focus').and.callThrough();
329+
it('should focus the first selected option when list receives focus', () => {
330+
spyOn(listOptions[2].componentInstance, 'focus').and.callThrough();
331331

332332
const manager = selectionList.componentInstance._keyManager;
333333
expect(manager.activeItemIndex).toBe(-1);
334334

335-
// Focus and blur the option to move the active item index. This option is now the previously
336-
// focused option.
337-
listOptions[1].componentInstance._handleFocus();
338-
listOptions[1].componentInstance._handleBlur();
335+
dispatchMouseEvent(listOptions[2].nativeElement, 'click');
336+
fixture.detectChanges();
337+
338+
dispatchMouseEvent(listOptions[3].nativeElement, 'click');
339+
fixture.detectChanges();
339340

340341
dispatchFakeEvent(selectionList.nativeElement, 'focus');
341342
fixture.detectChanges();
342343

343-
expect(manager.activeItemIndex).toBe(1);
344-
expect(listOptions[1].componentInstance.focus).toHaveBeenCalled();
344+
expect(manager.activeItemIndex).toBe(2);
345+
expect(listOptions[2].componentInstance.focus).toHaveBeenCalled();
345346
});
346347

347348
it('should allow focus to escape when tabbing away', fakeAsync(() => {

src/material/list/selection-list.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -478,15 +478,14 @@ export class MatSelectionList extends _MatSelectionListBase implements CanDisabl
478478
.pipe(takeUntil(this._destroyed))
479479
.subscribe(origin => {
480480
if (origin === 'keyboard' || origin === 'program') {
481-
const activeIndex = this._keyManager.activeItemIndex;
482-
483-
if (!activeIndex || activeIndex === -1) {
484-
// If there is no active index, set focus to the first option.
485-
this._keyManager.setFirstItemActive();
486-
} else {
487-
// Otherwise, set focus to the active option.
488-
this._keyManager.setActiveItem(activeIndex);
481+
let toFocus = 0;
482+
for (let i = 0; i < this.options.length; i++) {
483+
if (this.options.get(i)?.selected) {
484+
toFocus = i;
485+
break;
486+
}
489487
}
488+
this._keyManager.setActiveItem(toFocus);
490489
}
491490
});
492491
}

0 commit comments

Comments
 (0)