UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

727 lines (713 loc) • 42.5 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const index = require('./index-55f8a3b7.js'); const dom = require('./dom-18ca68ff.js'); const guid = require('./guid-db20443e.js'); const observers = require('./observers-83b3999d.js'); const interactive = require('./interactive-26294f2c.js'); const locale = require('./locale-fc347462.js'); const t9n = require('./t9n-14d528c4.js'); require('./resources-45d84c94.js'); require('./browser-28ea2ce1.js'); require('./key-2ce02f02.js'); const tabCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-right{0%{opacity:0;transform:translate3D(-5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-left{0%{opacity:0;transform:translate3D(5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-right{animation-name:in-right}.calcite-animate__in-left{animation-name:in-left}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing);--calcite-floating-ui-z-index:var(--calcite-app-z-index-dropdown)}:host([hidden]){display:none}:host([selected]) section,:host([selected]) .container{display:block}:host{display:none;block-size:100%;inline-size:100%}:host([selected]){display:block;block-size:100%;inline-size:100%;overflow:auto}section,.container{display:none;block-size:100%;inline-size:100%}:host([scale=s]){padding-block:0.25rem;font-size:var(--calcite-font-size--2);line-height:1rem}:host([scale=m]){padding-block:0.5rem;font-size:var(--calcite-font-size--1);line-height:1rem}:host([scale=l]){font-size:var(--calcite-font-size-0);line-height:1.25rem;padding-block:13px}"; const Tab = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.calciteInternalTabRegister = index.createEvent(this, "calciteInternalTabRegister", 6); this.guid = `calcite-tab-title-${guid.guid()}`; this.tab = undefined; this.selected = false; this.scale = "m"; this.labeledBy = undefined; } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- render() { const id = this.el.id || this.guid; return (index.h(index.Host, { "aria-labelledby": this.labeledBy, id: id }, index.h("div", { class: "container", role: "tabpanel", tabIndex: this.selected ? 0 : -1 }, index.h("section", null, index.h("slot", null))))); } connectedCallback() { this.parentTabsEl = this.el.closest("calcite-tabs"); } componentDidLoad() { this.calciteInternalTabRegister.emit(); } componentWillRender() { this.scale = this.parentTabsEl?.scale; } disconnectedCallback() { // Dispatching to body in order to be listened by other elements that are still connected to the DOM. document.body?.dispatchEvent(new CustomEvent("calciteTabUnregister", { detail: this.el })); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- internalTabChangeHandler(event) { const targetTabsEl = event .composedPath() .find((el) => el.tagName === "CALCITE-TABS"); // to allow `<calcite-tabs>` to be nested we need to make sure this // `calciteTabChange` event was actually fired from a within the same // `<calcite-tabs>` that is the a parent of this tab. if (targetTabsEl !== this.parentTabsEl) { return; } if (this.tab) { this.selected = this.tab === event.detail.tab; } else { this.getTabIndex().then((index) => { this.selected = index === event.detail.tab; }); } event.stopPropagation(); } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** * Returns the index of the component item within the tab array. */ async getTabIndex() { return Array.prototype.indexOf.call(dom.nodeListToArray(this.el.parentElement.children).filter((el) => el.matches("calcite-tab")), this.el); } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * @param tabIds * @param titleIds * @internal */ async updateAriaInfo(tabIds = [], titleIds = []) { this.labeledBy = titleIds[tabIds.indexOf(this.el.id)] || null; } get el() { return index.getElement(this); } }; Tab.style = tabCss; const tabNavCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-right{0%{opacity:0;transform:translate3D(-5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-left{0%{opacity:0;transform:translate3D(5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-right{animation-name:in-right}.calcite-animate__in-left{animation-name:in-left}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing);--calcite-floating-ui-z-index:var(--calcite-app-z-index-dropdown)}:host([hidden]){display:none}:host{position:relative;display:flex}:host([scale=s]){min-block-size:1.5rem}:host([scale=m]){min-block-size:2rem}:host([scale=l]){min-block-size:2.75rem}.tab-nav{display:flex;inline-size:100%;justify-content:flex-start;overflow:auto}.tab-nav-active-indicator-container{position:absolute;inset-inline:0px;inset-block-end:0px;block-size:0.125rem;inline-size:100%;overflow:hidden}.tab-nav-active-indicator{position:absolute;inset-block-end:0px;display:block;block-size:0.125rem;background-color:var(--calcite-ui-brand);transition-property:all;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;transition-timing-function:cubic-bezier(0, 0, 0.2, 1)}:host([layout=center]) .tab-nav{justify-content:space-evenly}:host([position=bottom]) .tab-nav-active-indicator{inset-block-end:unset;inset-block-start:0px}:host([position=bottom]) .tab-nav-active-indicator-container{inset-block-end:unset;inset-block-start:0px}:host([bordered]) .tab-nav-active-indicator-container{inset-block-end:unset}:host([bordered][position=bottom]) .tab-nav-active-indicator-container{inset-block-end:0;inset-block-start:unset}@media (forced-colors: active){.tab-nav-active-indicator{background-color:highlight}}"; const TabNav = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.calciteTabChange = index.createEvent(this, "calciteTabChange", 6); this.calciteInternalTabChange = index.createEvent(this, "calciteInternalTabChange", 6); this.animationActiveDuration = 0.3; this.resizeObserver = observers.createObserver("resize", () => { if (!this.activeIndicatorEl) { return; } // remove active indicator transition duration during resize to prevent wobble this.activeIndicatorEl.style.transitionDuration = "0s"; this.updateActiveWidth(); this.updateOffsetPosition(); }); //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- this.handleTabFocus = (event, el, destination) => { dom.focusElementInGroup(this.enabledTabTitles, el, destination); event.stopPropagation(); }; this.handleContainerScroll = () => { // remove active indicator transition duration while container is scrolling to prevent wobble this.activeIndicatorEl.style.transitionDuration = "0s"; this.updateOffsetPosition(); }; this.storageId = undefined; this.syncId = undefined; this.selectedTitle = null; this.scale = "m"; this.layout = "inline"; this.position = "bottom"; this.bordered = false; this.indicatorOffset = undefined; this.indicatorWidth = undefined; this.selectedTabId = undefined; } async selectedTabIdChanged() { if (localStorage && this.storageId && this.selectedTabId !== undefined && this.selectedTabId !== null) { localStorage.setItem(`calcite-tab-nav-${this.storageId}`, JSON.stringify(this.selectedTabId)); } this.calciteInternalTabChange.emit({ tab: this.selectedTabId }); this.selectedTitle = await this.getTabTitleById(this.selectedTabId); } selectedTitleChanged() { this.updateOffsetPosition(); this.updateActiveWidth(); // reset the animation time on tab selection this.activeIndicatorEl.style.transitionDuration = `${this.animationActiveDuration}s`; } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- connectedCallback() { this.parentTabsEl = this.el.closest("calcite-tabs"); this.resizeObserver?.observe(this.el); } disconnectedCallback() { this.resizeObserver?.disconnect(); } componentWillLoad() { const storageKey = `calcite-tab-nav-${this.storageId}`; if (localStorage && this.storageId && localStorage.getItem(storageKey)) { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } } componentWillRender() { const { parentTabsEl } = this; this.layout = parentTabsEl?.layout; this.position = parentTabsEl?.position; this.scale = parentTabsEl?.scale; this.bordered = parentTabsEl?.bordered; // fix issue with active tab-title not lining up with blue indicator if (this.selectedTitle) { this.updateOffsetPosition(); } } componentDidRender() { // if every tab title is active select the first tab. if (this.tabTitles.length && this.tabTitles.every((title) => !title.selected) && !this.selectedTabId) { this.tabTitles[0].getTabIdentifier().then((tab) => { this.calciteInternalTabChange.emit({ tab }); }); } } render() { const dir = dom.getElementDir(this.el); const width = `${this.indicatorWidth}px`; const offset = `${this.indicatorOffset}px`; const indicatorStyle = dir !== "rtl" ? { width, left: offset } : { width, right: offset }; return (index.h(index.Host, { role: "tablist" }, index.h("div", { class: "tab-nav", onScroll: this.handleContainerScroll, // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.tabNavEl = el) }, index.h("slot", null), index.h("div", { class: "tab-nav-active-indicator-container", // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.activeIndicatorContainerEl = el) }, index.h("div", { class: "tab-nav-active-indicator", style: indicatorStyle, // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.activeIndicatorEl = el) }))))); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- focusPreviousTabHandler(event) { this.handleTabFocus(event, event.target, "previous"); } focusNextTabHandler(event) { this.handleTabFocus(event, event.target, "next"); } focusFirstTabHandler(event) { this.handleTabFocus(event, event.target, "first"); } focusLastTabHandler(event) { this.handleTabFocus(event, event.target, "last"); } internalActivateTabHandler(event) { this.selectedTabId = event.detail.tab ? event.detail.tab : this.getIndexOfTabTitle(event.target); event.stopPropagation(); } activateTabHandler(event) { this.calciteTabChange.emit(); event.stopPropagation(); } internalCloseTabHandler(event) { const closedTabTitleEl = event.target; this.handleTabTitleClose(closedTabTitleEl); event.stopPropagation(); } /** * Check for active tabs on register and update selected * * @param event */ updateTabTitles(event) { if (event.target.selected) { this.selectedTabId = event.detail; } } globalInternalTabChangeHandler(event) { if (this.syncId && event.target !== this.el && event.target.syncId === this.syncId && this.selectedTabId !== event.detail.tab) { this.selectedTabId = event.detail.tab; } event.stopPropagation(); } iconStartChangeHandler() { this.updateActiveWidth(); } updateOffsetPosition() { const dir = dom.getElementDir(this.el); const navWidth = this.activeIndicatorContainerEl?.offsetWidth; const tabLeft = this.selectedTitle?.offsetLeft; const tabWidth = this.selectedTitle?.offsetWidth; const offsetRight = navWidth - (tabLeft + tabWidth); this.indicatorOffset = dir !== "rtl" ? tabLeft - this.tabNavEl?.scrollLeft : offsetRight + this.tabNavEl?.scrollLeft; } updateActiveWidth() { this.indicatorWidth = this.selectedTitle?.offsetWidth; } getIndexOfTabTitle(el, tabTitles = this.tabTitles) { // In most cases, since these indexes correlate with tab contents, we want to consider all tab titles. // However, when doing relative index operations, it makes sense to pass in this.enabledTabTitles as the 2nd arg. return tabTitles.indexOf(el); } async getTabTitleById(id) { return Promise.all(this.tabTitles.map((el) => el.getTabIdentifier())).then((ids) => { return this.tabTitles[ids.indexOf(id)]; }); } get tabTitles() { return dom.filterDirectChildren(this.el, "calcite-tab-title"); } get enabledTabTitles() { return dom.filterDirectChildren(this.el, "calcite-tab-title:not([disabled])").filter((tabTitle) => !tabTitle.closed); } handleTabTitleClose(closedTabTitleEl) { const { tabTitles } = this; const visibleTabTitlesIndices = tabTitles.reduce((tabTitleIndices, tabTitle, index) => !tabTitle.closed ? [...tabTitleIndices, index] : tabTitleIndices, []); const totalVisibleTabTitles = visibleTabTitlesIndices.length; if (totalVisibleTabTitles === 1 && tabTitles[visibleTabTitlesIndices[0]].closable) { tabTitles[visibleTabTitlesIndices[0]].closable = false; this.selectedTabId = visibleTabTitlesIndices[0]; } else if (totalVisibleTabTitles > 1) { const closedTabTitleIndex = tabTitles.findIndex((el) => el === closedTabTitleEl); const nextTabTitleIndex = visibleTabTitlesIndices.find((value) => value > closedTabTitleIndex); if (this.selectedTabId === closedTabTitleIndex) { this.selectedTabId = nextTabTitleIndex ? nextTabTitleIndex : totalVisibleTabTitles - 1; } } requestAnimationFrame(() => { this.updateOffsetPosition(); this.updateActiveWidth(); tabTitles[this.selectedTabId].focus(); }); } get el() { return index.getElement(this); } static get watchers() { return { "selectedTabId": ["selectedTabIdChanged"], "selectedTitle": ["selectedTitleChanged"] }; } }; TabNav.style = tabNavCss; const CSS = { closeButton: "close-button", container: "container", content: "content", contentHasText: "content--has-text", iconEnd: "icon-end", iconStart: "icon-start", iconPresent: "icon-present", titleIcon: "calcite-tab-title--icon" }; const ICONS = { close: "x" }; const tabTitleCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-right{0%{opacity:0;transform:translate3D(-5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-left{0%{opacity:0;transform:translate3D(5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-right{animation-name:in-right}.calcite-animate__in-left{animation-name:in-left}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing);--calcite-floating-ui-z-index:var(--calcite-app-z-index-dropdown)}:host([hidden]){display:none}:host([disabled]){cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-ui-opacity-disabled)}:host([disabled]) *,:host([disabled]) ::slotted(*){pointer-events:none}:host{display:block;flex:0 1 auto;outline:2px solid transparent;outline-offset:2px;margin-inline-start:0px;margin-inline-end:1.25rem}:host([layout=center][scale=s]),:host([layout=center][scale=m]),:host([layout=center][scale=l]){margin-block:0px;text-align:center;margin-inline-end:0px;flex-basis:12rem}:host([layout=center][scale=s]) .content,:host([layout=center][scale=m]) .content,:host([layout=center][scale=l]) .content{margin:auto}:host([layout=center][closable]) .content{padding-inline-start:32px}:host([layout=center][bordered][closable][scale=s]) .content{padding-inline-start:36px}:host([layout=center][bordered][closable][scale=m]) .content{padding-inline-start:40px}:host([layout=center][closable][scale=l]) .content{padding-inline-start:40px}:host([layout=center][closable][bordered][scale=l]) .content{padding-inline-start:52px}:host([position=bottom]) .container{border-block-end-width:0px;border-block-start-width:2px;border-block-start-color:transparent;border-block-start-style:solid}:host([closed]){display:none}.container{outline-color:transparent}:host(:focus) .container{outline:2px solid var(--calcite-ui-focus-color, var(--calcite-ui-brand));outline-offset:calc(\n -2px *\n calc(\n 1 -\n 2 * clamp(\n 0,\n var(--calcite-ui-focus-offset-invert),\n 1\n )\n )\n )}:host(:focus) .container:focus-within{outline-color:transparent}:host(:active) a,:host(:focus) a,:host(:hover) a{border-color:var(--calcite-ui-border-2);color:var(--calcite-ui-text-1);text-decoration-line:none}:host([selected]) .container{border-color:transparent;color:var(--calcite-ui-text-1)}:host([disabled]) .container{pointer-events:none;opacity:0.5}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}:host([scale=s]){margin-inline-end:1rem}:host([scale=s]) .content{padding-block:0.25rem;font-size:var(--calcite-font-size--2);line-height:1rem}:host([scale=m]) .content{padding-block:0.5rem;font-size:var(--calcite-font-size--1);line-height:1rem}:host([scale=l]){margin-inline-end:1.5rem}:host([scale=l]) .content{padding-block:0.625rem;font-size:var(--calcite-font-size-0);line-height:1.25rem}.container{box-sizing:border-box;display:flex;block-size:100%;inline-size:100%;cursor:pointer;align-content:center;justify-content:space-between;border-block-end-width:2px;padding-inline:0px;font-size:var(--calcite-font-size--1);line-height:1rem;color:var(--calcite-ui-text-3);transition:all var(--calcite-animation-timing) ease-in-out 0s, outline 0s, outline-offset 0s;border-block-end-color:transparent;border-block-end-style:solid}.content{display:flex;align-items:center;justify-content:center}.calcite-tab-title--icon{position:relative;margin:0px;display:inline-flex;align-self:center}.calcite-tab-title--icon svg{transition:all var(--calcite-animation-timing) ease-in-out 0s, outline 0s, outline-offset 0s}.content--has-text{padding:0.25rem}.content--has-text .calcite-tab-title--icon.icon-start{margin-inline-end:0.5rem}.content--has-text .calcite-tab-title--icon.icon-end{margin-inline-start:0.5rem}.close-button{display:flex;cursor:pointer;-webkit-appearance:none;appearance:none;align-content:center;align-items:center;justify-content:center;align-self:center;border-style:none;background-color:var(--calcite-ui-foreground-1);color:var(--calcite-ui-text-3);outline-color:transparent;transition:all var(--calcite-animation-timing) ease-in-out 0s, outline 0s, outline-offset 0s;block-size:calc(100% - 2px);background-color:var(--calcite-button-transparent-1);margin-inline-start:auto}.close-button:focus{outline:2px solid var(--calcite-ui-focus-color, var(--calcite-ui-brand));outline-offset:calc(\n -2px *\n calc(\n 1 -\n 2 * clamp(\n 0,\n var(--calcite-ui-focus-offset-invert),\n 1\n )\n )\n );outline-offset:-1px}.close-button:focus,.close-button:hover{color:var(--calcite-ui-text-1);background-color:var(--calcite-ui-foreground-2)}.close-button:active{color:var(--calcite-ui-text-1);background-color:var(--calcite-ui-foreground-3)}.close-button calcite-icon{color:inherit}:host([icon-start][icon-end]) .calcite-tab-title--icon:first-child{margin-inline-end:0.5rem}:host([bordered]){margin-inline-end:0}:host([bordered][selected]){box-shadow:inset 0px -2px var(--calcite-ui-foreground-1)}:host([bordered][selected][position=bottom]){box-shadow:inset 0 2px 0 var(--calcite-ui-foreground-1)}:host([bordered]:hover) .container,:host([bordered]:focus) .container,:host([bordered]:active) .container{position:relative}:host([bordered]:hover) .container{background-color:var(--calcite-button-transparent-hover)}:host([closable]) .container,:host([bordered]) .container{border-inline-start:1px solid transparent;border-inline-end:1px solid transparent}:host([closable]) .container .close-button,:host([bordered]) .container .close-button{margin-inline:0}:host([closable]) .content{box-sizing:border-box;block-size:100%;border-block-end-color:transparent}:host([closable][position=bottom]) .container,:host([bordered][position=bottom]) .container{border-block-start-style:unset}:host([selected][bordered]) .container{border-inline-start-color:var(--calcite-ui-border-1);border-inline-end-color:var(--calcite-ui-border-1)}:host([bordered]) .content{padding-inline:0.75rem}:host([bordered][scale=s]) .content{padding-inline:0.5rem}:host([bordered][scale=l]) .content{padding-inline:1rem}@media (forced-colors: active){:host{outline-width:0;outline-offset:0}:host(:focus) .container{outline-color:highlight}:host([bordered]) .container{border-block-end-style:solid}:host([bordered][position=bottom]) .container{border-block-start-style:solid}:host([bordered][selected]) .container{border-block-end-style:none}:host([bordered][position=bottom][selected]) .container{border-block-start-style:none}}"; const TabTitle = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.calciteTabsActivate = index.createEvent(this, "calciteTabsActivate", 6); this.calciteInternalTabsActivate = index.createEvent(this, "calciteInternalTabsActivate", 6); this.calciteTabsClose = index.createEvent(this, "calciteTabsClose", 6); this.calciteInternalTabsClose = index.createEvent(this, "calciteInternalTabsClose", 6); this.calciteInternalTabsFocusNext = index.createEvent(this, "calciteInternalTabsFocusNext", 6); this.calciteInternalTabsFocusPrevious = index.createEvent(this, "calciteInternalTabsFocusPrevious", 6); this.calciteInternalTabsFocusFirst = index.createEvent(this, "calciteInternalTabsFocusFirst", 6); this.calciteInternalTabsFocusLast = index.createEvent(this, "calciteInternalTabsFocusLast", 6); this.calciteInternalTabTitleRegister = index.createEvent(this, "calciteInternalTabTitleRegister", 6); this.calciteInternalTabIconChanged = index.createEvent(this, "calciteInternalTabIconChanged", 6); //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- this.closeClickHandler = () => { this.closeTabTitleAndNotify(); }; //-------------------------------------------------------------------------- // // Private State/Props // //-------------------------------------------------------------------------- /** watches for changing text content */ this.mutationObserver = observers.createObserver("mutation", () => this.updateHasText()); this.resizeObserver = observers.createObserver("resize", () => { this.calciteInternalTabIconChanged.emit(); }); this.guid = `calcite-tab-title-${guid.guid()}`; this.selected = false; this.closable = false; this.closed = false; this.disabled = false; this.iconEnd = undefined; this.iconFlipRtl = undefined; this.iconStart = undefined; this.layout = undefined; this.position = undefined; this.scale = undefined; this.bordered = false; this.tab = undefined; this.messages = undefined; this.messageOverrides = undefined; this.controls = undefined; this.defaultMessages = undefined; this.effectiveLocale = undefined; this.hasText = false; } selectedHandler() { if (this.selected) { this.emitActiveTab(false); } } onMessagesChange() { /* wired up by t9n util */ } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- connectedCallback() { interactive.connectInteractive(this); locale.connectLocalized(this); t9n.connectMessages(this); this.setupTextContentObserver(); this.parentTabNavEl = this.el.closest("calcite-tab-nav"); this.parentTabsEl = this.el.closest("calcite-tabs"); } disconnectedCallback() { this.mutationObserver?.disconnect(); // Dispatching to body in order to be listened by other elements that are still connected to the DOM. document.body?.dispatchEvent(new CustomEvent("calciteTabTitleUnregister", { detail: this.el })); this.resizeObserver?.disconnect(); interactive.disconnectInteractive(this); locale.disconnectLocalized(this); t9n.disconnectMessages(this); } async componentWillLoad() { await t9n.setUpMessages(this); { this.updateHasText(); } if (this.tab && this.selected) { this.emitActiveTab(false); } } componentWillRender() { if (this.parentTabsEl) { this.layout = this.parentTabsEl.layout; this.position = this.parentTabsEl.position; this.scale = this.parentTabsEl.scale; this.bordered = this.parentTabsEl.bordered; } // handle case when tab-nav is only parent if (!this.parentTabsEl && this.parentTabNavEl) { this.position = dom.getElementProp(this.parentTabNavEl, "position", this.position); this.scale = dom.getElementProp(this.parentTabNavEl, "scale", this.scale); } } render() { const { el, closed } = this; const id = el.id || this.guid; const iconStartEl = (index.h("calcite-icon", { class: { [CSS.titleIcon]: true, [CSS.iconStart]: true }, flipRtl: this.iconFlipRtl === "start" || this.iconFlipRtl === "both", icon: this.iconStart, scale: this.scale === "l" ? "m" : "s" })); const iconEndEl = (index.h("calcite-icon", { class: { [CSS.titleIcon]: true, [CSS.iconEnd]: true }, flipRtl: this.iconFlipRtl === "end" || this.iconFlipRtl === "both", icon: this.iconEnd, scale: this.scale === "l" ? "m" : "s" })); return (index.h(index.Host, { "aria-controls": this.controls, "aria-selected": dom.toAriaBoolean(this.selected), id: id, role: "tab", tabIndex: this.selected ? 0 : -1 }, index.h("div", { class: { container: true, [CSS.iconPresent]: !!this.iconStart || !!this.iconEnd }, hidden: closed, // eslint-disable-next-line react/jsx-sort-props ref: (el) => this.resizeObserver?.observe(el) }, index.h("div", { class: { [CSS.content]: true, [CSS.contentHasText]: this.hasText } }, this.iconStart ? iconStartEl : null, index.h("slot", null), this.iconEnd ? iconEndEl : null), this.renderCloseButton()))); } renderCloseButton() { const { closable, messages } = this; return closable ? (index.h("button", { "aria-label": messages.close, class: CSS.closeButton, disabled: false, key: CSS.closeButton, onClick: this.closeClickHandler, title: messages.close, type: "button", // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.closeButtonEl = el) }, index.h("calcite-icon", { icon: ICONS.close, scale: this.scale === "l" ? "m" : "s" }))) : null; } async componentDidLoad() { this.calciteInternalTabTitleRegister.emit(await this.getTabIdentifier()); } componentDidRender() { interactive.updateHostInteraction(this, () => { return this.selected; }); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- internalTabChangeHandler(event) { const targetTabsEl = event .composedPath() .find((el) => el.tagName === "CALCITE-TABS"); if (targetTabsEl !== this.parentTabsEl) { return; } if (this.tab) { this.selected = this.tab === event.detail.tab; } else { this.getTabIndex().then((index) => { this.selected = index === event.detail.tab; }); } event.stopPropagation(); } onClick() { if (this.disabled) { return; } this.emitActiveTab(); } keyDownHandler(event) { switch (event.key) { case " ": case "Enter": if (!event.composedPath().includes(this.closeButtonEl)) { this.emitActiveTab(); event.preventDefault(); } break; case "ArrowRight": event.preventDefault(); if (dom.getElementDir(this.el) === "ltr") { this.calciteInternalTabsFocusNext.emit(); } else { this.calciteInternalTabsFocusPrevious.emit(); } break; case "ArrowLeft": event.preventDefault(); if (dom.getElementDir(this.el) === "ltr") { this.calciteInternalTabsFocusPrevious.emit(); } else { this.calciteInternalTabsFocusNext.emit(); } break; case "Home": event.preventDefault(); this.calciteInternalTabsFocusFirst.emit(); break; case "End": event.preventDefault(); this.calciteInternalTabsFocusLast.emit(); break; } } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** * Returns the index of the title within the `calcite-tab-nav`. */ async getTabIndex() { return Array.prototype.indexOf.call(dom.nodeListToArray(this.el.parentElement.children).filter((el) => el.matches("calcite-tab-title")), this.el); } /** * @internal */ async getTabIdentifier() { return this.tab ? this.tab : this.getTabIndex(); } /** * @param tabIds * @param titleIds * @internal */ async updateAriaInfo(tabIds = [], titleIds = []) { this.controls = tabIds[titleIds.indexOf(this.el.id)] || null; } effectiveLocaleChange() { t9n.updateMessages(this, this.effectiveLocale); } updateHasText() { this.hasText = this.el.textContent.trim().length > 0; } setupTextContentObserver() { this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); } emitActiveTab(userTriggered = true) { if (this.disabled || this.closed) { return; } const payload = { tab: this.tab }; this.calciteInternalTabsActivate.emit(payload); if (userTriggered) { // emit in the next frame to let internal events sync up requestAnimationFrame(() => this.calciteTabsActivate.emit()); } } closeTabTitleAndNotify() { this.closed = true; this.calciteInternalTabsClose.emit({ tab: this.tab }); this.calciteTabsClose.emit(); } static get assetsDirs() { return ["assets"]; } get el() { return index.getElement(this); } static get watchers() { return { "selected": ["selectedHandler"], "messageOverrides": ["onMessagesChange"], "effectiveLocale": ["effectiveLocaleChange"] }; } }; TabTitle.style = tabTitleCss; const SLOTS = { titleGroup: "title-group" }; const tabsCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-right{0%{opacity:0;transform:translate3D(-5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-left{0%{opacity:0;transform:translate3D(5px, 0, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-right{animation-name:in-right}.calcite-animate__in-left{animation-name:in-left}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing);--calcite-floating-ui-z-index:var(--calcite-app-z-index-dropdown)}:host([hidden]){display:none}:host{display:flex;flex-direction:column}:host([bordered]){box-shadow:inset 0 1px 0 var(--calcite-ui-border-1);background-color:var(--calcite-ui-foreground-1)}:host([bordered]:not([position=bottom])) ::slotted(calcite-tab-nav){margin-block-end:-1px}:host([bordered][position=bottom]){box-shadow:inset 0 1px 0 var(--calcite-ui-border-1), inset 0 -1px 0 var(--calcite-ui-border-1)}:host([bordered]) section{border-width:1px;border-style:solid;border-color:var(--calcite-ui-border-1)}:host([bordered][scale=s]) section{padding:0.75rem}:host([bordered][scale=m]) section{padding:0.5rem}:host([bordered][scale=l]) section{padding:1rem}:host([position=bottom]){flex-direction:column-reverse}section{display:flex;flex-grow:1;overflow:hidden;border-block-start-width:1px;border-block-start-color:var(--calcite-ui-border-1);border-block-start-style:solid}:host([position=bottom]) section{flex-direction:column-reverse;border-block-start-width:0px;border-block-end-width:1px;border-block-end-color:var(--calcite-ui-border-1)}:host([position=bottom]:not([bordered])) section{border-block-end-style:solid}@media (forced-colors: active){:host([bordered]) section{border-block-start-width:0px;border-block-end-width:1px}:host([position=bottom][bordered]) section{border-block-start-width:1px;border-block-end-width:0px}}"; const Tabs = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.layout = "inline"; this.position = "top"; this.scale = "m"; this.bordered = false; this.titles = []; this.tabs = []; } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- render() { return (index.h(index.Fragment, null, index.h("slot", { name: SLOTS.titleGroup }), index.h("section", null, index.h("slot", null)))); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- /** * @param event * @internal */ calciteInternalTabTitleRegister(event) { this.titles = [...this.titles, event.target]; this.registryHandler(); event.stopPropagation(); } /** * @param event * @internal */ calciteTabTitleUnregister(event) { this.titles = this.titles.filter((el) => el !== event.detail); this.registryHandler(); event.stopPropagation(); } /** * @param event * @internal */ calciteInternalTabRegister(event) { this.tabs = [...this.tabs, event.target]; this.registryHandler(); event.stopPropagation(); } /** * @param event * @internal */ calciteTabUnregister(event) { this.tabs = this.tabs.filter((el) => el !== event.detail); this.registryHandler(); event.stopPropagation(); } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * * Matches up elements from the internal `tabs` and `titles` to automatically * update the ARIA attributes and link `<calcite-tab>` and * `<calcite-tab-title>` components. */ async registryHandler() { let tabIds; let titleIds; // determine if we are using `tab` based or `index` based tab identifiers. if (this.tabs.some((el) => el.tab) || this.titles.some((el) => el.tab)) { // if we are using `tab` based identifiers sort by `tab` to account for // possible out of order tabs and get the id of each tab tabIds = this.tabs.sort((a, b) => a.tab.localeCompare(b.tab)).map((el) => el.id); titleIds = this.titles.sort((a, b) => a.tab.localeCompare(b.tab)).map((el) => el.id); } else { // if we are using index based tabs then the `<calcite-tab>` and // `<calcite-tab-title>` might have been rendered out of order so the // order of `this.tabs` and `this.titles` might not reflect the DOM state, // and might not match each other so we need to get the index of all the // tabs and titles in the DOM order to match them up as a source of truth const tabDomIndexes = await Promise.all(this.tabs.map((el) => el.getTabIndex())); const titleDomIndexes = await Promise.all(this.titles.map((el) => el.getTabIndex())); // once we have the DOM order as a source of truth we can build the // matching tabIds and titleIds arrays tabIds = tabDomIndexes.reduce((ids, indexInDOM, registryIndex) => { ids[indexInDOM] = this.tabs[registryIndex].id; return ids; }, []); titleIds = titleDomIndexes.reduce((ids, indexInDOM, registryIndex) => { ids[indexInDOM] = this.titles[registryIndex].id; return ids; }, []); } // pass all our new aria information to each `<calcite-tab>` and // `<calcite-tab-title>` which will check if they can update their internal // `controlled` or `labeledBy` states and re-render if necessary this.tabs.forEach((el) => el.updateAriaInfo(tabIds, titleIds)); this.titles.forEach((el) => el.updateAriaInfo(tabIds, titleIds)); } get el() { return index.getElement(this); } }; Tabs.style = tabsCss; exports.calcite_tab = Tab; exports.calcite_tab_nav = TabNav; exports.calcite_tab_title = TabTitle; exports.calcite_tabs = Tabs;