igniteui-webcomponents
Version: 
Ignite UI for Web Components is a complete library of UI components, giving you the ability to build modern web applications using encapsulation and the concept of reusable components in a dependency-free approach.
330 lines • 12.7 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var IgcTabsComponent_1;
import { LitElement, html, nothing } from 'lit';
import { eventOptions, property, query, queryAssignedElements, state, } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { themes } from '../../theming/theming-decorator.js';
import IgcIconButtonComponent from '../button/icon-button.js';
import { addKeybindings, arrowLeft, arrowRight, endKey, homeKey, } from '../common/controllers/key-bindings.js';
import { createMutationController, } from '../common/controllers/mutation-observer.js';
import { blazorAdditionalDependencies } from '../common/decorators/blazorAdditionalDependencies.js';
import { watch } from '../common/decorators/watch.js';
import { registerComponent } from '../common/definitions/register.js';
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
import { createCounter, getOffset, isLTR, wrap } from '../common/util.js';
import IgcTabPanelComponent from './tab-panel.js';
import IgcTabComponent from './tab.js';
import { styles as shared } from './themes/shared/tabs/tabs.common.css.js';
import { all } from './themes/tabs-themes.js';
import { styles } from './themes/tabs.base.css.js';
let IgcTabsComponent = IgcTabsComponent_1 = class IgcTabsComponent extends EventEmitterMixin(LitElement) {
    static register() {
        registerComponent(IgcTabsComponent_1, IgcTabComponent, IgcTabPanelComponent, IgcIconButtonComponent);
    }
    get _closestActiveTabIndex() {
        const root = this.getRootNode();
        const tabs = this.enabledTabs;
        const tab = root.activeElement
            ? root.activeElement.closest(IgcTabComponent.tagName)
            : null;
        return tabs.indexOf(tab);
    }
    _mutationCallback({ changes: { attributes, added, removed }, }) {
        const ownAttributes = attributes.filter((tab) => tab.closest(this.tagName) === this);
        const ownAdded = added.filter(({ target }) => target.closest(this.tagName) === this);
        const ownRemoved = removed.filter(({ target }) => target.closest(this.tagName) === this);
        this.setSelectedTab(ownAttributes.find((tab) => tab.selected));
        if (ownRemoved.length || ownAdded.length) {
            for (const { node: tab } of ownRemoved) {
                this.resizeObserver?.unobserve(tab);
                if (tab.selected && tab === this.activeTab) {
                    this.activeTab = undefined;
                }
            }
            for (const { node: tab } of ownAdded) {
                this.resizeObserver?.observe(tab);
                if (tab.selected) {
                    this.setSelectedTab(tab);
                }
            }
            this.syncAttributes();
        }
        this.updateSelectedTab();
        this.activeTab?.scrollIntoView({ block: 'nearest' });
        this.alignIndicator();
    }
    get enabledTabs() {
        return this.tabs.filter((tab) => !tab.disabled);
    }
    get selected() {
        return this.activeTab?.panel ?? '';
    }
    alignIndicator() {
        const styles = {
            visibility: this.activeTab ? 'visible' : 'hidden',
            transitionDuration: '0.3s',
        };
        if (this.activeTab && this.wrapper) {
            Object.assign(styles, {
                width: `${this.activeTab.offsetWidth}px`,
                transform: `translate(${isLTR(this)
                    ? getOffset(this.activeTab, this.wrapper).left
                    : getOffset(this.activeTab, this.wrapper).right}px)`,
            });
        }
        Object.assign(this.selectedIndicator?.style ?? {}, styles);
    }
    constructor() {
        super();
        this.headerRef = createRef();
        this.showScrollButtons = false;
        this.disableStartScrollButton = true;
        this.disableEndScrollButton = false;
        this.alignment = 'start';
        this.activation = 'auto';
        addKeybindings(this, {
            ref: this.headerRef,
            bindingDefaults: { preventDefault: true },
        })
            .set(arrowLeft, this.onArrowLeft)
            .set(arrowRight, this.onArrowRight)
            .set(homeKey, this.onHomeKey)
            .set(endKey, this.onEndKey)
            .setActivateHandler(this.onActivationKey);
        createMutationController(this, {
            callback: this._mutationCallback,
            config: {
                attributeFilter: ['selected'],
                childList: true,
                subtree: true,
            },
            filter: [IgcTabComponent.tagName],
        });
    }
    async firstUpdated() {
        this.showScrollButtons =
            this.container.scrollWidth > this.container.clientWidth;
        await this.updateComplete;
        this.syncAttributes();
        this.setupObserver();
        this.setSelectedTab(this.tabs.filter((tab) => tab.selected).at(-1) ?? this.enabledTabs.at(0));
        this.updateSelectedTab();
    }
    disconnectedCallback() {
        this.resizeObserver?.disconnect();
        super.disconnectedCallback();
    }
    updateButtonsOnResize() {
        this.showScrollButtons = false;
        this.performUpdate();
        this.showScrollButtons =
            this.container.scrollWidth > this.container.clientWidth;
        this.updateScrollButtons();
    }
    updateScrollButtons() {
        const { scrollLeft, offsetWidth } = this.container;
        const { scrollWidth } = this.wrapper;
        this.disableEndScrollButton =
            scrollWidth <= Math.abs(scrollLeft) + offsetWidth;
        this.disableStartScrollButton = scrollLeft === 0;
    }
    setupObserver() {
        this.resizeObserver = new ResizeObserver(() => {
            this.updateButtonsOnResize();
            this.alignIndicator();
        });
        [this.container, this.wrapper, ...this.tabs].forEach((element) => this.resizeObserver.observe(element));
    }
    updateSelectedTab() {
        this.tabs.forEach((tab) => {
            tab.selected = tab === this.activeTab;
        });
        this.panels.forEach((panel) => {
            panel.hidden = panel.id !== this.activeTab?.panel;
        });
    }
    syncAttributes() {
        const prefix = this.id ? `${this.id}-` : '';
        this.tabs.forEach((tab, index) => {
            if (!tab.panel) {
                tab.panel =
                    this.panels.at(index)?.id ??
                        `${prefix}tab-${IgcTabsComponent_1.increment()}`;
            }
            this.panels
                .find((panel) => panel.id === tab.panel)
                ?.setAttribute('aria-labelledby', tab.id);
        });
    }
    setSelectedTab(tab) {
        if (!tab || tab === this.activeTab) {
            return;
        }
        if (this.activeTab) {
            this.activeTab.selected = false;
        }
        this.activeTab = tab;
        this.activeTab.selected = true;
    }
    scrollByTabOffset(direction) {
        const { scrollLeft, offsetWidth } = this.container;
        const LTR = isLTR(this);
        const next = direction === 'end';
        const pivot = Math.abs(next ? offsetWidth + scrollLeft : scrollLeft);
        let amount = this.tabs
            .map((tab) => ({
            start: LTR
                ? getOffset(tab, this.wrapper).left
                : Math.abs(getOffset(tab, this.wrapper).right),
            width: tab.offsetWidth,
        }))
            .filter((offset) => next ? offset.start + offset.width > pivot : offset.start < pivot)
            .at(next ? 0 : -1).width;
        amount *= next ? 1 : -1;
        this.container.scrollBy({ left: LTR ? amount : -amount });
    }
    handleClick(event) {
        const target = event.target;
        const tab = target.closest('igc-tab');
        if (!(tab && this.contains(tab)) || tab.disabled) {
            return;
        }
        tab.focus();
        this.setSelectedTab(tab);
        this.emitEvent('igcChange', { detail: tab });
    }
    _scrollToAndFocus(tab) {
        tab.scrollIntoView({ behavior: 'auto', block: 'nearest' });
        tab.focus();
    }
    _kbActivateTab(tab) {
        this._scrollToAndFocus(tab);
        if (this.activation === 'auto') {
            this.setSelectedTab(tab);
            this.emitEvent('igcChange', { detail: tab });
        }
    }
    onArrowLeft() {
        const tabs = this.enabledTabs;
        const delta = isLTR(this) ? -1 : 1;
        this._kbActivateTab(tabs[wrap(0, tabs.length - 1, this._closestActiveTabIndex + delta)]);
    }
    onArrowRight() {
        const tabs = this.enabledTabs;
        const delta = isLTR(this) ? 1 : -1;
        this._kbActivateTab(tabs[wrap(0, tabs.length - 1, this._closestActiveTabIndex + delta)]);
    }
    onHomeKey() {
        this._kbActivateTab(this.enabledTabs.at(0));
    }
    onEndKey() {
        this._kbActivateTab(this.enabledTabs.at(-1));
    }
    onActivationKey() {
        const tabs = this.enabledTabs;
        const index = this._closestActiveTabIndex;
        this.setSelectedTab(tabs[index]);
        this._kbActivateTab(tabs[index]);
    }
    handleScroll() {
        this.updateScrollButtons();
    }
    select(name) {
        this.setSelectedTab(this.tabs.find((el) => el.panel === name));
    }
    renderScrollButton(direction) {
        const start = direction === 'start';
        return this.showScrollButtons
            ? html `<igc-icon-button
          tabindex="-1"
          aria-hidden="true"
          variant="flat"
          collection="default"
          part="${direction}-scroll-button"
          exportparts="icon"
          name="${start ? 'prev' : 'next'}"
          .disabled=${start
                ? this.disableStartScrollButton
                : this.disableEndScrollButton}
          =${() => this.scrollByTabOffset(direction)}
        ></igc-icon-button>`
            : nothing;
    }
    render() {
        return html `
      <div part="headers">
        ${this.renderScrollButton('start')}
        <div part="headers-content" =${this.handleScroll}>
          <div part="headers-wrapper">
            <div
              ${ref(this.headerRef)}
              part="headers-scroll"
              role="tablist"
              =${this.handleClick}
            >
              <slot></slot>
            </div>
            <div part="selected-indicator"></div>
          </div>
        </div>
        ${this.renderScrollButton('end')}
      </div>
      <div part="content">
        <slot name="panel"></slot>
      </div>
    `;
    }
};
IgcTabsComponent.tagName = 'igc-tabs';
IgcTabsComponent.styles = [styles, shared];
IgcTabsComponent.increment = createCounter();
__decorate([
    queryAssignedElements({ selector: IgcTabComponent.tagName })
], IgcTabsComponent.prototype, "tabs", void 0);
__decorate([
    queryAssignedElements({ slot: 'panel' })
], IgcTabsComponent.prototype, "panels", void 0);
__decorate([
    query('[part="headers-wrapper"]')
], IgcTabsComponent.prototype, "wrapper", void 0);
__decorate([
    query('[part="headers-content"]')
], IgcTabsComponent.prototype, "container", void 0);
__decorate([
    query('[part="selected-indicator"]')
], IgcTabsComponent.prototype, "selectedIndicator", void 0);
__decorate([
    state()
], IgcTabsComponent.prototype, "showScrollButtons", void 0);
__decorate([
    state()
], IgcTabsComponent.prototype, "disableStartScrollButton", void 0);
__decorate([
    state()
], IgcTabsComponent.prototype, "disableEndScrollButton", void 0);
__decorate([
    state()
], IgcTabsComponent.prototype, "activeTab", void 0);
__decorate([
    property({ reflect: true })
], IgcTabsComponent.prototype, "alignment", void 0);
__decorate([
    property()
], IgcTabsComponent.prototype, "activation", void 0);
__decorate([
    watch('alignment', { waitUntilFirstUpdate: true })
], IgcTabsComponent.prototype, "alignIndicator", null);
__decorate([
    eventOptions({ passive: true })
], IgcTabsComponent.prototype, "handleScroll", null);
IgcTabsComponent = IgcTabsComponent_1 = __decorate([
    themes(all),
    blazorAdditionalDependencies('IgcTabComponent, IgcTabPanelComponent')
], IgcTabsComponent);
export default IgcTabsComponent;
//# sourceMappingURL=tabs.js.map