mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(tab-nav-bar): support disabling tab links (#5257)
Adds support for disabling tab links inside of the tab-nav-bar No longer requires having an extra directive for the ripples of tab links (no exposion of attributes like `mdRippleColor` - which could be flexible but should not be public API here) Closes #5208
This commit is contained in:
committed by
Jeremy Elbourn
parent
84746718fd
commit
fc809ede4b
@@ -13,6 +13,7 @@
|
||||
[active]="rla.isActive">
|
||||
{{tabLink.label}}
|
||||
</a>
|
||||
<a md-tab-link disabled>Disabled Link</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,16 @@ $mat-tab-animation-duration: 500ms !default;
|
||||
opacity: 0.6;
|
||||
min-width: 160px;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.mat-tab-disabled {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Mixin styles for the top section of the view; contains the tab labels.
|
||||
|
||||
@@ -15,7 +15,7 @@ import {MdTab} from './tab';
|
||||
import {MdTabGroup} from './tab-group';
|
||||
import {MdTabLabel} from './tab-label';
|
||||
import {MdTabLabelWrapper} from './tab-label-wrapper';
|
||||
import {MdTabNav, MdTabLink, MdTabLinkRipple} from './tab-nav-bar/tab-nav-bar';
|
||||
import {MdTabNav, MdTabLink} from './tab-nav-bar/tab-nav-bar';
|
||||
import {MdInkBar} from './ink-bar';
|
||||
import {MdTabBody} from './tab-body';
|
||||
import {VIEWPORT_RULER_PROVIDER} from '../core/overlay/position/viewport-ruler';
|
||||
@@ -38,7 +38,6 @@ import {ScrollDispatchModule} from '../core/overlay/scroll/index';
|
||||
MdTab,
|
||||
MdTabNav,
|
||||
MdTabLink,
|
||||
MdTabLinkRipple
|
||||
],
|
||||
declarations: [
|
||||
MdTabGroup,
|
||||
@@ -49,7 +48,6 @@ import {ScrollDispatchModule} from '../core/overlay/scroll/index';
|
||||
MdTabNav,
|
||||
MdTabLink,
|
||||
MdTabBody,
|
||||
MdTabLinkRipple,
|
||||
MdTabHeader
|
||||
],
|
||||
providers: [VIEWPORT_RULER_PROVIDER],
|
||||
|
||||
@@ -55,9 +55,3 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// Styling for any tab that is marked disabled
|
||||
.mat-tab-disabled {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,58 @@ describe('MdTabNavBar', () => {
|
||||
expect(fixture.componentInstance.activeIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should add the disabled class if disabled', () => {
|
||||
const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
|
||||
.map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);
|
||||
|
||||
expect(tabLinkElements.every(tabLinkEl => !tabLinkEl.classList.contains('mat-tab-disabled')))
|
||||
.toBe(true, 'Expected every tab link to not have the disabled class initially');
|
||||
|
||||
fixture.componentInstance.disabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tabLinkElements.every(tabLinkEl => tabLinkEl.classList.contains('mat-tab-disabled')))
|
||||
.toBe(true, 'Expected every tab link to have the disabled class if set through binding');
|
||||
});
|
||||
|
||||
it('should update aria-disabled if disabled', () => {
|
||||
const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
|
||||
.map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);
|
||||
|
||||
expect(tabLinkElements.every(tabLink => tabLink.getAttribute('aria-disabled') === 'false'))
|
||||
.toBe(true, 'Expected aria-disabled to be set to "false" by default.');
|
||||
|
||||
fixture.componentInstance.disabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tabLinkElements.every(tabLink => tabLink.getAttribute('aria-disabled') === 'true'))
|
||||
.toBe(true, 'Expected aria-disabled to be set to "true" if link is disabled.');
|
||||
});
|
||||
|
||||
it('should update the tabindex if links are disabled', () => {
|
||||
const tabLinkElements = fixture.debugElement.queryAll(By.css('a'))
|
||||
.map(tabLinkDebugEl => tabLinkDebugEl.nativeElement);
|
||||
|
||||
expect(tabLinkElements.every(tabLink => tabLink.tabIndex === 0))
|
||||
.toBe(true, 'Expected element to be keyboard focusable by default');
|
||||
|
||||
fixture.componentInstance.disabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tabLinkElements.every(tabLink => tabLink.tabIndex === -1))
|
||||
.toBe(true, 'Expected element to no longer be keyboard focusable if disabled.');
|
||||
});
|
||||
|
||||
it('should show ripples for tab links', () => {
|
||||
const tabLink = fixture.debugElement.nativeElement.querySelector('.mat-tab-link');
|
||||
|
||||
dispatchMouseEvent(tabLink, 'mousedown');
|
||||
dispatchMouseEvent(tabLink, 'mouseup');
|
||||
|
||||
expect(tabLink.querySelectorAll('.mat-ripple-element').length)
|
||||
.toBe(1, 'Expected one ripple to show up if user clicks on tab link.');
|
||||
});
|
||||
|
||||
it('should re-align the ink bar when the direction changes', () => {
|
||||
const inkBar = fixture.componentInstance.tabNavBar._inkBar;
|
||||
|
||||
@@ -125,6 +177,7 @@ describe('MdTabNavBar', () => {
|
||||
<a md-tab-link
|
||||
*ngFor="let tab of tabs; let index = index"
|
||||
[active]="activeIndex === index"
|
||||
[disabled]="disabled"
|
||||
(click)="activeIndex = index">
|
||||
Tab link {{label}}
|
||||
</a>
|
||||
@@ -135,6 +188,7 @@ class SimpleTabNavBarTestApp {
|
||||
@ViewChild(MdTabNav) tabNavBar: MdTabNav;
|
||||
|
||||
label = '';
|
||||
disabled: boolean = false;
|
||||
tabs = [0, 1, 2];
|
||||
|
||||
activeIndex = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Component,
|
||||
Directive,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
Inject,
|
||||
Input,
|
||||
NgZone,
|
||||
@@ -20,15 +21,16 @@ import {
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {MdInkBar} from '../ink-bar';
|
||||
import {MdRipple} from '../../core/ripple/index';
|
||||
import {CanDisable, mixinDisabled} from '../../core/common-behaviors/disabled';
|
||||
import {MdRipple} from '../../core';
|
||||
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
|
||||
import {Directionality, MD_RIPPLE_GLOBAL_OPTIONS, Platform, RippleGlobalOptions} from '../../core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/auditTime';
|
||||
import 'rxjs/add/operator/takeUntil';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/observable/merge';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
/**
|
||||
* Navigation component matching the styles of the tab group header.
|
||||
@@ -92,16 +94,30 @@ export class MdTabNav implements AfterContentInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Boilerplate for applying mixins to MdTabLink.
|
||||
export class MdTabLinkBase {}
|
||||
export const _MdTabLinkMixinBase = mixinDisabled(MdTabLinkBase);
|
||||
|
||||
/**
|
||||
* Link inside of a `md-tab-nav-bar`.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[md-tab-link], [mat-tab-link], [mdTabLink], [matTabLink]',
|
||||
host: {'class': 'mat-tab-link'}
|
||||
inputs: ['disabled'],
|
||||
host: {
|
||||
'class': 'mat-tab-link',
|
||||
'[attr.aria-disabled]': 'disabled.toString()',
|
||||
'[class.mat-tab-disabled]': 'disabled'
|
||||
}
|
||||
})
|
||||
export class MdTabLink {
|
||||
export class MdTabLink extends _MdTabLinkMixinBase implements OnDestroy, CanDisable {
|
||||
/** Whether the tab link is active or not. */
|
||||
private _isActive: boolean = false;
|
||||
|
||||
/** Reference to the instance of the ripple for the tab link. */
|
||||
private _tabLinkRipple: MdRipple;
|
||||
|
||||
/** Whether the link is active. */
|
||||
@Input()
|
||||
get active(): boolean { return this._isActive; }
|
||||
@@ -112,23 +128,28 @@ export class MdTabLink {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private _mdTabNavBar: MdTabNav, private _elementRef: ElementRef) {}
|
||||
}
|
||||
/** @docs-private */
|
||||
@HostBinding('tabIndex')
|
||||
get tabIndex(): number {
|
||||
return this.disabled ? -1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple directive that extends the ripple and matches the selector of the MdTabLink. This
|
||||
* adds the ripple behavior to nav bar labels.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[md-tab-link], [mat-tab-link], [mdTabLink], [matTabLink]',
|
||||
})
|
||||
export class MdTabLinkRipple extends MdRipple {
|
||||
constructor(
|
||||
elementRef: ElementRef,
|
||||
ngZone: NgZone,
|
||||
ruler: ViewportRuler,
|
||||
platform: Platform,
|
||||
@Optional() @Inject(MD_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
|
||||
super(elementRef, ngZone, ruler, platform, globalOptions);
|
||||
constructor(private _mdTabNavBar: MdTabNav,
|
||||
private _elementRef: ElementRef,
|
||||
ngZone: NgZone,
|
||||
ruler: ViewportRuler,
|
||||
platform: Platform,
|
||||
@Optional() @Inject(MD_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
|
||||
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 MdRipple(_elementRef, ngZone, ruler, platform, globalOptions);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// Manually call the ngOnDestroy lifecycle hook of the ripple instance because it won't be
|
||||
// called automatically since its instance is not created by Angular.
|
||||
this._tabLinkRipple.ngOnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user