From 9469bc55ceb38636e485ecb8d12d3d6aa3248521 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 11:55:55 -0800 Subject: [PATCH 01/20] feat(tabs): add animations when switching tabs --- src/demo-app/tabs/tabs-demo.html | 29 +------ src/demo-app/tabs/tabs-demo.scss | 2 +- src/demo-app/tabs/tabs-demo.ts | 2 +- src/lib/tabs/_tabs-common.scss | 2 +- src/lib/tabs/tab-body.html | 6 ++ src/lib/tabs/tab-group.html | 17 ++--- src/lib/tabs/tab-group.scss | 20 ++--- src/lib/tabs/tabs.ts | 127 +++++++++++++++++++++++++++---- 8 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 src/lib/tabs/tab-body.html diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 99d73d9ec327..7eac59ee5b42 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -17,37 +17,16 @@

Tab Nav Bar

Tab Group Demo

- + {{tab.content}}

-
- -
-
- - -

Async Tabs

- - - - - {{tab.content}} -
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec orci posuere, nec luctus mauris semper. +


- - -

Tabs with simplified api

- - - This tab is about the Earth! - - - This tab is about combustion! - - diff --git a/src/demo-app/tabs/tabs-demo.scss b/src/demo-app/tabs/tabs-demo.scss index 0a0337ee419c..2dc75c934f18 100644 --- a/src/demo-app/tabs/tabs-demo.scss +++ b/src/demo-app/tabs/tabs-demo.scss @@ -16,7 +16,7 @@ .md-tab-header { background: #f9f9f9; } - .md-tab-body { + .md-tab-body-content { padding: 12px; } } \ No newline at end of file diff --git a/src/demo-app/tabs/tabs-demo.ts b/src/demo-app/tabs/tabs-demo.ts index da9988ddd371..bcb26829a154 100644 --- a/src/demo-app/tabs/tabs-demo.ts +++ b/src/demo-app/tabs/tabs-demo.ts @@ -19,7 +19,7 @@ export class TabsDemo { tabs = [ {label: 'Tab One', content: 'This is the body of the first tab'}, - {label: 'Tab Two', content: 'This is the body of the second tab'}, + {label: 'Tab Two', extraContent: true, content: 'This is the body of the second tab'}, {label: 'Tab Three', content: 'This is the body of the third tab'}, ]; diff --git a/src/lib/tabs/_tabs-common.scss b/src/lib/tabs/_tabs-common.scss index 672cfa6495e2..2210ab2fba56 100644 --- a/src/lib/tabs/_tabs-common.scss +++ b/src/lib/tabs/_tabs-common.scss @@ -36,5 +36,5 @@ $md-tab-bar-height: 48px !default; position: absolute; bottom: 0; height: 2px; - transition: 350ms ease-out; + transition: 500ms ease-out; } \ No newline at end of file diff --git a/src/lib/tabs/tab-body.html b/src/lib/tabs/tab-body.html new file mode 100644 index 000000000000..07be488e60e9 --- /dev/null +++ b/src/lib/tabs/tab-body.html @@ -0,0 +1,6 @@ +
+ +
\ No newline at end of file diff --git a/src/lib/tabs/tab-group.html b/src/lib/tabs/tab-group.html index 6acbed0769f4..9682c1b81ffd 100644 --- a/src/lib/tabs/tab-group.html +++ b/src/lib/tabs/tab-group.html @@ -20,15 +20,14 @@ -
-
+ - -
+ [attr.aria-labelledby]="_getTabLabelId(i)" + [md-tab-body-position]="i - selectedIndex" + [md-tab-body-content]="tab.content" + (onTabBodyCentered)="_removeTabBodyWrapperHeight()" + (onTabBodyCentering)="_setTabBodyWrapperHeight($event)"> +
diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index 3e48ae569952..c120feb51b55 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -32,19 +32,21 @@ md-ink-bar { .md-tab-body-wrapper { position: relative; overflow: hidden; - flex-grow: 1; - display: flex; + transition: height 0.5s cubic-bezier(0.35, 0, 0.25, 1); } // Wraps each tab body -.md-tab-body { - display: none; - overflow: auto; - box-sizing: border-box; - flex-grow: 1; - flex-shrink: 1; +md-tab-body { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: hidden; &.md-tab-active { - display: block; + position: relative; + z-index: 1; } } diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 8c88f1cc1d78..af61ce8ec75a 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -13,7 +13,8 @@ import { ContentChildren, TemplateRef, ViewContainerRef, - OnInit, + OnInit, trigger, state, style, animate, transition, OnChanges, AnimationTransitionEvent, + ElementRef, Renderer, } from '@angular/core'; import {CommonModule} from '@angular/common'; import { @@ -30,6 +31,7 @@ import {MdTabNavBar, MdTabLink} from './tab-nav-bar/tab-nav-bar'; import {MdInkBar} from './ink-bar'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; +import {PortalHostDirective} from '../core/portal/portal-directives'; /** Used to generate unique ID's for each tab component */ @@ -90,11 +92,18 @@ export class MdTabGroup { @ViewChildren(MdTabLabelWrapper) _labelWrappers: QueryList; @ViewChildren(MdInkBar) _inkBar: QueryList; + @ViewChild('tabBodyWrapper') _tabBodyWrapper: ElementRef; + private _isInitialized: boolean = false; private _selectedIndex: number = 0; + + /** Snapshot of the height of the tab body wrapper before another tab is activated. */ + private _tabBodyWrapperHeight: number = 0; + @Input() set selectedIndex(value: number) { + this._tabBodyWrapperHeight = this._tabBodyWrapper.nativeElement.clientHeight; if (value != this._selectedIndex && this.isValidIndex(value)) { this._selectedIndex = value; @@ -137,8 +146,9 @@ export class MdTabGroup { private _focusIndex: number = 0; private _groupId: number; + private _bodyWrapperHeight: string = 'auto'; - constructor(private _zone: NgZone) { + constructor(private _zone: NgZone, private _elementRef: ElementRef, private _renderer: Renderer) { this._groupId = nextId++; } @@ -200,16 +210,6 @@ export class MdTabGroup { return event; } - /** Returns a unique id for each tab label element */ - _getTabLabelId(i: number): string { - return `md-tab-label-${this._groupId}-${i}`; - } - - /** Returns a unique id for each tab content element */ - _getTabContentId(i: number): string { - return `md-tab-content-${this._groupId}-${i}`; - } - handleKeydown(event: KeyboardEvent) { switch (event.keyCode) { case RIGHT_ARROW: @@ -248,15 +248,116 @@ export class MdTabGroup { focusPreviousTab(): void { this.moveFocus(-1); } + + /** Returns a unique id for each tab label element */ + _getTabLabelId(i: number): string { + return `md-tab-label-${this._groupId}-${i}`; + } + + /** Returns a unique id for each tab content element */ + _getTabContentId(i: number): string { + return `md-tab-content-${this._groupId}-${i}`; + } + + /** Sets the height of the body wrapper to the height of the activating tab. */ + _setTabBodyWrapperHeight(e: number) { + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', + this._tabBodyWrapperHeight + 'px'); + + // This statement is enough to tell the browser to paint the height so that + // the animation to the new height can have an origin. + this._tabBodyWrapper.nativeElement.offsetHeight; + + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', + e + 'px'); + } + + /** Removes the height of the tab body wrapper. */ + _removeTabBodyWrapperHeight() { + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', ''); + } } +export type MdTabBodyActiveState = 'left' | 'center' | 'right'; + +@Component({ + moduleId: module.id, + selector: 'md-tab-body', + templateUrl: 'tab-body.html', + animations: [ + trigger('position', [ + state('left', style({transform: 'translateX(-100%)'})), + state('center', style({transform: 'translateX(0%)'})), + state('right', style({transform: 'translateX(100%)'})), + transition('* => *', animate('0.5s cubic-bezier(0.35, 0, 0.25, 1)')), + ]) + ], + host: { + '[class.md-tab-active]': "_position === 'center'" + } +}) +export class MdTabBody implements OnInit { + /** The portal host inside of this container into which the tab body content will be loaded. */ + @ViewChild(PortalHostDirective) _portalHost: PortalHostDirective; + + /** Logical position of this tab body relative to the active (center) content being displayed. */ + _position: MdTabBodyActiveState; + + /** Event emitted when the tab begins to animate towards the center as the active tab. */ + @Output() + onTabBodyCentering: EventEmitter = new EventEmitter(); + + /** Event emitted when the tab completes its animation towards the center. */ + @Output() + onTabBodyCentered: EventEmitter = new EventEmitter(); + + /** The tab body content to display. */ + @Input('md-tab-body-content') _content: TemplatePortal; + + /** The shifted index position of the tab body, where zero represents the active center tab. */ + @Input('md-tab-body-position') + set position(v: number) { + if (v < 0) { this._position = 'left'; } + if (v == 0) { this._position = 'center'; } + if (v > 0) { this._position = 'right'; } + + if (this._position === 'center' && this._content) { + this._portalHost.attachTemplatePortal(this._content); + } + } + + constructor(private _elementRef: ElementRef) {} + + ngOnInit() { + if (this._position == 'center') { + this._portalHost.attachTemplatePortal(this._content); + } + } + + _onAnimationStarted(e: AnimationTransitionEvent) { + if (e.fromState != 'void' && e.toState == 'center') { + this.onTabBodyCentering.emit(this._elementRef.nativeElement.clientHeight); + } + } + + _onAnimationComplete(e: AnimationTransitionEvent) { + // If the end state is that the tab is not centered, then detach the content. + if ((e.toState == 'left' || e.toState == 'right') && this._position !== 'center') { + this._portalHost.detach(); + } + + if ((e.toState == 'center') && this._position == 'center') { + this.onTabBodyCentered.emit(); + } + } +} @NgModule({ imports: [CommonModule, PortalModule], // Don't export MdInkBar or MdTabLabelWrapper, as they are internal implementation details. exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink], declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper, - MdTabNavBar, MdTabLink], + MdTabNavBar, MdTabLink, MdTabBody], }) export class MdTabsModule { static forRoot(): ModuleWithProviders { From 8a47bcd50d5a48734b7bdcd590eef0e7022f6b2f Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 12:05:06 -0800 Subject: [PATCH 02/20] add demo for extra content on a tab --- src/demo-app/tabs/tabs-demo.html | 53 ++++++++++++++++++++++++++++++-- src/demo-app/tabs/tabs-demo.ts | 18 +++++++++-- src/lib/tabs/tabs.ts | 3 +- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 7eac59ee5b42..8a3182931cbb 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -17,16 +17,65 @@

Tab Nav Bar

Tab Group Demo

- + {{tab.content}}

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec orci posuere, nec luctus mauris semper. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. + Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. + In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, + feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, + orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius + gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat + diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod + ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim + venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. + Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec + orci posuere, nec luctus mauris semper. +
+
+ Morbi viverra, ante vel aliquet tincidunt, leo dolor pharetra quam, at semper massa orci nec + magna. Donec posuere nec sapien sed laoreet. Etiam cursus nunc in condimentum facilisis. + Etiam in tempor tortor. Vivamus faucibus egestas enim, at convallis diam pulvinar vel. + Cras ac orci eget nisi maximus cursus. Nunc urna libero, viverra sit amet nisl at, hendrerit + tempor turpis. Maecenas facilisis convallis mi vel tempor. Nullam vitae nunc leo. Cras sed + nisl consectetur, rhoncus sapien sit amet, tempus sapien. +
+
+ Integer turpis erat, porttitor vitae mi faucibus, laoreet interdum tellus. Curabitur posuere + molestie dictum. Morbi eget congue risus, quis rhoncus quam. Suspendisse vitae hendrerit erat, + at posuere mi. Cras eu fermentum nunc. Sed id ante eu orci commodo volutpat non ac est. + Praesent ligula diam, congue eu enim scelerisque, finibus commodo lectus.


+ + +

Async Tabs

+ + + + + {{tab.content}} +
+
+
+ +
+
+ + +

Tabs with simplified api

+ + + This tab is about the Earth! + + + This tab is about combustion! + + diff --git a/src/demo-app/tabs/tabs-demo.ts b/src/demo-app/tabs/tabs-demo.ts index bcb26829a154..488f28082077 100644 --- a/src/demo-app/tabs/tabs-demo.ts +++ b/src/demo-app/tabs/tabs-demo.ts @@ -18,9 +18,21 @@ export class TabsDemo { activeLinkIndex = 0; tabs = [ - {label: 'Tab One', content: 'This is the body of the first tab'}, - {label: 'Tab Two', extraContent: true, content: 'This is the body of the second tab'}, - {label: 'Tab Three', content: 'This is the body of the third tab'}, + { + label: 'Tab One', + content: 'This is the body of the first tab'}, + { + label: 'Tab Two', + disabled: true, + content: 'This is the body of the second tab'}, + { + label: 'Tab Three', + extraContent: true, + content: 'This is the body of the third tab'}, + { + label: 'Tab Four', + content: 'This is the body of the fourth tab' + }, ]; asyncTabs: Observable; diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index af61ce8ec75a..36d73a5a1eaa 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -329,6 +329,7 @@ export class MdTabBody implements OnInit { constructor(private _elementRef: ElementRef) {} ngOnInit() { + this._portalHost.attachTemplatePortal(this._content); if (this._position == 'center') { this._portalHost.attachTemplatePortal(this._content); } @@ -343,7 +344,7 @@ export class MdTabBody implements OnInit { _onAnimationComplete(e: AnimationTransitionEvent) { // If the end state is that the tab is not centered, then detach the content. if ((e.toState == 'left' || e.toState == 'right') && this._position !== 'center') { - this._portalHost.detach(); + //this._portalHost.detach(); } if ((e.toState == 'center') && this._position == 'center') { From 1424cbf71b447a40d7f7d6befc420228a645dc04 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 12:56:29 -0800 Subject: [PATCH 03/20] add flag for dynamic height --- src/demo-app/tabs/tabs-demo.html | 46 ++++++++++++++++++++++- src/lib/tabs/tab-group.scss | 5 +++ src/lib/tabs/tabs.ts | 64 ++++++++++++++++++-------------- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 8a3182931cbb..3d8943a29714 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -14,9 +14,51 @@

Tab Nav Bar

-

Tab Group Demo

+

Tab Group Demo - Dynamic Height

- + + + + {{tab.content}} +
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue. + Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus. + In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec, + feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor, + orci enim rutrum enim, vel tempor sapien arcu a tellus. Vivamus convallis sodales ante varius + gravida. Curabitur a purus vel augue ultrices ultricies id a nisl. Nullam malesuada consequat + diam, a facilisis tortor volutpat et. Sed urna dolor, aliquet vitae posuere vulputate, euismod + ac lorem. Sed felis risus, pulvinar at interdum quis, vehicula sed odio. Phasellus in enim + venenatis, iaculis tortor eu, bibendum ante. Donec ac tellus dictum neque volutpat blandit. + Praesent efficitur faucibus risus, ac auctor purus porttitor vitae. Phasellus ornare dui nec + orci posuere, nec luctus mauris semper. +
+
+ Morbi viverra, ante vel aliquet tincidunt, leo dolor pharetra quam, at semper massa orci nec + magna. Donec posuere nec sapien sed laoreet. Etiam cursus nunc in condimentum facilisis. + Etiam in tempor tortor. Vivamus faucibus egestas enim, at convallis diam pulvinar vel. + Cras ac orci eget nisi maximus cursus. Nunc urna libero, viverra sit amet nisl at, hendrerit + tempor turpis. Maecenas facilisis convallis mi vel tempor. Nullam vitae nunc leo. Cras sed + nisl consectetur, rhoncus sapien sit amet, tempus sapien. +
+
+ Integer turpis erat, porttitor vitae mi faucibus, laoreet interdum tellus. Curabitur posuere + molestie dictum. Morbi eget congue risus, quis rhoncus quam. Suspendisse vitae hendrerit erat, + at posuere mi. Cras eu fermentum nunc. Sed id ante eu orci commodo volutpat non ac est. + Praesent ligula diam, congue eu enim scelerisque, finibus commodo lectus. +
+
+
+ +
+
+ + +

Tab Group Demo - Fixed Height

+ + {{tab.content}} diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index c120feb51b55..534cefbd8faf 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -33,8 +33,13 @@ md-ink-bar { position: relative; overflow: hidden; transition: height 0.5s cubic-bezier(0.35, 0, 0.25, 1); + [md-dynamic-height] & { + overflow: auto; + } } + + // Wraps each tab body md-tab-body { display: block; diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 36d73a5a1eaa..1263afe2c78c 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -101,6 +101,11 @@ export class MdTabGroup { /** Snapshot of the height of the tab body wrapper before another tab is activated. */ private _tabBodyWrapperHeight: number = 0; + private _dynamicHeight: boolean = false; + @Input('md-dynamic-height') set dynamicHeight(value: boolean) { + this._dynamicHeight = coerceBooleanProperty(value); + } + @Input() set selectedIndex(value: number) { this._tabBodyWrapperHeight = this._tabBodyWrapper.nativeElement.clientHeight; @@ -116,19 +121,6 @@ export class MdTabGroup { return this._selectedIndex; } - /** - * Determines if an index is valid. If the tabs are not ready yet, we assume that the user is - * providing a valid index and return true. - */ - isValidIndex(index: number): boolean { - if (this._tabs) { - const tab = this._tabs.toArray()[index]; - return tab && !tab.disabled; - } else { - return true; - } - } - /** Output to enable support for two-way binding on `selectedIndex`. */ @Output() get selectedIndexChange(): Observable { return this.selectChange.map(event => event.index); @@ -146,9 +138,8 @@ export class MdTabGroup { private _focusIndex: number = 0; private _groupId: number; - private _bodyWrapperHeight: string = 'auto'; - constructor(private _zone: NgZone, private _elementRef: ElementRef, private _renderer: Renderer) { + constructor(private _zone: NgZone, private _renderer: Renderer) { this._groupId = nextId++; } @@ -166,6 +157,19 @@ export class MdTabGroup { this._isInitialized = true; } + /** + * Determines if an index is valid. If the tabs are not ready yet, we assume that the user is + * providing a valid index and return true. + */ + isValidIndex(index: number): boolean { + if (this._tabs) { + const tab = this._tabs.toArray()[index]; + return tab && !tab.disabled; + } else { + return true; + } + } + /** Tells the ink-bar to align itself to the current label wrapper */ private _updateInkBar(): void { this._inkBar.toArray()[0].alignToElement(this._currentLabelWrapper); @@ -201,15 +205,6 @@ export class MdTabGroup { } } - private _createChangeEvent(index: number): MdTabChangeEvent { - const event = new MdTabChangeEvent; - event.index = index; - if (this._tabs && this._tabs.length) { - event.tab = this._tabs.toArray()[index]; - } - return event; - } - handleKeydown(event: KeyboardEvent) { switch (event.keyCode) { case RIGHT_ARROW: @@ -249,6 +244,15 @@ export class MdTabGroup { this.moveFocus(-1); } + private _createChangeEvent(index: number): MdTabChangeEvent { + const event = new MdTabChangeEvent; + event.index = index; + if (this._tabs && this._tabs.length) { + event.tab = this._tabs.toArray()[index]; + } + return event; + } + /** Returns a unique id for each tab label element */ _getTabLabelId(i: number): string { return `md-tab-label-${this._groupId}-${i}`; @@ -259,12 +263,17 @@ export class MdTabGroup { return `md-tab-content-${this._groupId}-${i}`; } - /** Sets the height of the body wrapper to the height of the activating tab. */ + /** + * Sets the height of the body wrapper to the height of the activating tab if dynamic + * height property is true. + */ _setTabBodyWrapperHeight(e: number) { + if (!this._dynamicHeight) { return; } + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', this._tabBodyWrapperHeight + 'px'); - // This statement is enough to tell the browser to paint the height so that + // This statement is enough to tell the browser to force paint the height so that // the animation to the new height can have an origin. this._tabBodyWrapper.nativeElement.offsetHeight; @@ -329,7 +338,6 @@ export class MdTabBody implements OnInit { constructor(private _elementRef: ElementRef) {} ngOnInit() { - this._portalHost.attachTemplatePortal(this._content); if (this._position == 'center') { this._portalHost.attachTemplatePortal(this._content); } @@ -344,7 +352,7 @@ export class MdTabBody implements OnInit { _onAnimationComplete(e: AnimationTransitionEvent) { // If the end state is that the tab is not centered, then detach the content. if ((e.toState == 'left' || e.toState == 'right') && this._position !== 'center') { - //this._portalHost.detach(); + this._portalHost.detach(); } if ((e.toState == 'center') && this._position == 'center') { From 3021cc071da939a2fbbb959b38380f76d0d4a5b0 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 13:51:30 -0800 Subject: [PATCH 04/20] add scrolling when dynamic height is disabled --- src/lib/tabs/tab-group.scss | 8 +++----- src/lib/tabs/tabs.ts | 10 +++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index 534cefbd8faf..6fae3a7d8f16 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -31,15 +31,13 @@ md-ink-bar { // The bottom section of the view; contains the tab bodies .md-tab-body-wrapper { position: relative; - overflow: hidden; + overflow: auto; transition: height 0.5s cubic-bezier(0.35, 0, 0.25, 1); - [md-dynamic-height] & { - overflow: auto; + :host[md-dynamic-height] & { + overflow: hidden; } } - - // Wraps each tab body md-tab-body { display: block; diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 1263afe2c78c..6bfaa2b52ec0 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -84,7 +84,7 @@ export class MdTab implements OnInit { moduleId: module.id, selector: 'md-tab-group', templateUrl: 'tab-group.html', - styleUrls: ['tab-group.css'], + styleUrls: ['tab-group.css'] }) export class MdTabGroup { @ContentChildren(MdTab) _tabs: QueryList; @@ -96,18 +96,18 @@ export class MdTabGroup { private _isInitialized: boolean = false; - private _selectedIndex: number = 0; - /** Snapshot of the height of the tab body wrapper before another tab is activated. */ private _tabBodyWrapperHeight: number = 0; + /** Whether the tab group should grow to the size of the active tab */ private _dynamicHeight: boolean = false; @Input('md-dynamic-height') set dynamicHeight(value: boolean) { this._dynamicHeight = coerceBooleanProperty(value); } - @Input() - set selectedIndex(value: number) { + /** The index of the active tab. */ + private _selectedIndex: number = 0; + @Input() set selectedIndex(value: number) { this._tabBodyWrapperHeight = this._tabBodyWrapper.nativeElement.clientHeight; if (value != this._selectedIndex && this.isValidIndex(value)) { this._selectedIndex = value; From 367532de1f6887ebf8505832a8c8e84be7bde5cf Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 13:58:37 -0800 Subject: [PATCH 05/20] change style of imports on tabs --- src/lib/tabs/tabs.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 6bfaa2b52ec0..a0ec4503809b 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -13,7 +13,13 @@ import { ContentChildren, TemplateRef, ViewContainerRef, - OnInit, trigger, state, style, animate, transition, OnChanges, AnimationTransitionEvent, + OnInit, + trigger, + state, + style, + animate, + transition, + AnimationTransitionEvent, ElementRef, Renderer, } from '@angular/core'; import {CommonModule} from '@angular/common'; From 958718cc864a64040e8e1cbd55e71e330186ec59 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 14:39:19 -0800 Subject: [PATCH 06/20] fix scrolling when switching away from a tab --- src/lib/tabs/tab-group.html | 17 +++++++++-------- src/lib/tabs/tab-group.scss | 9 ++++++--- src/lib/tabs/tab-group.spec.ts | 12 ++++++------ src/lib/tabs/tabs.ts | 14 ++++---------- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/lib/tabs/tab-group.html b/src/lib/tabs/tab-group.html index 9682c1b81ffd..370373d22077 100644 --- a/src/lib/tabs/tab-group.html +++ b/src/lib/tabs/tab-group.html @@ -6,7 +6,7 @@ [tabIndex]="selectedIndex == i ? 0 : -1" [attr.aria-controls]="_getTabContentId(i)" [attr.aria-selected]="selectedIndex == i" - [class.md-tab-active]="selectedIndex == i" + [class.md-tab-label-active]="selectedIndex == i" [class.md-tab-disabled]="tab.disabled" (click)="focusIndex = selectedIndex = i"> @@ -22,12 +22,13 @@
+ *ngFor="let tab of _tabs; let i = index" + [id]="_getTabContentId(i)" + [attr.aria-labelledby]="_getTabLabelId(i)" + [class.md-tab-body-active]="selectedIndex == i" + [md-tab-body-position]="i - selectedIndex" + [md-tab-body-content]="tab.content" + (onTabBodyCentered)="_removeTabBodyWrapperHeight()" + (onTabBodyCentering)="_setTabBodyWrapperHeight($event)">
diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index 6fae3a7d8f16..e965806eb3f7 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -31,8 +31,9 @@ md-ink-bar { // The bottom section of the view; contains the tab bodies .md-tab-body-wrapper { position: relative; - overflow: auto; - transition: height 0.5s cubic-bezier(0.35, 0, 0.25, 1); + overflow: hidden; + display: flex; + transition: height 0.5s $ease-in-out-curve-function; :host[md-dynamic-height] & { overflow: hidden; } @@ -47,9 +48,11 @@ md-tab-body { bottom: 0; left: 0; overflow: hidden; - &.md-tab-active { + &.md-tab-body-active { position: relative; + overflow-y: auto; z-index: 1; + flex-grow: 1; } } diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index 1a635d9706bb..ff0c9a5ebdfa 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -5,7 +5,7 @@ import {By} from '@angular/platform-browser'; import {Observable} from 'rxjs/Observable'; -describe('MdTabGroup', () => { +fdescribe('MdTabGroup', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -279,7 +279,7 @@ describe('MdTabGroup', () => { /** * Checks that the `selectedIndex` has been updated; checks that the label and body have the - * `md-tab-active` class + * `active` class */ function checkSelectedIndex(index: number, fixture: ComponentFixture) { fixture.detectChanges(); @@ -290,19 +290,19 @@ describe('MdTabGroup', () => { let tabLabelElement = fixture.debugElement .query(By.css(`.md-tab-label:nth-of-type(${index + 1})`)).nativeElement; - expect(tabLabelElement.classList.contains('md-tab-active')).toBe(true); + expect(tabLabelElement.classList.contains('active')).toBe(true); let tabContentElement = fixture.debugElement .query(By.css(`#${tabLabelElement.id}`)).nativeElement; - expect(tabContentElement.classList.contains('md-tab-active')).toBe(true); + expect(tabContentElement.classList.contains('active')).toBe(true); } function getSelectedLabel(fixture: ComponentFixture): HTMLElement { - return fixture.nativeElement.querySelector('.md-tab-label.md-tab-active'); + return fixture.nativeElement.querySelector('.md-tab-label.active'); } function getSelectedContent(fixture: ComponentFixture): HTMLElement { - return fixture.nativeElement.querySelector('.md-tab-body.md-tab-active'); + return fixture.nativeElement.querySelector('md-tab-body.active'); } }); diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index a0ec4503809b..b0c3ff3bfee1 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -16,7 +16,7 @@ import { OnInit, trigger, state, - style, + style, animate, transition, AnimationTransitionEvent, @@ -306,18 +306,12 @@ export type MdTabBodyActiveState = 'left' | 'center' | 'right'; state('right', style({transform: 'translateX(100%)'})), transition('* => *', animate('0.5s cubic-bezier(0.35, 0, 0.25, 1)')), ]) - ], - host: { - '[class.md-tab-active]': "_position === 'center'" - } + ] }) export class MdTabBody implements OnInit { /** The portal host inside of this container into which the tab body content will be loaded. */ @ViewChild(PortalHostDirective) _portalHost: PortalHostDirective; - /** Logical position of this tab body relative to the active (center) content being displayed. */ - _position: MdTabBodyActiveState; - /** Event emitted when the tab begins to animate towards the center as the active tab. */ @Output() onTabBodyCentering: EventEmitter = new EventEmitter(); @@ -330,8 +324,8 @@ export class MdTabBody implements OnInit { @Input('md-tab-body-content') _content: TemplatePortal; /** The shifted index position of the tab body, where zero represents the active center tab. */ - @Input('md-tab-body-position') - set position(v: number) { + _position: MdTabBodyActiveState; + @Input('md-tab-body-position') set position(v: number) { if (v < 0) { this._position = 'left'; } if (v == 0) { this._position = 'center'; } if (v > 0) { this._position = 'right'; } From 8db598422b038b8b029383830f1f9c3ab7f11d20 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Tue, 8 Nov 2016 14:53:49 -0800 Subject: [PATCH 07/20] fix tests --- src/lib/tabs/tab-group.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index ff0c9a5ebdfa..3a4663484cb1 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -290,19 +290,20 @@ fdescribe('MdTabGroup', () => { let tabLabelElement = fixture.debugElement .query(By.css(`.md-tab-label:nth-of-type(${index + 1})`)).nativeElement; - expect(tabLabelElement.classList.contains('active')).toBe(true); + expect(tabLabelElement.classList.contains('md-tab-label-active')).toBe(true); let tabContentElement = fixture.debugElement - .query(By.css(`#${tabLabelElement.id}`)).nativeElement; - expect(tabContentElement.classList.contains('active')).toBe(true); + .query(By.css(`md-tab-body:nth-of-type(${index + 1})`)).nativeElement; + debugger; + expect(tabContentElement.classList.contains('md-tab-body-active')).toBe(true); } function getSelectedLabel(fixture: ComponentFixture): HTMLElement { - return fixture.nativeElement.querySelector('.md-tab-label.active'); + return fixture.nativeElement.querySelector('.md-tab-label-active'); } function getSelectedContent(fixture: ComponentFixture): HTMLElement { - return fixture.nativeElement.querySelector('md-tab-body.active'); + return fixture.nativeElement.querySelector('.md-tab-body-active'); } }); From 40b591630807dc35a5daffa23e1aa85c5d545029 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 10:18:41 -0800 Subject: [PATCH 08/20] add tests --- src/lib/core/portal/portal-directives.ts | 4 +- src/lib/core/portal/portal.ts | 1 + src/lib/tabs/tab-group.spec.ts | 63 +++++++++++++++++++++--- src/lib/tabs/tabs.ts | 10 ++-- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/lib/core/portal/portal-directives.ts b/src/lib/core/portal/portal-directives.ts index dab58967322e..75feb45f10eb 100644 --- a/src/lib/core/portal/portal-directives.ts +++ b/src/lib/core/portal/portal-directives.ts @@ -57,7 +57,9 @@ export class PortalHostDirective extends BasePortalHost implements OnDestroy { } set portal(p: Portal) { - this._replaceAttachedPortal(p); + if (p) { + this._replaceAttachedPortal(p); + } } ngOnDestroy() { diff --git a/src/lib/core/portal/portal.ts b/src/lib/core/portal/portal.ts index b75a69369135..a66c4992de6c 100644 --- a/src/lib/core/portal/portal.ts +++ b/src/lib/core/portal/portal.ts @@ -197,6 +197,7 @@ export abstract class BasePortalHost implements PortalHost { detach(): void { if (this._attachedPortal) { this._attachedPortal.setAttachedHost(null); } + debugger; this._attachedPortal = null; if (this._disposeFn != null) { this._disposeFn(); diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index 3a4663484cb1..e45dab52e314 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -1,11 +1,14 @@ -import {async, fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/testing'; +import { + async, fakeAsync, tick, ComponentFixture, TestBed, + flushMicrotasks +} from '@angular/core/testing'; import {MdTabGroup, MdTabsModule} from './tabs'; import {Component, ViewChild} from '@angular/core'; import {By} from '@angular/platform-browser'; import {Observable} from 'rxjs/Observable'; -fdescribe('MdTabGroup', () => { +describe('MdTabGroup', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -120,22 +123,69 @@ fdescribe('MdTabGroup', () => { expect(testComponent.focusEvent.index).toBe(0); })); - it('should change tabs based on selectedIndex', fakeAsync(() => { + it('should change tabs based on selectedIndex and update tab body positions', fakeAsync(() => { let component = fixture.componentInstance; let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; - spyOn(component, 'handleSelection').and.callThrough(); + fixture.detectChanges(); + checkSelectedIndex(1, fixture); tabComponent.selectedIndex = 2; - checkSelectedIndex(2, fixture); - tick(); + tick(); expect(component.handleSelection).toHaveBeenCalledTimes(1); expect(component.selectEvent.index).toBe(2); })); + + it('should update tab positions and attach content when selected', fakeAsync(() => { + fixture.detectChanges(); + let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; + const tabBodyList = fixture.debugElement.queryAll(By.css('md-tab-body')); + + // Begin on the second tab + flushMicrotasks(); // finish animation + + expect(tabBodyList[0].componentInstance._position).toBe('left'); + expect(tabBodyList[0].componentInstance._content.isAttached).toBe(false); + + expect(tabBodyList[1].componentInstance._position).toBe('center'); + expect(tabBodyList[1].componentInstance._content.isAttached).toBe(true); + + expect(tabBodyList[2].componentInstance._position).toBe('right'); + expect(tabBodyList[2].componentInstance._content.isAttached).toBe(false); + + // Move to third tab + tabComponent.selectedIndex = 2; + fixture.detectChanges(); + flushMicrotasks(); // finish animation + + expect(tabBodyList[0].componentInstance._position).toBe('left'); + expect(tabBodyList[0].componentInstance._content.isAttached).toBe(false); + + expect(tabBodyList[1].componentInstance._position).toBe('left'); + expect(tabBodyList[1].componentInstance._content.isAttached).toBe(false); + + expect(tabBodyList[2].componentInstance._position).toBe('center'); + expect(tabBodyList[2].componentInstance._content.isAttached).toBe(true); + + // Move to the first tab + tabComponent.selectedIndex = 0; + fixture.detectChanges(); + flushMicrotasks(); // finish animation + + // Check that the tab bodies have correctly positions themselves + expect(tabBodyList[0].componentInstance._position).toBe('center'); + expect(tabBodyList[0].componentInstance._content.isAttached).toBe(true); + + expect(tabBodyList[1].componentInstance._position).toBe('right'); + expect(tabBodyList[1].componentInstance._content.isAttached).toBe(false); + + expect(tabBodyList[2].componentInstance._position).toBe('right'); + expect(tabBodyList[2].componentInstance._content.isAttached).toBe(false); + })); }); describe('disabled tabs', () => { @@ -294,7 +344,6 @@ fdescribe('MdTabGroup', () => { let tabContentElement = fixture.debugElement .query(By.css(`md-tab-body:nth-of-type(${index + 1})`)).nativeElement; - debugger; expect(tabContentElement.classList.contains('md-tab-body-active')).toBe(true); } diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index b0c3ff3bfee1..e2f404450758 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -330,16 +330,16 @@ export class MdTabBody implements OnInit { if (v == 0) { this._position = 'center'; } if (v > 0) { this._position = 'right'; } - if (this._position === 'center' && this._content) { - this._portalHost.attachTemplatePortal(this._content); + if (this._position === 'center' && !this._portalHost.hasAttached() && this._content) { + this._portalHost.attach(this._content); } } constructor(private _elementRef: ElementRef) {} ngOnInit() { - if (this._position == 'center') { - this._portalHost.attachTemplatePortal(this._content); + if (this._position == 'center' && !this._portalHost.hasAttached()) { + this._portalHost.attach(this._content); } } @@ -350,8 +350,8 @@ export class MdTabBody implements OnInit { } _onAnimationComplete(e: AnimationTransitionEvent) { - // If the end state is that the tab is not centered, then detach the content. if ((e.toState == 'left' || e.toState == 'right') && this._position !== 'center') { + // If the end state is that the tab is not centered, then detach the content. this._portalHost.detach(); } From e1863da89db023316c250c9cc4bd345e64c54047 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 10:32:11 -0800 Subject: [PATCH 09/20] fix styling --- src/lib/core/portal/portal.ts | 1 - src/lib/tabs/tab-group.scss | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/core/portal/portal.ts b/src/lib/core/portal/portal.ts index a66c4992de6c..b75a69369135 100644 --- a/src/lib/core/portal/portal.ts +++ b/src/lib/core/portal/portal.ts @@ -197,7 +197,6 @@ export abstract class BasePortalHost implements PortalHost { detach(): void { if (this._attachedPortal) { this._attachedPortal.setAttachedHost(null); } - debugger; this._attachedPortal = null; if (this._disposeFn != null) { this._disposeFn(); diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index e965806eb3f7..b2d83dcfdfc1 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -34,9 +34,6 @@ md-ink-bar { overflow: hidden; display: flex; transition: height 0.5s $ease-in-out-curve-function; - :host[md-dynamic-height] & { - overflow: hidden; - } } // Wraps each tab body @@ -50,10 +47,14 @@ md-tab-body { overflow: hidden; &.md-tab-body-active { position: relative; + overflow-x: hidden; overflow-y: auto; z-index: 1; flex-grow: 1; } + :host[md-dynamic-height] &.md-tab-body-active { + overflow-y: hidden; + } } // Styling for any tab that is marked disabled From 9fe9e7c28132117bd1b83f50bf9b09563b2381aa Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 10:38:07 -0800 Subject: [PATCH 10/20] fix test --- src/demo-app/tabs/tabs-demo.scss | 2 +- src/lib/tabs/tab-group.spec.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/demo-app/tabs/tabs-demo.scss b/src/demo-app/tabs/tabs-demo.scss index 2dc75c934f18..0a0337ee419c 100644 --- a/src/demo-app/tabs/tabs-demo.scss +++ b/src/demo-app/tabs/tabs-demo.scss @@ -16,7 +16,7 @@ .md-tab-header { background: #f9f9f9; } - .md-tab-body-content { + .md-tab-body { padding: 12px; } } \ No newline at end of file diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index e45dab52e314..2fa2fa7bf22c 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -123,19 +123,19 @@ describe('MdTabGroup', () => { expect(testComponent.focusEvent.index).toBe(0); })); - it('should change tabs based on selectedIndex and update tab body positions', fakeAsync(() => { + it('should change tabs based on selectedIndex', fakeAsync(() => { let component = fixture.componentInstance; let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; - spyOn(component, 'handleSelection').and.callThrough(); - fixture.detectChanges(); + spyOn(component, 'handleSelection').and.callThrough(); checkSelectedIndex(1, fixture); tabComponent.selectedIndex = 2; - checkSelectedIndex(2, fixture); + checkSelectedIndex(2, fixture); tick(); + expect(component.handleSelection).toHaveBeenCalledTimes(1); expect(component.selectEvent.index).toBe(2); })); @@ -328,8 +328,8 @@ describe('MdTabGroup', () => { }); /** - * Checks that the `selectedIndex` has been updated; checks that the label and body have the - * `active` class + * Checks that the `selectedIndex` has been updated; checks that the label and body have their + * respective `active` classes */ function checkSelectedIndex(index: number, fixture: ComponentFixture) { fixture.detectChanges(); From ed5c17e3a0db0e6974b4dd9afcd07beb6291cd97 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 10:39:17 -0800 Subject: [PATCH 11/20] change demo styling --- src/demo-app/tabs/tabs-demo.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demo-app/tabs/tabs-demo.scss b/src/demo-app/tabs/tabs-demo.scss index 0a0337ee419c..35b7db43a9c0 100644 --- a/src/demo-app/tabs/tabs-demo.scss +++ b/src/demo-app/tabs/tabs-demo.scss @@ -16,7 +16,7 @@ .md-tab-header { background: #f9f9f9; } - .md-tab-body { + md-tab-body { padding: 12px; } } \ No newline at end of file From 9c2bab18e59dd7801f7b5d8e62687669b4531dd6 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 11:20:34 -0800 Subject: [PATCH 12/20] fix lint --- src/lib/tabs/tab-group.scss | 2 +- src/lib/tabs/tabs.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index b2d83dcfdfc1..607e3a8e2cc4 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -33,7 +33,7 @@ md-ink-bar { position: relative; overflow: hidden; display: flex; - transition: height 0.5s $ease-in-out-curve-function; + transition: height 500ms $ease-in-out-curve-function; } // Wraps each tab body diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index e2f404450758..aa64df80580a 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -279,12 +279,11 @@ export class MdTabGroup { this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', this._tabBodyWrapperHeight + 'px'); - // This statement is enough to tell the browser to force paint the height so that + // This conditional forces the browser to paint the height so that // the animation to the new height can have an origin. - this._tabBodyWrapper.nativeElement.offsetHeight; - - this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', - e + 'px'); + if (this._tabBodyWrapper.nativeElement.offsetHeight) { + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', e + 'px'); + } } /** Removes the height of the tab body wrapper. */ From 7c283c8b4aa491202a1f6c2b4712d18f9b767157 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 11:27:26 -0800 Subject: [PATCH 13/20] fix e2e --- e2e/components/tabs/tabs.e2e.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/e2e/components/tabs/tabs.e2e.ts b/e2e/components/tabs/tabs.e2e.ts index daf4171824a7..33ee045c791c 100644 --- a/e2e/components/tabs/tabs.e2e.ts +++ b/e2e/components/tabs/tabs.e2e.ts @@ -11,17 +11,17 @@ describe('tabs', () => { browser.get('/tabs'); tabGroup = element(by.css('md-tab-group')); tabLabels = element.all(by.css('.md-tab-label')); - tabBodies = element.all(by.css('.md-tab-body')); + tabBodies = element.all(by.css('md-tab-body')); }); it('should change tabs when the label is clicked', () => { tabLabels.get(1).click(); - expect(getActiveStates(tabLabels)).toEqual([false, true, false]); - expect(getActiveStates(tabBodies)).toEqual([false, true, false]); + expect(getLabelActiveStates(tabLabels)).toEqual([false, true, false]); + expect(getBodyActiveStates(tabBodies)).toEqual([false, true, false]); tabLabels.get(0).click(); - expect(getActiveStates(tabLabels)).toEqual([true, false, false]); - expect(getActiveStates(tabBodies)).toEqual([true, false, false]); + expect(getLabelActiveStates(tabLabels)).toEqual([true, false, false]); + expect(getBodyActiveStates(tabBodies)).toEqual([true, false, false]); }); it('should change focus with keyboard interaction', () => { @@ -77,8 +77,17 @@ function getFocusStates(elements: ElementArrayFinder) { * @param elements * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} */ -function getActiveStates(elements: ElementArrayFinder) { - return getClassStates(elements, 'md-tab-active'); +function getLabelActiveStates(elements: ElementArrayFinder) { + return getClassStates(elements, 'md-tab-label-active'); +} + +/** + * Returns an array of true/false that represents the active states for the provided elements + * @param elements + * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} + */ +function getBodyActiveStates(elements: ElementArrayFinder) { + return getClassStates(elements, 'md-tab-body-active'); } /** From e41113f19930ad4056d28d801075f430eb14e268 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 12:00:31 -0800 Subject: [PATCH 14/20] responding to feedback --- src/demo-app/tabs/tabs-demo.scss | 2 +- src/lib/tabs/_tabs-common.scss | 6 ++++-- src/lib/tabs/tab-body.html | 2 +- src/lib/tabs/tab-group.scss | 2 +- src/lib/tabs/tabs.ts | 8 ++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/demo-app/tabs/tabs-demo.scss b/src/demo-app/tabs/tabs-demo.scss index 35b7db43a9c0..2dc75c934f18 100644 --- a/src/demo-app/tabs/tabs-demo.scss +++ b/src/demo-app/tabs/tabs-demo.scss @@ -16,7 +16,7 @@ .md-tab-header { background: #f9f9f9; } - md-tab-body { + .md-tab-body-content { padding: 12px; } } \ No newline at end of file diff --git a/src/lib/tabs/_tabs-common.scss b/src/lib/tabs/_tabs-common.scss index 2210ab2fba56..fd6d640418d4 100644 --- a/src/lib/tabs/_tabs-common.scss +++ b/src/lib/tabs/_tabs-common.scss @@ -2,6 +2,8 @@ $md-tab-bar-height: 48px !default; +$md-tab-animation-duration: 500ms !default; + // Mixin styles for labels that are contained within the tab header. @mixin tab-label { line-height: $md-tab-bar-height; @@ -36,5 +38,5 @@ $md-tab-bar-height: 48px !default; position: absolute; bottom: 0; height: 2px; - transition: 500ms ease-out; -} \ No newline at end of file + transition: $md-tab-animation-duration $ease-in-out-curve-function; +} diff --git a/src/lib/tabs/tab-body.html b/src/lib/tabs/tab-body.html index 07be488e60e9..c811e6a7f106 100644 --- a/src/lib/tabs/tab-body.html +++ b/src/lib/tabs/tab-body.html @@ -3,4 +3,4 @@ (@position.start)="_onAnimationStarted($event)" (@position.done)="_onAnimationComplete($event)"> - \ No newline at end of file + diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index 607e3a8e2cc4..8b107e4909d2 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -33,7 +33,7 @@ md-ink-bar { position: relative; overflow: hidden; display: flex; - transition: height 500ms $ease-in-out-curve-function; + transition: height $md-tab-animation-duration $ease-in-out-curve-function; } // Wraps each tab body diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index aa64df80580a..386c7a81d1f0 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -300,10 +300,10 @@ export type MdTabBodyActiveState = 'left' | 'center' | 'right'; templateUrl: 'tab-body.html', animations: [ trigger('position', [ - state('left', style({transform: 'translateX(-100%)'})), - state('center', style({transform: 'translateX(0%)'})), - state('right', style({transform: 'translateX(100%)'})), - transition('* => *', animate('0.5s cubic-bezier(0.35, 0, 0.25, 1)')), + state('left', style({transform: 'translate3d(-100%, 0, 0)'})), + state('center', style({transform: 'translate3d(0, 0, 0)'})), + state('right', style({transform: 'translate3d(100%, 0, 0)'})), + transition('* => *', animate('500ms cubic-bezier(0.35, 0, 0.25, 1)')), ]) ] }) From a087c6e2c4af5cd5b86f4eccd813741a6c4db039 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 12:15:41 -0800 Subject: [PATCH 15/20] responding to comments --- src/lib/tabs/tab-body.html | 6 +++--- src/lib/tabs/tab-group.spec.ts | 2 +- src/lib/tabs/tabs.ts | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lib/tabs/tab-body.html b/src/lib/tabs/tab-body.html index c811e6a7f106..2d89174484c7 100644 --- a/src/lib/tabs/tab-body.html +++ b/src/lib/tabs/tab-body.html @@ -1,6 +1,6 @@
+ [@translateTab]="_position" + (@translateTab.start)="_onTranslateTabStarted($event)" + (@translateTab.done)="_onTranslateTabComplete($event)">
diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index 2fa2fa7bf22c..2151b5df1139 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -142,7 +142,7 @@ describe('MdTabGroup', () => { it('should update tab positions and attach content when selected', fakeAsync(() => { fixture.detectChanges(); - let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; + const tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; const tabBodyList = fixture.debugElement.queryAll(By.css('md-tab-body')); // Begin on the second tab diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 386c7a81d1f0..885ee78d4dd3 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -20,7 +20,8 @@ import { animate, transition, AnimationTransitionEvent, - ElementRef, Renderer, + ElementRef, + Renderer, } from '@angular/core'; import {CommonModule} from '@angular/common'; import { @@ -30,6 +31,7 @@ import { LEFT_ARROW, ENTER, coerceBooleanProperty, + PortalHostDirective, } from '../core'; import {MdTabLabel} from './tab-label'; import {MdTabLabelWrapper} from './tab-label-wrapper'; @@ -37,7 +39,6 @@ import {MdTabNavBar, MdTabLink} from './tab-nav-bar/tab-nav-bar'; import {MdInkBar} from './ink-bar'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; -import {PortalHostDirective} from '../core/portal/portal-directives'; /** Used to generate unique ID's for each tab component */ @@ -273,7 +274,7 @@ export class MdTabGroup { * Sets the height of the body wrapper to the height of the activating tab if dynamic * height property is true. */ - _setTabBodyWrapperHeight(e: number) { + _setTabBodyWrapperHeight(tabHeight: number): void { if (!this._dynamicHeight) { return; } this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', @@ -282,12 +283,13 @@ export class MdTabGroup { // This conditional forces the browser to paint the height so that // the animation to the new height can have an origin. if (this._tabBodyWrapper.nativeElement.offsetHeight) { - this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', e + 'px'); + this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', + tabHeight + 'px'); } } /** Removes the height of the tab body wrapper. */ - _removeTabBodyWrapperHeight() { + _removeTabBodyWrapperHeight(): void { this._renderer.setElementStyle(this._tabBodyWrapper.nativeElement, 'height', ''); } } @@ -299,7 +301,7 @@ export type MdTabBodyActiveState = 'left' | 'center' | 'right'; selector: 'md-tab-body', templateUrl: 'tab-body.html', animations: [ - trigger('position', [ + trigger('translateTab', [ state('left', style({transform: 'translate3d(-100%, 0, 0)'})), state('center', style({transform: 'translate3d(0, 0, 0)'})), state('right', style({transform: 'translate3d(100%, 0, 0)'})), @@ -342,13 +344,13 @@ export class MdTabBody implements OnInit { } } - _onAnimationStarted(e: AnimationTransitionEvent) { + _onTranslateTabStarted(e: AnimationTransitionEvent) { if (e.fromState != 'void' && e.toState == 'center') { this.onTabBodyCentering.emit(this._elementRef.nativeElement.clientHeight); } } - _onAnimationComplete(e: AnimationTransitionEvent) { + _onTranslateTabComplete(e: AnimationTransitionEvent) { if ((e.toState == 'left' || e.toState == 'right') && this._position !== 'center') { // If the end state is that the tab is not centered, then detach the content. this._portalHost.detach(); From df0bba513f58c930b45922c2b9764496d6a66ece Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 14:31:53 -0800 Subject: [PATCH 16/20] repurpose md-fullscreen as md-fill for absolute --- src/lib/core/style/_layout-common.scss | 8 ++++++++ src/lib/core/style/_sidenav-common.scss | 8 -------- src/lib/menu/menu.scss | 2 +- src/lib/sidenav/sidenav.scss | 6 +++--- src/lib/tabs/tab-group.scss | 7 ++----- 5 files changed, 14 insertions(+), 17 deletions(-) create mode 100644 src/lib/core/style/_layout-common.scss delete mode 100644 src/lib/core/style/_sidenav-common.scss diff --git a/src/lib/core/style/_layout-common.scss b/src/lib/core/style/_layout-common.scss new file mode 100644 index 000000000000..672af5c7c674 --- /dev/null +++ b/src/lib/core/style/_layout-common.scss @@ -0,0 +1,8 @@ +// This mixin ensures an element spans to fill the nearest ancestor with defined positioning. +@mixin md-fill { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} \ No newline at end of file diff --git a/src/lib/core/style/_sidenav-common.scss b/src/lib/core/style/_sidenav-common.scss deleted file mode 100644 index 1499cfc6bdde..000000000000 --- a/src/lib/core/style/_sidenav-common.scss +++ /dev/null @@ -1,8 +0,0 @@ -// This mixin ensures an element spans the whole viewport. -@mixin md-fullscreen { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; -} diff --git a/src/lib/menu/menu.scss b/src/lib/menu/menu.scss index a9e4ba09670d..8dfc6096a723 100644 --- a/src/lib/menu/menu.scss +++ b/src/lib/menu/menu.scss @@ -2,7 +2,7 @@ // TODO(kara): animation for menu opening @import '../core/style/button-common'; -@import '../core/style/sidenav-common'; +@import '../core/style/layout-common'; @import '../core/style/menu-common'; $md-menu-vertical-padding: 8px !default; diff --git a/src/lib/sidenav/sidenav.scss b/src/lib/sidenav/sidenav.scss index c38d47a4a00c..34b67918c9b8 100644 --- a/src/lib/sidenav/sidenav.scss +++ b/src/lib/sidenav/sidenav.scss @@ -1,6 +1,6 @@ @import '../core/style/variables'; @import '../core/style/elevation'; -@import '../core/style/sidenav-common'; +@import '../core/style/layout-common'; // Mixin to help with defining LTR/RTL 'transform: translate3d()' values. @@ -57,7 +57,7 @@ md-sidenav-layout { // TODO(hansl): Update this with a more robust solution. &[fullscreen] { - @include md-fullscreen(); + @include md-fill(); &.md-sidenav-opened { overflow: hidden; @@ -66,7 +66,7 @@ md-sidenav-layout { } .md-sidenav-backdrop { - @include md-fullscreen(); + @include md-fill(); display: block; diff --git a/src/lib/tabs/tab-group.scss b/src/lib/tabs/tab-group.scss index 8b107e4909d2..42a140d08751 100644 --- a/src/lib/tabs/tab-group.scss +++ b/src/lib/tabs/tab-group.scss @@ -1,4 +1,5 @@ @import '../core/style/variables'; +@import '../core/style/layout-common'; @import 'tabs-common'; :host { @@ -38,12 +39,8 @@ md-ink-bar { // Wraps each tab body md-tab-body { + @include md-fill; display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; overflow: hidden; &.md-tab-body-active { position: relative; From 54744b749e99aa336f5bb654b2ca5c7d02a5ca00 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 14:32:46 -0800 Subject: [PATCH 17/20] add extra line --- src/lib/core/style/_layout-common.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/core/style/_layout-common.scss b/src/lib/core/style/_layout-common.scss index 672af5c7c674..4748f3b6fb80 100644 --- a/src/lib/core/style/_layout-common.scss +++ b/src/lib/core/style/_layout-common.scss @@ -5,4 +5,4 @@ left: 0; right: 0; bottom: 0; -} \ No newline at end of file +} From 338b94dae086bdbb557db8ef0527401c8826eda6 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 14:50:30 -0800 Subject: [PATCH 18/20] support rtl direction --- src/lib/tabs/tabs.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 885ee78d4dd3..3b8795556a4b 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -21,7 +21,7 @@ import { transition, AnimationTransitionEvent, ElementRef, - Renderer, + Renderer, Optional, } from '@angular/core'; import {CommonModule} from '@angular/common'; import { @@ -32,6 +32,8 @@ import { ENTER, coerceBooleanProperty, PortalHostDirective, + Dir, + LayoutDirection } from '../core'; import {MdTabLabel} from './tab-label'; import {MdTabLabelWrapper} from './tab-label-wrapper'; @@ -327,16 +329,19 @@ export class MdTabBody implements OnInit { /** The shifted index position of the tab body, where zero represents the active center tab. */ _position: MdTabBodyActiveState; @Input('md-tab-body-position') set position(v: number) { - if (v < 0) { this._position = 'left'; } + if (v < 0) { + this._position = this.getLayoutDirection() == 'ltr' ? 'left' : 'right'; + } else if (v > 0) { + this._position = this.getLayoutDirection() == 'ltr' ? 'right' : 'left'; + } if (v == 0) { this._position = 'center'; } - if (v > 0) { this._position = 'right'; } if (this._position === 'center' && !this._portalHost.hasAttached() && this._content) { this._portalHost.attach(this._content); } } - constructor(private _elementRef: ElementRef) {} + constructor(private _elementRef: ElementRef, @Optional() private _dir: Dir) {} ngOnInit() { if (this._position == 'center' && !this._portalHost.hasAttached()) { @@ -360,6 +365,11 @@ export class MdTabBody implements OnInit { this.onTabBodyCentered.emit(); } } + + /** The text direction of the containing app. */ + getLayoutDirection(): LayoutDirection { + return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; + } } @NgModule({ From afd0ffb0a1e36878bf1e34dd2ea262919b21a3ea Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 9 Nov 2016 14:51:04 -0800 Subject: [PATCH 19/20] fix import --- src/lib/tabs/tabs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index 3b8795556a4b..d3d76066f00a 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -21,7 +21,8 @@ import { transition, AnimationTransitionEvent, ElementRef, - Renderer, Optional, + Renderer, + Optional, } from '@angular/core'; import {CommonModule} from '@angular/common'; import { From 29533e77a830c2533dcbba28124291f7254d0ba7 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Fri, 11 Nov 2016 09:26:33 -0800 Subject: [PATCH 20/20] add rtl test --- e2e/components/tabs/tabs.e2e.ts | 26 ++++----------------- src/lib/tabs/tab-group.spec.ts | 41 +++++++++++++++++++++++++++++++++ src/lib/tabs/tabs.ts | 3 ++- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/e2e/components/tabs/tabs.e2e.ts b/e2e/components/tabs/tabs.e2e.ts index 33ee045c791c..61f8a9b9f95e 100644 --- a/e2e/components/tabs/tabs.e2e.ts +++ b/e2e/components/tabs/tabs.e2e.ts @@ -49,18 +49,13 @@ describe('tabs', () => { }); }); -/** - * A helper function to perform the sendKey action - * @param key - */ +/** A helper function to perform the sendKey action. */ function pressKey(key: string) { browser.actions().sendKeys(key).perform(); } /** - * Returns an array of true/false that represents the focus states of the provided elements - * @param elements - * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} + * Returns an array of true/false that represents the focus states of the provided elements. */ function getFocusStates(elements: ElementArrayFinder) { return elements.map(element => { @@ -72,30 +67,19 @@ function getFocusStates(elements: ElementArrayFinder) { }); } -/** - * Returns an array of true/false that represents the active states for the provided elements - * @param elements - * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} - */ +/** Returns an array of true/false that represents the active states for the provided elements. */ function getLabelActiveStates(elements: ElementArrayFinder) { return getClassStates(elements, 'md-tab-label-active'); } -/** - * Returns an array of true/false that represents the active states for the provided elements - * @param elements - * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} - */ +/** Returns an array of true/false that represents the active states for the provided elements */ function getBodyActiveStates(elements: ElementArrayFinder) { return getClassStates(elements, 'md-tab-body-active'); } /** * Returns an array of true/false values that represents whether the provided className is on - * each element - * @param elements - * @param className - * @returns {webdriver.promise.Promise[]>|webdriver.promise.Promise} + * each element. */ function getClassStates(elements: ElementArrayFinder, className: string) { return elements.map(element => { diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index 2151b5df1139..f4e8adfeca9f 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -6,9 +6,11 @@ import {MdTabGroup, MdTabsModule} from './tabs'; import {Component, ViewChild} from '@angular/core'; import {By} from '@angular/platform-browser'; import {Observable} from 'rxjs/Observable'; +import {LayoutDirection, Dir} from '../core/rtl/dir'; describe('MdTabGroup', () => { + let dir: LayoutDirection = 'ltr'; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -19,6 +21,11 @@ describe('MdTabGroup', () => { DisabledTabsTestApp, TabGroupWithSimpleApi, ], + providers: [ + {provide: Dir, useFactory: () => { + return {value: dir}; + }} + ] }); TestBed.compileComponents(); @@ -186,6 +193,40 @@ describe('MdTabGroup', () => { expect(tabBodyList[2].componentInstance._position).toBe('right'); expect(tabBodyList[2].componentInstance._content.isAttached).toBe(false); })); + + + it('should support RTL for the tab positions', fakeAsync(() => { + dir = 'rtl'; + fixture.detectChanges(); + const tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance; + const tabBodyList = fixture.debugElement.queryAll(By.css('md-tab-body')); + + // Begin on the second tab + flushMicrotasks(); // finish animation + + expect(tabBodyList[0].componentInstance._position).toBe('right'); + expect(tabBodyList[1].componentInstance._position).toBe('center'); + expect(tabBodyList[2].componentInstance._position).toBe('left'); + + // Move to third tab + tabComponent.selectedIndex = 2; + fixture.detectChanges(); + flushMicrotasks(); // finish animation + + expect(tabBodyList[0].componentInstance._position).toBe('right'); + expect(tabBodyList[1].componentInstance._position).toBe('right'); + expect(tabBodyList[2].componentInstance._position).toBe('center'); + + // Move to the first tab + tabComponent.selectedIndex = 0; + fixture.detectChanges(); + flushMicrotasks(); // finish animation + + // Check that the tab bodies have correctly positions themselves + expect(tabBodyList[0].componentInstance._position).toBe('center'); + expect(tabBodyList[1].componentInstance._position).toBe('left'); + expect(tabBodyList[2].componentInstance._position).toBe('left'); + })); }); describe('disabled tabs', () => { diff --git a/src/lib/tabs/tabs.ts b/src/lib/tabs/tabs.ts index d3d76066f00a..fa287c929dec 100644 --- a/src/lib/tabs/tabs.ts +++ b/src/lib/tabs/tabs.ts @@ -334,8 +334,9 @@ export class MdTabBody implements OnInit { this._position = this.getLayoutDirection() == 'ltr' ? 'left' : 'right'; } else if (v > 0) { this._position = this.getLayoutDirection() == 'ltr' ? 'right' : 'left'; + } else { + this._position = 'center'; } - if (v == 0) { this._position = 'center'; } if (this._position === 'center' && !this._portalHost.hasAttached() && this._content) { this._portalHost.attach(this._content);