/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Host, Build, } from "@stencil/core";
import { h } from "../../utils/renderer";
import { getDirectChildren, getOffset } from "../../utils/dom";
import { debounce } from "../../utils/throttle";
import { scrollIntoView } from "../../utils/scroll";
/**
 * A container for content that can horizontally or vertically scroll
 *
 * @version 4.0.0
 * @status stable
 *
 * @slot - Default slot to place items.
 *
 * @part base - root, wrapper element
 * @part scroller - the scrolling element
 * @part items - the item wrapper element
 * @part indicator - the animated, indicator element
 * @part scroll-button - The buttons shown at either side of the scrolling area
 * @part scroll-button-prev - The button shown at the start of the scrolling area
 * @part scroll-button-next - The button shown at the end of the scrolling area
 */
export class MaskedOverflow {
    // public surface
    /** Used to appropriately label the scrolling element for screen-readers */
    label;
    /** Disables the scroll arrow buttons that appear when content overflows */
    scrollControls = true;
    /** The flex direction of the element */
    orientation = 'horizontal';
    /** Shows a track and a highlight line that automatically moves the currently active item. */
    showIndicator = false;
    /** Show or hide the browser's default scrollbars. Defaults to `true` on vertical orientation and `false` on `horizontal` orientation */
    hideScrollbars;
    watchHideScrollbars() {
        if (this.hideScrollbars === undefined || this.hideScrollbars === null) {
            this.hideScrollbars = this.orientation !== 'vertical';
        }
    }
    /**
     * A function called when an item becomes 'active' (via click or keyboard)
     * @param item - html element currently being assessed
     * @param _index - the index of the html element currently being assessed
     * @returns void */
    activeHandler = (item, _index) => item.classList.add('active');
    /**
     * A function called when an item becomes 'inactive'
     * (because another item was made active)
     * @param item - html element currently being assessed
     * @param _index - the index of the html element currently being assessed
     * @returns void */
    inactiveHandler = (item, _index) => item.classList.remove('active');
    /**
     * A function called to assess an initial, active item
     * @param item - html element currently being assessed
     * @param _index - the index of the html element currently being assessed
     * @returns void */
    isActiveHandler = (item, _index) => item.classList.contains('active');
    watchIsActiveHandler() {
        this.instantReCalc = true;
        this.activeItem = this.allActiveItems.find((el, i) => this.isActiveHandler(el, i));
        setTimeout(() => (this.instantReCalc = false), 300);
    }
    /**
     * Sync up the view to the active item.
     * Use this when the active item changes outside of this component.
     */
    async syncActiveItem() {
        this.activeItem = this.allActiveItems.find((el, i) => this.isActiveHandler(el, i));
    }
    // private state
    constructor() {
        this.recalculatePositions = debounce(this.recalculatePositions.bind(this), 80);
    }
    ro;
    nav;
    itemContainer;
    isRtl;
    endBtn;
    startBtn;
    activeIndicator;
    mutationObservers = new WeakMap();
    get scrollProp() {
        return this.orientation === 'horizontal' ? 'scrollWidth' : 'scrollHeight';
    }
    get clientDimensionProp() {
        return this.orientation === 'horizontal' ? 'clientWidth' : 'clientHeight';
    }
    get scrollOffset() {
        return this.orientation === 'horizontal' ? 'scrollLeft' : 'scrollTop';
    }
    host;
    instantReCalc = false;
    hasScrollControls = false;
    watchScrollControls() {
        if (this.hasScrollControls)
            this.handleScroll();
        else
            this.hideControlStart = this.hideControlEnd = true;
    }
    hideControlEnd = true;
    hideEndBtn() {
        if (!this.endBtn)
            return;
        this.activateBtn(this.endBtn, !this.hideControlEnd);
    }
    hideControlStart = true;
    hideStartBtn() {
        if (!this.startBtn)
            return;
        this.activateBtn(this.startBtn, !this.hideControlStart);
    }
    // private logic
    activateBtn(btn, activate = true) {
        if (!activate) {
            btn.tabIndex = -1;
            btn.disabled = true;
            btn.classList.remove('is-shown');
            return;
        }
        btn.removeAttribute('tabindex');
        btn.disabled = false;
        btn.classList.add('is-shown');
    }
    get allActiveItems() {
        return this.allItems.filter((el) => !el.disabled || el.classList.contains('disabled'));
    }
    get allItems() {
        return getDirectChildren(this.host, '*', 'slot');
    }
    get activeItem() {
        return this._activeItem;
    }
    set activeItem(item) {
        if (!item ||
            item === this.activeItem ||
            item.disabled ||
            !this.allActiveItems.includes(item)) {
            return;
        }
        this._activeItem = item;
        this.allActiveItems.forEach((el, i) => {
            if (el === this._activeItem)
                this.activeHandler(el, i);
            else
                this.inactiveHandler(el, i);
        });
        this.recalculatePositions();
    }
    _activeItem;
    get navDim() {
        if (!this.nav)
            return 0;
        const computedStyle = getComputedStyle(this.nav);
        let clientDim = this.nav[this.clientDimensionProp];
        if (this.orientation === 'horizontal') {
            return (clientDim -=
                parseFloat(computedStyle.paddingLeft) +
                    parseFloat(computedStyle.paddingRight));
        }
        else {
            return (clientDim -=
                parseFloat(computedStyle.paddingTop) +
                    parseFloat(computedStyle.paddingBottom));
        }
    }
    scrollToActiveItem() {
        if (!this.nav || !this.activeItem || !this.hasScrollControls)
            return;
        scrollIntoView(this.activeItem, this.nav, this.orientation, 'center');
    }
    syncActiveItemIndicator() {
        if (!this.activeItem || !this.showIndicator)
            return;
        const item = this.activeItem;
        const width = item.clientWidth || 0;
        const height = item.clientHeight + 2 || 0;
        const offset = getOffset(item, this.itemContainer);
        if (!this.activeIndicator)
            return;
        const offsetTop = offset.top;
        const offsetLeft = offset.left;
        if (this.instantReCalc)
            this.activeIndicator.style.transition = 'none';
        switch (this.orientation) {
            case 'horizontal':
                this.activeIndicator.style.width = `${width}px`;
                this.activeIndicator.style.height = null;
                this.activeIndicator.style.transform = `translateX(${offsetLeft}px)`;
                break;
            case 'vertical':
                this.activeIndicator.style.width = null;
                this.activeIndicator.style.height = `${height}px`;
                this.activeIndicator.style.transform = `translateY(${offsetTop}px)`;
                break;
        }
        setTimeout(() => (this.activeIndicator.style.transition = null), 400);
    }
    updateScrollControls = () => {
        if (!this.nav)
            return;
        this.hasScrollControls =
            this.nav[this.scrollProp] > this.nav[this.clientDimensionProp] &&
                this.nav[this.scrollProp] >= this.itemContainer[this.clientDimensionProp];
        this.handleScroll();
    };
    handleScroll = () => {
        if (!this.hasScrollControls) {
            this.hideControlStart = this.hideControlEnd = true;
            return;
        }
        const finishEnd = this.isRtl
            ? this.nav[this.scrollOffset] === 0
            : Math.abs(this.nav[this.clientDimensionProp] -
                (this.nav[this.scrollProp] - this.nav[this.scrollOffset])) < 2;
        const finishStart = this.isRtl
            ? Math.abs(this.nav[this.scrollProp] +
                this.nav[this.scrollOffset] -
                this.nav[this.clientDimensionProp]) < 2
            : this.nav[this.scrollOffset] === 0;
        if (finishStart) {
            this.hideControlStart = true;
            this.hideControlEnd = false;
        }
        else if (finishEnd) {
            this.hideControlStart = false;
            this.hideControlEnd = true;
        }
        else {
            this.hideControlEnd = false;
            this.hideControlStart = false;
        }
    };
    handleBtnClick = (goEnd = false) => {
        const navDim = this.navDim;
        let scrollAmt;
        if (goEnd)
            scrollAmt = this.nav[this.scrollOffset] + navDim - 20;
        else
            scrollAmt = this.nav[this.scrollOffset] - navDim + 20;
        try {
            this.nav.scroll({
                left: this.orientation === 'horizontal' ? scrollAmt : 0,
                top: this.orientation === 'vertical' ? scrollAmt : 0,
                behavior: 'smooth',
            });
        }
        catch (_e) {
            this.nav[this.scrollOffset] = scrollAmt;
        }
    };
    slotChangeHandler = () => {
        if (this.allActiveItems.length < 2)
            return;
        const item = this.allActiveItems.find((el, i) => this.isActiveHandler(el, i));
        if (item)
            this.activeItem = item;
        this.recalculatePositions();
        if (!this.showIndicator)
            return;
        /** maintain a weakmap of mutation observers to maintain item / indicator position on all changes. */
        this.allActiveItems.forEach((item) => {
            if (this.mutationObservers.get(item))
                return;
            const mo = new MutationObserver(() => this.syncActiveItemIndicator());
            mo.observe(item, {
                characterData: true,
                childList: true,
                subtree: true,
            });
            this.mutationObservers.set(item, mo);
        });
    };
    handleClick = (event) => {
        if (this.allActiveItems.length < 2)
            return;
        const foundItem = event
            .composedPath()
            .find((e) => this.allActiveItems.includes(e));
        if (foundItem)
            this.activeItem = foundItem;
    };
    handleKeyDown = (event) => {
        // Activate a tab
        if (['Enter', ' '].includes(event.key)) {
            const foundItem = event
                .composedPath()
                .find((e) => this.allActiveItems.includes(e));
            if (foundItem)
                this.activeItem = foundItem;
        }
    };
    recalculatePositions = () => {
        this.updateScrollControls();
        this.syncActiveItemIndicator();
        this.scrollToActiveItem();
    };
    // lifecycle
    connectedCallback() {
        this.instantReCalc = true;
        this.isRtl =
            this.orientation === 'horizontal' && !!this.host.closest('[dir="rtl"]');
        if (Build.isBrowser && window['MutationObserver'])
            this.slotChangeHandler();
        if (!window['ResizeObserver'])
            return;
        if (this.ro) {
            this.ro.disconnect();
            this.ro = undefined;
        }
        const ro = (this.ro = new ResizeObserver(this.recalculatePositions));
        ro.observe(this.host);
    }
    componentWillLoad() {
        this.watchHideScrollbars();
    }
    componentDidLoad() {
        this.activeItem = this.allActiveItems.find((el, i) => this.isActiveHandler(el, i));
        this.hideStartBtn();
        this.hideEndBtn();
        requestAnimationFrame(() => (this.instantReCalc = false));
    }
    disconnectedCallback() {
        if (this.ro) {
            this.ro.disconnect();
            this.ro = undefined;
        }
    }
    render() {
        return (h(Host, { key: 'e5c6238730817f0c7036060faa8c344d514a3924', dir: this.isRtl ? 'rtl' : null, class: "nano-masked-overflow", "has-scroll-controls": this.hasScrollControls }, h("div", { key: '4027acd51b80fd1bf2bb9d66a05ec051802732f3', part: "base", class: {
                onav: true,
                [`onav--${this.orientation}`]: true,
                'onav--has-scroll-controls': this.hasScrollControls,
                'onav--has-scroll-controls-start': !this.hideControlStart,
                'onav--has-scroll-controls-end': !this.hideControlEnd,
                'onav--no-transitions': this.instantReCalc,
                'onnav--has-indicator': this.showIndicator,
                'onnav--hide-scrollbars': this.hideScrollbars && this.hideScrollbars !== 'false',
            }, onClick: this.handleClick, onKeyDown: this.handleKeyDown }, h("div", { key: 'a552b2e3cdf9f84249655b21ce6f2acb5c330a75', class: "onav__nav" }, this.scrollControls && (h("nano-icon-button", { key: '5aca29990db29b3d0d00e6bca317cd3870d06d6b', part: "scroll-button scroll-button-prev", class: {
                'onav__scroll-button': true,
                'onav__scroll-button--start': true,
            }, ref: (btn) => (this.startBtn = btn), onClick: () => this.handleBtnClick(false), label: "Click to scroll", iconName: this.orientation === 'horizontal'
                ? 'light/chevron-left'
                : 'light/chevron-up' })), h("div", { key: '9c2b4c91859b5c6c132ab2eecc01cf56eeec18b9', part: "scroller", ref: (el) => (this.nav = el), class: "onav__scroller", onScroll: this.handleScroll, tabindex: this.hasScrollControls ? 0 : undefined, role: "region", "aria-label": this.label || undefined }, h("div", { key: 'a77097f7b9a70e08f9a1eb980948c5c92dff760d', part: "items", ref: (el) => (this.itemContainer = el), class: "onav__items" }, this.showIndicator && (h("div", { key: 'b8661b042f9e195850a33ce3570b75c9bed25d7e', part: "indicator", ref: (el) => (this.activeIndicator = el), class: "onav__active-indicator" })), h("slot", { key: '54af572f6ef591d8d3cb34221e048ac0e32140bb', onSlotchange: this.slotChangeHandler }))), this.scrollControls && (h("nano-icon-button", { key: 'e27a06cc2cec7ed373c527e3c6db5a0a56a90b71', part: "scroll-button scroll-button-next", class: {
                'onav__scroll-button': true,
                'onav__scroll-button--end': true,
            }, ref: (btn) => (this.endBtn = btn), onClick: () => this.handleBtnClick(true), label: "Click to scroll", iconName: this.orientation === 'horizontal'
                ? 'light/chevron-right'
                : 'light/chevron-down' }))))));
    }
    static get is() { return "nano-masked-overflow"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["masked-overflow.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["masked-overflow.css"]
        };
    }
    static get properties() {
        return {
            "label": {
                "type": "string",
                "attribute": "label",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Used to appropriately label the scrolling element for screen-readers"
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "scrollControls": {
                "type": "boolean",
                "attribute": "scroll-controls",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Disables the scroll arrow buttons that appear when content overflows"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "true"
            },
            "orientation": {
                "type": "string",
                "attribute": "orientation",
                "mutable": false,
                "complexType": {
                    "original": "'horizontal' | 'vertical'",
                    "resolved": "\"horizontal\" | \"vertical\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The flex direction of the element"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "'horizontal'"
            },
            "showIndicator": {
                "type": "boolean",
                "attribute": "show-indicator",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Shows a track and a highlight line that automatically moves the currently active item."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            },
            "hideScrollbars": {
                "type": "any",
                "attribute": "hide-scrollbars",
                "mutable": true,
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Show or hide the browser's default scrollbars. Defaults to `true` on vertical orientation and `false` on `horizontal` orientation"
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "activeHandler": {
                "type": "unknown",
                "attribute": "active-handler",
                "mutable": false,
                "complexType": {
                    "original": "(item: HTMLElement, _index: number) => void",
                    "resolved": "(item: HTMLElement, _index: number) => void",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "param",
                            "text": "item - html element currently being assessed"
                        }, {
                            "name": "param",
                            "text": "_index - the index of the html element currently being assessed"
                        }, {
                            "name": "returns",
                            "text": "void"
                        }],
                    "text": "A function called when an item becomes 'active' (via click or keyboard)"
                },
                "getter": false,
                "setter": false,
                "defaultValue": "(item: HTMLElement, _index: number) =>\n    item.classList.add('active')"
            },
            "inactiveHandler": {
                "type": "unknown",
                "attribute": "inactive-handler",
                "mutable": false,
                "complexType": {
                    "original": "(item: HTMLElement, _index: number) => void",
                    "resolved": "(item: HTMLElement, _index: number) => void",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "param",
                            "text": "item - html element currently being assessed"
                        }, {
                            "name": "param",
                            "text": "_index - the index of the html element currently being assessed"
                        }, {
                            "name": "returns",
                            "text": "void"
                        }],
                    "text": "A function called when an item becomes 'inactive'\n(because another item was made active)"
                },
                "getter": false,
                "setter": false,
                "defaultValue": "(item: HTMLElement, _index: number) =>\n    item.classList.remove('active')"
            },
            "isActiveHandler": {
                "type": "unknown",
                "attribute": "is-active-handler",
                "mutable": false,
                "complexType": {
                    "original": "(item: HTMLElement, _index: number) => boolean",
                    "resolved": "(item: HTMLElement, _index: number) => boolean",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "param",
                            "text": "item - html element currently being assessed"
                        }, {
                            "name": "param",
                            "text": "_index - the index of the html element currently being assessed"
                        }, {
                            "name": "returns",
                            "text": "void"
                        }],
                    "text": "A function called to assess an initial, active item"
                },
                "getter": false,
                "setter": false,
                "defaultValue": "(item: HTMLElement, _index: number) =>\n    item.classList.contains('active')"
            }
        };
    }
    static get states() {
        return {
            "instantReCalc": {},
            "hasScrollControls": {},
            "hideControlEnd": {},
            "hideControlStart": {}
        };
    }
    static get methods() {
        return {
            "syncActiveItem": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Sync up the view to the active item.\nUse this when the active item changes outside of this component.",
                    "tags": []
                }
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "hideScrollbars",
                "methodName": "watchHideScrollbars"
            }, {
                "propName": "isActiveHandler",
                "methodName": "watchIsActiveHandler"
            }, {
                "propName": "hasScrollControls",
                "methodName": "watchScrollControls"
            }, {
                "propName": "hideControlEnd",
                "methodName": "hideEndBtn"
            }, {
                "propName": "hideControlStart",
                "methodName": "hideStartBtn"
            }];
    }
}
