UNPKG

@angular/material

Version:
1 lines 173 kB
{"version":3,"file":"tabs.mjs","sources":["../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-content.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-label.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/ink-bar.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-label-wrapper.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/paginated-tab-header.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-header.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-header.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-config.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-body.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-body.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-group.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-group.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-nav-bar/tab-nav-bar.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-nav-bar/tab-nav-bar.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tab-nav-bar/tab-link.html","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/module.ts","../../../../../darwin_arm64-fastbuild-ST-46c76129e412/bin/src/material/tabs/tabs-animations.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Directive, InjectionToken, TemplateRef, inject} from '@angular/core';\n\n/**\n * Injection token that can be used to reference instances of `MatTabContent`. It serves as\n * alternative token to the actual `MatTabContent` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */\nexport const MAT_TAB_CONTENT = new InjectionToken<MatTabContent>('MatTabContent');\n\n/** Decorates the `ng-template` tags and reads out the template from it. */\n@Directive({\n selector: '[matTabContent]',\n providers: [{provide: MAT_TAB_CONTENT, useExisting: MatTabContent}],\n})\nexport class MatTabContent {\n template = inject<TemplateRef<any>>(TemplateRef);\n\n constructor(...args: unknown[]);\n constructor() {}\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Directive, InjectionToken, inject} from '@angular/core';\nimport {CdkPortal} from '@angular/cdk/portal';\n\n/**\n * Injection token that can be used to reference instances of `MatTabLabel`. It serves as\n * alternative token to the actual `MatTabLabel` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */\nexport const MAT_TAB_LABEL = new InjectionToken<MatTabLabel>('MatTabLabel');\n\n/**\n * Used to provide a tab label to a tab without causing a circular dependency.\n * @docs-private\n */\nexport const MAT_TAB = new InjectionToken<any>('MAT_TAB');\n\n/** Used to flag tab labels for use with the portal directive */\n@Directive({\n selector: '[mat-tab-label], [matTabLabel]',\n providers: [{provide: MAT_TAB_LABEL, useExisting: MatTabLabel}],\n})\nexport class MatTabLabel extends CdkPortal {\n _closestTab = inject(MAT_TAB, {optional: true});\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n InjectionToken,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewContainerRef,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n} from '@angular/core';\nimport {MatTabContent} from './tab-content';\nimport {MAT_TAB, MatTabLabel} from './tab-label';\nimport {TemplatePortal} from '@angular/cdk/portal';\nimport {Subject} from 'rxjs';\nimport {_CdkPrivateStyleLoader} from '@angular/cdk/private';\nimport {_StructuralStylesLoader} from '../core';\n\n/**\n * Used to provide a tab group to a tab without causing a circular dependency.\n * @docs-private\n */\nexport const MAT_TAB_GROUP = new InjectionToken<any>('MAT_TAB_GROUP');\n\n@Component({\n selector: 'mat-tab',\n // Note that usually we'd go through a bit more trouble and set up another class so that\n // the inlined template of `MatTab` isn't duplicated, however the template is small enough\n // that creating the extra class will generate more code than just duplicating the template.\n templateUrl: 'tab.html',\n // tslint:disable-next-line:validate-decorators\n changeDetection: ChangeDetectionStrategy.Default,\n encapsulation: ViewEncapsulation.None,\n exportAs: 'matTab',\n providers: [{provide: MAT_TAB, useExisting: MatTab}],\n host: {\n // This element will be rendered on the server in order to support hydration.\n // Hide it so it doesn't cause a layout shift when it's removed on the client.\n 'hidden': '',\n\n // Clear any custom IDs from the tab since they'll be forwarded to the actual tab.\n '[attr.id]': 'null',\n },\n})\nexport class MatTab implements OnInit, OnChanges, OnDestroy {\n private _viewContainerRef = inject(ViewContainerRef);\n _closestTabGroup = inject(MAT_TAB_GROUP, {optional: true});\n\n /** whether the tab is disabled. */\n @Input({transform: booleanAttribute})\n disabled: boolean = false;\n\n /** Content for the tab label given by `<ng-template mat-tab-label>`. */\n @ContentChild(MatTabLabel)\n get templateLabel(): MatTabLabel {\n return this._templateLabel;\n }\n set templateLabel(value: MatTabLabel) {\n this._setTemplateLabelInput(value);\n }\n private _templateLabel: MatTabLabel;\n\n /**\n * Template provided in the tab content that will be used if present, used to enable lazy-loading\n */\n @ContentChild(MatTabContent, {read: TemplateRef, static: true})\n // We need an initializer here to avoid a TS error. The value will be set in `ngAfterViewInit`.\n private _explicitContent: TemplateRef<any> = undefined!;\n\n /** Template inside the MatTab view that contains an `<ng-content>`. */\n @ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef<any>;\n\n /** Plain text label for the tab, used when there is no template label. */\n @Input('label') textLabel: string = '';\n\n /** Aria label for the tab. */\n @Input('aria-label') ariaLabel: string;\n\n /**\n * Reference to the element that the tab is labelled by.\n * Will be cleared if `aria-label` is set at the same time.\n */\n @Input('aria-labelledby') ariaLabelledby: string;\n\n /** Classes to be passed to the tab label inside the mat-tab-header container. */\n @Input() labelClass: string | string[];\n\n /** Classes to be passed to the tab mat-tab-body container. */\n @Input() bodyClass: string | string[];\n\n /**\n * Custom ID for the tab, overriding the auto-generated one by Material.\n * Note that when using this input, it's your responsibility to ensure that the ID is unique.\n */\n @Input() id: string | null = null;\n\n /** Portal that will be the hosted content of the tab */\n private _contentPortal: TemplatePortal | null = null;\n\n /** @docs-private */\n get content(): TemplatePortal | null {\n return this._contentPortal;\n }\n\n /** Emits whenever the internal state of the tab changes. */\n readonly _stateChanges = new Subject<void>();\n\n /**\n * The relatively indexed position where 0 represents the center, negative is left, and positive\n * represents the right.\n */\n position: number | null = null;\n\n // TODO(crisbeto): we no longer use this, but some internal apps appear to rely on it.\n /**\n * The initial relatively index origin of the tab if it was created and selected after there\n * was already a selected tab. Provides context of what position the tab should originate from.\n */\n origin: number | null = null;\n\n /**\n * Whether the tab is currently active.\n */\n isActive = false;\n\n constructor(...args: unknown[]);\n constructor() {\n inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.hasOwnProperty('textLabel') || changes.hasOwnProperty('disabled')) {\n this._stateChanges.next();\n }\n }\n\n ngOnDestroy(): void {\n this._stateChanges.complete();\n }\n\n ngOnInit(): void {\n this._contentPortal = new TemplatePortal(\n this._explicitContent || this._implicitContent,\n this._viewContainerRef,\n );\n }\n\n /**\n * This has been extracted to a util because of TS 4 and VE.\n * View Engine doesn't support property rename inheritance.\n * TS 4.0 doesn't allow properties to override accessors or vice-versa.\n * @docs-private\n */\n private _setTemplateLabelInput(value: MatTabLabel | undefined) {\n // Only update the label if the query managed to find one. This works around an issue where a\n // user may have manually set `templateLabel` during creation mode, which would then get\n // clobbered by `undefined` when the query resolves. Also note that we check that the closest\n // tab matches the current one so that we don't pick up labels from nested tabs.\n if (value && value._closestTab === this) {\n this._templateLabel = value;\n }\n }\n}\n","<!-- 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","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n Directive,\n ElementRef,\n InjectionToken,\n Input,\n OnDestroy,\n OnInit,\n QueryList,\n booleanAttribute,\n inject,\n} from '@angular/core';\n\n/**\n * Item inside a tab header relative to which the ink bar can be aligned.\n * @docs-private\n */\nexport interface MatInkBarItem extends OnInit, OnDestroy {\n elementRef: ElementRef<HTMLElement>;\n activateInkBar(previousIndicatorClientRect?: DOMRect): void;\n deactivateInkBar(): void;\n fitInkBarToContent: boolean;\n}\n\n/** Class that is applied when a tab indicator is active. */\nconst ACTIVE_CLASS = 'mdc-tab-indicator--active';\n\n/** Class that is applied when the tab indicator should not transition. */\nconst NO_TRANSITION_CLASS = 'mdc-tab-indicator--no-transition';\n\n/**\n * Abstraction around the MDC tab indicator that acts as the tab header's ink bar.\n * @docs-private\n */\nexport class MatInkBar {\n /** Item to which the ink bar is aligned currently. */\n private _currentItem: MatInkBarItem | undefined;\n\n constructor(private _items: QueryList<MatInkBarItem>) {}\n\n /** Hides the ink bar. */\n hide() {\n this._items.forEach(item => item.deactivateInkBar());\n this._currentItem = undefined;\n }\n\n /** Aligns the ink bar to a DOM node. */\n alignToElement(element: HTMLElement) {\n const correspondingItem = this._items.find(item => item.elementRef.nativeElement === element);\n const currentItem = this._currentItem;\n\n if (correspondingItem === currentItem) {\n return;\n }\n\n currentItem?.deactivateInkBar();\n\n if (correspondingItem) {\n const domRect = currentItem?.elementRef.nativeElement.getBoundingClientRect?.();\n\n // The ink bar won't animate unless we give it the `DOMRect` of the previous item.\n correspondingItem.activateInkBar(domRect);\n this._currentItem = correspondingItem;\n }\n }\n}\n\n@Directive()\nexport abstract class InkBarItem implements OnInit, OnDestroy {\n private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private _inkBarElement: HTMLElement | null;\n private _inkBarContentElement: HTMLElement | null;\n private _fitToContent = false;\n\n /** Whether the ink bar should fit to the entire tab or just its content. */\n @Input({transform: booleanAttribute})\n get fitInkBarToContent(): boolean {\n return this._fitToContent;\n }\n set fitInkBarToContent(newValue: boolean) {\n if (this._fitToContent !== newValue) {\n this._fitToContent = newValue;\n\n if (this._inkBarElement) {\n this._appendInkBarElement();\n }\n }\n }\n\n /** Aligns the ink bar to the current item. */\n activateInkBar(previousIndicatorClientRect?: DOMRect) {\n const element = this._elementRef.nativeElement;\n\n // Early exit if no indicator is present to handle cases where an indicator\n // may be activated without a prior indicator state\n if (\n !previousIndicatorClientRect ||\n !element.getBoundingClientRect ||\n !this._inkBarContentElement\n ) {\n element.classList.add(ACTIVE_CLASS);\n return;\n }\n\n // This animation uses the FLIP approach. You can read more about it at the link below:\n // https://aerotwist.com/blog/flip-your-animations/\n\n // Calculate the dimensions based on the dimensions of the previous indicator\n const currentClientRect = element.getBoundingClientRect();\n const widthDelta = previousIndicatorClientRect.width / currentClientRect.width;\n const xPosition = previousIndicatorClientRect.left - currentClientRect.left;\n element.classList.add(NO_TRANSITION_CLASS);\n this._inkBarContentElement.style.setProperty(\n 'transform',\n `translateX(${xPosition}px) scaleX(${widthDelta})`,\n );\n\n // Force repaint before updating classes and transform to ensure the transform properly takes effect\n element.getBoundingClientRect();\n\n element.classList.remove(NO_TRANSITION_CLASS);\n element.classList.add(ACTIVE_CLASS);\n this._inkBarContentElement.style.setProperty('transform', '');\n }\n\n /** Removes the ink bar from the current item. */\n deactivateInkBar() {\n this._elementRef.nativeElement.classList.remove(ACTIVE_CLASS);\n }\n\n /** Initializes the foundation. */\n ngOnInit() {\n this._createInkBarElement();\n }\n\n /** Destroys the foundation. */\n ngOnDestroy() {\n this._inkBarElement?.remove();\n this._inkBarElement = this._inkBarContentElement = null!;\n }\n\n /** Creates and appends the ink bar element. */\n private _createInkBarElement() {\n const documentNode = this._elementRef.nativeElement.ownerDocument || document;\n const inkBarElement = (this._inkBarElement = documentNode.createElement('span'));\n const inkBarContentElement = (this._inkBarContentElement = documentNode.createElement('span'));\n\n inkBarElement.className = 'mdc-tab-indicator';\n inkBarContentElement.className =\n 'mdc-tab-indicator__content mdc-tab-indicator__content--underline';\n\n inkBarElement.appendChild(this._inkBarContentElement);\n this._appendInkBarElement();\n }\n\n /**\n * Appends the ink bar to the tab host element or content, depending on whether\n * the ink bar should fit to content.\n */\n private _appendInkBarElement() {\n if (!this._inkBarElement && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('Ink bar element has not been created and cannot be appended');\n }\n\n const parentElement = this._fitToContent\n ? this._elementRef.nativeElement.querySelector('.mdc-tab__content')\n : this._elementRef.nativeElement;\n\n if (!parentElement && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('Missing element to host the ink bar');\n }\n\n parentElement!.appendChild(this._inkBarElement!);\n }\n}\n\n/**\n * Interface for a MatInkBar positioner method, defining the positioning and width of the ink\n * bar in a set of tabs.\n */\nexport interface _MatInkBarPositioner {\n (element: HTMLElement): {left: string; width: string};\n}\n\n/**\n * The default positioner function for the MatInkBar.\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nexport function _MAT_INK_BAR_POSITIONER_FACTORY(): _MatInkBarPositioner {\n const method = (element: HTMLElement) => ({\n left: element ? (element.offsetLeft || 0) + 'px' : '0',\n width: element ? (element.offsetWidth || 0) + 'px' : '0',\n });\n\n return method;\n}\n\n/** Injection token for the MatInkBar's Positioner. */\nexport const _MAT_INK_BAR_POSITIONER = new InjectionToken<_MatInkBarPositioner>(\n 'MatInkBarPositioner',\n {\n providedIn: 'root',\n factory: _MAT_INK_BAR_POSITIONER_FACTORY,\n },\n);\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {Directive, ElementRef, Input, booleanAttribute, inject} from '@angular/core';\nimport {InkBarItem} from './ink-bar';\n\n/**\n * Used in the `mat-tab-group` view to display tab labels.\n * @docs-private\n */\n@Directive({\n selector: '[matTabLabelWrapper]',\n host: {\n '[class.mat-mdc-tab-disabled]': 'disabled',\n '[attr.aria-disabled]': '!!disabled',\n },\n})\nexport class MatTabLabelWrapper extends InkBarItem {\n elementRef = inject(ElementRef);\n\n /** Whether the tab is disabled. */\n @Input({transform: booleanAttribute})\n disabled: boolean = false;\n\n /** Sets focus on the wrapper element */\n focus(): void {\n this.elementRef.nativeElement.focus();\n }\n\n getOffsetLeft(): number {\n return this.elementRef.nativeElement.offsetLeft;\n }\n\n getOffsetWidth(): number {\n return this.elementRef.nativeElement.offsetWidth;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {FocusKeyManager, FocusableOption} from '@angular/cdk/a11y';\nimport {Direction, Directionality} from '@angular/cdk/bidi';\nimport {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';\nimport {SharedResizeObserver} from '@angular/cdk/observers/private';\nimport {Platform, _bindEventWithOptions} from '@angular/cdk/platform';\nimport {ViewportRuler} from '@angular/cdk/scrolling';\nimport {\n ANIMATION_MODULE_TYPE,\n AfterContentChecked,\n AfterContentInit,\n AfterViewInit,\n ChangeDetectorRef,\n Directive,\n ElementRef,\n EventEmitter,\n Injector,\n Input,\n NgZone,\n OnDestroy,\n Output,\n QueryList,\n Renderer2,\n afterNextRender,\n booleanAttribute,\n inject,\n numberAttribute,\n} from '@angular/core';\nimport {EMPTY, Observable, Observer, Subject, merge, of as observableOf, timer} from 'rxjs';\nimport {debounceTime, filter, skip, startWith, switchMap, takeUntil} from 'rxjs/operators';\n\n/** Config used to bind passive event listeners */\nconst passiveEventListenerOptions = {\n passive: true,\n};\n\n/**\n * The directions that scrolling can go in when the header's tabs exceed the header width. 'After'\n * will scroll the header towards the end of the tabs list and 'before' will scroll towards the\n * beginning of the list.\n */\nexport type ScrollDirection = 'after' | 'before';\n\n/**\n * Amount of milliseconds to wait before starting to scroll the header automatically.\n * Set a little conservatively in order to handle fake events dispatched on touch devices.\n */\nconst HEADER_SCROLL_DELAY = 650;\n\n/**\n * Interval in milliseconds at which to scroll the header\n * while the user is holding their pointer.\n */\nconst HEADER_SCROLL_INTERVAL = 100;\n\n/** Item inside a paginated tab header. */\nexport type MatPaginatedTabHeaderItem = FocusableOption & {elementRef: ElementRef};\n\n/**\n * Base class for a tab header that supported pagination.\n * @docs-private\n */\n@Directive()\nexport abstract class MatPaginatedTabHeader\n implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy\n{\n protected _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n protected _changeDetectorRef = inject(ChangeDetectorRef);\n private _viewportRuler = inject(ViewportRuler);\n private _dir = inject(Directionality, {optional: true});\n private _ngZone = inject(NgZone);\n private _platform = inject(Platform);\n private _sharedResizeObserver = inject(SharedResizeObserver);\n private _injector = inject(Injector);\n private _renderer = inject(Renderer2);\n _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});\n private _eventCleanups: (() => void)[];\n\n abstract _items: QueryList<MatPaginatedTabHeaderItem>;\n abstract _inkBar: {hide: () => void; alignToElement: (element: HTMLElement) => void};\n abstract _tabListContainer: ElementRef<HTMLElement>;\n abstract _tabList: ElementRef<HTMLElement>;\n abstract _tabListInner: ElementRef<HTMLElement>;\n abstract _nextPaginator: ElementRef<HTMLElement>;\n abstract _previousPaginator: ElementRef<HTMLElement>;\n\n /** The distance in pixels that the tab labels should be translated to the left. */\n private _scrollDistance = 0;\n\n /** Whether the header should scroll to the selected index after the view has been checked. */\n private _selectedIndexChanged = false;\n\n /** Emits when the component is destroyed. */\n protected readonly _destroyed = new Subject<void>();\n\n /** Whether the controls for pagination should be displayed */\n _showPaginationControls = false;\n\n /** Whether the tab list can be scrolled more towards the end of the tab label list. */\n _disableScrollAfter = true;\n\n /** Whether the tab list can be scrolled more towards the beginning of the tab label list. */\n _disableScrollBefore = true;\n\n /**\n * The number of tab labels that are displayed on the header. When this changes, the header\n * should re-evaluate the scroll position.\n */\n private _tabLabelCount: number;\n\n /** Whether the scroll distance has changed and should be applied after the view is checked. */\n private _scrollDistanceChanged: boolean;\n\n /** Used to manage focus between the tabs. */\n private _keyManager: FocusKeyManager<MatPaginatedTabHeaderItem>;\n\n /** Cached text content of the header. */\n private _currentTextContent: string;\n\n /** Stream that will stop the automated scrolling. */\n private _stopScrolling = new Subject<void>();\n\n /**\n * Whether pagination should be disabled. This can be used to avoid unnecessary\n * layout recalculations if it's known that pagination won't be required.\n */\n @Input({transform: booleanAttribute})\n disablePagination: boolean = false;\n\n /** The index of the active tab. */\n @Input({transform: numberAttribute})\n get selectedIndex(): number {\n return this._selectedIndex;\n }\n set selectedIndex(v: number) {\n const value = isNaN(v) ? 0 : v;\n\n if (this._selectedIndex != value) {\n this._selectedIndexChanged = true;\n this._selectedIndex = value;\n\n if (this._keyManager) {\n this._keyManager.updateActiveItem(value);\n }\n }\n }\n private _selectedIndex: number = 0;\n\n /** Event emitted when the option is selected. */\n @Output() readonly selectFocusedIndex: EventEmitter<number> = new EventEmitter<number>();\n\n /** Event emitted when a label is focused. */\n @Output() readonly indexFocused: EventEmitter<number> = new EventEmitter<number>();\n\n constructor(...args: unknown[]);\n\n constructor() {\n // Bind the `mouseleave` event on the outside since it doesn't change anything in the view.\n this._eventCleanups = this._ngZone.runOutsideAngular(() => [\n this._renderer.listen(this._elementRef.nativeElement, 'mouseleave', () =>\n this._stopInterval(),\n ),\n ]);\n }\n\n /** Called when the user has selected an item via the keyboard. */\n protected abstract _itemSelected(event: KeyboardEvent): void;\n\n ngAfterViewInit() {\n // We need to handle these events manually, because we want to bind passive event listeners.\n\n this._eventCleanups.push(\n _bindEventWithOptions(\n this._renderer,\n this._previousPaginator.nativeElement,\n 'touchstart',\n () => this._handlePaginatorPress('before'),\n passiveEventListenerOptions,\n ),\n _bindEventWithOptions(\n this._renderer,\n this._nextPaginator.nativeElement,\n 'touchstart',\n () => this._handlePaginatorPress('after'),\n passiveEventListenerOptions,\n ),\n );\n }\n\n ngAfterContentInit() {\n const dirChange = this._dir ? this._dir.change : observableOf('ltr');\n // We need to debounce resize events because the alignment logic is expensive.\n // If someone animates the width of tabs, we don't want to realign on every animation frame.\n // Once we haven't seen any more resize events in the last 32ms (~2 animaion frames) we can\n // re-align.\n const resize = this._sharedResizeObserver\n .observe(this._elementRef.nativeElement)\n .pipe(debounceTime(32), takeUntil(this._destroyed));\n // Note: We do not actually need to watch these events for proper functioning of the tabs,\n // the resize events above should capture any viewport resize that we care about. However,\n // removing this is fairly breaking for screenshot tests, so we're leaving it here for now.\n const viewportResize = this._viewportRuler.change(150).pipe(takeUntil(this._destroyed));\n\n const realign = () => {\n this.updatePagination();\n this._alignInkBarToSelectedTab();\n };\n\n this._keyManager = new FocusKeyManager<MatPaginatedTabHeaderItem>(this._items)\n .withHorizontalOrientation(this._getLayoutDirection())\n .withHomeAndEnd()\n .withWrap()\n // Allow focus to land on disabled tabs, as per https://w3c.github.io/aria-practices/#kbd_disabled_controls\n .skipPredicate(() => false);\n\n this._keyManager.updateActiveItem(this._selectedIndex);\n\n // Note: We do not need to realign after the first render for proper functioning of the tabs\n // the resize events above should fire when we first start observing the element. However,\n // removing this is fairly breaking for screenshot tests, so we're leaving it here for now.\n afterNextRender(realign, {injector: this._injector});\n\n // On dir change or resize, realign the ink bar and update the orientation of\n // the key manager if the direction has changed.\n merge(dirChange, viewportResize, resize, this._items.changes, this._itemsResized())\n .pipe(takeUntil(this._destroyed))\n .subscribe(() => {\n // We need to defer this to give the browser some time to recalculate\n // the element dimensions. The call has to be wrapped in `NgZone.run`,\n // because the viewport change handler runs outside of Angular.\n this._ngZone.run(() => {\n Promise.resolve().then(() => {\n // Clamp the scroll distance, because it can change with the number of tabs.\n this._scrollDistance = Math.max(\n 0,\n Math.min(this._getMaxScrollDistance(), this._scrollDistance),\n );\n realign();\n });\n });\n this._keyManager.withHorizontalOrientation(this._getLayoutDirection());\n });\n\n // If there is a change in the focus key manager we need to emit the `indexFocused`\n // event in order to provide a public event that notifies about focus changes. Also we realign\n // the tabs container by scrolling the new focused tab into the visible section.\n this._keyManager.change.subscribe(newFocusIndex => {\n this.indexFocused.emit(newFocusIndex);\n this._setTabFocus(newFocusIndex);\n });\n }\n\n /** Sends any changes that could affect the layout of the items. */\n private _itemsResized(): Observable<ResizeObserverEntry[]> {\n if (typeof ResizeObserver !== 'function') {\n return EMPTY;\n }\n\n return this._items.changes.pipe(\n startWith(this._items),\n switchMap(\n (tabItems: QueryList<MatPaginatedTabHeaderItem>) =>\n new Observable((observer: Observer<ResizeObserverEntry[]>) =>\n this._ngZone.runOutsideAngular(() => {\n const resizeObserver = new ResizeObserver(entries => observer.next(entries));\n tabItems.forEach(item => resizeObserver.observe(item.elementRef.nativeElement));\n return () => {\n resizeObserver.disconnect();\n };\n }),\n ),\n ),\n // Skip the first emit since the resize observer emits when an item\n // is observed for new items when the tab is already inserted\n skip(1),\n // Skip emissions where all the elements are invisible since we don't want\n // the header to try and re-render with invalid measurements. See #25574.\n filter(entries => entries.some(e => e.contentRect.width > 0 && e.contentRect.height > 0)),\n );\n }\n\n ngAfterContentChecked(): void {\n // If the number of tab labels have changed, check if scrolling should be enabled\n if (this._tabLabelCount != this._items.length) {\n this.updatePagination();\n this._tabLabelCount = this._items.length;\n this._changeDetectorRef.markForCheck();\n }\n\n // If the selected index has changed, scroll to the label and check if the scrolling controls\n // should be disabled.\n if (this._selectedIndexChanged) {\n this._scrollToLabel(this._selectedIndex);\n this._checkScrollingControls();\n this._alignInkBarToSelectedTab();\n this._selectedIndexChanged = false;\n this._changeDetectorRef.markForCheck();\n }\n\n // If the scroll distance has been changed (tab selected, focused, scroll controls activated),\n // then translate the header to reflect this.\n if (this._scrollDistanceChanged) {\n this._updateTabScrollPosition();\n this._scrollDistanceChanged = false;\n this._changeDetectorRef.markForCheck();\n }\n }\n\n ngOnDestroy() {\n this._eventCleanups.forEach(cleanup => cleanup());\n this._keyManager?.destroy();\n this._destroyed.next();\n this._destroyed.complete();\n this._stopScrolling.complete();\n }\n\n /** Handles keyboard events on the header. */\n _handleKeydown(event: KeyboardEvent) {\n // We don't handle any key bindings with a modifier key.\n if (hasModifierKey(event)) {\n return;\n }\n\n switch (event.keyCode) {\n case ENTER:\n case SPACE:\n if (this.focusIndex !== this.selectedIndex) {\n const item = this._items.get(this.focusIndex);\n\n if (item && !item.disabled) {\n this.selectFocusedIndex.emit(this.focusIndex);\n this._itemSelected(event);\n }\n }\n break;\n default:\n this._keyManager.onKeydown(event);\n }\n }\n\n /**\n * Callback for when the MutationObserver detects that the content has changed.\n */\n _onContentChanges() {\n const textContent = this._elementRef.nativeElement.textContent;\n\n // We need to diff the text content of the header, because the MutationObserver callback\n // will fire even if the text content didn't change which is inefficient and is prone\n // to infinite loops if a poorly constructed expression is passed in (see #14249).\n if (textContent !== this._currentTextContent) {\n this._currentTextContent = textContent || '';\n\n // The content observer runs outside the `NgZone` by default, which\n // means that we need to bring the callback back in ourselves.\n this._ngZone.run(() => {\n this.updatePagination();\n this._alignInkBarToSelectedTab();\n this._changeDetectorRef.markForCheck();\n });\n }\n }\n\n /**\n * Updates the view whether pagination should be enabled or not.\n *\n * WARNING: Calling this method can be very costly in terms of performance. It should be called\n * as infrequently as possible from outside of the Tabs component as it causes a reflow of the\n * page.\n */\n updatePagination() {\n this._checkPaginationEnabled();\n this._checkScrollingControls();\n this._updateTabScrollPosition();\n }\n\n /** Tracks which element has focus; used for keyboard navigation */\n get focusIndex(): number {\n return this._keyManager ? this._keyManager.activeItemIndex! : 0;\n }\n\n /** When the focus index is set, we must manually send focus to the correct label */\n set focusIndex(value: number) {\n if (!this._isValidIndex(value) || this.focusIndex === value || !this._keyManager) {\n return;\n }\n\n this._keyManager.setActiveItem(value);\n }\n\n /**\n * Determines if an index is valid. If the tabs are not ready yet, we assume that the user is\n * providing a valid index and return true.\n */\n _isValidIndex(index: number): boolean {\n return this._items ? !!this._items.toArray()[index] : true;\n }\n\n /**\n * Sets focus on the HTML element for the label wrapper and scrolls it into the view if\n * scrolling is enabled.\n */\n _setTabFocus(tabIndex: number) {\n if (this._showPaginationControls) {\n this._scrollToLabel(tabIndex);\n }\n\n if (this._items && this._items.length) {\n this._items.toArray()[tabIndex].focus();\n\n // Do not let the browser manage scrolling to focus the element, this will be handled\n // by using translation. In LTR, the scroll left should be 0. In RTL, the scroll width\n // should be the full width minus the offset width.\n const containerEl = this._tabListContainer.nativeElement;\n const dir = this._getLayoutDirection();\n\n if (dir == 'ltr') {\n containerEl.scrollLeft = 0;\n } else {\n containerEl.scrollLeft = containerEl.scrollWidth - containerEl.offsetWidth;\n }\n }\n }\n\n /** The layout direction of the containing app. */\n _getLayoutDirection(): Direction {\n return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';\n }\n\n /** Performs the CSS transformation on the tab list that will cause the list to scroll. */\n _updateTabScrollPosition() {\n if (this.disablePagination) {\n return;\n }\n\n const scrollDistance = this.scrollDistance;\n const translateX = this._getLayoutDirection() === 'ltr' ? -scrollDistance : scrollDistance;\n\n // Don't use `translate3d` here because we don't want to create a new layer. A new layer\n // seems to cause flickering and overflow in Internet Explorer. For example, the ink bar\n // and ripples will exceed the boundaries of the visible tab bar.\n // See: https://github.com/angular/components/issues/10276\n // We round the `transform` here, because transforms with sub-pixel precision cause some\n // browsers to blur the content of the element.\n this._tabList.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`;\n\n // Setting the `transform` on IE will change the scroll offset of the parent, causing the\n // position to be thrown off in some cases. We have to reset it ourselves to ensure that\n // it doesn't get thrown off. Note that we scope it only to IE and Edge, because messing\n // with the scroll position throws off Chrome 71+ in RTL mode (see #14689).\n if (this._platform.TRIDENT || this._platform.EDGE) {\n this._tabListContainer.nativeElement.scrollLeft = 0;\n }\n }\n\n /** Sets the distance in pixels that the tab header should be transformed in the X-axis. */\n get scrollDistance(): number {\n return this._scrollDistance;\n }\n set scrollDistance(value: number) {\n this._scrollTo(value);\n }\n\n /**\n * Moves the tab list in the 'before' or 'after' direction (towards the beginning of the list or\n * the end of the list, respectively). The distance to scroll is computed to be a third of the\n * length of the tab list view window.\n *\n * This is an expensive call that forces a layout reflow to compute box and scroll metrics and\n * should be called sparingly.\n */\n _scrollHeader(direction: ScrollDirection) {\n const viewLength = this._tabListContainer.nativeElement.offsetWidth;\n\n // Move the scroll distance one-third the length of the tab list's viewport.\n const scrollAmount = ((direction == 'before' ? -1 : 1) * viewLength) / 3;\n\n return this._scrollTo(this._scrollDistance + scrollAmount);\n }\n\n /** Handles click events on the pagination arrows. */\n _handlePaginatorClick(direction: ScrollDirection) {\n this._stopInterval();\n this._scrollHeader(direction);\n }\n\n /**\n * Moves the tab list such that the desired tab label (marked by index) is moved into view.\n *\n * This is an expensive call that forces a layout reflow to compute box and scroll metrics and\n * should be called sparingly.\n */\n _scrollToLabel(labelIndex: number) {\n if (this.disablePagination) {\n return;\n }\n\n const selectedLabel = this._items ? this._items.toArray()[labelIndex] : null;\n\n if (!selectedLabel) {\n return;\n }\n\n // The view length is the visible width of the tab labels.\n const viewLength = this._tabListContainer.nativeElement.offsetWidth;\n const {offsetLeft, offsetWidth} = selectedLabel.elementRef.nativeElement;\n\n let labelBeforePos: number, labelAfterPos: number;\n if (this._getLayoutDirection() == 'ltr') {\n labelBeforePos = offsetLeft;\n labelAfterPos = labelBeforePos + offsetWidth;\n } else {\n labelAfterPos = this._tabListInner.nativeElement.offsetWidth - offsetLeft;\n labelBeforePos = labelAfterPos - offsetWidth;\n }\n\n const beforeVisiblePos = this.scrollDistance;\n const afterVisiblePos = this.scrollDistance + viewLength;\n\n if (labelBeforePos < beforeVisiblePos) {\n // Scroll header to move label to the before direction\n this.scrollDistance -= beforeVisiblePos - labelBeforePos;\n } else if (labelAfterPos > afterVisiblePos) {\n // Scroll header to move label to the after direction\n this.scrollDistance += Math.min(\n labelAfterPos - afterVisiblePos,\n labelBeforePos - beforeVisiblePos,\n );\n }\n }\n\n /**\n * Evaluate whether the pagination controls should be displayed. If the scroll width of the\n * tab list is wider than the size of the header container, then the pagination controls should\n * be shown.\n *\n * This is an expensive call that forces a layout reflow to compute box and scroll metrics and\n * should be called sparingly.\n */\n _checkPaginationEnabled() {\n if (this.disablePagination) {\n this._showPaginationControls = false;\n } else {\n const scrollWidth = this._tabListInner.nativeElement.scrollWidth;\n const containerWidth = this._elementRef.nativeElement.offsetWidth;\n\n // Usually checking that the scroll width is greater than the container width should be\n // enough, but on Safari at specific widths the browser ends up rounding up when there's\n // no pagination and rounding down once the pagination is added. This can throw the component\n // into an infinite loop where the pagination shows up and disappears constantly. We work\n // around it by adding a threshold to the calculation. From manual testing the threshold\n // can be lowered to 2px and still resolve the issue, but we set a higher one to be safe.\n // This shouldn't cause any content to be clipped, because tabs have a 24px horizontal\n // padding. See b/316395154 for more information.\n const isEnabled = scrollWidth - containerWidth >= 5;\n\n if (!isEnabled) {\n this.scrollDistance = 0;\n }\n\n if (isEnabled !== this._showPaginationControls) {\n this._showPaginationControls = isEnabled;\n this._changeDetectorRef.markForCheck();\n }\n }\n }\n\n /**\n * Evaluate whether the before and after controls should be enabled or disabled.\n * If the header is at the beginning of the list (scroll distance is equal to 0) then disable the\n * before button. If the header is at the end of the list (scroll distance is equal to the\n * maximum distance we can scroll), then disable the after button.\n *\n * This is an expensive call that forces a layout reflow to compute box and scroll metrics and\n * should be called sparingly.\n */\n _checkScrollingControls() {\n if (this.disablePagination) {\n this._disableScrollAfter = this._disableScrollBefore = true;\n } else {\n // Check if the pagination arrows should be activated.\n this._disableScrollBefore = this.scrollDistance == 0;\n this._disableScrollAfter = this.scrollDistance == this._getMaxScrollDistance();\n this._changeDetectorRef.markForCheck();\n }\n }\n\n /**\n * Determines what is the maximum length in pixels that can be set for the scroll distance. This\n * is equal to the difference in width between the tab list container and tab header container.\n *\n * This is an expensive call that forces a layout reflow to compute box and scroll metrics and\n * should be called sparingly.\n */\n _getMaxScrollDistance(): number {\n const lengthOfTabList = this._tabListInner.nativeElement.scrollWidth;\n const viewLength = this._tabListContainer.nativeElement.offsetWidth;\n return lengthOfTabList - viewLength || 0;\n }\n\n /** Tells the ink-bar to align itself to the current label wrapper */\n _alignInkBarToSelectedTab(): void {\n const selectedItem =\n this._items && this._items.length ? this._items.toArray()[this.selectedIndex] : null;\n const selectedLabelWrapper = selectedItem ? selectedItem.elementRef.nativeElement : null;\n\n if (selectedLabelWrapper) {\n this._inkBar.alignToElement(selectedLabelWrapper);\n } else {\n this._inkBar.hide();\n }\n }\n\n /** Stops the currently-running paginator interval. */\n _stopInterval() {\n this._stopScrolling.next();\n }\n\n /**\n * Handles the user pressing down on one of the paginators.\n * Starts scrolling the header after a certain amount of time.\n * @param direction In which direction the paginator should be scrolled.\n */\n _handlePaginatorPress(direction: ScrollDirection, mouseEvent?: MouseEvent) {\n // Don't start auto scrolling for right mouse button clicks. Note that we shouldn't have to\n // null check the `button`, but we do it so we don't break tests that use fake events.\n if (mouseEvent && mouseEvent.button != null && mouseEvent.button !== 0) {\n return;\n }\n\n // Avoid overlapping timers.\n this._stopInterval();\n\n // Start a timer after the delay and keep firing based on the interval.\n timer(HEADER_SCROLL_DELAY, HEADER_SCROLL_INTERVAL)\n // Keep the timer going until something tells it to stop or the component is destroyed.\n .pipe(takeUntil(merge(this._stopScrolling, this._destroyed)))\n .subscribe(() => {\n const {maxScrollDistance, distance} = this._scrollHeader(direction);\n\n // Stop the timer if we've reached the start or the end.\n if (distance === 0 || distance >= maxScrollDistance) {\n this._stopInterval();\n }\n });\n }\n\n /**\n * Scrolls the header to a given position.\n * @param position Position to which to scroll.\n * @returns Information on the current scroll distance and the maximum.\n */\n private _scrollTo(position: number) {\n if (this.disablePagination) {\n return {maxScrollDistance: 0, distance: 0};\n }\n\n const maxScrollDistance = this._getMaxScrollDistance();\n this._scrollDistance = Math.max(0, Math.min(maxScrollDistance, position));\n\n // Mark that the scroll distance has changed so that after the view is checked, the CSS\n // transformation can move the header.\n this._scrollDistanceChanged = true;\n this._checkScrollingControls();\n\n return {maxScrollDistance, distance: this._scrollDistance};\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n AfterContentChecked,\n AfterContentInit,\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ContentChildren,\n ElementRef,\n Input,\n OnDestroy,\n QueryList,\n ViewChild,\n ViewEncapsulation,\n booleanAttribute,\n} from '@angular/core';\nimport {MatTabLabelWrapper} from './tab-label-wrapper';\nimport {MatInkBar} from './ink-bar';\nimport {MatPaginatedTabHeader} from './paginated-tab-header';\nimport {CdkObserveContent} from '@angular/cdk/observers';\nimport {MatRipple} from '../core';\n\n/**\n * The header of the tab group which displays a list of all the tabs in the tab group. Includes\n * an ink bar that follows the currently selected tab. When the tabs list's width exceeds the\n * width of the header container, then arrows will be displayed to allow the user to scroll\n * left and right across the header.\n * @docs-private\n */\n@Component({\n selector: 'mat-tab-header',\n templateUrl: 'tab-header.html',\n styleUrl: 'tab-header.css',\n encapsulation: ViewEncapsulation.None,\n // tslint:disable-next-line:validate-decorators\n changeDetection: ChangeDetectionStrategy.Default,\n host: {\n 'class': 'mat-mdc-tab-header',\n '[class.mat-mdc-tab-header-pagination-controls-enabled]': '_showPaginationControls',\n '[class.mat-mdc-tab-header-rtl]': \"_getLayoutDirection() == 'rtl'\",\n },\n imports: [MatRipple, CdkObserveContent],\n})\nexport class MatTabHeader\n extends MatPaginatedTabHeader\n implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy\n{\n @ContentChildren(MatTabLabelWrapper, {descendants: false}) _items: QueryList<MatTabLabelWrapper>;\n @ViewChild('tabListContainer', {static: true}) _tabListContainer: ElementRef;\n @ViewChild('tabList', {static: true}) _tabList: ElementRef;\n @ViewChild('tabListInner', {static: true}) _tabListInner: ElementRef;\n @ViewChild('nextPaginator') _nextPaginator: ElementRef<HTMLElement>;\n @ViewChild('previousPaginator') _previousPaginator: ElementRef<HTMLElement>;\n _inkBar: MatInkBar;\n\n /** Aria label of the header. */\n @Input('aria-label') ariaLabel: string;\n\n /** Sets the `aria-labelledby` of the header. */\n @Input('aria-labelledby') ariaLabelledby: string;\n\n /** Whether the ripple effect is disabled or not. */\n @Input({transform: booleanAttribute})\n disableRipple: boolean = false;\n\n override ngAfterContentInit() {\n this._inkBar = new MatInkBar(this._items);\n super.ngAfterContentInit();\n }\n\n protected _itemSelected(event: KeyboardEvent) {\n event.preventDefault();\n }\n}\n","<!--\n Note that this intentionally uses a `div` instead of a `button`, because it's not part of\n the regular tabs flow and is only here to support mouse users. It should also not be focusable.\n-->\n<div class=\"mat-mdc-tab-header-pagination mat-mdc-tab-header-pagination-before\"\n #previousPaginator\n mat-ripple\n [matRippleDisabled]=\"_disableScrollBefore || disableRipple\"\n [class.mat-mdc-tab-header-pagination-disabled]=\"_disableScrollBefore\"\n (click)=\"_handlePaginatorClick('before')\"\n (mousedown)=\"_handlePaginatorPress('before', $event)\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-mdc-tab-header-pagination-chevron\"></div>\n</div>\n\n<div\n class=\"mat-mdc-tab-label-container\"\n #tabListContainer\n (keydown)=\"_handleKeydown($event)\"\n [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\">\n <div\n #tabList\n class=\"mat-mdc-tab-list\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"ariaLabelledby || null\"\n (cdkObserveContent)=\"_onContentChanges()\">\n <div class=\"mat-mdc-tab-labels\" #tabListInner>\n <ng-content></ng-content>\n </div>\n </div>\n</div>\n\n<div class=\"mat-mdc-tab-header-pagination mat-mdc-tab-header-pagination-after\"\n #nextPaginator\n mat-ripple\n [matRippleDisabled]=\"_disableScrollAfter || disableRipple\"\n [class.mat-mdc-tab-header-pagination-disabled]=\"_disableScrollAfter\"\n (mousedown)=\"_handlePaginatorPress('after', $event)\"\n (click)=\"_handlePaginatorClick('after')\"\n (touchend)=\"_stopInterval()\">\n <div class=\"mat-mdc-tab-header-pagination-chevron\"></div>\n</div>\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {InjectionToken} from '@angular/core';\n\n/** Object that can be used to configure the default options for the tabs module. */\nexport interface MatTabsConfig {\n /** Duration for the tab animation. Must be a valid CSS value (e.g. 600ms). */\n animationDuration?: string;\n\n /**\n * Whether pagination should be disabled. This can be used to avoid unnecessary\n * layout recalculations if it's known that pagination won't be required.\n */\n disablePagination?: boolean;\n\n /**\n * Whether the ink bar should fit its width to the size of the tab label content.\n * This only ap