Skip to content

Commit 49ec9ca

Browse files
devversionjosephperrott
authored andcommitted
fix(tabs): reposition tab body on direction change (#12229)
1 parent 7aa64df commit 49ec9ca

File tree

3 files changed

+80
-27
lines changed

3 files changed

+80
-27
lines changed

src/lib/tabs/tab-body.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
66
import {MatRippleModule} from '@angular/material/core';
77
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
88
import {MatTabBody, MatTabBodyPortal} from './tab-body';
9+
import {Subject} from 'rxjs';
910

1011

1112
describe('MatTabBody', () => {
1213
let dir: Direction = 'ltr';
14+
let dirChange: Subject<Direction> = new Subject<Direction>();
1315

1416
beforeEach(async(() => {
1517
dir = 'ltr';
@@ -21,7 +23,7 @@ describe('MatTabBody', () => {
2123
SimpleTabBodyApp,
2224
],
2325
providers: [
24-
{provide: Directionality, useFactory: () => ({value: dir})}
26+
{provide: Directionality, useFactory: () => ({value: dir, change: dirChange})}
2527
]
2628
});
2729

@@ -146,6 +148,22 @@ describe('MatTabBody', () => {
146148
expect(fixture.componentInstance.tabBody._position).toBe('left');
147149
});
148150
});
151+
152+
it('should update position if direction changed at runtime', () => {
153+
const fixture = TestBed.createComponent(SimpleTabBodyApp);
154+
155+
fixture.componentInstance.position = 1;
156+
fixture.detectChanges();
157+
158+
expect(fixture.componentInstance.tabBody._position).toBe('right');
159+
160+
dirChange.next('rtl');
161+
dir = 'rtl';
162+
163+
fixture.detectChanges();
164+
165+
expect(fixture.componentInstance.tabBody._position).toBe('left');
166+
});
149167
});
150168

151169

src/lib/tabs/tab-body.ts

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {
1010
Component,
11+
ChangeDetectorRef,
1112
Input,
1213
Inject,
1314
Output,
@@ -113,7 +114,17 @@ export class MatTabBodyPortal extends CdkPortalOutlet implements OnInit, OnDestr
113114
'class': 'mat-tab-body',
114115
},
115116
})
116-
export class MatTabBody implements OnInit {
117+
export class MatTabBody implements OnInit, OnDestroy {
118+
119+
/** Current position of the tab-body in the tab-group. Zero means that the tab is visible. */
120+
private _positionIndex: number;
121+
122+
/** Subscription to the directionality change observable. */
123+
private _dirChangeSubscription = Subscription.EMPTY;
124+
125+
/** Tab body position state. Used by the animation trigger for the current state. */
126+
_position: MatTabBodyPositionState;
127+
117128
/** Event emitted when the tab begins to animate towards the center as the active tab. */
118129
@Output() readonly _onCentering: EventEmitter<number> = new EventEmitter<number>();
119130

@@ -132,46 +143,45 @@ export class MatTabBody implements OnInit {
132143
/** The tab body content to display. */
133144
@Input('content') _content: TemplatePortal;
134145

146+
/** Position that will be used when the tab is immediately becoming visible after creation. */
147+
@Input() origin: number;
148+
135149
/** The shifted index position of the tab body, where zero represents the active center tab. */
136150
@Input()
137151
set position(position: number) {
138-
if (position < 0) {
139-
this._position = this._getLayoutDirection() == 'ltr' ? 'left' : 'right';
140-
} else if (position > 0) {
141-
this._position = this._getLayoutDirection() == 'ltr' ? 'right' : 'left';
142-
} else {
143-
this._position = 'center';
144-
}
152+
this._positionIndex = position;
153+
this._computePositionAnimationState();
145154
}
146-
_position: MatTabBodyPositionState;
147-
148-
/** The origin position from which this tab should appear when it is centered into view. */
149-
@Input()
150-
set origin(origin: number) {
151-
if (origin == null) { return; }
152155

153-
const dir = this._getLayoutDirection();
154-
if ((dir == 'ltr' && origin <= 0) || (dir == 'rtl' && origin > 0)) {
155-
this._origin = 'left';
156-
} else {
157-
this._origin = 'right';
156+
constructor(private _elementRef: ElementRef,
157+
@Optional() private _dir: Directionality,
158+
/**
159+
* @deletion-target 7.0.0 changeDetectorRef to be made required.
160+
*/
161+
changeDetectorRef?: ChangeDetectorRef) {
162+
163+
if (this._dir && changeDetectorRef) {
164+
this._dirChangeSubscription = this._dir.change.subscribe(dir => {
165+
this._computePositionAnimationState(dir);
166+
changeDetectorRef.markForCheck();
167+
});
158168
}
159169
}
160-
_origin: MatTabBodyOriginState;
161-
162-
constructor(private _elementRef: ElementRef,
163-
@Optional() private _dir: Directionality) { }
164170

165171
/**
166172
* After initialized, check if the content is centered and has an origin. If so, set the
167173
* special position states that transition the tab from the left or right before centering.
168174
*/
169175
ngOnInit() {
170-
if (this._position == 'center' && this._origin) {
171-
this._position = this._origin == 'left' ? 'left-origin-center' : 'right-origin-center';
176+
if (this._position == 'center' && this.origin !== undefined) {
177+
this._position = this._computePositionFromOrigin();
172178
}
173179
}
174180

181+
ngOnDestroy() {
182+
this._dirChangeSubscription.unsubscribe();
183+
}
184+
175185
_onTranslateTabStarted(e: AnimationEvent): void {
176186
const isCentering = this._isCenterPosition(e.toState);
177187
this._beforeCentering.emit(isCentering);
@@ -202,4 +212,29 @@ export class MatTabBody implements OnInit {
202212
position == 'left-origin-center' ||
203213
position == 'right-origin-center';
204214
}
215+
216+
/** Computes the position state that will be used for the tab-body animation trigger. */
217+
private _computePositionAnimationState(dir: Direction = this._getLayoutDirection()) {
218+
if (this._positionIndex < 0) {
219+
this._position = dir == 'ltr' ? 'left' : 'right';
220+
} else if (this._positionIndex > 0) {
221+
this._position = dir == 'ltr' ? 'right' : 'left';
222+
} else {
223+
this._position = 'center';
224+
}
225+
}
226+
227+
/**
228+
* Computes the position state based on the specified origin position. This is used if the
229+
* tab is becoming visible immediately after creation.
230+
*/
231+
private _computePositionFromOrigin(): MatTabBodyPositionState {
232+
const dir = this._getLayoutDirection();
233+
234+
if ((dir == 'ltr' && this.origin <= 0) || (dir == 'rtl' && this.origin > 0)) {
235+
return 'left-origin-center';
236+
}
237+
238+
return 'right-origin-center';
239+
}
205240
}

src/lib/tabs/tab-group.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class MatTabGroup extends _MatTabGroupMixinBase implements AfterContentIn
159159
* a new selected tab should transition in (from the left or right).
160160
*/
161161
ngAfterContentChecked() {
162-
// Clamp the next selected index to the boundsof 0 and the tabs length.
162+
// Clamp the next selected index to the bounds of 0 and the tabs length.
163163
// Note the `|| 0`, which ensures that values like NaN can't get through
164164
// and which would otherwise throw the component into an infinite loop
165165
// (since Math.max(NaN, 0) === NaN).

0 commit comments

Comments
 (0)