import { L as Logger, H as registerInstance, P as createEvent, M as h, N as Host, O as getElement, x as waitAfterFramePaint, Q as raf } from './index-PmQf2Yvi.js';
import { _ as __decorate, a as __metadata } from './tslib.es6-dfXERft2.js';
import { a as ariaBooleanToString } from './aria-DBsOHTWj.js';
import { B as BEM } from './bem-ByiN-QjD.js';
import { L as ListenToElementStates, B as BalElementStateListener } from './element-states.decorator-sRmB7nsL.js';
import { s as stopEventBubbling } from './form-input-BnvpIVmf.js';
import { i as isNil } from './index-zWZxKBfo.js';
import { i as inheritAttributes } from './attributes-C4sZQbkT.js';
import { i as includes, s as startsWith } from './utils-C_qktPg7.js';
import { d as defaultBalAriaForm } from './form-FCLKfv6B.js';
import './listener-DAmzgERU.js';
import './_commonjsHelpers-BFTU3MAI.js';

const balOptionCss = ":root{--bal-option-padding-x:var(--bal-space-small);--bal-option-min-height:1.5rem;--bal-option-background:var(--bal-color-white);--bal-option-background-hovered:var(--bal-color-grey-3);--bal-option-background-selected:var(--bal-color-primary-1);--bal-option-background-selected-hovered:#dddfeb;--bal-option-background-invalid:var(--bal-color-danger-1);--bal-option-background-invalid-hovered:#fbe0de;--bal-option-border-width:var(--bal-border-width-normal);--bal-option-border-color:var(--bal-color-grey-2);--bal-option-font-family:var(--bal-font-family-text);--bal-option-font-weight:var(--bal-font-weight-regular);--bal-option-line-height:1.125rem;--bal-option-text-hovered:var(--bal-color-text-primary-hovered);--bal-option-text-pressed:var(--bal-color-text-primary-pressed);--bal-option-text-disabled:var(--bal-color-text-grey-light)}.bal-option{font-size:16px;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:block;list-style:none;cursor:pointer;border-bottom-width:var(--bal-option-border-width);border-bottom-color:var(--bal-option-border-color);border-bottom-style:solid;background:var(--bal-option-background);padding-left:var(--bal-option-padding-x);padding-right:var(--bal-option-padding-x);min-height:var(--bal-option-min-height)}.bal-option>bal-stack{min-height:var(--bal-option-min-height)}.bal-option:last-child{border-bottom:none}.bal-option::-moz-focus-inner{border:none}.bal-option--hidden{display:none;visibility:hidden}.bal-option__inner{font-family:var(--bal-option-font-family);font-weight:var(--bal-option-font-weight);line-height:var(--bal-option-line-height);word-break:break-word;white-space:normal;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;-ms-flex:1;flex:1}.bal-option__inner--multiline{white-space:normal}.bal-option--focused{background:var(--bal-option-background-hovered)}@media (hover: hover)and (pointer: fine){.bal-option:hover{background:var(--bal-option-background-hovered);color:var(--bal-option-text-hovered)}}.bal-option:active{background:var(--bal-option-background-hovered);color:var(--bal-option-text-pressed)}.bal-option--selected{background:var(--bal-option-background-selected)}@media (hover: hover)and (pointer: fine){.bal-option--selected:hover{background:var(--bal-option-background-selected-hovered)}}.bal-option--selected:active{background:var(--bal-option-background-selected-hovered)}.bal-option--selected .bal-option__inner{font-weight:var(--bal-font-weight-bold)}.bal-option--selected.bal-option--focused{background:var(--bal-option-background-selected-hovered)}.bal-option--selected.bal-option--focused .bal-option__inner{font-weight:var(--bal-font-weight-bold)}.bal-option--invalid{background:var(--bal-option-background-invalid)}@media (hover: hover)and (pointer: fine){.bal-option--invalid:hover{background:var(--bal-option-background-invalid-hovered)}}.bal-option--invalid:active{background:var(--bal-option-background-invalid-hovered)}.bal-option--invalid.bal-option--focused{background:var(--bal-option-background-invalid-hovered)}.bal-option--invalid .bal-option__inner{color:var(--bal-color-text-danger-hovered)}@media (hover: hover)and (pointer: fine){.bal-option--invalid:hover .bal-option__inner{color:var(--bal-color-text-danger-hovered)}}.bal-option--invalid:active .bal-option__inner{color:var(--bal-color-text-danger-pressed)}.bal-option--disabled,.bal-option--disabled.bal-option--focused{background:var(--bal-option-background) !important;color:var(--bal-option-text-disabled) !important;cursor:default !important}.bal-option--disabled:hover,.bal-option--disabled.bal-option--focused:hover{background:var(--bal-option-background) !important}.bal-option{--bal-check-size:1.25rem}";

const Option = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        this.balOptionChange = createEvent(this, "balOptionChange");
        this.balOptionFocus = createEvent(this, "balOptionFocus");
        this.inputId = `bal-option-${balOptionIds++}`;
        this.checkbox = false;
        this.interactionState = BalElementStateListener.DefaultState;
        /**
         * PUBLIC PROPERTY API
         * ------------------------------------------------------
         */
        /**
         * Label will be shown in the input element when it got selected
         */
        this.label = '';
        /**
         * The value of the select option. This value will be returned by the parent `<bal-select>` element.
         */
        this.value = '';
        /**
         * If `true`, the user cannot interact with the option.
         */
        this.disabled = false;
        /**
         * If `true`, the option can present in more than one line.
         */
        this.multiline = false;
        /**
         * If `true`, the option is shown in red.
         */
        this.invalid = false;
        /**
         * If `true`, the option is selected.
         */
        this.selected = false;
        /**
         * If `true`, the option is focused.
         */
        this.focused = false;
        /**
         * If `true`, the option is hidden.
         */
        this.hidden = false;
        /**
         * EVENT BINDING
         * ------------------------------------------------------
         */
        this.onClick = (ev) => {
            const listEl = this.el.closest('bal-option-list');
            if (this.disabled || (listEl && listEl.disabled)) {
                stopEventBubbling(ev);
            }
            else {
                if (listEl && listEl.required && !listEl.multiple) {
                    if (!this.selected) {
                        this.select(true);
                    }
                }
                else {
                    this.select(!this.selected);
                }
            }
        };
    }
    createLogger(log) {
        this.log = log;
    }
    /**
     * LIFECYCLE
     * ------------------------------------------------------
     */
    componentWillRender() {
        if (this.el) {
            const optionListEl = this.el.closest('bal-option-list');
            if (optionListEl) {
                this.checkbox = !!optionListEl.multiple;
            }
        }
    }
    /**
     * LISTENERS
     * ------------------------------------------------------
     */
    listenToMouseEnter() {
        const { label, value, selected, disabled, hidden } = this;
        if (!hidden && !disabled) {
            this.balOptionFocus.emit({ label, value, selected });
        }
    }
    elementStateListener(info) {
        this.interactionChildElements.forEach(element => {
            element.hovered = info.hovered;
            element.pressed = info.pressed;
        });
    }
    /**
     * PUBLIC METHODS
     * ------------------------------------------------------
     */
    /**
     * Selects or deselects the option and informs other components
     */
    async select(selected = true) {
        this.selected = selected;
        const { label, value } = this;
        this.balOptionChange.emit({ label, value, selected });
    }
    /**
     * PRIVATE METHODS
     * ------------------------------------------------------
     */
    get interactionChildElements() {
        return Array.from(this.el.querySelectorAll('bal-check'));
    }
    /**
     * RENDER
     * ------------------------------------------------------
     */
    render() {
        const block = BEM.block('option');
        return (h(Host, { key: 'b1354e891bf0e8daf9dbd51f9f9c0343cb153cd7', class: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, block.class()), block.modifier('checkbox').class(this.checkbox)), block.modifier('focused').class(this.focused)), block.modifier('invalid').class(this.invalid)), block.modifier('selected').class(this.selected)), block.modifier('disabled').class(this.disabled)), block.modifier('hidden').class(this.hidden)), role: "option", id: this.inputId, "data-value": this.value, "data-label": this.label, "aria-label": this.label, "aria-hidden": ariaBooleanToString(this.hidden), "aria-selected": ariaBooleanToString(this.selected), "aria-checked": ariaBooleanToString(this.selected), "aria-disabled": ariaBooleanToString(this.disabled), "aria-invalid": ariaBooleanToString(this.invalid), tabIndex: -1, onClick: this.onClick }, h("bal-stack", { key: '86b29daf65cebd3b742841eb2ab3785a74ddf620', py: "small", space: "x-small" }, this.checkbox ? (h("bal-check", { checked: this.selected, disabled: this.disabled, invalid: this.invalid, hovered: this.interactionState.hovered, pressed: this.interactionState.pressed, tabindex: -1 })) : (''), h("div", { key: '59f0e1afb3e543123dd891a5fc439f737c2454e4', class: Object.assign(Object.assign({}, block.element('inner').class()), block.element('inner').modifier('multiline').class(this.multiline)) }, h("slot", { key: 'f817c6d88b07f8113194fbf6ba5f0298c9e99f81' })), this.selected && !this.checkbox ? (h("bal-icon", { name: "check", size: "small", color: this.disabled ? 'grey' : this.invalid ? 'danger' : 'primary' })) : (''))));
    }
    get el() { return getElement(this); }
};
__decorate([
    Logger('bal-option'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Function]),
    __metadata("design:returntype", void 0)
], Option.prototype, "createLogger", null);
__decorate([
    ListenToElementStates(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], Option.prototype, "elementStateListener", null);
let balOptionIds = 0;
Option.style = balOptionCss;

const balOptionListCss = ".bal-option-list{display:block;position:relative;width:100%;list-style:none;margin:0;outline:0;max-height:var(--bal-option-list-max-height);border-radius:var(--bal-radius-normal);overflow:hidden auto}";

const OptionList = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        this.inputId = `bal-option-list-${balOptionListIds++}`;
        this.inheritAttributes = {};
        this.ariaForm = defaultBalAriaForm;
        /**
         * PUBLIC PROPERTY API
         * ------------------------------------------------------
         */
        /**
         * If `true` the list supports multiple selections
         */
        this.multiple = false;
        /**
         * If `true`, the user cannot interact with the option.
         */
        this.disabled = false;
        /**
         * If `true`, the user must fill in a value before submitting a form.
         */
        this.required = false;
        /**
         * Defines the focused option with his index value
         */
        this.focusIndex = -1;
        /**
         * Defines the filter logic of the list
         */
        this.filter = 'includes';
        /**
         * Defines the max height of the list element
         */
        this.contentHeight = 262;
    }
    createLogger(log) {
        this.log = log;
    }
    contentHeightChanged(value) {
        if (value === undefined) {
            this.el.style.removeProperty('--bal-option-list-max-height');
        }
        else {
            this.el.style.setProperty('--bal-option-list-max-height', `${value}px`);
        }
    }
    /**
     * LIFECYCLE
     * ------------------------------------------------------
     */
    componentDidLoad() {
        this.contentHeightChanged(this.contentHeight);
    }
    componentWillRender() {
        this.inheritAttributes = inheritAttributes(this.el, ['aria-multiselectable', 'aria-labelledby']);
    }
    /**
     * LISTENERS
     * ------------------------------------------------------
     */
    listenToMouseEnter(ev) {
        const options = this.options;
        const indexToFocus = this.getOptionIndex(options, ev.detail.value);
        if (indexToFocus !== undefined) {
            this.updateFocus(options, indexToFocus);
        }
    }
    listenToOptionChange({ detail }) {
        if (!this.multiple) {
            this.options.filter(option => option.value !== detail.value).forEach(option => (option.selected = false));
        }
    }
    /**
     * PUBLIC METHODS
     * ------------------------------------------------------
     */
    /**
     * Focus the selected visible option in the list, if no option is selected it selects the first one
     */
    async focusSelected() {
        const options = this.options;
        const indexToFocus = this.getSelectedOptionIndex(options);
        if (indexToFocus > 0) {
            this.updateFocus(options, indexToFocus);
            await waitAfterFramePaint();
            return indexToFocus;
        }
        else {
            return await this.focusFirst();
        }
    }
    /**
     * Focus the first visible option in the list
     * @returns focusIndex
     */
    async focusFirst() {
        const options = this.options;
        const indexToFocus = this.getFirstOptionIndex(options);
        this.updateFocus(options, indexToFocus);
        const option = options[indexToFocus];
        this.updateScrollTopPosition(option);
        await waitAfterFramePaint();
        return indexToFocus;
    }
    /**
     * Focus the last visible option in the list
     * @returns focusIndex
     */
    async focusLast() {
        const options = this.options;
        const indexToFocus = this.getLastOptionIndex(options);
        this.updateFocus(options, indexToFocus);
        const option = options[indexToFocus];
        this.updateScrollBottomPosition(option);
        await waitAfterFramePaint();
        return indexToFocus;
    }
    /**
     * Focus the next visible option in the list
     * @returns focusIndex
     */
    async focusNext() {
        const options = this.options;
        const indexToFocus = this.getNextOptionIndex(options);
        this.updateFocus(options, indexToFocus);
        const option = options[indexToFocus];
        this.updateScrollBottomPosition(option);
        await waitAfterFramePaint();
        return indexToFocus;
    }
    /**
     * Focus the previous visible option in the list
     * @returns focusIndex
     */
    async focusPrevious() {
        const options = this.options;
        const indexToFocus = this.getPreviousOptionIndex(options);
        this.updateFocus(options, indexToFocus);
        const option = options[indexToFocus];
        this.updateScrollTopPosition(option);
        await waitAfterFramePaint();
        return indexToFocus;
    }
    /**
     * Focus the option with the label that starts with the search property
     * @returns focusIndex
     */
    async focusByLabel(search, config) {
        const options = this.options;
        const indexToFocus = this.getOptionIndexByLabel(options, search);
        this.updateFocus(options, indexToFocus);
        const option = options[indexToFocus];
        this.updateScrollTopPosition(option);
        await waitAfterFramePaint();
        if (config.select) {
            await option.select();
        }
        return indexToFocus;
    }
    /**
     * Filter the options by the given filter property and hides options
     * @returns focusIndex
     */
    async filterByContent(search) {
        const options = this.allOptions;
        this.filterOptions(options, search);
        await waitAfterFramePaint();
        return this.focusFirst();
    }
    /**
     * Shows or hides all options
     */
    async resetHidden(hidden = false) {
        this.options.forEach(option => (option.hidden = hidden));
        await waitAfterFramePaint();
        this.resetFocus();
    }
    /**
     * Selects or deselects all options
     */
    async resetSelected(selected = false) {
        this.options.forEach(option => (option.selected = selected));
        await waitAfterFramePaint();
    }
    /**
     * Updates options
     */
    async updateSelected(values) {
        this.options.forEach(option => (option.selected = values.includes(option.value)));
        await waitAfterFramePaint();
    }
    /**
     * Resets the focus index to pristine and scrolls to the top of the list
     */
    async resetFocus() {
        const options = this.options;
        const indexToFocus = -1;
        this.updateFocus(options, indexToFocus);
        this.scrollTo(0);
        await waitAfterFramePaint();
        return indexToFocus;
    }
    /**
     * Returns a list of option values
     */
    async getSelectedValues() {
        const options = this.options;
        return options.filter(option => option.selected).map(option => option.value);
    }
    /**
     * Returns a list of option labels
     */
    async getSelectedOptions(values) {
        const options = this.options;
        if (values && values.length > 0) {
            return options.filter(option => values.includes(option.value)).map(option => option);
        }
        return options.filter(option => option.selected).map(option => option);
    }
    /**
     * Returns a list of options
     */
    async getValues() {
        return this.options.map(option => option.value);
    }
    /**
     * Returns a list of options
     */
    async getLabels() {
        return this.options.map(option => option.label);
    }
    /**
     * Returns a list of accessible options
     */
    async getOptions() {
        return this.options.filter(o => !o.disabled || !o.hidden);
    }
    /**
     * Selects the option with the current focus
     */
    async selectByFocus() {
        const options = this.options;
        const option = options[this.focusIndex];
        if (option) {
            if (this.multiple) {
                option.select(!option.selected);
            }
            else {
                option.select(true);
            }
        }
    }
    /**
     * @internal
     */
    async setAriaForm(ariaForm) {
        this.ariaForm = Object.assign({}, ariaForm);
    }
    /**
     * GETTERS
     * ------------------------------------------------------
     */
    get options() {
        return Array.from(this.el.querySelectorAll('bal-option')).filter(o => !o.hidden);
    }
    get allOptions() {
        return Array.from(this.el.querySelectorAll('bal-option'));
    }
    /**
     * PRIVATE METHODS
     * ------------------------------------------------------
     */
    filterOptions(options, search) {
        const filteredOptions = [];
        const filter = this.filter === 'includes' ? includes : startsWith;
        for (let index = 0; index < options.length; index++) {
            const option = options[index];
            const content = option.textContent || '';
            if (filter(content, search)) {
                filteredOptions.push(option);
                option.hidden = false;
            }
            else {
                option.hidden = true;
            }
        }
        return filteredOptions;
    }
    isOptionVisible(option) {
        const visibleHeight = this.el.clientHeight;
        const topPosition = this.el.scrollTop;
        const bottomPosition = topPosition + visibleHeight;
        const isVisible = topPosition <= option.offsetTop && option.offsetTop + option.clientHeight <= bottomPosition;
        return isVisible;
    }
    updateScrollTopPosition(option) {
        if (option) {
            const isVisible = this.isOptionVisible(option);
            if (!isVisible) {
                this.scrollTo(this.getScrollTopForTopPosition(option));
            }
        }
    }
    updateScrollBottomPosition(option) {
        if (option) {
            const isVisible = this.isOptionVisible(option);
            if (!isVisible) {
                this.scrollTo(this.getScrollTopForBottomPosition(option));
            }
        }
    }
    getScrollTopForTopPosition(option) {
        const topPosition = option.offsetTop;
        const scrollTop = topPosition;
        if (scrollTop < 0) {
            return 0;
        }
        return scrollTop;
    }
    getScrollTopForBottomPosition(option) {
        const visibleHeight = this.el.clientHeight;
        const bottomPosition = option.offsetTop + option.clientHeight;
        const scrollTop = bottomPosition - visibleHeight;
        if (scrollTop < 0) {
            return 0;
        }
        return scrollTop;
    }
    async scrollTo(scrollTop) {
        if (scrollTop !== undefined && scrollTop !== null) {
            if (this.focusRaf !== undefined) {
                cancelAnimationFrame(this.focusRaf);
            }
            this.focusRaf = raf(async () => {
                this.el.scrollTop = scrollTop;
            });
        }
    }
    updateFocus(options, indexToFocus) {
        this.focusIndex = indexToFocus;
        for (let index = 0; index < options.length; index++) {
            const option = options[index];
            option.focused = index === indexToFocus;
        }
    }
    getOptionIndex(options, value) {
        for (let index = 0; index < options.length; index++) {
            const option = options[index];
            if (option.value === value) {
                return index;
            }
        }
        return undefined;
    }
    getSelectedOptionIndex(options) {
        for (let index = 0; index < options.length; index++) {
            const option = options[index];
            if (option.selected && !option.hidden) {
                return index;
            }
        }
        return this.focusIndex;
    }
    getFirstOptionIndex(options) {
        for (let index = 0; index < options.length; index++) {
            const option = options[index];
            if (!option.disabled && !option.hidden) {
                return index;
            }
        }
        return this.focusIndex;
    }
    getNextOptionIndex(options, index = this.focusIndex) {
        if (index < 0) {
            return this.getFirstOptionIndex(options);
        }
        const lastIndex = this.getLength(options);
        let newIndex = index;
        if (index < lastIndex) {
            newIndex = index + 1;
            if (options[newIndex].disabled) {
                return this.getNextOptionIndex(options, newIndex);
            }
        }
        return newIndex;
    }
    getPreviousOptionIndex(options, index = this.focusIndex) {
        const firstIndex = this.getFirstOptionIndex(options);
        if (index <= firstIndex) {
            return firstIndex;
        }
        let newIndex = index;
        newIndex = index - 1;
        if (options[newIndex].disabled) {
            return this.getPreviousOptionIndex(options, newIndex);
        }
        return newIndex;
    }
    getLastOptionIndex(options) {
        for (let index = options.length - 1; index >= 0; index--) {
            const option = options[index];
            if (!option.disabled) {
                return index;
            }
        }
        return this.focusIndex;
    }
    getOptionIndexByLabel(options, label) {
        if (label === undefined || label === '') {
            return this.focusIndex;
        }
        const option = options.find(o => startsWith(o.label || '', label));
        if (!isNil(option) && option.id) {
            return options.indexOf(option);
        }
        return this.focusIndex;
    }
    getLength(options) {
        const indexes = options.map((option, index) => (option.disabled ? 0 : index));
        const length = Math.max(...indexes);
        return length;
    }
    /**
     * RENDER
     * ------------------------------------------------------
     */
    render() {
        const block = BEM.block('option-list');
        const labelledby = this.labelledby || this.ariaForm.labelId;
        return (h(Host, { key: 'b232136a55b7116f1894fca167540d50b7d974db', class: Object.assign({}, block.class()), id: this.inputId }, h("div", Object.assign({ key: '2c8a33c3e9baf88f7acd26bb097690909c2fdacc', role: "listbox", "aria-labelledby": labelledby, "aria-disabled": ariaBooleanToString(this.disabled), class: Object.assign({}, block.element('container').class()) }, this.inheritAttributes), h("slot", { key: 'ae72b336a225342786af05f2a722d3d2f6409871' }))));
    }
    get el() { return getElement(this); }
    static get watchers() { return {
        "contentHeight": ["contentHeightChanged"]
    }; }
};
__decorate([
    Logger('bal-option-list'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Function]),
    __metadata("design:returntype", void 0)
], OptionList.prototype, "createLogger", null);
let balOptionListIds = 0;
OptionList.style = balOptionListCss;

export { Option as bal_option, OptionList as bal_option_list };
