Skip to content

Commit

Permalink
feat(material/tabs): add method for programmatically setting focus
Browse files Browse the repository at this point in the history
Adds a method that allows for focus to be moved to a particular tab. This is usually
tricky, because all of the DOM elements are hidden away inside the tab group template.

Fixes #15007.
  • Loading branch information
crisbeto committed Dec 25, 2020
1 parent 71b7b15 commit b450db7
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 2 deletions.
24 changes: 23 additions & 1 deletion src/material-experimental/mdc-tabs/tab-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import {By} from '@angular/platform-browser';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {CommonModule} from '@angular/common';
import {Observable} from 'rxjs';
import {MAT_TABS_CONFIG, MatTab, MatTabGroup, MatTabHeaderPosition, MatTabsModule} from './index';
import {
MAT_TABS_CONFIG,
MatTab,
MatTabGroup,
MatTabHeaderPosition,
MatTabsModule,
MatTabHeader,
} from './index';


describe('MDC-based MatTabGroup', () => {
Expand Down Expand Up @@ -300,6 +307,21 @@ describe('MDC-based MatTabGroup', () => {
.toBe(true);
});

it('should be able to programmatically focus a particular tab', () => {
fixture.detectChanges();
const tabGroup: MatTabGroup =
fixture.debugElement.query(By.css('mat-tab-group')).componentInstance;
const tabHeader: MatTabHeader =
fixture.debugElement.query(By.css('mat-tab-header')).componentInstance;

expect(tabHeader.focusIndex).not.toBe(3);

tabGroup.focusTab(3);
fixture.detectChanges();

expect(tabHeader.focusIndex).not.toBe(3);
});

});

describe('aria labelling', () => {
Expand Down
25 changes: 24 additions & 1 deletion src/material/tabs/tab-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import {By} from '@angular/platform-browser';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {CommonModule} from '@angular/common';
import {Observable} from 'rxjs';
import {MatTab, MatTabGroup, MatTabHeaderPosition, MatTabsModule, MAT_TABS_CONFIG} from './index';
import {
MatTab,
MatTabGroup,
MatTabHeader,
MatTabHeaderPosition,
MatTabsModule,
MAT_TABS_CONFIG
} from './index';


describe('MatTabGroup', () => {
Expand Down Expand Up @@ -298,6 +305,22 @@ describe('MatTabGroup', () => {
expect(tabLabelNativeElements.every(el => el.classList.contains('mat-focus-indicator')))
.toBe(true);
});

it('should be able to programmatically focus a particular tab', () => {
fixture.detectChanges();
const tabGroup: MatTabGroup =
fixture.debugElement.query(By.css('mat-tab-group')).componentInstance;
const tabHeader: MatTabHeader =
fixture.debugElement.query(By.css('mat-tab-header')).componentInstance;

expect(tabHeader.focusIndex).not.toBe(3);

tabGroup.focusTab(3);
fixture.detectChanges();

expect(tabHeader.focusIndex).not.toBe(3);
});

});

describe('aria labelling', () => {
Expand Down
12 changes: 12 additions & 0 deletions src/material/tabs/tab-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,18 @@ export abstract class _MatTabGroupBase extends _MatTabGroupMixinBase implements
}
}

/**
* Sets focus to a particular tab.
* @param index Index of the tab to be focused.
*/
focusTab(index: number) {
const header = this._tabHeader;

if (header) {
header.focusIndex = index;
}
}

_focusChanged(index: number) {
this.focusChange.emit(this._createChangeEvent(index));
}
Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/material/tabs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export declare abstract class _MatTabGroupBase extends _MatTabGroupMixinBase imp
_handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void;
_removeTabBodyWrapperHeight(): void;
_setTabBodyWrapperHeight(tabHeight: number): void;
focusTab(index: number): void;
ngAfterContentChecked(): void;
ngAfterContentInit(): void;
ngOnDestroy(): void;
Expand Down

0 comments on commit b450db7

Please sign in to comment.