Skip to content

Commit b62e248

Browse files
crisbetoannieyw
authored andcommitted
fix(tabs): focusChange not being emitted when tabbing into a tab header (#15094)
Fixes the `focusChange` output not emitting when the user tabs into a tab header which is different from the previously-focused one. Fixes #14142. (cherry picked from commit ef18b75)
1 parent 4083b10 commit b62e248

File tree

4 files changed

+30
-1
lines changed

4 files changed

+30
-1
lines changed

src/material/tabs/tab-group.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
[class.mat-tab-label-active]="selectedIndex == i"
1818
[disabled]="tab.disabled"
1919
[matRippleDisabled]="tab.disabled || disableRipple"
20-
(click)="_handleClick(tab, tabHeader, i)">
20+
(click)="_handleClick(tab, tabHeader, i)"
21+
(cdkFocusChange)="_tabFocusChanged($event, i)">
2122

2223

2324
<div class="mat-tab-label-content">

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,25 @@ describe('MatTabGroup', () => {
298298
expect(tabLabelNativeElements.every(el => el.classList.contains('mat-focus-indicator')))
299299
.toBe(true);
300300
});
301+
302+
it('should emit focusChange when a tab receives focus', () => {
303+
spyOn(fixture.componentInstance, 'handleFocus');
304+
fixture.detectChanges();
305+
306+
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-tab-label'));
307+
308+
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(0);
309+
310+
// In order to verify that the `focusChange` event also fires with the correct
311+
// index, we focus the second tab before testing the keyboard navigation.
312+
dispatchFakeEvent(tabLabels[1].nativeElement, 'focus');
313+
fixture.detectChanges();
314+
315+
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1);
316+
expect(fixture.componentInstance.handleFocus)
317+
.toHaveBeenCalledWith(jasmine.objectContaining({index: 1}));
318+
});
319+
301320
});
302321

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

src/material/tabs/tab-group.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
ViewChild,
3232
ViewEncapsulation,
3333
} from '@angular/core';
34+
import {FocusOrigin} from '@angular/cdk/a11y';
3435
import {
3536
CanColor,
3637
CanColorCtor,
@@ -373,6 +374,13 @@ export abstract class _MatTabGroupBase extends _MatTabGroupMixinBase implements
373374
return this.selectedIndex === idx ? 0 : -1;
374375
}
375376

377+
/** Callback for when the focused state of a tab has changed. */
378+
_tabFocusChanged(focusOrigin: FocusOrigin, index: number) {
379+
if (focusOrigin) {
380+
this._tabHeader.focusIndex = index;
381+
}
382+
}
383+
376384
static ngAcceptInputType_dynamicHeight: BooleanInput;
377385
static ngAcceptInputType_animationDuration: NumberInput;
378386
static ngAcceptInputType_selectedIndex: NumberInput;

tools/public_api_guard/material/tabs.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export declare abstract class _MatTabGroupBase extends _MatTabGroupMixinBase imp
5858
_handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void;
5959
_removeTabBodyWrapperHeight(): void;
6060
_setTabBodyWrapperHeight(tabHeight: number): void;
61+
_tabFocusChanged(focusOrigin: FocusOrigin, index: number): void;
6162
ngAfterContentChecked(): void;
6263
ngAfterContentInit(): void;
6364
ngOnDestroy(): void;

0 commit comments

Comments
 (0)