{"version":3,"names":["CSS","container","content","tabCss","CalciteTabStyle0","Tab","this","guid","render","id","el","h","Host","key","labeledBy","class","scale","role","tabIndex","selected","connectedCallback","parentTabsEl","closest","disconnectedCallback","document","body","dispatchEvent","CustomEvent","detail","internalTabChangeHandler","event","targetTabsEl","composedPath","find","tagName","tab","getTabIndex","then","index","stopPropagation","Array","prototype","indexOf","call","nodeListToArray","parentElement","children","filter","matches","updateAriaInfo","tabIds","titleIds","ICON","chevronRight","chevronLeft","containerHasEndTabTitleOverflow","containerHasStartTabTitleOverflow","scrollButton","scrollButtonContainer","scrollBackwardContainerButton","scrollForwardContainerButton","tabTitleSlotWrapper","tabNavCss","CalciteTabNavStyle0","TabNav","effectiveDir","lastScrollWheelAxis","resizeObserver","createObserver","updateScrollingState","onTabTitleWheel","preventDefault","deltaX","deltaY","x","Math","abs","y","scrollBy","scrollByX","currentTarget","onSlotChange","intersectionObserver","disconnect","slottedElements","slotChangeGetAssignedElements","forEach","child","observe","calciteInternalTabNavSlotChange","emit","storeTabTitleWrapperRef","tabTitleContainerEl","root","threshold","scrollToTabTitles","direction","readTask","tabTitleContainer","containerBounds","getBoundingClientRect","tabTitles","from","querySelectorAll","reverse","closestToEdge","tabTitle","tabTitleBounds","containerEndX","width","tabTitleEndX","afterContainerEnd","crossingContainerEnd","beforeContainerStart","crossingContainerStart","scrollerButtonWidth","offsetAdjustment","offsetWidth","clientWidth","scrollTo","offsetLeft","left","behavior","scrollToNextTabTitles","scrollToPreviousTabTitles","handleTabFocus","destination","focused","focusElementInGroup","enabledTabTitles","scrollTabTitleIntoView","onTabTitleScroll","renderScrollButton","overflowDirection","bordered","messages","hasOverflowingStartTabTitle","hasOverflowingEndTabTitle","isEnd","hidden","appearance","nextTabTitles","previousTabTitles","iconFlipRtl","iconStart","kind","onClick","selectedTitleChanged","calciteInternalTabChange","selectedTabId","onMessagesChange","connectLocalized","connectMessages","componentWillLoad","storageKey","storageId","localStorage","getItem","storedTab","JSON","parse","setUpMessages","componentDidLoad","selectedTitle","componentWillRender","layout","getElementDir","componentDidRender","length","every","title","getTabIdentifier","disconnectLocalized","disconnectMessages","position","CSS_UTILITY","rtl","onScroll","onWheel","ref","onSlotchange","focusPreviousTabHandler","target","focusNextTabHandler","focusFirstTabHandler","focusLastTabHandler","internalActivateTabHandler","activatedTabTitle","getIndexOfTabTitle","isLTR","scrollPosition","scrollLeft","overflowingStartTabTitle","overflowingEndTabTitle","right","activateTabHandler","calciteTabChange","internalCloseTabHandler","closedTabTitleEl","handleTabTitleClose","updateTabTitles","getTabTitleById","globalInternalTabChangeHandler","syncId","effectiveLocaleChange","updateMessages","effectiveLocale","selectedTabIdChanged","undefined","setItem","stringify","parseInt","calciteSize24","calciteSize32","calciteSize44","isOverflowStart","isOverflowEnd","visibleWidth","totalContentWidth","scrollWidth","Promise","all","map","ids","filterDirectChildren","closed","selectionModified","visibleTabTitlesIndices","reduce","tabTitleIndices","totalVisibleTabTitles","closable","activateTab","closedTabTitleIndex","findIndex","nextTabTitleIndex","value","requestAnimationFrame","focus","closeButton","containerBottom","contentHasText","iconEnd","iconPresent","titleIcon","selectedIndicator","ICONS","close","tabTitleCss","CalciteTabTitleStyle0","TabTitle","closeClickHandler","closeTabTitleAndNotify","mutationObserver","updateHasText","calciteInternalTabIconChanged","selectedHandler","setupTextContentObserver","isBrowser","iconStartEl","flipRtl","icon","getIconScale","iconEndEl","controls","toAriaBoolean","disabled","InteractiveContainer","hasText","renderCloseButton","closeButtonEl","type","calciteInternalTabTitleRegister","updateHostInteraction","keyDownHandler","includes","calciteInternalTabsFocusNext","calciteInternalTabsFocusPrevious","calciteInternalTabsFocusFirst","calciteInternalTabsFocusLast","userTriggered","payload","calciteInternalTabsActivate","calciteTabsActivate","textContent","trim","childList","subtree","calciteInternalTabsClose","calciteTabsClose","SLOTS","titleGroup","tabsCss","CalciteTabsStyle0","Tabs","defaultSlotChangeHandler","tabs","setDefaultSlotRef","slotEl","handleInheritableProps","updateItems","calciteInternalTabNavSlotChangeHandler","titles","titlesWatcher","updateAriaSettings","tabsWatcher","getSlotAssignedElements","some","sort","a","b","localeCompare","tabDomIndexes","titleDomIndexes","indexInDOM","registryIndex","nav","querySelector","Fragment","name"],"sources":["src/components/tab/resources.ts","src/components/tab/tab.scss?tag=calcite-tab&encapsulation=shadow","src/components/tab/tab.tsx","src/components/tab-nav/resources.ts","src/components/tab-nav/tab-nav.scss?tag=calcite-tab-nav&encapsulation=shadow","src/components/tab-nav/tab-nav.tsx","src/components/tab-title/resources.ts","src/components/tab-title/tab-title.scss?tag=calcite-tab-title&encapsulation=shadow","src/components/tab-title/tab-title.tsx","src/components/tabs/resources.ts","src/components/tabs/tabs.scss?tag=calcite-tabs&encapsulation=shadow","src/components/tabs/tabs.tsx"],"sourcesContent":["export const CSS = {\n container: \"container\",\n content: \"content\",\n};\n","/**\n * CSS Custom Properties\n *\n * These properties can be overridden using the component's tag as selector.\n *\n * @prop --calcite-tab-content-block-padding: Specifies the block padding of the component's content in the `default` slot.\n */\n\n:host([selected]) {\n section,\n .container {\n @apply block;\n }\n}\n\n:host {\n @apply hidden h-full w-full;\n}\n\n:host([selected]) {\n @apply block h-full w-full overflow-auto;\n}\n\n.content {\n @apply box-border;\n padding-block: var(--calcite-internal-tab-content-block-padding);\n}\n\n.scale-s {\n --calcite-internal-tab-content-block-padding: var(--calcite-tab-content-block-padding, theme(\"spacing.1\"));\n @apply text-n2h;\n}\n\n.scale-m {\n --calcite-internal-tab-content-block-padding: var(--calcite-tab-content-block-padding, theme(\"spacing.2\"));\n @apply text-n1h;\n}\n\n.scale-l {\n --calcite-internal-tab-content-block-padding: var(--calcite-tab-content-block-padding, theme(\"spacing.[2.5]\"));\n @apply text-0h;\n}\n\nsection,\n.container {\n @apply hidden h-full w-full;\n}\n\n.container {\n @apply focus-base;\n\n &:focus {\n @apply focus-inset;\n }\n}\n\n@include base-component();\n","import { Component, Element, h, Host, Listen, Method, Prop, State, VNode } from \"@stencil/core\";\nimport { nodeListToArray } from \"../../utils/dom\";\nimport { guid } from \"../../utils/guid\";\nimport { Scale } from \"../interfaces\";\nimport { CSS } from \"./resources\";\nimport { TabChangeEventDetail } from \"./interfaces\";\n\n/**\n * @slot - A slot for adding custom content.\n */\n@Component({\n tag: \"calcite-tab\",\n styleUrl: \"tab.scss\",\n shadow: true,\n})\nexport class Tab {\n //--------------------------------------------------------------------------\n //\n // Properties\n //\n //--------------------------------------------------------------------------\n\n /**\n * Specifies a unique name for the component.\n *\n * When specified, use the same value on the `calcite-tab-title`.\n */\n @Prop({ reflect: true }) tab: string;\n\n /**\n * When `true`, the component's contents are selected.\n *\n * Only one tab can be selected within the `calcite-tabs` parent.\n */\n @Prop({ reflect: true, mutable: true }) selected = false;\n\n /**\n * Specifies the size of the component inherited from the parent `calcite-tabs`, defaults to `m`.\n *\n * @internal\n */\n @Prop() scale: Scale = \"m\";\n\n //--------------------------------------------------------------------------\n //\n // Lifecycle\n //\n //--------------------------------------------------------------------------\n\n render(): VNode {\n const id = this.el.id || this.guid;\n\n return (\n \n \n
\n \n
\n \n
\n );\n }\n\n connectedCallback(): void {\n this.parentTabsEl = this.el.closest(\"calcite-tabs\");\n }\n\n disconnectedCallback(): void {\n // Dispatching to body in order to be listened by other elements that are still connected to the DOM.\n document.body?.dispatchEvent(\n new CustomEvent(\"calciteTabUnregister\", {\n detail: this.el,\n }),\n );\n }\n\n //--------------------------------------------------------------------------\n //\n // Event Listeners\n //\n //--------------------------------------------------------------------------\n\n @Listen(\"calciteInternalTabChange\", { target: \"body\" })\n internalTabChangeHandler(event: CustomEvent): void {\n const targetTabsEl = event\n .composedPath()\n .find((el: HTMLElement) => el.tagName === \"CALCITE-TABS\");\n\n // to allow `` to be nested we need to make sure this\n // `calciteTabChange` event was actually fired from a within the same\n // `` that is the a parent of this tab.\n if (targetTabsEl !== this.parentTabsEl) {\n return;\n }\n\n if (this.tab) {\n this.selected = this.tab === event.detail.tab;\n } else {\n this.getTabIndex().then((index) => {\n this.selected = index === event.detail.tab;\n });\n }\n event.stopPropagation();\n }\n\n //--------------------------------------------------------------------------\n //\n // Public Methods\n //\n //--------------------------------------------------------------------------\n\n /**\n * Returns the index of the component item within the tab array.\n */\n @Method()\n async getTabIndex(): Promise {\n return Array.prototype.indexOf.call(\n nodeListToArray(this.el.parentElement.children).filter((el) => el.matches(\"calcite-tab\")),\n this.el,\n );\n }\n\n //--------------------------------------------------------------------------\n //\n // Private State/Props\n //\n //--------------------------------------------------------------------------\n\n @Element() el: HTMLCalciteTabElement;\n\n parentTabsEl: HTMLCalciteTabsElement;\n\n guid = `calcite-tab-title-${guid()}`;\n\n @State() labeledBy: string;\n\n //--------------------------------------------------------------------------\n //\n // Private Methods\n //\n //--------------------------------------------------------------------------\n\n /**\n * @param tabIds\n * @param titleIds\n * @internal\n */\n @Method()\n async updateAriaInfo(tabIds: string[] = [], titleIds: string[] = []): Promise {\n this.labeledBy = titleIds[tabIds.indexOf(this.el.id)] || null;\n }\n}\n","export const ICON = {\n chevronRight: \"chevron-right\",\n chevronLeft: \"chevron-left\",\n} as const;\n\nexport const CSS = {\n container: \"tab-nav\",\n containerHasEndTabTitleOverflow: \"tab-nav--end-overflow\",\n containerHasStartTabTitleOverflow: \"tab-nav--start-overflow\",\n scrollButton: \"scroll-button\",\n scrollButtonContainer: \"scroll-button-container\",\n scrollBackwardContainerButton: \"scroll-button-container--backward\",\n scrollForwardContainerButton: \"scroll-button-container--forward\",\n tabTitleSlotWrapper: \"tab-titles-slot-wrapper\",\n};\n",":host {\n --calcite-internal-tab-nav-gradient-start-side: left;\n --calcite-internal-tab-nav-gradient-end-side: right;\n\n @apply relative flex;\n}\n\n.scale-s {\n --calcite-internal-tab-nav-scroller-button-width: #{$calcite-size-24};\n min-block-size: theme(\"spacing.6\");\n}\n\n.scale-m {\n --calcite-internal-tab-nav-scroller-button-width: #{$calcite-size-32};\n min-block-size: theme(\"spacing.8\");\n}\n\n.scale-l {\n --calcite-internal-tab-nav-scroller-button-width: #{$calcite-size-44};\n min-block-size: theme(\"spacing.11\");\n}\n\n.calcite--rtl {\n --calcite-internal-tab-nav-gradient-start-side: right;\n --calcite-internal-tab-nav-gradient-end-side: left;\n}\n\n$last-mask-color-stop-position: 51%; // we go beyond the half point to ensure the mask color stops overlap when both start and end are overflowing\n\n.tab-nav--start-overflow {\n .tab-titles-slot-wrapper {\n mask-image: linear-gradient(\n to var(--calcite-internal-tab-nav-gradient-end-side),\n transparent,\n transparent var(--calcite-internal-tab-nav-scroller-button-width),\n white var(--calcite-internal-tab-nav-scroller-button-width),\n white $last-mask-color-stop-position\n );\n }\n}\n\n.tab-nav--end-overflow {\n .tab-titles-slot-wrapper {\n mask-image: linear-gradient(\n to var(--calcite-internal-tab-nav-gradient-start-side),\n transparent,\n transparent var(--calcite-internal-tab-nav-scroller-button-width),\n white var(--calcite-internal-tab-nav-scroller-button-width),\n white $last-mask-color-stop-position\n );\n }\n}\n\n.tab-nav--start-overflow.tab-nav--end-overflow {\n .tab-titles-slot-wrapper {\n mask-image: linear-gradient(\n to var(--calcite-internal-tab-nav-gradient-end-side),\n transparent,\n transparent var(--calcite-internal-tab-nav-scroller-button-width),\n white var(--calcite-internal-tab-nav-scroller-button-width),\n white $last-mask-color-stop-position,\n transparent $last-mask-color-stop-position\n ),\n linear-gradient(\n to var(--calcite-internal-tab-nav-gradient-start-side),\n transparent,\n transparent var(--calcite-internal-tab-nav-scroller-button-width),\n white var(--calcite-internal-tab-nav-scroller-button-width),\n white $last-mask-color-stop-position,\n transparent $last-mask-color-stop-position\n );\n }\n}\n\n.tab-nav::-webkit-scrollbar {\n display: none;\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n:host([layout=\"center\"]) {\n ::slotted(calcite-tab-title) {\n display: flex;\n flex-grow: 1;\n flex-shrink: 0;\n min-inline-size: auto;\n white-space: nowrap;\n }\n\n ::slotted(calcite-tab-title[selected]) {\n overflow: unset;\n }\n}\n\n:host(:not([bordered])) {\n .scale-l {\n --calcite-internal-tab-nav-gap: var(--calcite-size-xxl);\n }\n .scale-m {\n --calcite-internal-tab-nav-gap: var(--calcite-size-xl);\n }\n .scale-s {\n --calcite-internal-tab-nav-gap: var(--calcite-size-lg);\n }\n\n .tab-titles-slot-wrapper {\n gap: var(--calcite-internal-tab-nav-gap);\n }\n}\n\n:host([layout=\"center\"]:not([bordered])) {\n .tab-titles-slot-wrapper {\n padding-inline: var(--calcite-spacing-xxl);\n }\n}\n\n.tab-nav,\n.tab-titles-slot-wrapper {\n @apply flex\n w-full\n justify-start\n whitespace-nowrap\n overflow-hidden;\n}\n\n.scroll-button-container {\n @apply absolute bottom-0 top-0;\n\n calcite-button {\n --calcite-offset-invert-focus: 1;\n --calcite-color-text-1: var(--calcite-color-text-3);\n\n block-size: 100%;\n\n &:hover {\n --calcite-color-text-1: unset;\n --calcite-color-foreground-1: var(--calcite-color-transparent-hover);\n --calcite-color-foreground-3: var(--calcite-color-transparent);\n }\n }\n}\n\n.scroll-button-container--forward {\n inset-inline-end: 0;\n z-index: var(--calcite-z-index);\n}\n\n.scroll-button-container--backward {\n inset-inline-start: 0;\n z-index: var(--calcite-z-index);\n}\n\n:host(:not([bordered])) {\n .scroll-button-container--backward,\n .scroll-button-container--forward {\n &::before {\n background-color: var(--calcite-color-border-3);\n content: \"\";\n inline-size: var(--calcite-border-width-sm);\n inset-block-start: var(--calcite-border-width-md);\n inset-block-end: var(--calcite-border-width-md);\n position: absolute;\n }\n }\n\n .scroll-button-container--backward::before {\n inset-inline-end: 0;\n }\n\n .scroll-button-container--forward::before {\n inset-inline-start: 0;\n }\n}\n\n@include base-component();\n","import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Prop,\n readTask,\n State,\n VNode,\n Watch,\n} from \"@stencil/core\";\nimport {\n calciteSize24,\n calciteSize32,\n calciteSize44,\n} from \"@esri/calcite-design-tokens/dist/es6/core\";\nimport {\n Direction,\n filterDirectChildren,\n focusElementInGroup,\n FocusElementInGroupDestination,\n getElementDir,\n slotChangeGetAssignedElements,\n} from \"../../utils/dom\";\nimport { createObserver } from \"../../utils/observers\";\nimport { Scale } from \"../interfaces\";\nimport { TabChangeEventDetail, TabCloseEventDetail } from \"../tab/interfaces\";\nimport { TabID, TabLayout, TabPosition } from \"../tabs/interfaces\";\nimport { connectLocalized, disconnectLocalized, LocalizedComponent } from \"../../utils/locale\";\nimport {\n connectMessages,\n disconnectMessages,\n setUpMessages,\n T9nComponent,\n updateMessages,\n} from \"../../utils/t9n\";\nimport { CSS_UTILITY } from \"../../utils/resources\";\nimport { CSS, ICON } from \"./resources\";\nimport { TabNavMessages } from \"./assets/tab-nav/t9n\";\n\n/**\n * @slot - A slot for adding `calcite-tab-title`s.\n */\n@Component({\n tag: \"calcite-tab-nav\",\n styleUrl: \"tab-nav.scss\",\n shadow: true,\n assetsDirs: [\"assets\"],\n})\nexport class TabNav implements LocalizedComponent, T9nComponent {\n //--------------------------------------------------------------------------\n //\n // Properties\n //\n //--------------------------------------------------------------------------\n\n /**\n * Specifies the name when saving selected `calcite-tab` data to `localStorage`.\n */\n @Prop({ reflect: true }) storageId: string;\n\n /**\n * Specifies text to update multiple components to keep in sync if one changes.\n */\n @Prop({ reflect: true }) syncId: string;\n\n /**\n * Specifies the component's selected `calcite-tab-title`.\n *\n * @readonly\n */\n @Prop({ mutable: true }) selectedTitle: HTMLCalciteTabTitleElement = null;\n\n @Watch(\"selectedTitle\")\n selectedTitleChanged(): void {\n this.calciteInternalTabChange.emit({\n tab: this.selectedTabId,\n });\n }\n\n /**\n * Specifies the size of the component inherited from the parent `calcite-tabs`, defaults to `m`.\n *\n * @internal\n */\n @Prop() scale: Scale = \"m\";\n\n /**\n * @internal\n */\n @Prop({ reflect: true, mutable: true }) layout: TabLayout = \"inline\";\n\n /**\n * Specifies the position of `calcite-tab-nav` and `calcite-tab-title` components in relation to, and is inherited from the parent `calcite-tabs`, defaults to `top`.\n *\n * @internal\n */\n @Prop() position: TabPosition = \"bottom\";\n\n /**\n * @internal\n */\n @Prop({ reflect: true, mutable: true }) bordered = false;\n\n /**\n * Made into a prop for testing purposes only.\n *\n * @internal\n */\n // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module\n @Prop({ mutable: true }) messages: TabNavMessages;\n\n /**\n * Use this property to override individual strings used by the component.\n */\n // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module\n @Prop({ mutable: true }) messageOverrides: Partial;\n\n @Watch(\"messageOverrides\")\n onMessagesChange(): void {\n /* wired up by t9n util */\n }\n\n //--------------------------------------------------------------------------\n //\n // Lifecycle\n //\n //--------------------------------------------------------------------------\n\n connectedCallback(): void {\n this.parentTabsEl = this.el.closest(\"calcite-tabs\");\n this.resizeObserver?.observe(this.el);\n connectLocalized(this);\n connectMessages(this);\n }\n\n async componentWillLoad(): Promise {\n const storageKey = `calcite-tab-nav-${this.storageId}`;\n if (localStorage && this.storageId && localStorage.getItem(storageKey)) {\n const storedTab = JSON.parse(localStorage.getItem(storageKey));\n this.selectedTabId = storedTab;\n }\n await setUpMessages(this);\n }\n\n componentDidLoad(): void {\n this.scrollTabTitleIntoView(this.selectedTitle, \"instant\");\n }\n\n componentWillRender(): void {\n const { parentTabsEl } = this;\n\n this.layout = parentTabsEl?.layout;\n this.bordered = parentTabsEl?.bordered;\n this.effectiveDir = getElementDir(this.el);\n }\n\n componentDidRender(): void {\n // if every tab title is active select the first tab.\n if (\n this.tabTitles.length &&\n this.tabTitles.every((title) => !title.selected) &&\n !this.selectedTabId\n ) {\n this.tabTitles[0].getTabIdentifier().then((tab) => {\n this.calciteInternalTabChange.emit({\n tab,\n });\n });\n }\n }\n\n disconnectedCallback(): void {\n this.resizeObserver?.disconnect();\n disconnectLocalized(this);\n disconnectMessages(this);\n }\n\n //--------------------------------------------------------------------------\n //\n // Render Methods\n //\n //--------------------------------------------------------------------------\n\n render(): VNode {\n return (\n \n \n {this.renderScrollButton(\"start\")}\n \n \n \n {this.renderScrollButton(\"end\")}\n \n \n );\n }\n\n //--------------------------------------------------------------------------\n //\n // Event Listeners\n //\n //--------------------------------------------------------------------------\n\n @Listen(\"calciteInternalTabsFocusPrevious\")\n focusPreviousTabHandler(event: CustomEvent): void {\n this.handleTabFocus(event, event.target as HTMLCalciteTabTitleElement, \"previous\");\n }\n\n @Listen(\"calciteInternalTabsFocusNext\")\n focusNextTabHandler(event: CustomEvent): void {\n this.handleTabFocus(event, event.target as HTMLCalciteTabTitleElement, \"next\");\n }\n\n @Listen(\"calciteInternalTabsFocusFirst\")\n focusFirstTabHandler(event: CustomEvent): void {\n this.handleTabFocus(event, event.target as HTMLCalciteTabTitleElement, \"first\");\n }\n\n @Listen(\"calciteInternalTabsFocusLast\")\n focusLastTabHandler(event: CustomEvent): void {\n this.handleTabFocus(event, event.target as HTMLCalciteTabTitleElement, \"last\");\n }\n\n @Listen(\"calciteInternalTabsActivate\")\n internalActivateTabHandler(event: CustomEvent): void {\n const activatedTabTitle = event.target as HTMLCalciteTabTitleElement;\n\n this.selectedTabId = event.detail.tab\n ? event.detail.tab\n : this.getIndexOfTabTitle(activatedTabTitle);\n event.stopPropagation();\n\n this.selectedTitle = activatedTabTitle;\n this.scrollTabTitleIntoView(activatedTabTitle);\n }\n\n private scrollTabTitleIntoView(\n activatedTabTitle: HTMLCalciteTabTitleElement,\n behavior: ScrollBehavior = \"smooth\",\n ): void {\n if (!activatedTabTitle) {\n return;\n }\n\n readTask(() => {\n const isLTR = this.effectiveDir === \"ltr\";\n const tabTitleContainer = this.tabTitleContainerEl;\n const containerBounds = tabTitleContainer.getBoundingClientRect();\n const tabTitleBounds = activatedTabTitle.getBoundingClientRect();\n const scrollPosition = tabTitleContainer.scrollLeft;\n const overflowingStartTabTitle = isLTR\n ? this.hasOverflowingStartTabTitle\n : this.hasOverflowingEndTabTitle;\n const overflowingEndTabTitle = isLTR\n ? this.hasOverflowingEndTabTitle\n : this.hasOverflowingStartTabTitle;\n\n if (\n tabTitleBounds.left <\n containerBounds.left + (overflowingStartTabTitle ? this.scrollerButtonWidth : 0)\n ) {\n const left =\n scrollPosition + (tabTitleBounds.left - containerBounds.left) - this.scrollerButtonWidth;\n tabTitleContainer.scrollTo({ left, behavior });\n } else if (\n tabTitleBounds.right >\n containerBounds.right - (overflowingEndTabTitle ? this.scrollerButtonWidth : 0)\n ) {\n const left =\n scrollPosition +\n (tabTitleBounds.right - containerBounds.right) +\n this.scrollerButtonWidth;\n tabTitleContainer.scrollTo({ left, behavior });\n }\n });\n }\n\n @Listen(\"calciteTabsActivate\")\n activateTabHandler(event: CustomEvent): void {\n this.calciteTabChange.emit();\n event.stopPropagation();\n }\n\n @Listen(\"calciteInternalTabsClose\")\n internalCloseTabHandler(event: CustomEvent): void {\n const closedTabTitleEl = event.target as HTMLCalciteTabTitleElement;\n this.handleTabTitleClose(closedTabTitleEl);\n event.stopPropagation();\n }\n\n /**\n * Check for active tabs on register and update selected\n *\n * @param event\n */\n @Listen(\"calciteInternalTabTitleRegister\")\n async updateTabTitles(event: CustomEvent): Promise {\n if ((event.target as HTMLCalciteTabTitleElement).selected) {\n this.selectedTabId = event.detail;\n this.selectedTitle = await this.getTabTitleById(this.selectedTabId);\n }\n }\n\n @Listen(\"calciteInternalTabChange\", { target: \"body\" })\n globalInternalTabChangeHandler(event: CustomEvent): void {\n if (\n this.syncId &&\n event.target !== this.el &&\n (event.target as HTMLCalciteTabNavElement).syncId === this.syncId &&\n this.selectedTabId !== event.detail.tab\n ) {\n this.selectedTabId = event.detail.tab;\n }\n event.stopPropagation();\n }\n\n //--------------------------------------------------------------------------\n //\n // Events\n //\n //--------------------------------------------------------------------------\n\n /**\n * Emits when the selected `calcite-tab` changes.\n */\n @Event({ cancelable: false }) calciteTabChange: EventEmitter;\n\n /**\n * @internal\n */\n @Event() calciteInternalTabNavSlotChange: EventEmitter;\n\n /**\n * @internal\n */\n @Event({ cancelable: false }) calciteInternalTabChange: EventEmitter;\n\n //--------------------------------------------------------------------------\n //\n // Private State/Props\n //\n //--------------------------------------------------------------------------\n\n @Element() el: HTMLCalciteTabNavElement;\n\n @State() defaultMessages: TabNavMessages;\n\n @State() effectiveLocale = \"\";\n\n @Watch(\"effectiveLocale\")\n effectiveLocaleChange(): void {\n updateMessages(this, this.effectiveLocale);\n }\n\n @State() private hasOverflowingStartTabTitle = false;\n\n @State() private hasOverflowingEndTabTitle = false;\n\n @State() private selectedTabId: TabID;\n\n @Watch(\"selectedTabId\")\n async selectedTabIdChanged(): Promise {\n if (\n localStorage &&\n this.storageId &&\n this.selectedTabId !== undefined &&\n this.selectedTabId !== null\n ) {\n localStorage.setItem(`calcite-tab-nav-${this.storageId}`, JSON.stringify(this.selectedTabId));\n }\n\n this.calciteInternalTabChange.emit({\n tab: this.selectedTabId,\n });\n }\n\n private effectiveDir: Direction = \"ltr\";\n\n private lastScrollWheelAxis: \"x\" | \"y\" = \"x\";\n\n private parentTabsEl: HTMLCalciteTabsElement;\n\n private tabTitleContainerEl: HTMLDivElement;\n\n private intersectionObserver: IntersectionObserver;\n\n private resizeObserver = createObserver(\"resize\", () => {\n this.updateScrollingState();\n });\n\n private get scrollerButtonWidth(): number {\n const { scale } = this;\n return parseInt(scale === \"s\" ? calciteSize24 : scale === \"m\" ? calciteSize32 : calciteSize44);\n }\n\n //--------------------------------------------------------------------------\n //\n // Private Methods\n //\n //--------------------------------------------------------------------------\n\n private onTabTitleWheel = (event: WheelEvent): void => {\n event.preventDefault();\n\n const { deltaX, deltaY } = event;\n const x = Math.abs(deltaX);\n const y = Math.abs(deltaY);\n\n let scrollBy: number;\n\n if (x === y) {\n scrollBy = this.lastScrollWheelAxis === \"x\" ? deltaX : deltaY;\n } else if (x > y) {\n scrollBy = deltaX;\n this.lastScrollWheelAxis = \"x\";\n } else {\n scrollBy = deltaY;\n this.lastScrollWheelAxis = \"y\";\n }\n\n const scrollByX = (this.effectiveDir === \"rtl\" ? -1 : 1) * scrollBy;\n (event.currentTarget as HTMLDivElement).scrollBy(scrollByX, 0);\n };\n\n private onSlotChange = (event: Event): void => {\n this.intersectionObserver?.disconnect();\n\n const slottedElements = slotChangeGetAssignedElements(event, \"calcite-tab-title\");\n slottedElements.forEach((child) => {\n this.intersectionObserver?.observe(child);\n });\n this.calciteInternalTabNavSlotChange.emit(slottedElements);\n };\n\n private storeTabTitleWrapperRef = (el: HTMLDivElement) => {\n this.tabTitleContainerEl = el;\n this.intersectionObserver = createObserver(\"intersection\", () => this.updateScrollingState(), {\n root: el,\n threshold: [0, 0.5, 1],\n });\n };\n\n private updateScrollingState(): void {\n const tabTitleContainer = this.tabTitleContainerEl;\n\n if (!tabTitleContainer) {\n return;\n }\n\n let isOverflowStart: boolean;\n let isOverflowEnd: boolean;\n\n const scrollPosition = tabTitleContainer.scrollLeft;\n const visibleWidth = tabTitleContainer.clientWidth;\n const totalContentWidth = tabTitleContainer.scrollWidth;\n\n if (this.effectiveDir === \"ltr\") {\n isOverflowStart = scrollPosition > 0;\n isOverflowEnd = scrollPosition + visibleWidth < totalContentWidth;\n } else {\n isOverflowStart = scrollPosition < 0;\n isOverflowEnd = scrollPosition !== -(totalContentWidth - visibleWidth);\n }\n\n this.hasOverflowingStartTabTitle = isOverflowStart;\n this.hasOverflowingEndTabTitle = isOverflowEnd;\n }\n\n private scrollToTabTitles = (direction: \"forward\" | \"backward\"): void => {\n readTask(() => {\n const tabTitleContainer = this.tabTitleContainerEl;\n const containerBounds = tabTitleContainer.getBoundingClientRect();\n const tabTitles = Array.from(this.el.querySelectorAll(\"calcite-tab-title\"));\n const { effectiveDir } = this;\n\n if (direction === \"forward\") {\n tabTitles.reverse();\n }\n\n let closestToEdge: HTMLCalciteTabTitleElement = null;\n\n tabTitles.forEach((tabTitle) => {\n const tabTitleBounds = tabTitle.getBoundingClientRect();\n const containerEndX = containerBounds.x + containerBounds.width;\n const tabTitleEndX = tabTitleBounds.x + tabTitleBounds.width;\n\n if (\n (direction === \"forward\" && effectiveDir === \"ltr\") ||\n (direction === \"backward\" && effectiveDir === \"rtl\")\n ) {\n const afterContainerEnd = tabTitleBounds.x > containerEndX;\n\n if (afterContainerEnd) {\n closestToEdge = tabTitle;\n } else {\n const crossingContainerEnd =\n tabTitleEndX > containerEndX && tabTitleBounds.x > containerBounds.x;\n\n if (crossingContainerEnd) {\n closestToEdge = tabTitle;\n }\n }\n } else {\n const beforeContainerStart = tabTitleEndX < containerBounds.x;\n\n if (beforeContainerStart) {\n closestToEdge = tabTitle;\n } else {\n const crossingContainerStart =\n tabTitleEndX < containerEndX && tabTitleBounds.x < containerBounds.x;\n\n if (crossingContainerStart) {\n closestToEdge = tabTitle;\n }\n }\n }\n });\n\n if (closestToEdge) {\n const { scrollerButtonWidth } = this;\n const offsetAdjustment =\n (direction === \"forward\" && effectiveDir === \"ltr\") ||\n (direction === \"backward\" && effectiveDir === \"rtl\")\n ? -scrollerButtonWidth\n : closestToEdge.offsetWidth - tabTitleContainer.clientWidth + scrollerButtonWidth;\n const scrollTo = closestToEdge.offsetLeft + offsetAdjustment;\n\n tabTitleContainer.scrollTo({\n left: scrollTo,\n behavior: \"smooth\",\n });\n }\n });\n };\n\n private scrollToNextTabTitles = (): void => this.scrollToTabTitles(\"forward\");\n\n private scrollToPreviousTabTitles = (): void => this.scrollToTabTitles(\"backward\");\n\n handleTabFocus = (\n event: CustomEvent,\n el: HTMLCalciteTabTitleElement,\n destination: FocusElementInGroupDestination,\n ): void => {\n const focused = focusElementInGroup(\n this.enabledTabTitles,\n el,\n destination,\n );\n this.scrollTabTitleIntoView(focused, \"instant\");\n\n event.stopPropagation();\n };\n\n getIndexOfTabTitle(el: HTMLCalciteTabTitleElement, tabTitles = this.tabTitles): number {\n // In most cases, since these indexes correlate with tab contents, we want to consider all tab titles.\n // However, when doing relative index operations, it makes sense to pass in this.enabledTabTitles as the 2nd arg.\n return tabTitles.indexOf(el);\n }\n\n private onTabTitleScroll = (): void => {\n this.updateScrollingState();\n };\n\n async getTabTitleById(id: TabID): Promise {\n return Promise.all(this.tabTitles.map((el) => el.getTabIdentifier())).then((ids) => {\n return this.tabTitles[ids.indexOf(id)];\n });\n }\n\n get tabTitles(): HTMLCalciteTabTitleElement[] {\n return filterDirectChildren(this.el, \"calcite-tab-title\");\n }\n\n get enabledTabTitles(): HTMLCalciteTabTitleElement[] {\n return filterDirectChildren(\n this.el,\n \"calcite-tab-title:not([disabled])\",\n ).filter((tabTitle) => !tabTitle.closed);\n }\n\n private handleTabTitleClose(closedTabTitleEl: HTMLCalciteTabTitleElement): void {\n const { tabTitles } = this;\n const selectionModified = closedTabTitleEl.selected;\n\n const visibleTabTitlesIndices = tabTitles.reduce(\n (tabTitleIndices: number[], tabTitle, index) =>\n !tabTitle.closed ? [...tabTitleIndices, index] : tabTitleIndices,\n [],\n );\n const totalVisibleTabTitles = visibleTabTitlesIndices.length;\n\n if (totalVisibleTabTitles === 1 && tabTitles[visibleTabTitlesIndices[0]].closable) {\n tabTitles[visibleTabTitlesIndices[0]].closable = false;\n this.selectedTabId = visibleTabTitlesIndices[0];\n\n if (selectionModified) {\n tabTitles[visibleTabTitlesIndices[0]].activateTab();\n }\n } else if (totalVisibleTabTitles > 1) {\n const closedTabTitleIndex = tabTitles.findIndex((el) => el === closedTabTitleEl);\n\n const nextTabTitleIndex = visibleTabTitlesIndices.find(\n (value) => value > closedTabTitleIndex,\n );\n\n if (this.selectedTabId === closedTabTitleIndex) {\n this.selectedTabId = nextTabTitleIndex ? nextTabTitleIndex : totalVisibleTabTitles - 1;\n tabTitles[this.selectedTabId].activateTab();\n }\n }\n\n requestAnimationFrame(() => {\n tabTitles[this.selectedTabId].focus();\n });\n }\n\n private renderScrollButton = (overflowDirection: \"start\" | \"end\"): VNode => {\n const { bordered, messages, hasOverflowingStartTabTitle, hasOverflowingEndTabTitle, scale } =\n this;\n const isEnd = overflowDirection === \"end\";\n\n return (\n