Skip to content

Commit

Permalink
feat(tab-nav-bar): allow setting tabindex for links (#7809)
Browse files Browse the repository at this point in the history
* For navigation bars, navigation through the arrow keys is not supported. Since every element needs to be tabbable for accessibility reasons, the tabindex should be changeable.
* Also uses the tabindex mixin to reduce payload size.
  • Loading branch information
devversion authored and jelbourn committed Nov 20, 2017
1 parent 8e9dade commit a041253
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 13 deletions.
52 changes: 48 additions & 4 deletions src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {By} from '@angular/platform-browser';
import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {Subject} from 'rxjs/Subject';
import {MatTabNav, MatTabsModule, MatTabLink} from '../index';
import {MatTabLink, MatTabNav, MatTabsModule} from '../index';


describe('MatTabNavBar', () => {
Expand All @@ -17,6 +17,8 @@ describe('MatTabNavBar', () => {
declarations: [
SimpleTabNavBarTestApp,
TabLinkWithNgIf,
TabLinkWithTabIndexBinding,
TabLinkWithNativeTabindexAttr,
],
providers: [
{provide: Directionality, useFactory: () => ({
Expand Down Expand Up @@ -119,9 +121,7 @@ describe('MatTabNavBar', () => {
fixture.componentInstance.disabled = true;
fixture.detectChanges();

expect(tabLinkElements.every(tabLink => {
return tabLink.getAttribute('tabIndex') === null;
}))
expect(tabLinkElements.every(tabLink => tabLink.tabIndex === -1))
.toBe(true, 'Expected element to no longer be keyboard focusable if disabled.');
});

Expand Down Expand Up @@ -212,6 +212,30 @@ describe('MatTabNavBar', () => {
expect(link.querySelector('.mat-ripple-element'))
.toBeFalsy('Expected no ripple to be created when ripple target is destroyed.');
});

it('should support the native tabindex attribute', () => {
const fixture = TestBed.createComponent(TabLinkWithNativeTabindexAttr);
fixture.detectChanges();

const tabLink = fixture.debugElement.query(By.directive(MatTabLink)).injector.get(MatTabLink);

expect(tabLink.tabIndex)
.toBe(5, 'Expected the tabIndex to be set from the native tabindex attribute.');
});

it('should support binding to the tabIndex', () => {
const fixture = TestBed.createComponent(TabLinkWithTabIndexBinding);
fixture.detectChanges();

const tabLink = fixture.debugElement.query(By.directive(MatTabLink)).injector.get(MatTabLink);

expect(tabLink.tabIndex).toBe(0, 'Expected the tabIndex to be set to 0 by default.');

fixture.componentInstance.tabIndex = 3;
fixture.detectChanges();

expect(tabLink.tabIndex).toBe(3, 'Expected the tabIndex to be have been set to 3.');
});
});

@Component({
Expand Down Expand Up @@ -249,3 +273,23 @@ class SimpleTabNavBarTestApp {
class TabLinkWithNgIf {
isDestroyed = false;
}

@Component({
template: `
<nav mat-tab-nav-bar>
<a mat-tab-link [tabIndex]="tabIndex">TabIndex Link</a>
</nav>
`
})
class TabLinkWithTabIndexBinding {
tabIndex = 0;
}

@Component({
template: `
<nav mat-tab-nav-bar>
<a mat-tab-link tabindex="5">Link</a>
</nav>
`
})
class TabLinkWithNativeTabindexAttr {}
21 changes: 12 additions & 9 deletions src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {takeUntil} from 'rxjs/operators/takeUntil';
import {ViewportRuler} from '@angular/cdk/scrolling';
import {
AfterContentInit,
Attribute,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Expand All @@ -33,11 +34,13 @@ import {
CanColor,
CanDisable,
CanDisableRipple,
HasTabIndex,
MAT_RIPPLE_GLOBAL_OPTIONS,
MatRipple,
mixinColor,
mixinDisabled,
mixinDisableRipple,
mixinTabIndex,
RippleGlobalOptions,
ThemePalette,
} from '@angular/material/core';
Expand Down Expand Up @@ -170,15 +173,15 @@ export class MatTabNav extends _MatTabNavMixinBase implements AfterContentInit,

// Boilerplate for applying mixins to MatTabLink.
export class MatTabLinkBase {}
export const _MatTabLinkMixinBase = mixinDisabled(MatTabLinkBase);
export const _MatTabLinkMixinBase = mixinTabIndex(mixinDisabled(MatTabLinkBase));

/**
* Link inside of a `mat-tab-nav-bar`.
*/
@Directive({
selector: '[mat-tab-link], [matTabLink]',
exportAs: 'matTabLink',
inputs: ['disabled'],
inputs: ['disabled', 'tabIndex'],
host: {
'class': 'mat-tab-link',
'[attr.aria-disabled]': 'disabled.toString()',
Expand All @@ -187,7 +190,9 @@ export const _MatTabLinkMixinBase = mixinDisabled(MatTabLinkBase);
'[class.mat-tab-label-active]': 'active',
}
})
export class MatTabLink extends _MatTabLinkMixinBase implements OnDestroy, CanDisable {
export class MatTabLink extends _MatTabLinkMixinBase
implements OnDestroy, CanDisable, HasTabIndex {

/** Whether the tab link is active or not. */
private _isActive: boolean = false;

Expand Down Expand Up @@ -215,21 +220,19 @@ export class MatTabLink extends _MatTabLinkMixinBase implements OnDestroy, CanDi
this._tabLinkRipple._updateRippleRenderer();
}

/** @docs-private */
get tabIndex(): number | null {
return this.disabled ? null : 0;
}

constructor(private _tabNavBar: MatTabNav,
private _elementRef: ElementRef,
ngZone: NgZone,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions,
@Attribute('tabindex') tabIndex: string) {
super();

// Manually create a ripple instance that uses the tab link element as trigger element.
// Notice that the lifecycle hooks for the ripple config won't be called anymore.
this._tabLinkRipple = new MatRipple(_elementRef, ngZone, platform, globalOptions);

this.tabIndex = parseInt(tabIndex) || 0;
}

ngOnDestroy() {
Expand Down

0 comments on commit a041253

Please sign in to comment.