-
Notifications
You must be signed in to change notification settings - Fork 6.7k
/
tab-body.ts
245 lines (210 loc) · 8.44 KB
/
tab-body.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
Component,
ChangeDetectorRef,
Input,
Inject,
Output,
EventEmitter,
OnDestroy,
OnInit,
ElementRef,
Directive,
Optional,
ViewEncapsulation,
ChangeDetectionStrategy,
ComponentFactoryResolver,
ViewContainerRef,
forwardRef,
ViewChild,
} from '@angular/core';
import {AnimationEvent} from '@angular/animations';
import {TemplatePortal, CdkPortalOutlet, PortalHostDirective} from '@angular/cdk/portal';
import {Directionality, Direction} from '@angular/cdk/bidi';
import {Subscription} from 'rxjs';
import {matTabsAnimations} from './tabs-animations';
import {startWith} from 'rxjs/operators';
/**
* These position states are used internally as animation states for the tab body. Setting the
* position state to left, right, or center will transition the tab body from its current
* position to its respective state. If there is not current position (void, in the case of a new
* tab body), then there will be no transition animation to its state.
*
* In the case of a new tab body that should immediately be centered with an animating transition,
* then left-origin-center or right-origin-center can be used, which will use left or right as its
* psuedo-prior state.
*/
export type MatTabBodyPositionState =
'left' | 'center' | 'right' | 'left-origin-center' | 'right-origin-center';
/**
* The origin state is an internally used state that is set on a new tab body indicating if it
* began to the left or right of the prior selected index. For example, if the selected index was
* set to 1, and a new tab is created and selected at index 2, then the tab body would have an
* origin of right because its index was greater than the prior selected index.
*/
export type MatTabBodyOriginState = 'left' | 'right';
/**
* The portal host directive for the contents of the tab.
* @docs-private
*/
@Directive({
selector: '[matTabBodyHost]'
})
export class MatTabBodyPortal extends CdkPortalOutlet implements OnInit, OnDestroy {
/** Subscription to events for when the tab body begins centering. */
private _centeringSub = Subscription.EMPTY;
/** Subscription to events for when the tab body finishes leaving from center position. */
private _leavingSub = Subscription.EMPTY;
constructor(
componentFactoryResolver: ComponentFactoryResolver,
viewContainerRef: ViewContainerRef,
@Inject(forwardRef(() => MatTabBody)) private _host: MatTabBody) {
super(componentFactoryResolver, viewContainerRef);
}
/** Set initial visibility or set up subscription for changing visibility. */
ngOnInit(): void {
super.ngOnInit();
this._centeringSub = this._host._beforeCentering
.pipe(startWith(this._host._isCenterPosition(this._host._position)))
.subscribe((isCentering: boolean) => {
if (isCentering && !this.hasAttached()) {
this.attach(this._host._content);
}
});
this._leavingSub = this._host._afterLeavingCenter.subscribe(() => {
this.detach();
});
}
/** Clean up centering subscription. */
ngOnDestroy(): void {
super.ngOnDestroy();
this._centeringSub.unsubscribe();
this._leavingSub.unsubscribe();
}
}
/**
* Wrapper for the contents of a tab.
* @docs-private
*/
@Component({
moduleId: module.id,
selector: 'mat-tab-body',
templateUrl: 'tab-body.html',
styleUrls: ['tab-body.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [matTabsAnimations.translateTab],
host: {
'class': 'mat-tab-body',
},
})
export class MatTabBody implements OnInit, OnDestroy {
/** Current position of the tab-body in the tab-group. Zero means that the tab is visible. */
private _positionIndex: number;
/** Subscription to the directionality change observable. */
private _dirChangeSubscription = Subscription.EMPTY;
/** Tab body position state. Used by the animation trigger for the current state. */
_position: MatTabBodyPositionState;
/** Event emitted when the tab begins to animate towards the center as the active tab. */
@Output() readonly _onCentering: EventEmitter<number> = new EventEmitter<number>();
/** Event emitted before the centering of the tab begins. */
@Output() readonly _beforeCentering: EventEmitter<boolean> = new EventEmitter<boolean>();
/** Event emitted before the centering of the tab begins. */
@Output() readonly _afterLeavingCenter: EventEmitter<boolean> = new EventEmitter<boolean>();
/** Event emitted when the tab completes its animation towards the center. */
@Output() readonly _onCentered: EventEmitter<void> = new EventEmitter<void>(true);
/** The portal host inside of this container into which the tab body content will be loaded. */
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
/** The tab body content to display. */
@Input('content') _content: TemplatePortal;
/** Position that will be used when the tab is immediately becoming visible after creation. */
@Input() origin: number;
// Note that the default value will always be overwritten by `MatTabBody`, but we need one
// anyway to prevent the animations module from throwing an error if the body is used on its own.
/** Duration for the tab's animation. */
@Input() animationDuration: string = '500ms';
/** The shifted index position of the tab body, where zero represents the active center tab. */
@Input()
set position(position: number) {
this._positionIndex = position;
this._computePositionAnimationState();
}
constructor(private _elementRef: ElementRef<HTMLElement>,
@Optional() private _dir: Directionality,
/**
* @breaking-change 8.0.0 changeDetectorRef to be made required.
*/
changeDetectorRef?: ChangeDetectorRef) {
if (this._dir && changeDetectorRef) {
this._dirChangeSubscription = this._dir.change.subscribe((dir: Direction) => {
this._computePositionAnimationState(dir);
changeDetectorRef.markForCheck();
});
}
}
/**
* After initialized, check if the content is centered and has an origin. If so, set the
* special position states that transition the tab from the left or right before centering.
*/
ngOnInit() {
if (this._position == 'center' && this.origin != null) {
this._position = this._computePositionFromOrigin();
}
}
ngOnDestroy() {
this._dirChangeSubscription.unsubscribe();
}
_onTranslateTabStarted(e: AnimationEvent): void {
const isCentering = this._isCenterPosition(e.toState);
this._beforeCentering.emit(isCentering);
if (isCentering) {
this._onCentering.emit(this._elementRef.nativeElement.clientHeight);
}
}
_onTranslateTabComplete(e: AnimationEvent): void {
// If the transition to the center is complete, emit an event.
if (this._isCenterPosition(e.toState) && this._isCenterPosition(this._position)) {
this._onCentered.emit();
}
if (this._isCenterPosition(e.fromState) && !this._isCenterPosition(this._position)) {
this._afterLeavingCenter.emit();
}
}
/** The text direction of the containing app. */
_getLayoutDirection(): Direction {
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
}
/** Whether the provided position state is considered center, regardless of origin. */
_isCenterPosition(position: MatTabBodyPositionState|string): boolean {
return position == 'center' ||
position == 'left-origin-center' ||
position == 'right-origin-center';
}
/** Computes the position state that will be used for the tab-body animation trigger. */
private _computePositionAnimationState(dir: Direction = this._getLayoutDirection()) {
if (this._positionIndex < 0) {
this._position = dir == 'ltr' ? 'left' : 'right';
} else if (this._positionIndex > 0) {
this._position = dir == 'ltr' ? 'right' : 'left';
} else {
this._position = 'center';
}
}
/**
* Computes the position state based on the specified origin position. This is used if the
* tab is becoming visible immediately after creation.
*/
private _computePositionFromOrigin(): MatTabBodyPositionState {
const dir = this._getLayoutDirection();
if ((dir == 'ltr' && this.origin <= 0) || (dir == 'rtl' && this.origin > 0)) {
return 'left-origin-center';
}
return 'right-origin-center';
}
}