/** * Copyright Aquera Inc 2023 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import { html, nothing } from 'lit'; import { customElement, property, query } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import type { CSSResultGroup } from 'lit'; import { styles } from './nile-nav-tab.css'; import '../nile-icon-button/nile-icon-button'; import { MouseKey } from '../internal/enum'; import { watch } from '../internal/watch'; import NileElement from '../internal/nile-element'; import '../nile-link/nile-link'; let id = 0; @customElement('nile-nav-tab') export class NileNavTab extends NileElement { static styles: CSSResultGroup = styles; private readonly attrId = ++id; private readonly componentId = `nile-nav-tab-${this.attrId}`; @query('.nav-tab') navTab!: HTMLElement; /** The name of the tab panel this tab is associated with. */ @property({ reflect: true }) panel = ''; /** Draws the tab in an active state. */ @property({ type: Boolean, reflect: true }) active = false; /** Makes the tab closable and shows a close button. */ @property({ type: Boolean }) closable = false; /** Disables the tab and prevents selection. */ @property({ type: Boolean, reflect: true }) disabled = false; @property({ type: Boolean, reflect: true }) centered = false; @property({ type: String, reflect: true }) link?: string; connectedCallback() { super.connectedCallback(); this.setAttribute('role', 'tab'); this.syncA11yState(); } firstUpdated() { this.syncA11yState(); this.updateAccessibleName(); } updated() { this.syncA11yState(); this.updateAccessibleName(); } private syncA11yState() { this.id = this.id || this.componentId; this.tabIndex = this.disabled ? -1 : this.active ? 0 : -1; this.setAttribute('aria-selected', this.active ? 'true' : 'false'); this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); if (this.disabled) { this.setAttribute('aria-hidden', 'true'); } else { this.removeAttribute('aria-hidden'); } } /** * Give the host element the accessible name, since nile-link is hidden * from the accessibility tree. * Author `aria-label` (without `data-auto-aria-label`) is never overwritten so icon-only tabs can keep explicit names. * Auto-derived names set `data-auto-aria-label="true"` so slot/text updates can refresh them. */ private updateAccessibleName() { const userDefinedAriaLabel = this.hasAttribute('aria-label') && this.getAttribute('data-auto-aria-label') !== 'true'; if (userDefinedAriaLabel) { return; } const label = (this.textContent || '').replace(/\s+/g, ' ').trim(); if (label) { this.setAttribute('aria-label', label); this.setAttribute('data-auto-aria-label', 'true'); } else { this.removeAttribute('aria-label'); this.removeAttribute('data-auto-aria-label'); } } private handleCloseClick(event: Event) { event.preventDefault(); event.stopPropagation(); this.emit('nile-close'); } private handleTabClick(event: MouseEvent) { if (this.disabled) { event.preventDefault(); event.stopPropagation(); return; } const isModifiedClick = Object.values(MouseKey).some(key => event[key as keyof MouseEvent]) || event.button !== 0; if (isModifiedClick) { return; } event.preventDefault(); } @watch('active') handleActiveChange() { this.syncA11yState(); } @watch('disabled') handleDisabledChange() { this.syncA11yState(); } focus(options?: FocusOptions) { super.focus(options); } blur() { super.blur(); } render() { this.id = this.id || this.componentId; return html`