zul/src/main/resources/web/js/zul/tab/Tabpanel.ts

Summary

Maintainability
A
0 mins
Test Coverage
/* Tabpanel.ts

{{IS_NOTE
    Purpose:

    Description:

    History:
        Fri Jan 23 10:33:02 TST 2009, Created by Flyworld
}}IS_NOTE

Copyright (C) 2008 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
/**
 * A tab panel.
 * @defaultValue {@link getZclass}: z-tabpanel.
 */
@zk.WrapClass('zul.tab.Tabpanel')
export class Tabpanel extends zul.ContainerWidget {
    override parent!: zul.tab.Tabpanels | undefined;
    /** @internal */
    _lastScrollTop?: number | undefined;

    /**
     * @returns the tabbox owns this component.
     */
    getTabbox(): zul.tab.Tabbox | undefined {
        return this.parent ? this.parent.parent : undefined;
    }

    override isVisible(strict?: boolean): boolean {
        return super.isVisible() && this.isSelected();
    }

    override setVisible(visible: boolean): this {
        super.setVisible(visible);
        if (this.desktop && !this.isSelected()) //Bug ZK-1618: not show if current tabpanel is not selected
            this.$n_().style.display = 'none';
        return this;
    }

    /** @internal */
    override domClass_(no?: zk.DomClassOptions): string {
        var /*safe*/ cls = super.domClass_(no),
            tabbox = this.getTabbox()!;
        if (tabbox.inAccordionMold())
            cls += ' ' + this.$s('content');
        return cls;
    }

    /**
     * @returns the tab associated with this tab panel.
     */
    getLinkedTab(): zul.tab.Tab | undefined {
        var tabbox = this.getTabbox();
        if (!tabbox) return undefined;

        var tabs = tabbox.getTabs();
        return tabs ? tabs.getChildAt<zul.tab.Tab>(this.getIndex()) : undefined;
    }

    /**
     * @returns the index of this panel, or -1 if it doesn't belong to any
     * tabpanels.
     */
    getIndex(): number {
        return this.getChildIndex();
    }

    /**
     * @returns whether this tab panel is selected.
     */
    isSelected(): boolean {
        return !!this.getLinkedTab()?.isSelected();
    }

    // Bug 3026669
    /** @internal */
    _changeSel(oldPanel: zul.tab.Tabpanel | undefined): void {
        if (oldPanel) {
            var cave = this.$n('cave'),
                panel: HTMLElement | undefined;
            if (cave && !cave.style.height && (panel = oldPanel.$n('cave')))
                cave.style.height = panel.style.height;
        }
    }

    /** @internal */
    _sel(toSel: boolean, animation?: boolean): void { //don't rename (zkmax counts on it)!!
        var tabbox = this.getTabbox();

        // B95-ZK-4695.zul, the Tabpanel is removed so that its desktop should be null.
        if (!tabbox || !this.desktop) return; //Bug ZK-1808 removed tabpanel is no longer in hierarchy, and cannot be removed
        var accd = tabbox.inAccordionMold();

        if (accd && animation) {
            var zkp = zk(this.$n('cave'));
            if (toSel) {
                /* ZK-1441
                 * When a tabpanel is animating, set tabbox.animating
                 * to block other tabpanels enter _sel().
                 * Reference: doClick_() in Tab.js
                 */
                tabbox._animating = true;
                zkp.slideDown(
                    this,
                    {'afterAnima': function () {delete tabbox!._animating;}}
                );
            } else {
                zkp.slideUp(this);
            }
        } else {
            var $pl = jq(accd ? this.$n_('cave') : this.$n_()),
                vis = $pl.zk.isVisible();
            if (toSel) {
                if (!vis) {
                    $pl.show();
                    // Bug ZK-1454: Scrollbar forgets its position when switching tabs in Tabbox
                    if (zk.webkit)
                        $pl.scrollTop(this._lastScrollTop!);
                    zUtl.fireShown(this);
                }
            } else if (vis) {
                zWatch.fireDown('onHide', this);
                // Bug ZK-1454: Scrollbar forgets its position when switching tabs in Tabbox
                if (zk.webkit)
                    this._lastScrollTop = $pl.scrollTop();
                $pl.hide();
            }
        }
    }

    // Could return NaN. Should validate the return value before using it.
    /** @internal */
    getPanelContentHeight_(): number {
        // NOTE: Adding undefined to a number results in NaN. If any argument of
        // Math.max is undefined, the result is NaN. Fortunately, zul.tab.Tabs.prototype._fixHgh
        // validates the return value of this function before using it.
        const node = this.$n(),
            tabpanelsNode = this.parent?.$n(),
            panelContentHeight = (tabpanelsNode?.scrollHeight!) + zk(tabpanelsNode).padBorderHeight();

        return Math.max(node?.offsetHeight!, panelContentHeight); // B50-ZK-298: concern panel height
    }

    /** @internal */
    _fixPanelHgh(): void {
        var tabbox = this.getTabbox()!,
            tbx = tabbox.$n_(),
            hgh: string | number = tbx.style.height;
        if (!hgh && tabbox._vflex)
            hgh = tbx.offsetHeight;

        if (hgh && hgh != 'auto') {
            if (!tabbox.inAccordionMold()) {
                const n = this.$n_(),
                    extraHgh = tabbox.isHorizontal() ? zk(tabbox.tabs).offsetHeight()
                                                     : zk(n.parentNode).padBorderHeight();
                // B50-ZK-473: Tabpanel in vertical Tabbox should always have full height
                n.style.height = jq.px0(zk(tabbox).contentHeight() - extraHgh);
            } else {
                const n = this.$n_();
                let hgh = tbx.offsetHeight - zk(n.parentNode).padBorderHeight();
                for (let e = n.parentNode!.firstChild; e; e = e.nextSibling)
                    if (e != n)
                        hgh -= (e as HTMLElement).offsetHeight;
                hgh -= (n.firstChild as HTMLElement).offsetHeight;
                this.$n_('cave').style.height = jq.px0(hgh);
            }
        }
    }

    override onSize(): void {
        var tabbox = this.getTabbox()!;
        if (tabbox.inAccordionMold() && !zk(this.$n('cave')).isVisible())
            return;
        this._fixPanelHgh();        //Bug 2104974
    }

    //bug #3014664
    override setVflex(vflex: boolean | string | undefined): this { //vflex ignored for Tabpanel
        if (vflex != 'min') vflex = false;
        return super.setVflex(vflex);
    }

    //bug #3014664
    override setHflex(hflex: boolean | string | undefined): this { //hflex ignored for Tabpanel
        if (hflex != 'min') hflex = false;
        return super.setHflex(hflex);
    }

    /** @internal */
    override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
        super.bind_(desktop, skipper, after);
        zWatch.listen({onSize: this});
        // B50-ZK-660: Dynamically generated accordion tabs cannot be closed
        var tab: zul.tab.Tab | undefined;
        if (this.getTabbox()!.inAccordionMold()
                && (tab = this.getLinkedTab())) {

            if (!tab.$n())
                tab.unbind().bind(desktop);
            else if (!jq.isAncestor(this.$n(), tab.$n())) {
                // not display if got wrong tab,
                // it will fixed by Tabpanels#onChildAdded_ if tab add first
                // or by afterMount in tab#bind_ if panel add first
                var cave = this.$n('cave');
                if (cave) cave.style.display = 'none';
            }
        }
    }

    /** @internal */
    override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
        zWatch.unlisten({onSize: this});
        this._lastScrollTop = undefined;
        super.unbind_(skipper, after, keepRod);
    }
}