Skip to content

Commit a5aa875

Browse files
committed
fix(material/tabs): focus wrapping back to selected label when using shift + tab (#14194)
Currently the `tabindex` of each of the tab labels is determined by the selected index. This means that if the user tabbed into the header, pressed the right arrow and then pressed shift + tab, their focus would end up on the selected tab. These changes switch to basing the `tabindex` on the focused index which is tied both to the selected index and the user's keyboard navigation. (cherry picked from commit b9bfaee)
1 parent e857777 commit a5aa875

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

src/material-experimental/mdc-tabs/tab-group.spec.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {LEFT_ARROW} from '@angular/cdk/keycodes';
1+
import {LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
22
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../../cdk/testing/private';
33
import {Component, DebugElement, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
44
import {
@@ -379,6 +379,23 @@ describe('MDC-based MatTabGroup', () => {
379379

380380
expect(contentElements.map(e => e.getAttribute('tabindex'))).toEqual(['1', null, null]);
381381
});
382+
383+
it('should update the tabindex of the labels when navigating via keyboard', () => {
384+
fixture.detectChanges();
385+
386+
const tabLabels = fixture.debugElement
387+
.queryAll(By.css('.mat-mdc-tab'))
388+
.map(label => label.nativeElement);
389+
const tabLabelContainer = fixture.debugElement.query(By.css('.mat-mdc-tab-label-container'))
390+
.nativeElement as HTMLElement;
391+
392+
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '0', '-1']);
393+
394+
dispatchKeyboardEvent(tabLabelContainer, 'keydown', RIGHT_ARROW);
395+
fixture.detectChanges();
396+
397+
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '-1', '0']);
398+
});
382399
});
383400

384401
describe('aria labelling', () => {

src/material/tabs/tab-group.spec.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {LEFT_ARROW} from '@angular/cdk/keycodes';
1+
import {LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
22
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../../cdk/testing/private';
33
import {Component, DebugElement, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
44
import {
@@ -378,6 +378,23 @@ describe('MatTabGroup', () => {
378378

379379
expect(contentElements.map(e => e.getAttribute('tabindex'))).toEqual(['1', null, null]);
380380
});
381+
382+
it('should update the tabindex of the labels when navigating via keyboard', () => {
383+
fixture.detectChanges();
384+
385+
const tabLabels = fixture.debugElement
386+
.queryAll(By.css('.mat-tab-label'))
387+
.map(label => label.nativeElement);
388+
const tabLabelContainer = fixture.debugElement.query(By.css('.mat-tab-label-container'))
389+
.nativeElement as HTMLElement;
390+
391+
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '0', '-1']);
392+
393+
dispatchKeyboardEvent(tabLabelContainer, 'keydown', RIGHT_ARROW);
394+
fixture.detectChanges();
395+
396+
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '-1', '0']);
397+
});
381398
});
382399

383400
describe('aria labelling', () => {

src/material/tabs/tab-group.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ export abstract class _MatTabGroupBase
9999
/** The tab index that should be selected after the content has been checked. */
100100
private _indexToSelect: number | null = 0;
101101

102+
/** Index of the tab that was focused last. */
103+
private _lastFocusedTabIndex: number | null = null;
104+
102105
/** Snapshot of the height of the tab body wrapper before another tab is activated. */
103106
private _tabBodyWrapperHeight: number = 0;
104107

@@ -267,6 +270,7 @@ export abstract class _MatTabGroupBase
267270

268271
if (this._selectedIndex !== indexToSelect) {
269272
this._selectedIndex = indexToSelect;
273+
this._lastFocusedTabIndex = null;
270274
this._changeDetectorRef.markForCheck();
271275
}
272276
}
@@ -292,6 +296,7 @@ export abstract class _MatTabGroupBase
292296
// event, otherwise the consumer may end up in an infinite loop in some edge cases like
293297
// adding a tab within the `selectedIndexChange` event.
294298
this._indexToSelect = this._selectedIndex = i;
299+
this._lastFocusedTabIndex = null;
295300
selectedTab = tabs[i];
296301
break;
297302
}
@@ -366,6 +371,7 @@ export abstract class _MatTabGroupBase
366371
}
367372

368373
_focusChanged(index: number) {
374+
this._lastFocusedTabIndex = index;
369375
this.focusChange.emit(this._createChangeEvent(index));
370376
}
371377

@@ -448,11 +454,12 @@ export abstract class _MatTabGroupBase
448454
}
449455

450456
/** Retrieves the tabindex for the tab. */
451-
_getTabIndex(tab: MatTab, idx: number): number | null {
457+
_getTabIndex(tab: MatTab, index: number): number | null {
452458
if (tab.disabled) {
453459
return null;
454460
}
455-
return this.selectedIndex === idx ? 0 : -1;
461+
const targetIndex = this._lastFocusedTabIndex ?? this.selectedIndex;
462+
return index === targetIndex ? 0 : -1;
456463
}
457464

458465
/** Callback for when the focused state of a tab has changed. */

tools/public_api_guard/material/tabs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export abstract class _MatTabGroupBase extends _MatTabGroupMixinBase implements
240240
_focusChanged(index: number): void;
241241
focusTab(index: number): void;
242242
_getTabContentId(i: number): string;
243-
_getTabIndex(tab: MatTab, idx: number): number | null;
243+
_getTabIndex(tab: MatTab, index: number): number | null;
244244
_getTabLabelId(i: number): string;
245245
_handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void;
246246
headerPosition: MatTabHeaderPosition;

0 commit comments

Comments
 (0)