fix(tabs): re-align the ink bar when the viewport size changes (#3877)

Fixes #3845.
Fixes #3044.
Fixes #2518.
Fixes #1231.
This commit is contained in:
Kristiyan Kostadinov
2017-04-21 23:49:33 +02:00
committed by Kara
parent fe0bfe766f
commit 7942948f0b
4 changed files with 109 additions and 27 deletions
+17 -1
View File
@@ -1,4 +1,4 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, ViewChild, ViewContainerRef} from '@angular/core';
import {LayoutDirection, Dir} from '../core/rtl/dir';
import {MdTabHeader} from './tab-header';
@@ -11,6 +11,7 @@ import {RIGHT_ARROW, LEFT_ARROW, ENTER} from '../core/keyboard/keycodes';
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {dispatchKeyboardEvent} from '../core/testing/dispatch-events';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
import {Subject} from 'rxjs/Subject';
@@ -211,6 +212,21 @@ describe('MdTabHeader', () => {
expect(inkBar.alignToElement).toHaveBeenCalled();
});
it('should re-align the ink bar when the window is resized', fakeAsync(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();
const inkBar = fixture.componentInstance.mdTabHeader._inkBar;
spyOn(inkBar, 'alignToElement');
dispatchFakeEvent(window, 'resize');
tick(10);
fixture.detectChanges();
expect(inkBar.alignToElement).toHaveBeenCalled();
}));
});
});
+29 -12
View File
@@ -12,13 +12,20 @@ import {
AfterContentChecked,
AfterContentInit,
OnDestroy,
NgZone,
} from '@angular/core';
import {RIGHT_ARROW, LEFT_ARROW, ENTER, Dir, LayoutDirection} from '../core';
import {MdTabLabelWrapper} from './tab-label-wrapper';
import {MdInkBar} from './ink-bar';
import {Subscription} from 'rxjs/Subscription';
import {Observable} from 'rxjs/Observable';
import {applyCssTransform} from '../core/style/apply-transform';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/auditTime';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/startWith';
/**
* The directions that scrolling can go in when the header's tabs exceed the header width. 'After'
@@ -68,8 +75,8 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
/** Whether the header should scroll to the selected index after the view has been checked. */
private _selectedIndexChanged = false;
/** Subscription to changes in the layout direction. */
private _directionChange: Subscription;
/** Combines listeners that will re-align the ink bar whenever they're invoked. */
private _realignInkBar: Subscription = null;
/** Whether the controls for pagination should be displayed */
_showPaginationControls = false;
@@ -92,13 +99,14 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
private _selectedIndex: number = 0;
/** The index of the active tab. */
@Input() set selectedIndex(value: number) {
@Input()
get selectedIndex(): number { return this._selectedIndex; }
set selectedIndex(value: number) {
this._selectedIndexChanged = this._selectedIndex != value;
this._selectedIndex = value;
this._focusIndex = value;
}
get selectedIndex(): number { return this._selectedIndex; }
/** Event emitted when the option is selected. */
@Output() selectFocusedIndex = new EventEmitter();
@@ -106,7 +114,10 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
/** Event emitted when a label is focused. */
@Output() indexFocused = new EventEmitter();
constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {}
constructor(
private _elementRef: ElementRef,
private _ngZone: NgZone,
@Optional() private _dir: Dir) { }
ngAfterContentChecked(): void {
// If the number of tab labels have changed, check if scrolling should be enabled
@@ -150,17 +161,23 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes
* Aligns the ink bar to the selected tab on load.
*/
ngAfterContentInit() {
this._alignInkBarToSelectedTab();
this._realignInkBar = this._ngZone.runOutsideAngular(() => {
let dirChange = this._dir ? this._dir.dirChange : Observable.of(null);
let resize = typeof window !== 'undefined' ?
Observable.fromEvent(window, 'resize').auditTime(10) :
Observable.of(null);
if (this._dir) {
this._directionChange = this._dir.dirChange.subscribe(() => this._alignInkBarToSelectedTab());
}
return Observable.merge(dirChange, resize).startWith(null).subscribe(() => {
this._updatePagination();
this._alignInkBarToSelectedTab();
});
});
}
ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
if (this._realignInkBar) {
this._realignInkBar.unsubscribe();
this._realignInkBar = null;
}
}
+36 -3
View File
@@ -1,13 +1,18 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {MdTabsModule} from '../index';
import {Component} from '@angular/core';
import {MdTabNavBar} from './tab-nav-bar';
import {Component, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
import {FakeViewportRuler} from '../../core/overlay/position/fake-viewport-ruler';
import {dispatchMouseEvent} from '../../core/testing/dispatch-events';
import {dispatchMouseEvent, dispatchFakeEvent} from '../../core/testing/dispatch-events';
import {LayoutDirection, Dir} from '../../core/rtl/dir';
import {Subject} from 'rxjs/Subject';
describe('MdTabNavBar', () => {
let dir: LayoutDirection = 'ltr';
let dirChange = new Subject();
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -17,6 +22,9 @@ describe('MdTabNavBar', () => {
TabLinkWithNgIf,
],
providers: [
{provide: Dir, useFactory: () => {
return {value: dir, dirChange: dirChange.asObservable()};
}},
{provide: ViewportRuler, useClass: FakeViewportRuler},
]
});
@@ -44,6 +52,29 @@ describe('MdTabNavBar', () => {
tabLink.nativeElement.click();
expect(component.activeIndex).toBe(2);
});
it('should re-align the ink bar when the direction changes', () => {
const inkBar = fixture.componentInstance.tabNavBar._inkBar;
spyOn(inkBar, 'alignToElement');
dirChange.next();
fixture.detectChanges();
expect(inkBar.alignToElement).toHaveBeenCalled();
});
it('should re-align the ink bar when the window is resized', fakeAsync(() => {
const inkBar = fixture.componentInstance.tabNavBar._inkBar;
spyOn(inkBar, 'alignToElement');
dispatchFakeEvent(window, 'resize');
tick(10);
fixture.detectChanges();
expect(inkBar.alignToElement).toHaveBeenCalled();
}));
});
it('should clean up the ripple event handlers on destroy', () => {
@@ -73,6 +104,8 @@ describe('MdTabNavBar', () => {
`
})
class SimpleTabNavBarTestApp {
@ViewChild(MdTabNavBar) tabNavBar: MdTabNavBar;
activeIndex = 0;
}
+27 -11
View File
@@ -9,12 +9,17 @@ import {
Inject,
Optional,
OnDestroy,
AfterContentInit,
} from '@angular/core';
import {MdInkBar} from '../ink-bar';
import {MdRipple} from '../../core/ripple/index';
import {ViewportRuler} from '../../core/overlay/position/viewport-ruler';
import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir} from '../../core';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/operator/auditTime';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/merge';
/**
* Navigation component matching the styles of the tab group header.
@@ -30,18 +35,16 @@ import {Subscription} from 'rxjs/Subscription';
},
encapsulation: ViewEncapsulation.None,
})
export class MdTabNavBar implements OnDestroy {
private _directionChange: Subscription;
export class MdTabNavBar implements AfterContentInit, OnDestroy {
/** Combines listeners that will re-align the ink bar whenever they're invoked. */
private _realignInkBar: Subscription = null;
_activeLinkChanged: boolean;
_activeLinkElement: ElementRef;
@ViewChild(MdInkBar) _inkBar: MdInkBar;
constructor(@Optional() private _dir: Dir) {
if (_dir) {
this._directionChange = _dir.dirChange.subscribe(() => this._alignInkBar());
}
}
constructor(@Optional() private _dir: Dir, private _ngZone: NgZone) { }
/** Notifies the component that the active link has been changed. */
updateActiveLink(element: ElementRef) {
@@ -49,6 +52,17 @@ export class MdTabNavBar implements OnDestroy {
this._activeLinkElement = element;
}
ngAfterContentInit(): void {
this._realignInkBar = this._ngZone.runOutsideAngular(() => {
let dirChange = this._dir ? this._dir.dirChange : Observable.of(null);
let resize = typeof window !== 'undefined' ?
Observable.fromEvent(window, 'resize').auditTime(10) :
Observable.of(null);
return Observable.merge(dirChange, resize).subscribe(() => this._alignInkBar());
});
}
/** Checks if the active link has been changed and, if so, will update the ink bar. */
ngAfterContentChecked(): void {
if (this._activeLinkChanged) {
@@ -58,15 +72,17 @@ export class MdTabNavBar implements OnDestroy {
}
ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
if (this._realignInkBar) {
this._realignInkBar.unsubscribe();
this._realignInkBar = null;
}
}
/** Aligns the ink bar to the active link. */
private _alignInkBar(): void {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
if (this._activeLinkElement) {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
}
}
}