UNPKG

@govbr-ds/webcomponents

Version:

Biblioteca de Web Components baseado no GovBR-DS

373 lines (372 loc) 12.9 kB
/*! * Construído por SERPRO * © https://serpro.gov.br/ - MIT License. */ import { h, Host } from "@stencil/core"; /** * Para uma descrição detalhada, consulte a [documentação do GovBR-DS](https://www.gov.br/ds/components/tab-item?tab=designer). * * @slot default - Slot com os componentes item da tab */ export class Tab { el; elementInternals; /** * A propriedade 'density' define a densidade do componente. * Os valores possíveis são 'small', 'medium' e 'large'. * @type {string} * @default 'medium' */ density = 'medium'; /** * Define se o Tab usará um esquema de cores. * Apenas o esquema 'dark' está disponível para o modo escuro. * @type {string} */ colorMode; /** * A propriedade 'activeTab' define o tab ativo. */ activeTab; /** * A propriedade 'tabItems' lista dos tabs items . */ tabItems = []; /** * A propriedade 'tabContent' armazena o conteúdo dos tabs. * @type {string} * @memberof Tab */ tabContent = ''; maxWidth; hostStyle = {}; containerStyle = {}; mutationObserver; focusElementIndex = 0; brDidActive; getCssClassMap() { return { 'br-tab': true, [this.density]: true, 'dark-mode': this.colorMode === 'dark', }; } setupHostStyles() { this.hostStyle = { maxWidth: '100%', display: 'block', boxSizing: 'border-box', }; } updateContainerStyles() { this.containerStyle = { maxWidth: this.maxWidth, overflow: 'hidden', width: '100%', boxSizing: 'border-box', }; } /** * Obtém os elementos de tab items dentro do componente. * Se não houver elementos, emite um aviso. * @private * @returns {void} * @memberof Tab */ getTabItemsElements() { const slotItems = Array.from(this.el.children); slotItems.forEach((tab) => { if (this.colorMode) tab.setAttribute('color-mode', this.colorMode); if (!tab.hasAttribute('tab-item-id')) { tab.setAttribute('tab-item-id', crypto.randomUUID()); } }); this.tabItems = slotItems; } /** * Atualiza o conteúdo dos tabs com base nos elementos de tab items. * Cada tab item é transformado em um painel de tab com o ID apropriado. * O painel ativo é marcado com a classe 'active'. * @private * @returns {void} * @memberof Tab * @description */ updateTabContent() { this.tabContent = this.tabItems .map((tab) => { const tabActive = this.isTabActive(tab); return ` <div id="tab-panel-${this.getTabID(tab)}" role="tabpanel" aria-labelledby="tab-button-${tab.getAttribute('tab-item-id')}" class="tab-panel${tabActive ? ' active' : ''}" > ${tab.innerHTML} </div> `; }) .join(''); } /** * Atualiza a largura máxima do componente com base na largura da janela. * Se a largura da janela não estiver disponível, define a largura máxima como 100%. * @private * @returns {void} * @memberof Tab * @description * Esta função calcula a largura máxima do componente com base na posição do elemento na tela. */ updateMaxWidth() { if (typeof window !== 'undefined') { const componentRect = this.el.getBoundingClientRect(); const availableWidth = window.innerWidth - componentRect.left; this.maxWidth = `${availableWidth - 5}px`; } else { this.maxWidth = '100%'; } this.updateContainerStyles(); } /** * Obtém o ID do tab item. * @private * @param {Element} tab - O elemento do tab item. * @returns {string} - O ID do tab item. * @memberof Tab * @description * Esta função retorna o ID do tab item, que é armazenado no atributo 'tab-item-id'. */ getTabID(tab) { return tab.getAttribute('tab-item-id'); } /** * * @param tab * @returns */ isTabActive(tab) { return this.getTabID(tab) === this.activeTab; } verifActiveTab() { const selected = this.el.querySelector('br-tab-item[active]'); if (!selected && this.tabItems.length > 0) { this.activeTab = this.getTabID(this.tabItems[0]); } else if (selected) { this.activeTab = this.getTabID(selected); } } activeTabChanged(newValue) { this.tabItems.forEach((tab) => { try { if (this.getTabID(tab) === newValue) { tab.setAttribute('is-active', 'true'); tab.setAttribute('aria-selected', 'true'); } else { if (tab.hasAttribute('is-active')) { if (tab) { tab.removeAttribute('is-active'); } tab.removeAttribute('aria-selected'); } } } catch (error) { console.error('Failed to remove attribute "is-active":', error); } }); this.updateTabContent(); this.brDidActive.emit({ id: newValue }); } /** * Evento de navegação por teclado * @param event */ handleKeyNavigation = (event) => { const focusableTabs = Array.from(this.tabItems); if (event.key === 'ArrowDown') { const activeTabPanel = this.el.shadowRoot.querySelector('.tab-panel.active'); if (activeTabPanel) { const focusableElements = Array.from(activeTabPanel.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')); if (focusableElements.length > 0) { focusableElements[0].focus(); } } } const activeIndex = this.focusElementIndex; if (event.key === 'ArrowRight') { const nextIndex = (activeIndex + 1) % focusableTabs.length; const nextButton = focusableTabs[nextIndex]?.shadowRoot?.querySelector('button'); if (nextButton) { nextButton.focus(); this.focusElementIndex = nextIndex; } } else if (event.key === 'ArrowLeft') { const prevIndex = (activeIndex - 1 + focusableTabs.length) % focusableTabs.length; const prevButton = focusableTabs[prevIndex]?.shadowRoot?.querySelector('button'); if (prevButton) { prevButton.focus(); this.focusElementIndex = prevIndex; } } }; componentWillLoad() { this.getTabItemsElements(); this.setupHostStyles(); this.updateTabContent(); if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') { // window.addEventListener('resize', () => this.updateMaxWidth()); this.mutationObserver = new MutationObserver(() => this.updateMaxWidth()); this.mutationObserver.observe(this.el, { childList: true, subtree: true }); this.updateMaxWidth(); } else { this.maxWidth = '100%'; this.updateContainerStyles(); } } componentDidLoad() { if (!this.el) { console.error('Host element is not defined.'); return; } requestAnimationFrame(() => this.verifActiveTab()); // Use the class method directly if (this.el) { try { const hndClick = this.handleTabClick; this.el.addEventListener('keydown', this.handleKeyNavigation); this.el.addEventListener('itemTabClick', hndClick); } catch (error) { console.error('Failed to add event listener for itemTabClick:', error); } } } disconnectedCallback() { if (typeof window !== 'undefined') { window.removeEventListener('resize', () => this.updateMaxWidth()); } if (this.mutationObserver) this.mutationObserver.disconnect(); this.el.removeEventListener('itemTabClick', this.handleTabClick); this.el.removeEventListener('keydown', this.handleKeyNavigation); // Remove the keydown listener } handleTabClick = (event) => { if (event.detail && event.detail.id) { this.setActiveTab(event.detail.id.trim()); } }; setActiveTab(newActiveTab) { // if (this.activeTab !== newActiveTab) { this.activeTab = newActiveTab; // } } render() { return (h(Host, { key: '232362c581075291cd12e1c06a2a3f0973b65346', style: this.hostStyle }, h("div", { key: '0d2d2698435851b9bad658cef3f6a40689cb8c74', class: this.getCssClassMap(), style: this.containerStyle }, h("nav", { key: '151847ede5ea292a45b075c8d1adf24aef6f9c83', class: "tab-nav", "data-counter": this.tabItems.some((tab) => tab.hasAttribute('counter')) ? 'true' : 'false' }, h("ul", { key: '23ae9367b3c2ff08fdab966b5f461eed09eb2bbd', role: "tablist" }, h("slot", { key: 'f5e6847d0ff64af9c923bf6b3cb41e788ab8a635' }))), h("div", { key: '4e9ee40e9f7adef948beaf6f3bddea601663138a', class: "tab-content", innerHTML: this.tabContent })))); } static get is() { return "br-tab"; } static get encapsulation() { return "shadow"; } static get formAssociated() { return true; } static get originalStyleUrls() { return { "$": ["tab.scss"] }; } static get styleUrls() { return { "$": ["tab.css"] }; } static get properties() { return { "density": { "type": "string", "mutable": false, "complexType": { "original": "'small' | 'medium' | 'large'", "resolved": "\"large\" | \"medium\" | \"small\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "type", "text": "{string}" }, { "name": "default", "text": "'medium'" }], "text": "A propriedade 'density' define a densidade do componente.\nOs valores poss\u00EDveis s\u00E3o 'small', 'medium' e 'large'." }, "getter": false, "setter": false, "attribute": "density", "reflect": true, "defaultValue": "'medium'" }, "colorMode": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "type", "text": "{string}" }], "text": "Define se o Tab usar\u00E1 um esquema de cores.\nApenas o esquema 'dark' est\u00E1 dispon\u00EDvel para o modo escuro." }, "getter": false, "setter": false, "attribute": "color-mode", "reflect": true } }; } static get states() { return { "activeTab": {}, "tabItems": {}, "tabContent": {} }; } static get events() { return [{ "method": "brDidActive", "name": "brDidActive", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }]; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "activeTab", "methodName": "activeTabChanged" }]; } static get attachInternalsMemberName() { return "elementInternals"; } } //# sourceMappingURL=tab.js.map