@govbr-ds/webcomponents
Version:
Biblioteca de Web Components baseado no GovBR-DS
373 lines (372 loc) • 12.9 kB
JavaScript
/*!
* 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