/
tab.ts
164 lines (142 loc) · 5.2 KB
/
tab.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
/**
* @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 {
ChangeDetectionStrategy,
Component,
ContentChild,
Inject,
InjectionToken,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
SimpleChanges,
TemplateRef,
ViewChild,
ViewContainerRef,
ViewEncapsulation,
booleanAttribute,
} from '@angular/core';
import {MatTabContent} from './tab-content';
import {MAT_TAB, MatTabLabel} from './tab-label';
import {TemplatePortal} from '@angular/cdk/portal';
import {Subject} from 'rxjs';
/**
* Used to provide a tab group to a tab without causing a circular dependency.
* @docs-private
*/
export const MAT_TAB_GROUP = new InjectionToken<any>('MAT_TAB_GROUP');
@Component({
selector: 'mat-tab',
// Note that usually we'd go through a bit more trouble and set up another class so that
// the inlined template of `MatTab` isn't duplicated, however the template is small enough
// that creating the extra class will generate more code than just duplicating the template.
templateUrl: 'tab.html',
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
exportAs: 'matTab',
providers: [{provide: MAT_TAB, useExisting: MatTab}],
standalone: true,
host: {
// This element will be rendered on the server in order to support hydration.
// Hide it so it doesn't cause a layout shift when it's removed on the client.
'hidden': '',
},
})
export class MatTab implements OnInit, OnChanges, OnDestroy {
/** whether the tab is disabled. */
@Input({transform: booleanAttribute})
disabled: boolean = false;
/** Content for the tab label given by `<ng-template mat-tab-label>`. */
@ContentChild(MatTabLabel)
get templateLabel(): MatTabLabel {
return this._templateLabel;
}
set templateLabel(value: MatTabLabel) {
this._setTemplateLabelInput(value);
}
private _templateLabel: MatTabLabel;
/**
* Template provided in the tab content that will be used if present, used to enable lazy-loading
*/
@ContentChild(MatTabContent, {read: TemplateRef, static: true})
// We need an initializer here to avoid a TS error. The value will be set in `ngAfterViewInit`.
private _explicitContent: TemplateRef<any> = undefined!;
/** Template inside the MatTab view that contains an `<ng-content>`. */
@ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef<any>;
/** Plain text label for the tab, used when there is no template label. */
@Input('label') textLabel: string = '';
/** Aria label for the tab. */
@Input('aria-label') ariaLabel: string;
/**
* Reference to the element that the tab is labelled by.
* Will be cleared if `aria-label` is set at the same time.
*/
@Input('aria-labelledby') ariaLabelledby: string;
/** Classes to be passed to the tab label inside the mat-tab-header container. */
@Input() labelClass: string | string[];
/** Classes to be passed to the tab mat-tab-body container. */
@Input() bodyClass: string | string[];
/** Portal that will be the hosted content of the tab */
private _contentPortal: TemplatePortal | null = null;
/** @docs-private */
get content(): TemplatePortal | null {
return this._contentPortal;
}
/** Emits whenever the internal state of the tab changes. */
readonly _stateChanges = new Subject<void>();
/**
* The relatively indexed position where 0 represents the center, negative is left, and positive
* represents the right.
*/
position: number | null = null;
/**
* The initial relatively index origin of the tab if it was created and selected after there
* was already a selected tab. Provides context of what position the tab should originate from.
*/
origin: number | null = null;
/**
* Whether the tab is currently active.
*/
isActive = false;
constructor(
private _viewContainerRef: ViewContainerRef,
@Inject(MAT_TAB_GROUP) @Optional() public _closestTabGroup: any,
) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.hasOwnProperty('textLabel') || changes.hasOwnProperty('disabled')) {
this._stateChanges.next();
}
}
ngOnDestroy(): void {
this._stateChanges.complete();
}
ngOnInit(): void {
this._contentPortal = new TemplatePortal(
this._explicitContent || this._implicitContent,
this._viewContainerRef,
);
}
/**
* This has been extracted to a util because of TS 4 and VE.
* View Engine doesn't support property rename inheritance.
* TS 4.0 doesn't allow properties to override accessors or vice-versa.
* @docs-private
*/
private _setTemplateLabelInput(value: MatTabLabel | undefined) {
// Only update the label if the query managed to find one. This works around an issue where a
// user may have manually set `templateLabel` during creation mode, which would then get
// clobbered by `undefined` when the query resolves. Also note that we check that the closest
// tab matches the current one so that we don't pick up labels from nested tabs.
if (value && value._closestTab === this) {
this._templateLabel = value;
}
}
}