/** * @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.dev/license */ import { ChangeDetectionStrategy, Component, ContentChild, InjectionToken, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation, booleanAttribute, inject, } 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'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {_StructuralStylesLoader} from '@angular/material/core'; /** * Used to provide a tab group to a tab without causing a circular dependency. * @docs-private */ export const MAT_TAB_GROUP = new InjectionToken('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}], 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 { private _viewContainerRef = inject(ViewContainerRef); _closestTabGroup = inject(MAT_TAB_GROUP, {optional: true}); /** whether the tab is disabled. */ @Input({transform: booleanAttribute}) disabled: boolean = false; /** Content for the tab label given by ``. */ @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 = undefined!; /** Template inside the MatTab view that contains an ``. */ @ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef; /** 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(); /** * 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(...args: unknown[]); constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); } 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; } } }