| import { FocusKeyManager, FocusMonitor, A11yModule } from '@angular/cdk/a11y'; |
| import { ObserversModule } from '@angular/cdk/observers'; |
| import { CdkPortal, TemplatePortal, CdkPortalOutlet, PortalModule } from '@angular/cdk/portal'; |
| import { DOCUMENT, CommonModule } from '@angular/common'; |
| import { InjectionToken, Directive, ElementRef, NgZone, Inject, Optional, TemplateRef, Component, ChangeDetectionStrategy, ViewEncapsulation, ViewContainerRef, ContentChild, ViewChild, Input, ComponentFactoryResolver, forwardRef, EventEmitter, ChangeDetectorRef, Output, QueryList, ContentChildren, Attribute, NgModule } from '@angular/core'; |
| import { mixinDisabled, mixinColor, mixinDisableRipple, mixinTabIndex, MAT_RIPPLE_GLOBAL_OPTIONS, RippleRenderer, MatCommonModule, MatRippleModule } from '@angular/material/core'; |
| import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations'; |
| import { Subject, Subscription, merge, fromEvent, of, timer } from 'rxjs'; |
| import { Directionality } from '@angular/cdk/bidi'; |
| import { trigger, state, style, transition, animate } from '@angular/animations'; |
| import { startWith, distinctUntilChanged, takeUntil } from 'rxjs/operators'; |
| import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion'; |
| import { ViewportRuler } from '@angular/cdk/scrolling'; |
| import { normalizePassiveListenerOptions, Platform } from '@angular/cdk/platform'; |
| import { hasModifierKey, SPACE, ENTER } from '@angular/cdk/keycodes'; |
| |
| /** |
| * @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 |
| */ |
| /** Injection token for the MatInkBar's Positioner. */ |
| const _MAT_INK_BAR_POSITIONER = new InjectionToken('MatInkBarPositioner', { |
| providedIn: 'root', |
| factory: _MAT_INK_BAR_POSITIONER_FACTORY |
| }); |
| /** |
| * The default positioner function for the MatInkBar. |
| * @docs-private |
| */ |
| function _MAT_INK_BAR_POSITIONER_FACTORY() { |
| const method = (element) => ({ |
| left: element ? (element.offsetLeft || 0) + 'px' : '0', |
| width: element ? (element.offsetWidth || 0) + 'px' : '0', |
| }); |
| return method; |
| } |
| /** |
| * The ink-bar is used to display and animate the line underneath the current active tab label. |
| * @docs-private |
| */ |
| class MatInkBar { |
| constructor(_elementRef, _ngZone, _inkBarPositioner, _animationMode) { |
| this._elementRef = _elementRef; |
| this._ngZone = _ngZone; |
| this._inkBarPositioner = _inkBarPositioner; |
| this._animationMode = _animationMode; |
| } |
| /** |
| * Calculates the styles from the provided element in order to align the ink-bar to that element. |
| * Shows the ink bar if previously set as hidden. |
| * @param element |
| */ |
| alignToElement(element) { |
| this.show(); |
| if (typeof requestAnimationFrame !== 'undefined') { |
| this._ngZone.runOutsideAngular(() => { |
| requestAnimationFrame(() => this._setStyles(element)); |
| }); |
| } |
| else { |
| this._setStyles(element); |
| } |
| } |
| /** Shows the ink bar. */ |
| show() { |
| this._elementRef.nativeElement.style.visibility = 'visible'; |
| } |
| /** Hides the ink bar. */ |
| hide() { |
| this._elementRef.nativeElement.style.visibility = 'hidden'; |
| } |
| /** |
| * Sets the proper styles to the ink bar element. |
| * @param element |
| */ |
| _setStyles(element) { |
| const positions = this._inkBarPositioner(element); |
| const inkBar = this._elementRef.nativeElement; |
| inkBar.style.left = positions.left; |
| inkBar.style.width = positions.width; |
| } |
| } |
| MatInkBar.decorators = [ |
| { type: Directive, args: [{ |
| selector: 'mat-ink-bar', |
| host: { |
| 'class': 'mat-ink-bar', |
| '[class._mat-animation-noopable]': `_animationMode === 'NoopAnimations'`, |
| }, |
| },] } |
| ]; |
| MatInkBar.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: NgZone }, |
| { type: undefined, decorators: [{ type: Inject, args: [_MAT_INK_BAR_POSITIONER,] }] }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Injection token that can be used to reference instances of `MatTabContent`. It serves as |
| * alternative token to the actual `MatTabContent` class which could cause unnecessary |
| * retention of the class and its directive metadata. |
| */ |
| const MAT_TAB_CONTENT = new InjectionToken('MatTabContent'); |
| /** Decorates the `ng-template` tags and reads out the template from it. */ |
| class MatTabContent { |
| constructor( |
| /** Content for the tab. */ template) { |
| this.template = template; |
| } |
| } |
| MatTabContent.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[matTabContent]', |
| providers: [{ provide: MAT_TAB_CONTENT, useExisting: MatTabContent }], |
| },] } |
| ]; |
| MatTabContent.ctorParameters = () => [ |
| { type: TemplateRef } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Injection token that can be used to reference instances of `MatTabLabel`. It serves as |
| * alternative token to the actual `MatTabLabel` class which could cause unnecessary |
| * retention of the class and its directive metadata. |
| */ |
| const MAT_TAB_LABEL = new InjectionToken('MatTabLabel'); |
| /** Used to flag tab labels for use with the portal directive */ |
| class MatTabLabel extends CdkPortal { |
| } |
| MatTabLabel.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[mat-tab-label], [matTabLabel]', |
| providers: [{ provide: MAT_TAB_LABEL, useExisting: MatTabLabel }], |
| },] } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| // Boilerplate for applying mixins to MatTab. |
| /** @docs-private */ |
| class MatTabBase { |
| } |
| const _MatTabMixinBase = mixinDisabled(MatTabBase); |
| /** |
| * Used to provide a tab group to a tab without causing a circular dependency. |
| * @docs-private |
| */ |
| const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); |
| class MatTab extends _MatTabMixinBase { |
| constructor(_viewContainerRef, _closestTabGroup) { |
| super(); |
| this._viewContainerRef = _viewContainerRef; |
| this._closestTabGroup = _closestTabGroup; |
| /** Plain text label for the tab, used when there is no template label. */ |
| this.textLabel = ''; |
| /** Portal that will be the hosted content of the tab */ |
| this._contentPortal = null; |
| /** Emits whenever the internal state of the tab changes. */ |
| this._stateChanges = new Subject(); |
| /** |
| * The relatively indexed position where 0 represents the center, negative is left, and positive |
| * represents the right. |
| */ |
| this.position = 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. |
| */ |
| this.origin = null; |
| /** |
| * Whether the tab is currently active. |
| */ |
| this.isActive = false; |
| } |
| /** Content for the tab label given by `<ng-template mat-tab-label>`. */ |
| get templateLabel() { return this._templateLabel; } |
| set templateLabel(value) { this._setTemplateLabelInput(value); } |
| /** @docs-private */ |
| get content() { |
| return this._contentPortal; |
| } |
| ngOnChanges(changes) { |
| if (changes.hasOwnProperty('textLabel') || changes.hasOwnProperty('disabled')) { |
| this._stateChanges.next(); |
| } |
| } |
| ngOnDestroy() { |
| this._stateChanges.complete(); |
| } |
| ngOnInit() { |
| 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 |
| */ |
| _setTemplateLabelInput(value) { |
| // Only update the templateLabel via query if there is actually |
| // a MatTabLabel found. This works around an issue where a user may have |
| // manually set `templateLabel` during creation mode, which would then get clobbered |
| // by `undefined` when this query resolves. |
| if (value) { |
| this._templateLabel = value; |
| } |
| } |
| } |
| MatTab.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-tab', |
| template: "<!-- Create a template for the content of the <mat-tab> so that we can grab a reference to this\n TemplateRef and use it in a Portal to render the tab content in the appropriate place in the\n tab-group. -->\n<ng-template><ng-content></ng-content></ng-template>\n", |
| inputs: ['disabled'], |
| // tslint:disable-next-line:validate-decorators |
| changeDetection: ChangeDetectionStrategy.Default, |
| encapsulation: ViewEncapsulation.None, |
| exportAs: 'matTab' |
| },] } |
| ]; |
| MatTab.ctorParameters = () => [ |
| { type: ViewContainerRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_TAB_GROUP,] }] } |
| ]; |
| MatTab.propDecorators = { |
| templateLabel: [{ type: ContentChild, args: [MAT_TAB_LABEL,] }], |
| _explicitContent: [{ type: ContentChild, args: [MAT_TAB_CONTENT, { read: TemplateRef, static: true },] }], |
| _implicitContent: [{ type: ViewChild, args: [TemplateRef, { static: true },] }], |
| textLabel: [{ type: Input, args: ['label',] }], |
| ariaLabel: [{ type: Input, args: ['aria-label',] }], |
| ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Animations used by the Material tabs. |
| * @docs-private |
| */ |
| const matTabsAnimations = { |
| /** Animation translates a tab along the X axis. */ |
| translateTab: trigger('translateTab', [ |
| // Note: transitions to `none` instead of 0, because some browsers might blur the content. |
| state('center, void, left-origin-center, right-origin-center', style({ transform: 'none' })), |
| // If the tab is either on the left or right, we additionally add a `min-height` of 1px |
| // in order to ensure that the element has a height before its state changes. This is |
| // necessary because Chrome does seem to skip the transition in RTL mode if the element does |
| // not have a static height and is not rendered. See related issue: #9465 |
| state('left', style({ transform: 'translate3d(-100%, 0, 0)', minHeight: '1px' })), |
| state('right', style({ transform: 'translate3d(100%, 0, 0)', minHeight: '1px' })), |
| transition('* => left, * => right, left => center, right => center', animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)')), |
| transition('void => left-origin-center', [ |
| style({ transform: 'translate3d(-100%, 0, 0)' }), |
| animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)') |
| ]), |
| transition('void => right-origin-center', [ |
| style({ transform: 'translate3d(100%, 0, 0)' }), |
| animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)') |
| ]) |
| ]) |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * The portal host directive for the contents of the tab. |
| * @docs-private |
| */ |
| class MatTabBodyPortal extends CdkPortalOutlet { |
| constructor(componentFactoryResolver, viewContainerRef, _host, _document) { |
| super(componentFactoryResolver, viewContainerRef, _document); |
| this._host = _host; |
| /** Subscription to events for when the tab body begins centering. */ |
| this._centeringSub = Subscription.EMPTY; |
| /** Subscription to events for when the tab body finishes leaving from center position. */ |
| this._leavingSub = Subscription.EMPTY; |
| } |
| /** Set initial visibility or set up subscription for changing visibility. */ |
| ngOnInit() { |
| super.ngOnInit(); |
| this._centeringSub = this._host._beforeCentering |
| .pipe(startWith(this._host._isCenterPosition(this._host._position))) |
| .subscribe((isCentering) => { |
| if (isCentering && !this.hasAttached()) { |
| this.attach(this._host._content); |
| } |
| }); |
| this._leavingSub = this._host._afterLeavingCenter.subscribe(() => { |
| this.detach(); |
| }); |
| } |
| /** Clean up centering subscription. */ |
| ngOnDestroy() { |
| super.ngOnDestroy(); |
| this._centeringSub.unsubscribe(); |
| this._leavingSub.unsubscribe(); |
| } |
| } |
| MatTabBodyPortal.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[matTabBodyHost]' |
| },] } |
| ]; |
| MatTabBodyPortal.ctorParameters = () => [ |
| { type: ComponentFactoryResolver }, |
| { type: ViewContainerRef }, |
| { type: MatTabBody, decorators: [{ type: Inject, args: [forwardRef(() => MatTabBody),] }] }, |
| { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } |
| ]; |
| /** |
| * Base class with all of the `MatTabBody` functionality. |
| * @docs-private |
| */ |
| class _MatTabBodyBase { |
| constructor(_elementRef, _dir, changeDetectorRef) { |
| this._elementRef = _elementRef; |
| this._dir = _dir; |
| /** Subscription to the directionality change observable. */ |
| this._dirChangeSubscription = Subscription.EMPTY; |
| /** Emits when an animation on the tab is complete. */ |
| this._translateTabComplete = new Subject(); |
| /** Event emitted when the tab begins to animate towards the center as the active tab. */ |
| this._onCentering = new EventEmitter(); |
| /** Event emitted before the centering of the tab begins. */ |
| this._beforeCentering = new EventEmitter(); |
| /** Event emitted before the centering of the tab begins. */ |
| this._afterLeavingCenter = new EventEmitter(); |
| /** Event emitted when the tab completes its animation towards the center. */ |
| this._onCentered = new EventEmitter(true); |
| // 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. */ |
| this.animationDuration = '500ms'; |
| if (_dir) { |
| this._dirChangeSubscription = _dir.change.subscribe((dir) => { |
| this._computePositionAnimationState(dir); |
| changeDetectorRef.markForCheck(); |
| }); |
| } |
| // Ensure that we get unique animation events, because the `.done` callback can get |
| // invoked twice in some browsers. See https://github.com/angular/angular/issues/24084. |
| this._translateTabComplete.pipe(distinctUntilChanged((x, y) => { |
| return x.fromState === y.fromState && x.toState === y.toState; |
| })).subscribe(event => { |
| // If the transition to the center is complete, emit an event. |
| if (this._isCenterPosition(event.toState) && this._isCenterPosition(this._position)) { |
| this._onCentered.emit(); |
| } |
| if (this._isCenterPosition(event.fromState) && !this._isCenterPosition(this._position)) { |
| this._afterLeavingCenter.emit(); |
| } |
| }); |
| } |
| /** The shifted index position of the tab body, where zero represents the active center tab. */ |
| set position(position) { |
| this._positionIndex = position; |
| this._computePositionAnimationState(); |
| } |
| /** |
| * 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(this.origin); |
| } |
| } |
| ngOnDestroy() { |
| this._dirChangeSubscription.unsubscribe(); |
| this._translateTabComplete.complete(); |
| } |
| _onTranslateTabStarted(event) { |
| const isCentering = this._isCenterPosition(event.toState); |
| this._beforeCentering.emit(isCentering); |
| if (isCentering) { |
| this._onCentering.emit(this._elementRef.nativeElement.clientHeight); |
| } |
| } |
| /** The text direction of the containing app. */ |
| _getLayoutDirection() { |
| return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; |
| } |
| /** Whether the provided position state is considered center, regardless of origin. */ |
| _isCenterPosition(position) { |
| 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. */ |
| _computePositionAnimationState(dir = 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. |
| */ |
| _computePositionFromOrigin(origin) { |
| const dir = this._getLayoutDirection(); |
| if ((dir == 'ltr' && origin <= 0) || (dir == 'rtl' && origin > 0)) { |
| return 'left-origin-center'; |
| } |
| return 'right-origin-center'; |
| } |
| } |
| _MatTabBodyBase.decorators = [ |
| { type: Directive } |
| ]; |
| _MatTabBodyBase.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: ChangeDetectorRef } |
| ]; |
| _MatTabBodyBase.propDecorators = { |
| _onCentering: [{ type: Output }], |
| _beforeCentering: [{ type: Output }], |
| _afterLeavingCenter: [{ type: Output }], |
| _onCentered: [{ type: Output }], |
| _content: [{ type: Input, args: ['content',] }], |
| origin: [{ type: Input }], |
| animationDuration: [{ type: Input }], |
| position: [{ type: Input }] |
| }; |
| /** |
| * Wrapper for the contents of a tab. |
| * @docs-private |
| */ |
| class MatTabBody extends _MatTabBodyBase { |
| constructor(elementRef, dir, changeDetectorRef) { |
| super(elementRef, dir, changeDetectorRef); |
| } |
| } |
| MatTabBody.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-tab-body', |
| template: "<div class=\"mat-tab-body-content\" #content\n [@translateTab]=\"{\n value: _position,\n params: {animationDuration: animationDuration}\n }\"\n (@translateTab.start)=\"_onTranslateTabStarted($event)\"\n (@translateTab.done)=\"_translateTabComplete.next($event)\"\n cdkScrollable>\n <ng-template matTabBodyHost></ng-template>\n</div>\n", |
| encapsulation: ViewEncapsulation.None, |
| // tslint:disable-next-line:validate-decorators |
| changeDetection: ChangeDetectionStrategy.Default, |
| animations: [matTabsAnimations.translateTab], |
| host: { |
| 'class': 'mat-tab-body', |
| }, |
| styles: [".mat-tab-body-content{height:100%;overflow:auto}.mat-tab-group-dynamic-height .mat-tab-body-content{overflow:hidden}\n"] |
| },] } |
| ]; |
| MatTabBody.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: ChangeDetectorRef } |
| ]; |
| MatTabBody.propDecorators = { |
| _portalHost: [{ type: ViewChild, args: [CdkPortalOutlet,] }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** Injection token that can be used to provide the default options the tabs module. */ |
| const MAT_TABS_CONFIG = new InjectionToken('MAT_TABS_CONFIG'); |
| |
| /** |
| * @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 |
| */ |
| /** Used to generate unique ID's for each tab component */ |
| let nextId = 0; |
| /** A simple change event emitted on focus or selection changes. */ |
| class MatTabChangeEvent { |
| } |
| // Boilerplate for applying mixins to MatTabGroup. |
| /** @docs-private */ |
| class MatTabGroupMixinBase { |
| constructor(_elementRef) { |
| this._elementRef = _elementRef; |
| } |
| } |
| const _MatTabGroupMixinBase = mixinColor(mixinDisableRipple(MatTabGroupMixinBase), 'primary'); |
| /** |
| * Base class with all of the `MatTabGroupBase` functionality. |
| * @docs-private |
| */ |
| class _MatTabGroupBase extends _MatTabGroupMixinBase { |
| constructor(elementRef, _changeDetectorRef, defaultConfig, _animationMode) { |
| super(elementRef); |
| this._changeDetectorRef = _changeDetectorRef; |
| this._animationMode = _animationMode; |
| /** All of the tabs that belong to the group. */ |
| this._tabs = new QueryList(); |
| /** The tab index that should be selected after the content has been checked. */ |
| this._indexToSelect = 0; |
| /** Snapshot of the height of the tab body wrapper before another tab is activated. */ |
| this._tabBodyWrapperHeight = 0; |
| /** Subscription to tabs being added/removed. */ |
| this._tabsSubscription = Subscription.EMPTY; |
| /** Subscription to changes in the tab labels. */ |
| this._tabLabelSubscription = Subscription.EMPTY; |
| this._selectedIndex = null; |
| /** Position of the tab header. */ |
| this.headerPosition = 'above'; |
| /** Output to enable support for two-way binding on `[(selectedIndex)]` */ |
| this.selectedIndexChange = new EventEmitter(); |
| /** Event emitted when focus has changed within a tab group. */ |
| this.focusChange = new EventEmitter(); |
| /** Event emitted when the body animation has completed */ |
| this.animationDone = new EventEmitter(); |
| /** Event emitted when the tab selection has changed. */ |
| this.selectedTabChange = new EventEmitter(true); |
| this._groupId = nextId++; |
| this.animationDuration = defaultConfig && defaultConfig.animationDuration ? |
| defaultConfig.animationDuration : '500ms'; |
| this.disablePagination = defaultConfig && defaultConfig.disablePagination != null ? |
| defaultConfig.disablePagination : false; |
| this.dynamicHeight = defaultConfig && defaultConfig.dynamicHeight != null ? |
| defaultConfig.dynamicHeight : false; |
| } |
| /** Whether the tab group should grow to the size of the active tab. */ |
| get dynamicHeight() { return this._dynamicHeight; } |
| set dynamicHeight(value) { this._dynamicHeight = coerceBooleanProperty(value); } |
| /** The index of the active tab. */ |
| get selectedIndex() { return this._selectedIndex; } |
| set selectedIndex(value) { |
| this._indexToSelect = coerceNumberProperty(value, null); |
| } |
| /** Duration for the tab animation. Will be normalized to milliseconds if no units are set. */ |
| get animationDuration() { return this._animationDuration; } |
| set animationDuration(value) { |
| this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value; |
| } |
| /** Background color of the tab group. */ |
| get backgroundColor() { return this._backgroundColor; } |
| set backgroundColor(value) { |
| const nativeElement = this._elementRef.nativeElement; |
| nativeElement.classList.remove(`mat-background-${this.backgroundColor}`); |
| if (value) { |
| nativeElement.classList.add(`mat-background-${value}`); |
| } |
| this._backgroundColor = value; |
| } |
| /** |
| * After the content is checked, this component knows what tabs have been defined |
| * and what the selected index should be. This is where we can know exactly what position |
| * each tab should be in according to the new selected index, and additionally we know how |
| * a new selected tab should transition in (from the left or right). |
| */ |
| ngAfterContentChecked() { |
| // Don't clamp the `indexToSelect` immediately in the setter because it can happen that |
| // the amount of tabs changes before the actual change detection runs. |
| const indexToSelect = this._indexToSelect = this._clampTabIndex(this._indexToSelect); |
| // If there is a change in selected index, emit a change event. Should not trigger if |
| // the selected index has not yet been initialized. |
| if (this._selectedIndex != indexToSelect) { |
| const isFirstRun = this._selectedIndex == null; |
| if (!isFirstRun) { |
| this.selectedTabChange.emit(this._createChangeEvent(indexToSelect)); |
| // Preserve the height so page doesn't scroll up during tab change. |
| // Fixes https://stackblitz.com/edit/mat-tabs-scroll-page-top-on-tab-change |
| const wrapper = this._tabBodyWrapper.nativeElement; |
| wrapper.style.minHeight = wrapper.clientHeight + 'px'; |
| } |
| // Changing these values after change detection has run |
| // since the checked content may contain references to them. |
| Promise.resolve().then(() => { |
| this._tabs.forEach((tab, index) => tab.isActive = index === indexToSelect); |
| if (!isFirstRun) { |
| this.selectedIndexChange.emit(indexToSelect); |
| // Clear the min-height, this was needed during tab change to avoid |
| // unnecessary scrolling. |
| this._tabBodyWrapper.nativeElement.style.minHeight = ''; |
| } |
| }); |
| } |
| // Setup the position for each tab and optionally setup an origin on the next selected tab. |
| this._tabs.forEach((tab, index) => { |
| tab.position = index - indexToSelect; |
| // If there is already a selected tab, then set up an origin for the next selected tab |
| // if it doesn't have one already. |
| if (this._selectedIndex != null && tab.position == 0 && !tab.origin) { |
| tab.origin = indexToSelect - this._selectedIndex; |
| } |
| }); |
| if (this._selectedIndex !== indexToSelect) { |
| this._selectedIndex = indexToSelect; |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| ngAfterContentInit() { |
| this._subscribeToAllTabChanges(); |
| this._subscribeToTabLabels(); |
| // Subscribe to changes in the amount of tabs, in order to be |
| // able to re-render the content as new tabs are added or removed. |
| this._tabsSubscription = this._tabs.changes.subscribe(() => { |
| const indexToSelect = this._clampTabIndex(this._indexToSelect); |
| // Maintain the previously-selected tab if a new tab is added or removed and there is no |
| // explicit change that selects a different tab. |
| if (indexToSelect === this._selectedIndex) { |
| const tabs = this._tabs.toArray(); |
| for (let i = 0; i < tabs.length; i++) { |
| if (tabs[i].isActive) { |
| // Assign both to the `_indexToSelect` and `_selectedIndex` so we don't fire a changed |
| // event, otherwise the consumer may end up in an infinite loop in some edge cases like |
| // adding a tab within the `selectedIndexChange` event. |
| this._indexToSelect = this._selectedIndex = i; |
| break; |
| } |
| } |
| } |
| this._changeDetectorRef.markForCheck(); |
| }); |
| } |
| /** Listens to changes in all of the tabs. */ |
| _subscribeToAllTabChanges() { |
| // Since we use a query with `descendants: true` to pick up the tabs, we may end up catching |
| // some that are inside of nested tab groups. We filter them out manually by checking that |
| // the closest group to the tab is the current one. |
| this._allTabs.changes |
| .pipe(startWith(this._allTabs)) |
| .subscribe((tabs) => { |
| this._tabs.reset(tabs.filter(tab => tab._closestTabGroup === this)); |
| this._tabs.notifyOnChanges(); |
| }); |
| } |
| ngOnDestroy() { |
| this._tabs.destroy(); |
| this._tabsSubscription.unsubscribe(); |
| this._tabLabelSubscription.unsubscribe(); |
| } |
| /** Re-aligns the ink bar to the selected tab element. */ |
| realignInkBar() { |
| if (this._tabHeader) { |
| this._tabHeader._alignInkBarToSelectedTab(); |
| } |
| } |
| _focusChanged(index) { |
| this.focusChange.emit(this._createChangeEvent(index)); |
| } |
| _createChangeEvent(index) { |
| const event = new MatTabChangeEvent; |
| event.index = index; |
| if (this._tabs && this._tabs.length) { |
| event.tab = this._tabs.toArray()[index]; |
| } |
| return event; |
| } |
| /** |
| * Subscribes to changes in the tab labels. This is needed, because the @Input for the label is |
| * on the MatTab component, whereas the data binding is inside the MatTabGroup. In order for the |
| * binding to be updated, we need to subscribe to changes in it and trigger change detection |
| * manually. |
| */ |
| _subscribeToTabLabels() { |
| if (this._tabLabelSubscription) { |
| this._tabLabelSubscription.unsubscribe(); |
| } |
| this._tabLabelSubscription = merge(...this._tabs.map(tab => tab._stateChanges)) |
| .subscribe(() => this._changeDetectorRef.markForCheck()); |
| } |
| /** Clamps the given index to the bounds of 0 and the tabs length. */ |
| _clampTabIndex(index) { |
| // Note the `|| 0`, which ensures that values like NaN can't get through |
| // and which would otherwise throw the component into an infinite loop |
| // (since Math.max(NaN, 0) === NaN). |
| return Math.min(this._tabs.length - 1, Math.max(index || 0, 0)); |
| } |
| /** Returns a unique id for each tab label element */ |
| _getTabLabelId(i) { |
| return `mat-tab-label-${this._groupId}-${i}`; |
| } |
| /** Returns a unique id for each tab content element */ |
| _getTabContentId(i) { |
| return `mat-tab-content-${this._groupId}-${i}`; |
| } |
| /** |
| * Sets the height of the body wrapper to the height of the activating tab if dynamic |
| * height property is true. |
| */ |
| _setTabBodyWrapperHeight(tabHeight) { |
| if (!this._dynamicHeight || !this._tabBodyWrapperHeight) { |
| return; |
| } |
| const wrapper = this._tabBodyWrapper.nativeElement; |
| wrapper.style.height = this._tabBodyWrapperHeight + 'px'; |
| // 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) { |
| wrapper.style.height = tabHeight + 'px'; |
| } |
| } |
| /** Removes the height of the tab body wrapper. */ |
| _removeTabBodyWrapperHeight() { |
| const wrapper = this._tabBodyWrapper.nativeElement; |
| this._tabBodyWrapperHeight = wrapper.clientHeight; |
| wrapper.style.height = ''; |
| this.animationDone.emit(); |
| } |
| /** Handle click events, setting new selected index if appropriate. */ |
| _handleClick(tab, tabHeader, index) { |
| if (!tab.disabled) { |
| this.selectedIndex = tabHeader.focusIndex = index; |
| } |
| } |
| /** Retrieves the tabindex for the tab. */ |
| _getTabIndex(tab, idx) { |
| if (tab.disabled) { |
| return null; |
| } |
| return this.selectedIndex === idx ? 0 : -1; |
| } |
| /** Callback for when the focused state of a tab has changed. */ |
| _tabFocusChanged(focusOrigin, index) { |
| if (focusOrigin) { |
| this._tabHeader.focusIndex = index; |
| } |
| } |
| } |
| _MatTabGroupBase.decorators = [ |
| { type: Directive } |
| ]; |
| _MatTabGroupBase.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: ChangeDetectorRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_TABS_CONFIG,] }, { type: Optional }] }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| _MatTabGroupBase.propDecorators = { |
| dynamicHeight: [{ type: Input }], |
| selectedIndex: [{ type: Input }], |
| headerPosition: [{ type: Input }], |
| animationDuration: [{ type: Input }], |
| disablePagination: [{ type: Input }], |
| backgroundColor: [{ type: Input }], |
| selectedIndexChange: [{ type: Output }], |
| focusChange: [{ type: Output }], |
| animationDone: [{ type: Output }], |
| selectedTabChange: [{ type: Output }] |
| }; |
| /** |
| * Material design tab-group component. Supports basic tab pairs (label + content) and includes |
| * animated ink-bar, keyboard navigation, and screen reader. |
| * See: https://material.io/design/components/tabs.html |
| */ |
| class MatTabGroup extends _MatTabGroupBase { |
| constructor(elementRef, changeDetectorRef, defaultConfig, animationMode) { |
| super(elementRef, changeDetectorRef, defaultConfig, animationMode); |
| } |
| } |
| MatTabGroup.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-tab-group', |
| exportAs: 'matTabGroup', |
| template: "<mat-tab-header #tabHeader\n [selectedIndex]=\"selectedIndex || 0\"\n [disableRipple]=\"disableRipple\"\n [disablePagination]=\"disablePagination\"\n (indexFocused)=\"_focusChanged($event)\"\n (selectFocusedIndex)=\"selectedIndex = $event\">\n <div class=\"mat-tab-label mat-focus-indicator\" role=\"tab\" matTabLabelWrapper mat-ripple cdkMonitorElementFocus\n *ngFor=\"let tab of _tabs; let i = index\"\n [id]=\"_getTabLabelId(i)\"\n [attr.tabIndex]=\"_getTabIndex(tab, i)\"\n [attr.aria-posinset]=\"i + 1\"\n [attr.aria-setsize]=\"_tabs.length\"\n [attr.aria-controls]=\"_getTabContentId(i)\"\n [attr.aria-selected]=\"selectedIndex == i\"\n [attr.aria-label]=\"tab.ariaLabel || null\"\n [attr.aria-labelledby]=\"(!tab.ariaLabel && tab.ariaLabelledby) ? tab.ariaLabelledby : null\"\n [class.mat-tab-label-active]=\"selectedIndex == i\"\n [disabled]=\"tab.disabled\"\n [matRippleDisabled]=\"tab.disabled || disableRipple\"\n (click)=\"_handleClick(tab, tabHeader, i)\"\n (cdkFocusChange)=\"_tabFocusChanged($event, i)\">\n\n\n <div class=\"mat-tab-label-content\">\n <!-- If there is a label template, use it. -->\n <ng-template [ngIf]=\"tab.templateLabel\">\n <ng-template [cdkPortalOutlet]=\"tab.templateLabel\"></ng-template>\n </ng-template>\n\n <!-- If there is not a label template, fall back to the text label. -->\n <ng-template [ngIf]=\"!tab.templateLabel\">{{tab.textLabel}}</ng-template>\n </div>\n </div>\n</mat-tab-header>\n\n<div\n class=\"mat-tab-body-wrapper\"\n [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\"\n #tabBodyWrapper>\n <mat-tab-body role=\"tabpanel\"\n *ngFor=\"let tab of _tabs; let i = index\"\n [id]=\"_getTabContentId(i)\"\n [attr.aria-labelledby]=\"_getTabLabelId(i)\"\n [class.mat-tab-body-active]=\"selectedIndex == i\"\n [content]=\"tab.content!\"\n [position]=\"tab.position!\"\n [origin]=\"tab.origin\"\n [animationDuration]=\"animationDuration\"\n (_onCentered)=\"_removeTabBodyWrapperHeight()\"\n (_onCentering)=\"_setTabBodyWrapperHeight($event)\">\n </mat-tab-body>\n</div>\n", |
| encapsulation: ViewEncapsulation.None, |
| // tslint:disable-next-line:validate-decorators |
| changeDetection: ChangeDetectionStrategy.Default, |
| inputs: ['color', 'disableRipple'], |
| providers: [{ |
| provide: MAT_TAB_GROUP, |
| useExisting: MatTabGroup |
| }], |
| host: { |
| 'class': 'mat-tab-group', |
| '[class.mat-tab-group-dynamic-height]': 'dynamicHeight', |
| '[class.mat-tab-group-inverted-header]': 'headerPosition === "below"', |
| }, |
| styles: [".mat-tab-group{display:flex;flex-direction:column}.mat-tab-group.mat-tab-group-inverted-header{flex-direction:column-reverse}.mat-tab-label{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;position:relative}.mat-tab-label:focus{outline:none}.mat-tab-label:focus:not(.mat-tab-disabled){opacity:1}.cdk-high-contrast-active .mat-tab-label:focus{outline:dotted 2px;outline-offset:-2px}.mat-tab-label.mat-tab-disabled{cursor:default}.cdk-high-contrast-active .mat-tab-label.mat-tab-disabled{opacity:.5}.mat-tab-label .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}.cdk-high-contrast-active .mat-tab-label{opacity:1}@media(max-width: 599px){.mat-tab-label{padding:0 12px}}@media(max-width: 959px){.mat-tab-label{padding:0 12px}}.mat-tab-group[mat-stretch-tabs]>.mat-tab-header .mat-tab-label{flex-basis:0;flex-grow:1}.mat-tab-body-wrapper{position:relative;overflow:hidden;display:flex;transition:height 500ms cubic-bezier(0.35, 0, 0.25, 1)}._mat-animation-noopable.mat-tab-body-wrapper{transition:none;animation:none}.mat-tab-body{top:0;left:0;right:0;bottom:0;position:absolute;display:block;overflow:hidden;flex-basis:100%}.mat-tab-body.mat-tab-body-active{position:relative;overflow-x:hidden;overflow-y:auto;z-index:1;flex-grow:1}.mat-tab-group.mat-tab-group-dynamic-height .mat-tab-body.mat-tab-body-active{overflow-y:hidden}\n"] |
| },] } |
| ]; |
| MatTabGroup.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: ChangeDetectorRef }, |
| { type: undefined, decorators: [{ type: Inject, args: [MAT_TABS_CONFIG,] }, { type: Optional }] }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| MatTabGroup.propDecorators = { |
| _allTabs: [{ type: ContentChildren, args: [MatTab, { descendants: true },] }], |
| _tabBodyWrapper: [{ type: ViewChild, args: ['tabBodyWrapper',] }], |
| _tabHeader: [{ type: ViewChild, args: ['tabHeader',] }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| // Boilerplate for applying mixins to MatTabLabelWrapper. |
| /** @docs-private */ |
| class MatTabLabelWrapperBase { |
| } |
| const _MatTabLabelWrapperMixinBase = mixinDisabled(MatTabLabelWrapperBase); |
| /** |
| * Used in the `mat-tab-group` view to display tab labels. |
| * @docs-private |
| */ |
| class MatTabLabelWrapper extends _MatTabLabelWrapperMixinBase { |
| constructor(elementRef) { |
| super(); |
| this.elementRef = elementRef; |
| } |
| /** Sets focus on the wrapper element */ |
| focus() { |
| this.elementRef.nativeElement.focus(); |
| } |
| getOffsetLeft() { |
| return this.elementRef.nativeElement.offsetLeft; |
| } |
| getOffsetWidth() { |
| return this.elementRef.nativeElement.offsetWidth; |
| } |
| } |
| MatTabLabelWrapper.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[matTabLabelWrapper]', |
| inputs: ['disabled'], |
| host: { |
| '[class.mat-tab-disabled]': 'disabled', |
| '[attr.aria-disabled]': '!!disabled', |
| } |
| },] } |
| ]; |
| MatTabLabelWrapper.ctorParameters = () => [ |
| { type: ElementRef } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| /** Config used to bind passive event listeners */ |
| const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true }); |
| /** |
| * The distance in pixels that will be overshot when scrolling a tab label into view. This helps |
| * provide a small affordance to the label next to it. |
| */ |
| const EXAGGERATED_OVERSCROLL = 60; |
| /** |
| * Amount of milliseconds to wait before starting to scroll the header automatically. |
| * Set a little conservatively in order to handle fake events dispatched on touch devices. |
| */ |
| const HEADER_SCROLL_DELAY = 650; |
| /** |
| * Interval in milliseconds at which to scroll the header |
| * while the user is holding their pointer. |
| */ |
| const HEADER_SCROLL_INTERVAL = 100; |
| /** |
| * Base class for a tab header that supported pagination. |
| * @docs-private |
| */ |
| class MatPaginatedTabHeader { |
| constructor(_elementRef, _changeDetectorRef, _viewportRuler, _dir, _ngZone, _platform, _animationMode) { |
| this._elementRef = _elementRef; |
| this._changeDetectorRef = _changeDetectorRef; |
| this._viewportRuler = _viewportRuler; |
| this._dir = _dir; |
| this._ngZone = _ngZone; |
| this._platform = _platform; |
| this._animationMode = _animationMode; |
| /** The distance in pixels that the tab labels should be translated to the left. */ |
| this._scrollDistance = 0; |
| /** Whether the header should scroll to the selected index after the view has been checked. */ |
| this._selectedIndexChanged = false; |
| /** Emits when the component is destroyed. */ |
| this._destroyed = new Subject(); |
| /** Whether the controls for pagination should be displayed */ |
| this._showPaginationControls = false; |
| /** Whether the tab list can be scrolled more towards the end of the tab label list. */ |
| this._disableScrollAfter = true; |
| /** Whether the tab list can be scrolled more towards the beginning of the tab label list. */ |
| this._disableScrollBefore = true; |
| /** Stream that will stop the automated scrolling. */ |
| this._stopScrolling = new Subject(); |
| /** |
| * Whether pagination should be disabled. This can be used to avoid unnecessary |
| * layout recalculations if it's known that pagination won't be required. |
| */ |
| this.disablePagination = false; |
| this._selectedIndex = 0; |
| /** Event emitted when the option is selected. */ |
| this.selectFocusedIndex = new EventEmitter(); |
| /** Event emitted when a label is focused. */ |
| this.indexFocused = new EventEmitter(); |
| // Bind the `mouseleave` event on the outside since it doesn't change anything in the view. |
| _ngZone.runOutsideAngular(() => { |
| fromEvent(_elementRef.nativeElement, 'mouseleave') |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(() => { |
| this._stopInterval(); |
| }); |
| }); |
| } |
| /** The index of the active tab. */ |
| get selectedIndex() { return this._selectedIndex; } |
| set selectedIndex(value) { |
| value = coerceNumberProperty(value); |
| if (this._selectedIndex != value) { |
| this._selectedIndexChanged = true; |
| this._selectedIndex = value; |
| if (this._keyManager) { |
| this._keyManager.updateActiveItem(value); |
| } |
| } |
| } |
| ngAfterViewInit() { |
| // We need to handle these events manually, because we want to bind passive event listeners. |
| fromEvent(this._previousPaginator.nativeElement, 'touchstart', passiveEventListenerOptions) |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(() => { |
| this._handlePaginatorPress('before'); |
| }); |
| fromEvent(this._nextPaginator.nativeElement, 'touchstart', passiveEventListenerOptions) |
| .pipe(takeUntil(this._destroyed)) |
| .subscribe(() => { |
| this._handlePaginatorPress('after'); |
| }); |
| } |
| ngAfterContentInit() { |
| const dirChange = this._dir ? this._dir.change : of(null); |
| const resize = this._viewportRuler.change(150); |
| const realign = () => { |
| this.updatePagination(); |
| this._alignInkBarToSelectedTab(); |
| }; |
| this._keyManager = new FocusKeyManager(this._items) |
| .withHorizontalOrientation(this._getLayoutDirection()) |
| .withHomeAndEnd() |
| .withWrap(); |
| this._keyManager.updateActiveItem(this._selectedIndex); |
| // Defer the first call in order to allow for slower browsers to lay out the elements. |
| // This helps in cases where the user lands directly on a page with paginated tabs. |
| typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(realign) : realign(); |
| // On dir change or window resize, realign the ink bar and update the orientation of |
| // the key manager if the direction has changed. |
| merge(dirChange, resize, this._items.changes).pipe(takeUntil(this._destroyed)).subscribe(() => { |
| // We need to defer this to give the browser some time to recalculate |
| // the element dimensions. The call has to be wrapped in `NgZone.run`, |
| // because the viewport change handler runs outside of Angular. |
| this._ngZone.run(() => Promise.resolve().then(realign)); |
| this._keyManager.withHorizontalOrientation(this._getLayoutDirection()); |
| }); |
| // If there is a change in the focus key manager we need to emit the `indexFocused` |
| // event in order to provide a public event that notifies about focus changes. Also we realign |
| // the tabs container by scrolling the new focused tab into the visible section. |
| this._keyManager.change.pipe(takeUntil(this._destroyed)).subscribe(newFocusIndex => { |
| this.indexFocused.emit(newFocusIndex); |
| this._setTabFocus(newFocusIndex); |
| }); |
| } |
| ngAfterContentChecked() { |
| // If the number of tab labels have changed, check if scrolling should be enabled |
| if (this._tabLabelCount != this._items.length) { |
| this.updatePagination(); |
| this._tabLabelCount = this._items.length; |
| this._changeDetectorRef.markForCheck(); |
| } |
| // If the selected index has changed, scroll to the label and check if the scrolling controls |
| // should be disabled. |
| if (this._selectedIndexChanged) { |
| this._scrollToLabel(this._selectedIndex); |
| this._checkScrollingControls(); |
| this._alignInkBarToSelectedTab(); |
| this._selectedIndexChanged = false; |
| this._changeDetectorRef.markForCheck(); |
| } |
| // If the scroll distance has been changed (tab selected, focused, scroll controls activated), |
| // then translate the header to reflect this. |
| if (this._scrollDistanceChanged) { |
| this._updateTabScrollPosition(); |
| this._scrollDistanceChanged = false; |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| ngOnDestroy() { |
| this._destroyed.next(); |
| this._destroyed.complete(); |
| this._stopScrolling.complete(); |
| } |
| /** Handles keyboard events on the header. */ |
| _handleKeydown(event) { |
| // We don't handle any key bindings with a modifier key. |
| if (hasModifierKey(event)) { |
| return; |
| } |
| switch (event.keyCode) { |
| case ENTER: |
| case SPACE: |
| if (this.focusIndex !== this.selectedIndex) { |
| this.selectFocusedIndex.emit(this.focusIndex); |
| this._itemSelected(event); |
| } |
| break; |
| default: |
| this._keyManager.onKeydown(event); |
| } |
| } |
| /** |
| * Callback for when the MutationObserver detects that the content has changed. |
| */ |
| _onContentChanges() { |
| const textContent = this._elementRef.nativeElement.textContent; |
| // We need to diff the text content of the header, because the MutationObserver callback |
| // will fire even if the text content didn't change which is inefficient and is prone |
| // to infinite loops if a poorly constructed expression is passed in (see #14249). |
| if (textContent !== this._currentTextContent) { |
| this._currentTextContent = textContent || ''; |
| // The content observer runs outside the `NgZone` by default, which |
| // means that we need to bring the callback back in ourselves. |
| this._ngZone.run(() => { |
| this.updatePagination(); |
| this._alignInkBarToSelectedTab(); |
| this._changeDetectorRef.markForCheck(); |
| }); |
| } |
| } |
| /** |
| * Updates the view whether pagination should be enabled or not. |
| * |
| * WARNING: Calling this method can be very costly in terms of performance. It should be called |
| * as infrequently as possible from outside of the Tabs component as it causes a reflow of the |
| * page. |
| */ |
| updatePagination() { |
| this._checkPaginationEnabled(); |
| this._checkScrollingControls(); |
| this._updateTabScrollPosition(); |
| } |
| /** Tracks which element has focus; used for keyboard navigation */ |
| get focusIndex() { |
| return this._keyManager ? this._keyManager.activeItemIndex : 0; |
| } |
| /** When the focus index is set, we must manually send focus to the correct label */ |
| set focusIndex(value) { |
| if (!this._isValidIndex(value) || this.focusIndex === value || !this._keyManager) { |
| return; |
| } |
| this._keyManager.setActiveItem(value); |
| } |
| /** |
| * 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) { |
| if (!this._items) { |
| return true; |
| } |
| const tab = this._items ? this._items.toArray()[index] : null; |
| return !!tab && !tab.disabled; |
| } |
| /** |
| * Sets focus on the HTML element for the label wrapper and scrolls it into the view if |
| * scrolling is enabled. |
| */ |
| _setTabFocus(tabIndex) { |
| if (this._showPaginationControls) { |
| this._scrollToLabel(tabIndex); |
| } |
| if (this._items && this._items.length) { |
| this._items.toArray()[tabIndex].focus(); |
| // Do not let the browser manage scrolling to focus the element, this will be handled |
| // by using translation. In LTR, the scroll left should be 0. In RTL, the scroll width |
| // should be the full width minus the offset width. |
| const containerEl = this._tabListContainer.nativeElement; |
| const dir = this._getLayoutDirection(); |
| if (dir == 'ltr') { |
| containerEl.scrollLeft = 0; |
| } |
| else { |
| containerEl.scrollLeft = containerEl.scrollWidth - containerEl.offsetWidth; |
| } |
| } |
| } |
| /** The layout direction of the containing app. */ |
| _getLayoutDirection() { |
| return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; |
| } |
| /** Performs the CSS transformation on the tab list that will cause the list to scroll. */ |
| _updateTabScrollPosition() { |
| if (this.disablePagination) { |
| return; |
| } |
| const scrollDistance = this.scrollDistance; |
| const translateX = this._getLayoutDirection() === 'ltr' ? -scrollDistance : scrollDistance; |
| // Don't use `translate3d` here because we don't want to create a new layer. A new layer |
| // seems to cause flickering and overflow in Internet Explorer. For example, the ink bar |
| // and ripples will exceed the boundaries of the visible tab bar. |
| // See: https://github.com/angular/components/issues/10276 |
| // We round the `transform` here, because transforms with sub-pixel precision cause some |
| // browsers to blur the content of the element. |
| this._tabList.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; |
| // Setting the `transform` on IE will change the scroll offset of the parent, causing the |
| // position to be thrown off in some cases. We have to reset it ourselves to ensure that |
| // it doesn't get thrown off. Note that we scope it only to IE and Edge, because messing |
| // with the scroll position throws off Chrome 71+ in RTL mode (see #14689). |
| if (this._platform.TRIDENT || this._platform.EDGE) { |
| this._tabListContainer.nativeElement.scrollLeft = 0; |
| } |
| } |
| /** Sets the distance in pixels that the tab header should be transformed in the X-axis. */ |
| get scrollDistance() { return this._scrollDistance; } |
| set scrollDistance(value) { |
| this._scrollTo(value); |
| } |
| /** |
| * Moves the tab list in the 'before' or 'after' direction (towards the beginning of the list or |
| * the end of the list, respectively). The distance to scroll is computed to be a third of the |
| * length of the tab list view window. |
| * |
| * This is an expensive call that forces a layout reflow to compute box and scroll metrics and |
| * should be called sparingly. |
| */ |
| _scrollHeader(direction) { |
| const viewLength = this._tabListContainer.nativeElement.offsetWidth; |
| // Move the scroll distance one-third the length of the tab list's viewport. |
| const scrollAmount = (direction == 'before' ? -1 : 1) * viewLength / 3; |
| return this._scrollTo(this._scrollDistance + scrollAmount); |
| } |
| /** Handles click events on the pagination arrows. */ |
| _handlePaginatorClick(direction) { |
| this._stopInterval(); |
| this._scrollHeader(direction); |
| } |
| /** |
| * Moves the tab list such that the desired tab label (marked by index) is moved into view. |
| * |
| * This is an expensive call that forces a layout reflow to compute box and scroll metrics and |
| * should be called sparingly. |
| */ |
| _scrollToLabel(labelIndex) { |
| if (this.disablePagination) { |
| return; |
| } |
| const selectedLabel = this._items ? this._items.toArray()[labelIndex] : null; |
| if (!selectedLabel) { |
| return; |
| } |
| // The view length is the visible width of the tab labels. |
| const viewLength = this._tabListContainer.nativeElement.offsetWidth; |
| const { offsetLeft, offsetWidth } = selectedLabel.elementRef.nativeElement; |
| let labelBeforePos, labelAfterPos; |
| if (this._getLayoutDirection() == 'ltr') { |
| labelBeforePos = offsetLeft; |
| labelAfterPos = labelBeforePos + offsetWidth; |
| } |
| else { |
| labelAfterPos = this._tabList.nativeElement.offsetWidth - offsetLeft; |
| labelBeforePos = labelAfterPos - offsetWidth; |
| } |
| const beforeVisiblePos = this.scrollDistance; |
| const afterVisiblePos = this.scrollDistance + viewLength; |
| if (labelBeforePos < beforeVisiblePos) { |
| // Scroll header to move label to the before direction |
| this.scrollDistance -= beforeVisiblePos - labelBeforePos + EXAGGERATED_OVERSCROLL; |
| } |
| else if (labelAfterPos > afterVisiblePos) { |
| // Scroll header to move label to the after direction |
| this.scrollDistance += labelAfterPos - afterVisiblePos + EXAGGERATED_OVERSCROLL; |
| } |
| } |
| /** |
| * Evaluate whether the pagination controls should be displayed. If the scroll width of the |
| * tab list is wider than the size of the header container, then the pagination controls should |
| * be shown. |
| * |
| * This is an expensive call that forces a layout reflow to compute box and scroll metrics and |
| * should be called sparingly. |
| */ |
| _checkPaginationEnabled() { |
| if (this.disablePagination) { |
| this._showPaginationControls = false; |
| } |
| else { |
| const isEnabled = this._tabList.nativeElement.scrollWidth > this._elementRef.nativeElement.offsetWidth; |
| if (!isEnabled) { |
| this.scrollDistance = 0; |
| } |
| if (isEnabled !== this._showPaginationControls) { |
| this._changeDetectorRef.markForCheck(); |
| } |
| this._showPaginationControls = isEnabled; |
| } |
| } |
| /** |
| * Evaluate whether the before and after controls should be enabled or disabled. |
| * If the header is at the beginning of the list (scroll distance is equal to 0) then disable the |
| * before button. If the header is at the end of the list (scroll distance is equal to the |
| * maximum distance we can scroll), then disable the after button. |
| * |
| * This is an expensive call that forces a layout reflow to compute box and scroll metrics and |
| * should be called sparingly. |
| */ |
| _checkScrollingControls() { |
| if (this.disablePagination) { |
| this._disableScrollAfter = this._disableScrollBefore = true; |
| } |
| else { |
| // Check if the pagination arrows should be activated. |
| this._disableScrollBefore = this.scrollDistance == 0; |
| this._disableScrollAfter = this.scrollDistance == this._getMaxScrollDistance(); |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| /** |
| * Determines what is the maximum length in pixels that can be set for the scroll distance. This |
| * is equal to the difference in width between the tab list container and tab header container. |
| * |
| * This is an expensive call that forces a layout reflow to compute box and scroll metrics and |
| * should be called sparingly. |
| */ |
| _getMaxScrollDistance() { |
| const lengthOfTabList = this._tabList.nativeElement.scrollWidth; |
| const viewLength = this._tabListContainer.nativeElement.offsetWidth; |
| return (lengthOfTabList - viewLength) || 0; |
| } |
| /** Tells the ink-bar to align itself to the current label wrapper */ |
| _alignInkBarToSelectedTab() { |
| const selectedItem = this._items && this._items.length ? |
| this._items.toArray()[this.selectedIndex] : null; |
| const selectedLabelWrapper = selectedItem ? selectedItem.elementRef.nativeElement : null; |
| if (selectedLabelWrapper) { |
| this._inkBar.alignToElement(selectedLabelWrapper); |
| } |
| else { |
| this._inkBar.hide(); |
| } |
| } |
| /** Stops the currently-running paginator interval. */ |
| _stopInterval() { |
| this._stopScrolling.next(); |
| } |
| /** |
| * Handles the user pressing down on one of the paginators. |
| * Starts scrolling the header after a certain amount of time. |
| * @param direction In which direction the paginator should be scrolled. |
| */ |
| _handlePaginatorPress(direction, mouseEvent) { |
| // Don't start auto scrolling for right mouse button clicks. Note that we shouldn't have to |
| // null check the `button`, but we do it so we don't break tests that use fake events. |
| if (mouseEvent && mouseEvent.button != null && mouseEvent.button !== 0) { |
| return; |
| } |
| // Avoid overlapping timers. |
| this._stopInterval(); |
| // Start a timer after the delay and keep firing based on the interval. |
| timer(HEADER_SCROLL_DELAY, HEADER_SCROLL_INTERVAL) |
| // Keep the timer going until something tells it to stop or the component is destroyed. |
| .pipe(takeUntil(merge(this._stopScrolling, this._destroyed))) |
| .subscribe(() => { |
| const { maxScrollDistance, distance } = this._scrollHeader(direction); |
| // Stop the timer if we've reached the start or the end. |
| if (distance === 0 || distance >= maxScrollDistance) { |
| this._stopInterval(); |
| } |
| }); |
| } |
| /** |
| * Scrolls the header to a given position. |
| * @param position Position to which to scroll. |
| * @returns Information on the current scroll distance and the maximum. |
| */ |
| _scrollTo(position) { |
| if (this.disablePagination) { |
| return { maxScrollDistance: 0, distance: 0 }; |
| } |
| const maxScrollDistance = this._getMaxScrollDistance(); |
| this._scrollDistance = Math.max(0, Math.min(maxScrollDistance, position)); |
| // Mark that the scroll distance has changed so that after the view is checked, the CSS |
| // transformation can move the header. |
| this._scrollDistanceChanged = true; |
| this._checkScrollingControls(); |
| return { maxScrollDistance, distance: this._scrollDistance }; |
| } |
| } |
| MatPaginatedTabHeader.decorators = [ |
| { type: Directive } |
| ]; |
| MatPaginatedTabHeader.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: ChangeDetectorRef }, |
| { type: ViewportRuler }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: NgZone }, |
| { type: Platform }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| MatPaginatedTabHeader.propDecorators = { |
| disablePagination: [{ type: Input }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Base class with all of the `MatTabHeader` functionality. |
| * @docs-private |
| */ |
| class _MatTabHeaderBase extends MatPaginatedTabHeader { |
| constructor(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode) { |
| super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode); |
| this._disableRipple = false; |
| } |
| /** Whether the ripple effect is disabled or not. */ |
| get disableRipple() { return this._disableRipple; } |
| set disableRipple(value) { this._disableRipple = coerceBooleanProperty(value); } |
| _itemSelected(event) { |
| event.preventDefault(); |
| } |
| } |
| _MatTabHeaderBase.decorators = [ |
| { type: Directive } |
| ]; |
| _MatTabHeaderBase.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: ChangeDetectorRef }, |
| { type: ViewportRuler }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: NgZone }, |
| { type: Platform }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| _MatTabHeaderBase.propDecorators = { |
| disableRipple: [{ type: Input }] |
| }; |
| /** |
| * The header of the tab group which displays a list of all the tabs in the tab group. Includes |
| * an ink bar that follows the currently selected tab. When the tabs list's width exceeds the |
| * width of the header container, then arrows will be displayed to allow the user to scroll |
| * left and right across the header. |
| * @docs-private |
| */ |
| class MatTabHeader extends _MatTabHeaderBase { |
| constructor(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode) { |
| super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode); |
| } |
| } |
| MatTabHeader.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-tab-header', |
| template: "<div class=\"mat-tab-header-pagination mat-tab-header-pagination-before mat-elevation-z4\"\n #previousPaginator\n aria-hidden=\"true\"\n mat-ripple [matRippleDisabled]=\"_disableScrollBefore || disableRipple\"\n [class.mat-tab-header-pagination-disabled]=\"_disableScrollBefore\"\n (click)=\"_handlePaginatorClick('before')\"\n (mousedown)=\"_handlePaginatorPress('before', $event)\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-tab-header-pagination-chevron\"></div>\n</div>\n\n<div class=\"mat-tab-label-container\" #tabListContainer (keydown)=\"_handleKeydown($event)\">\n <div\n #tabList\n class=\"mat-tab-list\"\n [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\"\n role=\"tablist\"\n (cdkObserveContent)=\"_onContentChanges()\">\n <div class=\"mat-tab-labels\">\n <ng-content></ng-content>\n </div>\n <mat-ink-bar></mat-ink-bar>\n </div>\n</div>\n\n<div class=\"mat-tab-header-pagination mat-tab-header-pagination-after mat-elevation-z4\"\n #nextPaginator\n aria-hidden=\"true\"\n mat-ripple [matRippleDisabled]=\"_disableScrollAfter || disableRipple\"\n [class.mat-tab-header-pagination-disabled]=\"_disableScrollAfter\"\n (mousedown)=\"_handlePaginatorPress('after', $event)\"\n (click)=\"_handlePaginatorClick('after')\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-tab-header-pagination-chevron\"></div>\n</div>\n", |
| inputs: ['selectedIndex'], |
| outputs: ['selectFocusedIndex', 'indexFocused'], |
| encapsulation: ViewEncapsulation.None, |
| // tslint:disable-next-line:validate-decorators |
| changeDetection: ChangeDetectionStrategy.Default, |
| host: { |
| 'class': 'mat-tab-header', |
| '[class.mat-tab-header-pagination-controls-enabled]': '_showPaginationControls', |
| '[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'", |
| }, |
| styles: [".mat-tab-header{display:flex;overflow:hidden;position:relative;flex-shrink:0}.mat-tab-header-pagination{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:none;justify-content:center;align-items:center;min-width:32px;cursor:pointer;z-index:2;-webkit-tap-highlight-color:transparent;touch-action:none}.mat-tab-header-pagination-controls-enabled .mat-tab-header-pagination{display:flex}.mat-tab-header-pagination-before,.mat-tab-header-rtl .mat-tab-header-pagination-after{padding-left:4px}.mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(-135deg)}.mat-tab-header-rtl .mat-tab-header-pagination-before,.mat-tab-header-pagination-after{padding-right:4px}.mat-tab-header-rtl .mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(45deg)}.mat-tab-header-pagination-chevron{border-style:solid;border-width:2px 2px 0 0;content:\"\";height:8px;width:8px}.mat-tab-header-pagination-disabled{box-shadow:none;cursor:default}.mat-tab-list{flex-grow:1;position:relative;transition:transform 500ms cubic-bezier(0.35, 0, 0.25, 1)}.mat-ink-bar{position:absolute;bottom:0;height:2px;transition:500ms cubic-bezier(0.35, 0, 0.25, 1)}._mat-animation-noopable.mat-ink-bar{transition:none;animation:none}.mat-tab-group-inverted-header .mat-ink-bar{bottom:auto;top:0}.cdk-high-contrast-active .mat-ink-bar{outline:solid 2px;height:0}.mat-tab-labels{display:flex}[mat-align-tabs=center]>.mat-tab-header .mat-tab-labels{justify-content:center}[mat-align-tabs=end]>.mat-tab-header .mat-tab-labels{justify-content:flex-end}.mat-tab-label-container{display:flex;flex-grow:1;overflow:hidden;z-index:1}._mat-animation-noopable.mat-tab-list{transition:none;animation:none}.mat-tab-label{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;position:relative}.mat-tab-label:focus{outline:none}.mat-tab-label:focus:not(.mat-tab-disabled){opacity:1}.cdk-high-contrast-active .mat-tab-label:focus{outline:dotted 2px;outline-offset:-2px}.mat-tab-label.mat-tab-disabled{cursor:default}.cdk-high-contrast-active .mat-tab-label.mat-tab-disabled{opacity:.5}.mat-tab-label .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}.cdk-high-contrast-active .mat-tab-label{opacity:1}@media(max-width: 599px){.mat-tab-label{min-width:72px}}\n"] |
| },] } |
| ]; |
| MatTabHeader.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: ChangeDetectorRef }, |
| { type: ViewportRuler }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: NgZone }, |
| { type: Platform }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| MatTabHeader.propDecorators = { |
| _items: [{ type: ContentChildren, args: [MatTabLabelWrapper, { descendants: false },] }], |
| _inkBar: [{ type: ViewChild, args: [MatInkBar, { static: true },] }], |
| _tabListContainer: [{ type: ViewChild, args: ['tabListContainer', { static: true },] }], |
| _tabList: [{ type: ViewChild, args: ['tabList', { static: true },] }], |
| _nextPaginator: [{ type: ViewChild, args: ['nextPaginator',] }], |
| _previousPaginator: [{ type: ViewChild, args: ['previousPaginator',] }] |
| }; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Base class with all of the `MatTabNav` functionality. |
| * @docs-private |
| */ |
| class _MatTabNavBase extends MatPaginatedTabHeader { |
| constructor(elementRef, dir, ngZone, changeDetectorRef, viewportRuler, platform, animationMode) { |
| super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode); |
| this._disableRipple = false; |
| /** Theme color of the nav bar. */ |
| this.color = 'primary'; |
| } |
| /** Background color of the tab nav. */ |
| get backgroundColor() { return this._backgroundColor; } |
| set backgroundColor(value) { |
| const classList = this._elementRef.nativeElement.classList; |
| classList.remove(`mat-background-${this.backgroundColor}`); |
| if (value) { |
| classList.add(`mat-background-${value}`); |
| } |
| this._backgroundColor = value; |
| } |
| /** Whether the ripple effect is disabled or not. */ |
| get disableRipple() { return this._disableRipple; } |
| set disableRipple(value) { this._disableRipple = coerceBooleanProperty(value); } |
| _itemSelected() { |
| // noop |
| } |
| ngAfterContentInit() { |
| // We need this to run before the `changes` subscription in parent to ensure that the |
| // selectedIndex is up-to-date by the time the super class starts looking for it. |
| this._items.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => { |
| this.updateActiveLink(); |
| }); |
| super.ngAfterContentInit(); |
| } |
| /** Notifies the component that the active link has been changed. */ |
| updateActiveLink() { |
| if (!this._items) { |
| return; |
| } |
| const items = this._items.toArray(); |
| for (let i = 0; i < items.length; i++) { |
| if (items[i].active) { |
| this.selectedIndex = i; |
| this._changeDetectorRef.markForCheck(); |
| return; |
| } |
| } |
| // The ink bar should hide itself if no items are active. |
| this.selectedIndex = -1; |
| this._inkBar.hide(); |
| } |
| } |
| _MatTabNavBase.decorators = [ |
| { type: Directive } |
| ]; |
| _MatTabNavBase.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: NgZone }, |
| { type: ChangeDetectorRef }, |
| { type: ViewportRuler }, |
| { type: Platform }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| _MatTabNavBase.propDecorators = { |
| backgroundColor: [{ type: Input }], |
| disableRipple: [{ type: Input }], |
| color: [{ type: Input }] |
| }; |
| /** |
| * Navigation component matching the styles of the tab group header. |
| * Provides anchored navigation with animated ink bar. |
| */ |
| class MatTabNav extends _MatTabNavBase { |
| constructor(elementRef, dir, ngZone, changeDetectorRef, viewportRuler, platform, animationMode) { |
| super(elementRef, dir, ngZone, changeDetectorRef, viewportRuler, platform, animationMode); |
| } |
| } |
| MatTabNav.decorators = [ |
| { type: Component, args: [{ |
| selector: '[mat-tab-nav-bar]', |
| exportAs: 'matTabNavBar, matTabNav', |
| inputs: ['color'], |
| template: "<div class=\"mat-tab-header-pagination mat-tab-header-pagination-before mat-elevation-z4\"\n #previousPaginator\n aria-hidden=\"true\"\n mat-ripple [matRippleDisabled]=\"_disableScrollBefore || disableRipple\"\n [class.mat-tab-header-pagination-disabled]=\"_disableScrollBefore\"\n (click)=\"_handlePaginatorClick('before')\"\n (mousedown)=\"_handlePaginatorPress('before', $event)\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-tab-header-pagination-chevron\"></div>\n</div>\n\n<div class=\"mat-tab-link-container\" #tabListContainer (keydown)=\"_handleKeydown($event)\">\n <div\n class=\"mat-tab-list\"\n [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\"\n #tabList\n (cdkObserveContent)=\"_onContentChanges()\">\n <div class=\"mat-tab-links\">\n <ng-content></ng-content>\n </div>\n <mat-ink-bar></mat-ink-bar>\n </div>\n</div>\n\n<div class=\"mat-tab-header-pagination mat-tab-header-pagination-after mat-elevation-z4\"\n #nextPaginator\n aria-hidden=\"true\"\n mat-ripple [matRippleDisabled]=\"_disableScrollAfter || disableRipple\"\n [class.mat-tab-header-pagination-disabled]=\"_disableScrollAfter\"\n (mousedown)=\"_handlePaginatorPress('after', $event)\"\n (click)=\"_handlePaginatorClick('after')\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-tab-header-pagination-chevron\"></div>\n</div>\n", |
| host: { |
| 'class': 'mat-tab-nav-bar mat-tab-header', |
| '[class.mat-tab-header-pagination-controls-enabled]': '_showPaginationControls', |
| '[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'", |
| '[class.mat-primary]': 'color !== "warn" && color !== "accent"', |
| '[class.mat-accent]': 'color === "accent"', |
| '[class.mat-warn]': 'color === "warn"', |
| }, |
| encapsulation: ViewEncapsulation.None, |
| // tslint:disable-next-line:validate-decorators |
| changeDetection: ChangeDetectionStrategy.Default, |
| styles: [".mat-tab-header{display:flex;overflow:hidden;position:relative;flex-shrink:0}.mat-tab-header-pagination{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:none;justify-content:center;align-items:center;min-width:32px;cursor:pointer;z-index:2;-webkit-tap-highlight-color:transparent;touch-action:none}.mat-tab-header-pagination-controls-enabled .mat-tab-header-pagination{display:flex}.mat-tab-header-pagination-before,.mat-tab-header-rtl .mat-tab-header-pagination-after{padding-left:4px}.mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(-135deg)}.mat-tab-header-rtl .mat-tab-header-pagination-before,.mat-tab-header-pagination-after{padding-right:4px}.mat-tab-header-rtl .mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(45deg)}.mat-tab-header-pagination-chevron{border-style:solid;border-width:2px 2px 0 0;content:\"\";height:8px;width:8px}.mat-tab-header-pagination-disabled{box-shadow:none;cursor:default}.mat-tab-list{flex-grow:1;position:relative;transition:transform 500ms cubic-bezier(0.35, 0, 0.25, 1)}.mat-tab-links{display:flex}[mat-align-tabs=center]>.mat-tab-link-container .mat-tab-links{justify-content:center}[mat-align-tabs=end]>.mat-tab-link-container .mat-tab-links{justify-content:flex-end}.mat-ink-bar{position:absolute;bottom:0;height:2px;transition:500ms cubic-bezier(0.35, 0, 0.25, 1)}._mat-animation-noopable.mat-ink-bar{transition:none;animation:none}.mat-tab-group-inverted-header .mat-ink-bar{bottom:auto;top:0}.cdk-high-contrast-active .mat-ink-bar{outline:solid 2px;height:0}.mat-tab-link-container{display:flex;flex-grow:1;overflow:hidden;z-index:1}.mat-tab-link{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;vertical-align:top;text-decoration:none;position:relative;overflow:hidden;-webkit-tap-highlight-color:transparent}.mat-tab-link:focus{outline:none}.mat-tab-link:focus:not(.mat-tab-disabled){opacity:1}.cdk-high-contrast-active .mat-tab-link:focus{outline:dotted 2px;outline-offset:-2px}.mat-tab-link.mat-tab-disabled{cursor:default}.cdk-high-contrast-active .mat-tab-link.mat-tab-disabled{opacity:.5}.mat-tab-link .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}.cdk-high-contrast-active .mat-tab-link{opacity:1}[mat-stretch-tabs] .mat-tab-link{flex-basis:0;flex-grow:1}.mat-tab-link.mat-tab-disabled{pointer-events:none}@media(max-width: 599px){.mat-tab-link{min-width:72px}}\n"] |
| },] } |
| ]; |
| MatTabNav.ctorParameters = () => [ |
| { type: ElementRef }, |
| { type: Directionality, decorators: [{ type: Optional }] }, |
| { type: NgZone }, |
| { type: ChangeDetectorRef }, |
| { type: ViewportRuler }, |
| { type: Platform }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| MatTabNav.propDecorators = { |
| _items: [{ type: ContentChildren, args: [forwardRef(() => MatTabLink), { descendants: true },] }], |
| _inkBar: [{ type: ViewChild, args: [MatInkBar, { static: true },] }], |
| _tabListContainer: [{ type: ViewChild, args: ['tabListContainer', { static: true },] }], |
| _tabList: [{ type: ViewChild, args: ['tabList', { static: true },] }], |
| _nextPaginator: [{ type: ViewChild, args: ['nextPaginator',] }], |
| _previousPaginator: [{ type: ViewChild, args: ['previousPaginator',] }] |
| }; |
| // Boilerplate for applying mixins to MatTabLink. |
| class MatTabLinkMixinBase { |
| } |
| const _MatTabLinkMixinBase = mixinTabIndex(mixinDisableRipple(mixinDisabled(MatTabLinkMixinBase))); |
| /** Base class with all of the `MatTabLink` functionality. */ |
| class _MatTabLinkBase extends _MatTabLinkMixinBase { |
| constructor(_tabNavBar, |
| /** @docs-private */ elementRef, globalRippleOptions, tabIndex, _focusMonitor, animationMode) { |
| super(); |
| this._tabNavBar = _tabNavBar; |
| this.elementRef = elementRef; |
| this._focusMonitor = _focusMonitor; |
| /** Whether the tab link is active or not. */ |
| this._isActive = false; |
| this.rippleConfig = globalRippleOptions || {}; |
| this.tabIndex = parseInt(tabIndex) || 0; |
| if (animationMode === 'NoopAnimations') { |
| this.rippleConfig.animation = { enterDuration: 0, exitDuration: 0 }; |
| } |
| } |
| /** Whether the link is active. */ |
| get active() { return this._isActive; } |
| set active(value) { |
| const newValue = coerceBooleanProperty(value); |
| if (newValue !== this._isActive) { |
| this._isActive = value; |
| this._tabNavBar.updateActiveLink(); |
| } |
| } |
| /** |
| * Whether ripples are disabled on interaction. |
| * @docs-private |
| */ |
| get rippleDisabled() { |
| return this.disabled || this.disableRipple || this._tabNavBar.disableRipple || |
| !!this.rippleConfig.disabled; |
| } |
| /** Focuses the tab link. */ |
| focus() { |
| this.elementRef.nativeElement.focus(); |
| } |
| ngAfterViewInit() { |
| this._focusMonitor.monitor(this.elementRef); |
| } |
| ngOnDestroy() { |
| this._focusMonitor.stopMonitoring(this.elementRef); |
| } |
| } |
| _MatTabLinkBase.decorators = [ |
| { type: Directive } |
| ]; |
| _MatTabLinkBase.ctorParameters = () => [ |
| { type: _MatTabNavBase }, |
| { type: ElementRef }, |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_RIPPLE_GLOBAL_OPTIONS,] }] }, |
| { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] }, |
| { type: FocusMonitor }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| _MatTabLinkBase.propDecorators = { |
| active: [{ type: Input }] |
| }; |
| /** |
| * Link inside of a `mat-tab-nav-bar`. |
| */ |
| class MatTabLink extends _MatTabLinkBase { |
| constructor(tabNavBar, elementRef, ngZone, platform, globalRippleOptions, tabIndex, focusMonitor, animationMode) { |
| super(tabNavBar, elementRef, globalRippleOptions, tabIndex, focusMonitor, animationMode); |
| this._tabLinkRipple = new RippleRenderer(this, ngZone, elementRef, platform); |
| this._tabLinkRipple.setupTriggerEvents(elementRef.nativeElement); |
| } |
| ngOnDestroy() { |
| super.ngOnDestroy(); |
| this._tabLinkRipple._removeTriggerEvents(); |
| } |
| } |
| MatTabLink.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[mat-tab-link], [matTabLink]', |
| exportAs: 'matTabLink', |
| inputs: ['disabled', 'disableRipple', 'tabIndex'], |
| host: { |
| 'class': 'mat-tab-link mat-focus-indicator', |
| '[attr.aria-current]': 'active ? "page" : null', |
| '[attr.aria-disabled]': 'disabled', |
| '[attr.tabIndex]': 'tabIndex', |
| '[class.mat-tab-disabled]': 'disabled', |
| '[class.mat-tab-label-active]': 'active', |
| } |
| },] } |
| ]; |
| MatTabLink.ctorParameters = () => [ |
| { type: MatTabNav }, |
| { type: ElementRef }, |
| { type: NgZone }, |
| { type: Platform }, |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_RIPPLE_GLOBAL_OPTIONS,] }] }, |
| { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] }, |
| { type: FocusMonitor }, |
| { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| class MatTabsModule { |
| } |
| MatTabsModule.decorators = [ |
| { type: NgModule, args: [{ |
| imports: [ |
| CommonModule, |
| MatCommonModule, |
| PortalModule, |
| MatRippleModule, |
| ObserversModule, |
| A11yModule, |
| ], |
| // Don't export all components because some are only to be used internally. |
| exports: [ |
| MatCommonModule, |
| MatTabGroup, |
| MatTabLabel, |
| MatTab, |
| MatTabNav, |
| MatTabLink, |
| MatTabContent, |
| ], |
| declarations: [ |
| MatTabGroup, |
| MatTabLabel, |
| MatTab, |
| MatInkBar, |
| MatTabLabelWrapper, |
| MatTabNav, |
| MatTabLink, |
| MatTabBody, |
| MatTabBodyPortal, |
| MatTabHeader, |
| MatTabContent, |
| ], |
| },] } |
| ]; |
| |
| /** |
| * @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 |
| */ |
| |
| /** |
| * @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 |
| */ |
| |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| export { MAT_TABS_CONFIG, MAT_TAB_GROUP, MatInkBar, MatTab, MatTabBody, MatTabBodyPortal, MatTabChangeEvent, MatTabContent, MatTabGroup, MatTabHeader, MatTabLabel, MatTabLabelWrapper, MatTabLink, MatTabNav, MatTabsModule, _MAT_INK_BAR_POSITIONER, _MatTabBodyBase, _MatTabGroupBase, _MatTabHeaderBase, _MatTabLinkBase, _MatTabNavBase, matTabsAnimations, _MAT_INK_BAR_POSITIONER_FACTORY as ɵangular_material_src_material_tabs_tabs_a, MAT_TAB_LABEL as ɵangular_material_src_material_tabs_tabs_b, MAT_TAB_CONTENT as ɵangular_material_src_material_tabs_tabs_c, MatPaginatedTabHeader as ɵangular_material_src_material_tabs_tabs_d }; |
| //# sourceMappingURL=tabs.js.map |