mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Kara
parent
fe0bfe766f
commit
7942948f0b
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user