Skip to content

Commit 4f99850

Browse files
crisbetommalerba
authored andcommitted
fix(material/tabs): update tab nav bar focused index on direct focus (#22584)
The tab nav bar allows navigation both through tabbing and keyboard events. This introduces a problem when the user tabs into it and then starts using the arrow keys, because the `ListKeyManager` state isn't up-to-date. These changes add some logic to update the index when the tabs receives focus. Fixes #22576. (cherry picked from commit 40a3e16)
1 parent f9ea718 commit 4f99850

File tree

5 files changed

+23
-0
lines changed

5 files changed

+23
-0
lines changed

src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ describe('MDC-based MatTabNavBar', () => {
211211
expect(inkBar.hide).toHaveBeenCalled();
212212
});
213213

214+
it('should update the focusIndex when a tab receives focus directly', () => {
215+
const thirdLink = fixture.debugElement.queryAll(By.css('a'))[2];
216+
dispatchFakeEvent(thirdLink.nativeElement, 'focus');
217+
fixture.detectChanges();
218+
219+
expect(fixture.componentInstance.tabNavBar.focusIndex).toBe(2);
220+
});
214221
});
215222

216223
it('should hide the ink bar if no tabs are active on init', fakeAsync(() => {

src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit {
125125
'[attr.tabIndex]': 'tabIndex',
126126
'[class.mat-mdc-tab-disabled]': 'disabled',
127127
'[class.mdc-tab--active]': 'active',
128+
'(focus)': '_handleFocus()'
128129
}
129130
})
130131
export class MatTabLink extends _MatTabLinkBase implements MatInkBarItem, OnInit, OnDestroy {

src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ describe('MatTabNavBar', () => {
202202
expect(inkBar.hide).toHaveBeenCalled();
203203
});
204204

205+
it('should update the focusIndex when a tab receives focus directly', () => {
206+
const thirdLink = fixture.debugElement.queryAll(By.css('a'))[2];
207+
dispatchFakeEvent(thirdLink.nativeElement, 'focus');
208+
fixture.detectChanges();
209+
210+
expect(fixture.componentInstance.tabNavBar.focusIndex).toBe(2);
211+
});
205212
});
206213

207214
it('should hide the ink bar if no tabs are active on init', fakeAsync(() => {

src/material/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ export class _MatTabLinkBase extends _MatTabLinkMixinBase implements AfterViewIn
243243
this._focusMonitor.stopMonitoring(this.elementRef);
244244
}
245245

246+
_handleFocus() {
247+
// Since we allow navigation through tabbing in the nav bar, we
248+
// have to update the focused index whenever the link receives focus.
249+
this._tabNavBar.focusIndex = this._tabNavBar._items.toArray().indexOf(this);
250+
}
251+
246252
static ngAcceptInputType_active: BooleanInput;
247253
static ngAcceptInputType_disabled: BooleanInput;
248254
static ngAcceptInputType_disableRipple: BooleanInput;
@@ -264,6 +270,7 @@ export class _MatTabLinkBase extends _MatTabLinkMixinBase implements AfterViewIn
264270
'[attr.tabIndex]': 'tabIndex',
265271
'[class.mat-tab-disabled]': 'disabled',
266272
'[class.mat-tab-label-active]': 'active',
273+
'(focus)': '_handleFocus()'
267274
}
268275
})
269276
export class MatTabLink extends _MatTabLinkBase implements OnDestroy {

tools/public_api_guard/material/tabs.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export declare class _MatTabLinkBase extends _MatTabLinkMixinBase implements Aft
9191
rippleConfig: RippleConfig & RippleGlobalOptions;
9292
get rippleDisabled(): boolean;
9393
constructor(_tabNavBar: _MatTabNavBase, elementRef: ElementRef, globalRippleOptions: RippleGlobalOptions | null, tabIndex: string, _focusMonitor: FocusMonitor, animationMode?: string);
94+
_handleFocus(): void;
9495
focus(): void;
9596
ngAfterViewInit(): void;
9697
ngOnDestroy(): void;

0 commit comments

Comments
 (0)