Skip to content

Commit

Permalink
fix(tabs): re-align ink bar on direction change (#3622)
Browse files Browse the repository at this point in the history
Re-aligns the ink bar if the user's layout direction changed.

Fixes #3615.
  • Loading branch information
crisbeto authored and tinayuangao committed Mar 29, 2017
1 parent 52ea7a3 commit 07793a4
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 24 deletions.
20 changes: 14 additions & 6 deletions src/lib/tabs/ink-bar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, Renderer, ElementRef} from '@angular/core';
import {Directive, Renderer, ElementRef, NgZone} from '@angular/core';


/**
Expand All @@ -12,7 +12,10 @@ import {Directive, Renderer, ElementRef} from '@angular/core';
},
})
export class MdInkBar {
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
constructor(
private _renderer: Renderer,
private _elementRef: ElementRef,
private _ngZone: NgZone) {}

/**
* Calculates the styles from the provided element in order to align the ink-bar to that element.
Expand All @@ -21,10 +24,15 @@ export class MdInkBar {
*/
alignToElement(element: HTMLElement) {
this.show();
this._renderer.setElementStyle(this._elementRef.nativeElement, 'left',
this._getLeftPosition(element));
this._renderer.setElementStyle(this._elementRef.nativeElement, 'width',
this._getElementWidth(element));

this._ngZone.runOutsideAngular(() => {
requestAnimationFrame(() => {
this._renderer.setElementStyle(this._elementRef.nativeElement, 'left',
this._getLeftPosition(element));
this._renderer.setElementStyle(this._elementRef.nativeElement, 'width',
this._getElementWidth(element));
});
});
}

/** Shows the ink bar. */
Expand Down
24 changes: 21 additions & 3 deletions src/lib/tabs/tab-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ 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 {Subject} from 'rxjs/Subject';


describe('MdTabHeader', () => {
let dir: LayoutDirection = 'ltr';
let dirChange = new Subject();
let fixture: ComponentFixture<SimpleTabHeaderApp>;
let appComponent: SimpleTabHeaderApp;

Expand All @@ -29,7 +31,9 @@ describe('MdTabHeader', () => {
SimpleTabHeaderApp,
],
providers: [
{provide: Dir, useFactory: () => { return {value: dir}; }},
{provide: Dir, useFactory: () => {
return {value: dir, dirChange: dirChange.asObservable()};
}},
{provide: ViewportRuler, useClass: FakeViewportRuler},
]
});
Expand Down Expand Up @@ -192,8 +196,22 @@ describe('MdTabHeader', () => {
expect(appComponent.mdTabHeader.scrollDistance).toBe(0);
});
});
});

it('should re-align the ink bar when the direction changes', () => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();

const inkBar = fixture.componentInstance.mdTabHeader._inkBar;

spyOn(inkBar, 'alignToElement');

dirChange.next();
fixture.detectChanges();

expect(inkBar.alignToElement).toHaveBeenCalled();
});

});
});

interface Tab {
Expand All @@ -211,7 +229,7 @@ interface Tab {
*ngFor="let tab of tabs; let i = index"
[disabled]="!!tab.disabled"
(click)="selectedIndex = i">
{{tab.label}}
{{tab.label}}
</div>
</md-tab-header>
</div>
Expand Down
31 changes: 20 additions & 11 deletions src/lib/tabs/tab-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ViewChild,
Component,
Input,
NgZone,
QueryList,
ElementRef,
ViewEncapsulation,
Expand All @@ -12,12 +11,14 @@ import {
Optional,
AfterContentChecked,
AfterContentInit,
OnDestroy,
} 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 'rxjs/add/operator/map';
import {Subscription} from 'rxjs/Subscription';
import {applyCssTransform} from '../core/style/apply-transform';
import 'rxjs/add/operator/map';

/**
* The directions that scrolling can go in when the header's tabs exceed the header width. 'After'
Expand Down Expand Up @@ -51,7 +52,7 @@ const EXAGGERATED_OVERSCROLL = 60;
'[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'",
}
})
export class MdTabHeader implements AfterContentChecked, AfterContentInit {
export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDestroy {
@ContentChildren(MdTabLabelWrapper) _labelWrappers: QueryList<MdTabLabelWrapper>;

@ViewChild(MdInkBar) _inkBar: MdInkBar;
Expand All @@ -67,6 +68,9 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
/** 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;

/** Whether the controls for pagination should be displayed */
_showPaginationControls = false;

Expand Down Expand Up @@ -102,9 +106,7 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
/** Event emitted when a label is focused. */
@Output() indexFocused = new EventEmitter();

constructor(private _zone: NgZone,
private _elementRef: ElementRef,
@Optional() private _dir: Dir) {}
constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {}

ngAfterContentChecked(): void {
// If the number of tab labels have changed, check if scrolling should be enabled
Expand Down Expand Up @@ -149,6 +151,17 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
*/
ngAfterContentInit() {
this._alignInkBarToSelectedTab();

if (this._dir) {
this._directionChange = this._dir.dirChange.subscribe(() => this._alignInkBarToSelectedTab());
}
}

ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
}
}

/**
Expand Down Expand Up @@ -373,10 +386,6 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit {
? this._labelWrappers.toArray()[this.selectedIndex].elementRef.nativeElement
: null;

this._zone.runOutsideAngular(() => {
requestAnimationFrame(() => {
this._inkBar.alignToElement(selectedLabelWrapper);
});
});
this._inkBar.alignToElement(selectedLabelWrapper);
}
}
32 changes: 28 additions & 4 deletions src/lib/tabs/tab-nav-bar/tab-nav-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import {
ViewChild,
ElementRef,
ViewEncapsulation,
Directive, NgZone, Inject, Optional,
Directive,
NgZone,
Inject,
Optional,
OnDestroy,
} 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} from '../../core/ripple/ripple';
import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, Dir} from '../../core';
import {Subscription} from 'rxjs/Subscription';

/**
* Navigation component matching the styles of the tab group header.
Expand All @@ -25,12 +30,19 @@ import {MD_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '../../core/ripple/r
},
encapsulation: ViewEncapsulation.None,
})
export class MdTabNavBar {
export class MdTabNavBar implements OnDestroy {
private _directionChange: Subscription;
_activeLinkChanged: boolean;
_activeLinkElement: ElementRef;

@ViewChild(MdInkBar) _inkBar: MdInkBar;

constructor(@Optional() private _dir: Dir) {
if (_dir) {
this._directionChange = _dir.dirChange.subscribe(() => this._alignInkBar());
}
}

/** Notifies the component that the active link has been changed. */
updateActiveLink(element: ElementRef) {
this._activeLinkChanged = this._activeLinkElement != element;
Expand All @@ -40,10 +52,22 @@ export class MdTabNavBar {
/** Checks if the active link has been changed and, if so, will update the ink bar. */
ngAfterContentChecked(): void {
if (this._activeLinkChanged) {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
this._alignInkBar();
this._activeLinkChanged = false;
}
}

ngOnDestroy() {
if (this._directionChange) {
this._directionChange.unsubscribe();
this._directionChange = null;
}
}

/** Aligns the ink bar to the active link. */
private _alignInkBar(): void {
this._inkBar.alignToElement(this._activeLinkElement.nativeElement);
}
}

/**
Expand Down

0 comments on commit 07793a4

Please sign in to comment.