Skip to content
Snippets Groups Projects
Select Git revision
  • c0ac77bf8566f5ba878447cccac48b4660e3f297
  • master default protected
  • 1.31
  • 4.24.3
  • 4.24.2
  • 4.24.1
  • 4.24.0
  • 4.23.6
  • 4.23.5
  • 4.23.4
  • 4.23.3
  • 4.23.2
  • 4.23.1
  • 4.23.0
  • 4.22.3
  • 4.22.2
  • 4.22.1
  • 4.22.0
  • 4.21.0
  • 4.20.1
  • 4.20.0
  • 4.19.0
  • 4.18.0
23 results

action-button.mjs

Blame
  • select.mjs 82.40 KiB
    /**
     * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
     * Node module: @schukai/monster
     *
     * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
     * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
     *
     * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
     * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
     * For more information about purchasing a commercial license, please contact schukai GmbH.
     *
     * SPDX-License-Identifier: AGPL-3.0
     */
    
    import {instanceSymbol} from "../../constants.mjs";
    import {internalSymbol} from "../../constants.mjs";
    import {buildMap} from "../../data/buildmap.mjs";
    import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
    import {positionPopper} from "./util/floating-ui.mjs";
    import {
        addAttributeToken,
        findClosestByAttribute,
        removeAttributeToken,
    } from "../../dom/attributes.mjs";
    import {ATTRIBUTE_PREFIX, ATTRIBUTE_ROLE} from "../../dom/constants.mjs";
    import {CustomControl} from "../../dom/customcontrol.mjs";
    import {
        assembleMethodSymbol,
        getSlottedElements,
        registerCustomElement,
    } from "../../dom/customelement.mjs";
    import {
        findTargetElementFromEvent,
        fireCustomEvent,
        fireEvent,
    } from "../../dom/events.mjs";
    import {getDocument} from "../../dom/util.mjs";
    import {Formatter} from "../../text/formatter.mjs";
    import {getGlobal} from "../../types/global.mjs";
    import {ID} from "../../types/id.mjs";
    import {
        isArray,
        isFunction,
        isInteger,
        isIterable,
        isObject,
        isPrimitive,
        isString,
    } from "../../types/is.mjs";
    import {Observer} from "../../types/observer.mjs";
    import {ProxyObserver} from "../../types/proxyobserver.mjs";
    import {validateArray, validateString} from "../../types/validate.mjs";
    import {Processing} from "../../util/processing.mjs";
    import {STYLE_DISPLAY_MODE_BLOCK} from "./constants.mjs";
    import {SelectStyleSheet} from "./stylesheet/select.mjs";
    import {
        getDocumentTranslations,
        Translations,
    } from "../../i18n/translations.mjs";
    import {getLocaleOfDocument} from "../../dom/locale.mjs";
    import {addErrorAttribute, removeErrorAttribute} from "../../dom/error.mjs";
    
    export {
        Select,
        popperElementSymbol,
        getSummaryTemplate,
        getSelectionTemplate,
    };
    
    /**
     * @private
     * @type {Symbol}
     */
    const timerCallbackSymbol = Symbol("timerCallback");
    
    /**
     * @private
     * @type {Symbol}
     */
    const keyFilterEventSymbol = Symbol("keyFilterEvent");
    
    /**
     * @private
     * @type {Symbol}
     */
    const lazyLoadDoneSymbol = Symbol("lazyLoadDone");
    
    /**
     * @private
     * @type {Symbol}
     */
    const isLoadingSymbol = Symbol("isLoading");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const closeEventHandler = Symbol("closeEventHandler");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const clearOptionEventHandler = Symbol("clearOptionEventHandler");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const resizeObserverSymbol = Symbol("resizeObserver");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const keyEventHandler = Symbol("keyEventHandler");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const lastFetchedDataSymbol = Symbol("lastFetchedData");
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const inputEventHandler = Symbol("inputEventHandler");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const changeEventHandler = Symbol("changeEventHandler");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const controlElementSymbol = Symbol("controlElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const selectionElementSymbol = Symbol("selectionElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const containerElementSymbol = Symbol("containerElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const popperElementSymbol = Symbol("popperElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const inlineFilterElementSymbol = Symbol("inlineFilterElement");
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const popperFilterElementSymbol = Symbol("popperFilterElement");
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const popperFilterContainerElementSymbol = Symbol(
        "popperFilterContainerElement",
    );
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const optionsElementSymbol = Symbol("optionsElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const noOptionsAvailableElementSymbol = Symbol("noOptionsAvailableElement");
    
    /**
     * local symbol
     * @private
     * @type {Symbol}
     */
    const statusOrRemoveBadgesElementSymbol = Symbol("statusOrRemoveBadgesElement");
    
    /**
     * @private
     * @type {Symbol}
     */
    const areOptionsAvailableAndInitSymbol = Symbol("@@areOptionsAvailableAndInit");
    
    /**
     * @private
     * @type {symbol}
     */
    const disabledRequestMarker = Symbol("@@disabledRequestMarker");
    
    /**
     * @private
     * @type {number}
     */
    const FOCUS_DIRECTION_UP = 1;
    /**
     * @private
     * @type {number}
     */
    const FOCUS_DIRECTION_DOWN = 2;
    
    /**
     * @private
     * @type {string}
     */
    const FILTER_MODE_REMOTE = "remote";
    
    /**
     * @private
     * @type {string}
     */
    const FILTER_MODE_OPTIONS = "options";
    
    /**
     * @private
     * @type {string}
     */
    const FILTER_MODE_DISABLED = "disabled";
    
    /**
     * @private
     * @type {string}
     */
    const FILTER_POSITION_POPPER = "popper";
    /**
     * @private
     * @type {string}
     */
    const FILTER_POSITION_INLINE = "inline";
    
    /**
     * A select control that can be used to select o
     *
     * ne or more options from a list.
     *
     * @issue @issue https://localhost.alvine.dev:8444/development/issues/closed/280.html
     *
     * @fragments /fragments/components/form/select/
     *
     * @example /examples/components/form/select-with-options Select with options
     * @example /examples/components/form/select-with-html-options Select with HTML options
     * @example /examples/components/form/select-multiple Multiple selection
     * @example /examples/components/form/select-filter Filter
     * @example /examples/components/form/select-fetch Fetch options
     * @example /examples/components/form/select-lazy Lazy load
     * @example /examples/components/form/select-remote-filter Remote filter
     *
     * @copyright schukai GmbH
     * @summary A beautiful select control that can make your life easier and also looks good.
     * @fires monster-change
     * @fires monster-changed
     */
    class Select extends CustomControl {
        /**
         *
         */
        constructor() {
            super();
            initOptionObserver.call(this);
        }
    
        /**
         * This method is called by the `instanceof` operator.
         * @return {Symbol}
         */
        static get [instanceSymbol]() {
            return Symbol.for("@schukai/monster/components/form/select@@instance");
        }
    
        /**
         * The current selection of the Select
         *
         * ```
         * e = document.querySelector('monster-select');
         * console.log(e.value)
         * // ↦ 1
         * // ↦ ['1','2']
         * ```
         *
         * @return {string}
         */
        get value() {
            return convertSelectionToValue.call(this, this.getOption("selection"));
        }
    
        /**
         * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
         * @return {boolean}
         */
        static get formAssociated() {
            return true;
        }
    
        /**
         * Set selection
         *
         * ```
         * e = document.querySelector('monster-select');
         * e.value=1
         * ```
         *
         * @property {string|array} value
         * @throws {Error} unsupported type
         * @fires monster-selected this event is fired when the selection is set
         */
        set value(value) {
            const result = convertValueToSelection.call(this, value);
            setSelection
                .call(this, result.selection)
                .then(() => {
                })
                .catch((e) => {
                    addErrorAttribute(this, e);
                });
        }
    
        /**
         * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
         * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
         *
         * The individual configuration values can be found in the table.
         *
         * @property {Object} toggleEventType List of event types to be observed for opening the dropdown
         * @property {boolean} delegatesFocus lorem [see mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus)
         * @property {Object[]} options Selection of key identifier pairs available for selection and displayed in the dropdown.
         * @property {string} options[].label
         * @property {string} options[].value
         * @property {string} options[].visibility hidden or visible
         * @property {Array} selection Selected options
         * @property {Integer} showMaxOptions Maximum number of visible options before a scroll bar should be displayed.
         * @property {string} type Multiple (checkbox) or single selection (radio)
         * @property {string} name Name of the form field
         * @property {string} url Load options from server per url
         * @property {object} lookup Load options from server per url
         * @property {string} lookup.url Load options from server per url
         * @property {boolean} lookup.grouping Load all selected options from server per url at once (true) or one by one (false)
         * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
         * @property {String} fetch.redirect
         * @property {String} fetch.method
         * @property {String} fetch.mode
         * @property {String} fetch.credentials
         * @property {Object} fetch.headers
         * @property {Object} labels
         * @property {string} labels.cannot-be-loaded cannot be loaded
         * @property {string} labels.no-options-available no options available
         * @property {string} labels.select-an-option select an option
         * @property {string} labels.no-option no option in the list, maybe you have to change the filter
         * @property {Object} features List with features
         * @property {Boolean} features.clearAll Display of a delete button to delete the entire selection
         * @property {Boolean} features.clear Display of a delete key for deleting the specific selection
         * @property {Boolean} features.lazyLoad Load options when first opening the dropdown. (Hint; lazylLoad is not supported with remote filter)
         * @property {Boolean} features.closeOnSelect Close the dropdown when an option is selected (since 3.54.0)
         * @property {Boolean} features.emptyValueIfNoOptions If no options are available, the selection is set to an empty array
         * @property {Boolean} features.storeFetchedData Store fetched data in the object
         * @property {Boolean} features.useStrictValueComparison Use strict value comparison for the selection
         * @property {string} filter.defaultValue Default filter value, if the filter is empty, if the default value is null, then no request is made
         * @property {Boolean} filter.mode Filter mode, values: options, remote, disabled (Hint; lazylLoad is not supported with remote filter, if you use remote filter, the lazyLoad is disabled)
         * @property {Object} templates Template definitions
         * @property {string} templates.main Main template
         * @property {string} templateMapping Mapping of the template placeholders
         * @property {string} templateMapping.selected Selected Template
         * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
         * @property {string} popper.placement PopperJS placement
         * @property {Object[]} modifiers PopperJS placement
         * @property {Object} mapping
         * @property {String} mapping.selector Path to select the appropriate entries
         * @property {String} mapping.labelTemplate template with the label placeholders in the form ${name}, where name is the key (**)
         * @property {String} mapping.valueTemplate template with the value placeholders in the form ${name}, where name is the key
         * @property {function|undefined} mapping.filter Filtering of values via a function
         * @property {Object} formatter
         * @property {function|undefined} formatter.selection format selection label
         */
        get defaults() {
            return Object.assign(
                {},
                super.defaults,
                {
                    toggleEventType: ["click", "touch"],
                    delegatesFocus: false,
                    options: [],
                    selection: [],
                    showMaxOptions: 10,
                    type: "radio",
                    name: new ID("s").toString(),
                    features: {
                        clearAll: true,
                        clear: true,
                        lazyLoad: false,
                        closeOnSelect: false,
                        emptyValueIfNoOptions: false,
                        storeFetchedData: false,
                        useStrictValueComparison: false,
                    },
                    url: null,
                    lookup: {
                        url: null,
                        grouping: false,
                    },
                    labels: getTranslations(),
                    messages: {
                        control: null,
                        selected: null,
                        emptyOptions: null,
                    },
                    fetch: {
                        redirect: "error",
                        method: "GET",
                        mode: "same-origin",
                        credentials: "same-origin",
                        headers: {
                            accept: "application/json",
                        },
                    },
                    filter: {
                        defaultValue: null,
                        mode: FILTER_MODE_DISABLED,
                        position: FILTER_POSITION_INLINE,
                        marker: {
                            open: "{",
                            close: "}",
                        },
                    },
                    classes: {
                        badge: "monster-badge-primary",
                        statusOrRemoveBadge: "empty",
                    },
                    mapping: {
                        selector: "*",
                        labelTemplate: "",
                        valueTemplate: "",
                        filter: null,
                    },
                    formatter: {
                        selection: buildSelectionLabel,
                    },
                    templates: {
                        main: getTemplate(),
                    },
                    templateMapping: {
                        /** with the attribute `data-monster-selected-template` the template for the selected options can be defined. */
                        selected: getSelectionTemplate(),
                    },
    
                    popper: {
                        placement: "bottom",
                        middleware: ["flip", "offset:1"],
                    },
                },
                initOptionsFromArguments.call(this),
            );
        }
    
        /**
         * @return {Select}
         */
        [assembleMethodSymbol]() {
            const self = this;
            super[assembleMethodSymbol]();
    
            initControlReferences.call(self);
            initEventHandler.call(self);
    
            let lazyLoadFlag = self.getOption("features.lazyLoad", false);
            let remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE;
    
            if (getFilterMode.call(this) === FILTER_MODE_REMOTE) {
                self.getOption("features.lazyLoad", false);
                if (lazyLoadFlag === true) {
                    addErrorAttribute(this, "lazyLoad is not supported with remote filter");
                    lazyLoadFlag = false;
                }
            }
    
            if (self.hasAttribute("value")) {
                new Processing(10, () => {
                    this.value = this.getAttribute("value");
                })
                    .run()
                    .catch((e) => {
                        addErrorAttribute(this, e);
                    });
            }
    
            if (self.getOption("url") !== null) {
                if (lazyLoadFlag || remoteFilterFlag) {
                    lookupSelection.call(self);
                } else {
                    self.fetch().catch((e) => {
                        addErrorAttribute(self, e);
                    });
                }
            }
    
            requestAnimationFrame(() => {
                let lastValue = self.value;
                self[internalSymbol].attachObserver(
                    new Observer(function () {
                        if (isObject(this) && this instanceof ProxyObserver) {
                            const n = this.getSubject()?.options?.value;
    
                            if (lastValue !== n && n !== undefined) {
                                lastValue = n;
                                setSelection
                                    .call(self, n)
                                    .then(() => {
                                    })
                                    .catch((e) => {
                                        addErrorAttribute(self, e);
                                    });
                            }
                        }
                    }),
                );
    
                areOptionsAvailableAndInit.call(self);
            });
    
            return this;
        }
    
        /**
         *
         * @return {*}
         * @throws {Error} storeFetchedData is not enabled
         * @since 3.66.0
         */
        getLastFetchedData() {
            if (this.getOption("features.storeFetchedData") === false) {
                throw new Error("storeFetchedData is not enabled");
            }
    
            return this?.[lastFetchedDataSymbol];
        }
    
        /**
         * The Button.click() method simulates a click on the internal button element.
         *
         * @since 3.27.0
         * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
         */
        click() {
            if (this.getOption("disabled") === true) {
                return;
            }
    
            toggle.call(this);
        }
    
        /**
         * The Button.focus() method sets focus on the internal button element.
         *
         * @since 3.27.0
         * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
         */
        focus(options) {
            if (this.getOption("disabled") === true) {
                return;
            }
    
            new Processing(() => {
                gatherState.call(this);
                focusFilter.call(this, options);
            })
                .run()
                .catch((e) => {
                    addErrorAttribute(this, e);
                });
        }
    
        /**
         * The Button.blur() method removes focus from the internal button element.
         * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur
         */
        blur() {
            new Processing(() => {
                gatherState.call(this);
                blurFilter.call(this);
            })
                .run()
                .catch((e) => {
                    addErrorAttribute(this, e);
                });
        }
    
        /**
         * If no url is specified, the options are taken from the Component itself.
         *
         * @param {string|URL} url URL to fetch the options
         * @return {Promise}
         */
        fetch(url) {
            return fetchIt.call(this, url);
        }
    
        /**
         * @return {void}
         */
        connectedCallback() {
            super.connectedCallback();
            const document = getDocument();
    
            for (const [, type] of Object.entries(["click", "touch"])) {
                // close on outside ui-events
                document.addEventListener(type, this[closeEventHandler]);
            }
    
            parseSlotsToOptions.call(this);
            attachResizeObserver.call(this);
            updatePopper.call(this);
    
            new Processing(() => {
                gatherState.call(this);
                focusFilter.call(this);
            })
                .run()
                .catch((e) => {
                    addErrorAttribute(this, e);
                });
        }
    
        /**
         * @return {void}
         */
        disconnectedCallback() {
            super.disconnectedCallback();
            const document = getDocument();
    
            // close on outside ui-events
            for (const [, type] of Object.entries(["click", "touch"])) {
                document.removeEventListener(type, this[closeEventHandler]);
            }
    
            disconnectResizeObserver.call(this);
        }
    
        /**
         * Import Select Options from dataset
         * Not to be confused with the control defaults/options
         *
         * @param {array|object|Map|Set} data
         * @return {Select}
         * @throws {Error} map is not iterable
         * @throws {Error} missing label configuration
         * @fires monster-options-set this event is fired when the options are set
         */
        importOptions(data) {
            const mappingOptions = this.getOption("mapping", {});
            const selector = mappingOptions?.["selector"];
            const labelTemplate = mappingOptions?.["labelTemplate"];
            const valueTemplate = mappingOptions?.["valueTemplate"];
            const filter = mappingOptions?.["filter"];
    
            let flag = false;
            if (labelTemplate === "") {
                addErrorAttribute(this, "empty label template");
                flag = true;
            }
    
            if (valueTemplate === "") {
                addErrorAttribute(this, "empty value template");
                flag = true;
            }
    
            if (flag === true) {
                throw new Error("missing label configuration");
            }
    
            const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
    
            const options = [];
    
            if (!isIterable(map)) {
                throw new Error("map is not iterable");
            }
    
            const visibility = "visible";
    
            map.forEach((label, value) => {
                options.push({
                    value,
                    label,
                    visibility,
                    data: map.get(value),
                });
            });
    
            runAsOptionLengthChanged.call(this, map.size);
            this.setOption("options", options);
    
            fireCustomEvent(this, "monster-options-set", {
                options,
            });
    
            setTimeout(() => {
                setSelection
                    .call(this, this.getOption("selection"))
                    .then(() => {
                    })
                    .catch((e) => {
                        addErrorAttribute(this, e);
                    });
            }, 10);
    
            return this;
        }
    
        /**
         * @private
         * @return {Select}
         */
        calcAndSetOptionsDimension() {
            calcAndSetOptionsDimension.call(this);
            return this;
        }
    
        /**
         *
         * @return {string}
         */
        static getTag() {
            return "monster-select";
        }
    
        /**
         *
         * @return {CSSStyleSheet[]}
         */
        static getCSSStyleSheet() {
            return [SelectStyleSheet];
        }
    }
    
    /**
     * @private
     * @returns {object}
     */
    function getTranslations() {
        const locale = getLocaleOfDocument();
        switch (locale.language) {
            case "de":
                return {
                    "cannot-be-loaded": "Kann nicht geladen werden",
                    "no-options-available": "Keine Optionen verfügbar.",
                    "click-to-load-options": "Klicken, um Optionen zu laden.",
                    "select-an-option": "Wähle eine Option",
                    "summary-text": {
                        zero: "Keine Einträge ausgewählt",
                        one: '<span class="monster-badge-primary-pill">1</span> Eintrag ausgewählt',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> Einträge ausgewählt',
                    },
                    "no-options": "Leider gibt es keine Optionen in der Liste.",
                    "no-options-found":
                        "Keine Optionen in der Liste verfügbar. Bitte ändern Sie den Filter.",
                };
            case "fr":
                return {
                    "cannot-be-loaded": "Impossible de charger",
                    "no-options-available": "Aucune option disponible.",
                    "click-to-load-options": "Cliquez pour charger les options.",
                    "select-an-option": "Sélectionnez une option",
                    "summary-text": {
                        zero: "Aucune entrée sélectionnée",
                        one: '<span class="monster-badge-primary-pill">1</span> entrée sélectionnée',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> entrées sélectionnées',
                    },
                    "no-options":
                        "Malheureusement, il n'y a pas d'options disponibles dans la liste.",
                    "no-options-found":
                        "Aucune option disponible dans la liste. Veuillez modifier le filtre.",
                };
    
            case "sp":
                return {
                    "cannot-be-loaded": "No se puede cargar",
                    "no-options-available": "No hay opciones disponibles.",
                    "click-to-load-options": "Haga clic para cargar opciones.",
                    "select-an-option": "Seleccione una opción",
                    "summary-text": {
                        zero: "No se seleccionaron entradas",
                        one: '<span class="monster-badge-primary-pill">1</span> entrada seleccionada',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> entradas seleccionadas',
                    },
                    "no-options":
                        "Desafortunadamente, no hay opciones disponibles en la lista.",
                    "no-options-found":
                        "No hay opciones disponibles en la lista. Considere modificar el filtro.",
                };
            case "it":
                return {
                    "cannot-be-loaded": "Non può essere caricato",
                    "no-options-available": "Nessuna opzione disponibile.",
                    "click-to-load-options": "Clicca per caricare le opzioni.",
                    "select-an-option": "Seleziona un'opzione",
                    "summary-text": {
                        zero: "Nessuna voce selezionata",
                        one: '<span class="monster-badge-primary-pill">1</span> voce selezionata',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> voci selezionate',
                    },
                    "no-options": "Purtroppo, non ci sono opzioni disponibili nella lista.",
                    "no-options-found":
                        "Nessuna opzione disponibile nella lista. Si prega di modificare il filtro.",
                };
            case "pl":
                return {
                    "cannot-be-loaded": "Nie można załadować",
                    "no-options-available": "Brak dostępnych opcji.",
                    "click-to-load-options": "Kliknij, aby załadować opcje.",
                    "select-an-option": "Wybierz opcję",
                    "summary-text": {
                        zero: "Nie wybrano żadnych wpisów",
                        one: '<span class="monster-badge-primary-pill">1</span> wpis został wybrany',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> wpisy zostały wybrane',
                    },
                    "no-options": "Niestety, nie ma dostępnych opcji na liście.",
                    "no-options-found":
                        "Brak dostępnych opcji na liście. Rozważ zmianę filtra.",
                };
            case "no":
                return {
                    "cannot-be-loaded": "Kan ikke lastes",
                    "no-options-available": "Ingen alternativer tilgjengelig.",
                    "click-to-load-options": "Klikk for å laste alternativer.",
                    "select-an-option": "Velg et alternativ",
                    "summary-text": {
                        zero: "Ingen oppføringer ble valgt",
                        one: '<span class="monster-badge-primary-pill">1</span> oppføring valgt',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> oppføringer valgt',
                    },
                    "no-options":
                        "Dessverre er det ingen alternativer tilgjengelig i listen.",
                    "no-options-found":
                        "Ingen alternativer tilgjengelig på listen. Vurder å endre filteret.",
                };
    
            case "dk":
                return {
                    "cannot-be-loaded": "Kan ikke indlæses",
                    "no-options-available": "Ingen muligheder tilgængelige.",
                    "click-to-load-options": "Klik for at indlæse muligheder.",
                    "select-an-option": "Vælg en mulighed",
                    "summary-text": {
                        zero: "Ingen indlæg blev valgt",
                        one: '<span class="monster-badge-primary-pill">1</span> indlæg blev valgt',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> indlæg blev valgt',
                    },
                    "no-options":
                        "Desværre er der ingen muligheder tilgængelige på listen.",
                    "no-options-found":
                        "Ingen muligheder tilgængelige på listen. Overvej at ændre filteret.",
                };
            case "sw":
                return {
                    "cannot-be-loaded": "Kan inte laddas",
                    "no-options-available": "Inga alternativ tillgängliga.",
                    "click-to-load-options": "Klicka för att ladda alternativ.",
                    "select-an-option": "Välj ett alternativ",
                    "summary-text": {
                        zero: "Inga poster valdes",
                        one: '<span class="monster-badge-primary-pill">1</span> post valdes',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> poster valdes',
                    },
                    "no-options": "Tyvärr finns det inga alternativ tillgängliga i listan.",
                    "no-options-found":
                        "Inga alternativ finns tillgängliga i listan. Överväg att modifiera filtret.",
                };
    
            default:
            case "en":
                return {
                    "cannot-be-loaded": "Cannot be loaded",
                    "no-options-available": "No options available.",
                    "click-to-load-options": "Click to load options.",
                    "select-an-option": "Select an option",
                    "summary-text": {
                        zero: "No entries were selected",
                        one: '<span class="monster-badge-primary-pill">1</span> entry was selected',
                        other:
                            '<span class="monster-badge-primary-pill">${count}</span> entries were selected',
                    },
                    "no-options":
                        "Unfortunately, there are no options available in the list.",
                    "no-options-found":
                        "No options are available in the list. Please consider modifying the filter.",
                };
        }
    }
    
    /**
     * @private
     */
    function lookupSelection() {
        const self = this;
    
        setTimeout(() => {
            const selection = self.getOption("selection");
            if (selection.length === 0) {
                return;
            }
    
            if (self[isLoadingSymbol] === true) {
                return;
            }
    
            if (self[lazyLoadDoneSymbol] === true) {
                return;
            }
    
            let url = self.getOption("url");
            let lookupUrl = self.getOption("lookup.url");
            if (lookupUrl !== null) {
                url = lookupUrl;
            }
    
            if (this.getOption("lookup.grouping") === true) {
                filterFromRemoteByValue
                    .call(
                        self,
                        url,
                        selection.map((s) => s?.["value"]),
                    )
                    .catch((e) => {
                        addErrorAttribute(self, e);
                    });
                return;
            }
    
            for (const s of selection) {
                if (s?.["value"]) {
                    filterFromRemoteByValue.call(self, url, s?.["value"]).catch((e) => {
                        addErrorAttribute(self, e);
                    });
                }
            }
        }, 100);
    }
    
    function fetchIt(url, controlOptions) {
        if (url instanceof URL) {
            url = url.toString();
        }
    
        if (url !== undefined && url !== null) {
            url = validateString(url);
        } else {
            url = this.getOption("url");
            if (url === null) {
                return Promise.reject(new Error("No url defined"));
            }
        }
    
        return new Promise((resolve, reject) => {
            setStatusOrRemoveBadges.call(this, "loading");
    
            new Processing(10, () => {
                fetchData
                    .call(this, url)
                    .then((map) => {
                        if (
                            isObject(map) ||
                            isArray(map) ||
                            map instanceof Set ||
                            map instanceof Map
                        ) {
                            try {
                                this.importOptions(map);
                            } catch (e) {
                                setStatusOrRemoveBadges.call(this, "error");
                                reject(e);
                                return;
                            }
    
                            this[lastFetchedDataSymbol] = map;
    
                            let result;
                            const selection = this.getOption("selection");
                            let newValue = [];
                            if (selection) {
                                newValue = selection;
                            } else if (this.hasAttribute("value")) {
                                newValue = this.getAttribute("value");
                            }
    
                            result = setSelection.call(this, newValue);
                            requestAnimationFrame(() => {
                                checkOptionState.call(this);
                                setStatusOrRemoveBadges.call(this, "closed");
                                updatePopper.call(this);
                                resolve(result);
                            });
    
                            return;
                        }
    
                        setStatusOrRemoveBadges.call(this, "error");
                        reject(new Error("invalid response"));
                    })
                    .catch((e) => {
                        setStatusOrRemoveBadges.call(this, "error");
                        reject(e);
                    });
            })
                .run()
                .catch((e) => {
                    setStatusOrRemoveBadges.call(this, "error");
                    addErrorAttribute(this, e);
                    reject(e);
                });
        });
    }
    
    /**
     * This attribute can be used to pass a URL to this select.
     *
     * ```
     * <monster-select data-monster-url="https://example.com/"></monster-select>
     * ```
     *
     * @private
     * @deprecated 2024-01-21 (you should use data-monster-option-...)
     * @return {object}
     */
    function initOptionsFromArguments() {
        const options = {};
    
        const template = this.getAttribute("data-monster-selected-template");
        if (isString(template)) {
            if (!options["templateMapping"]) options["templateMapping"] = {};
    
            switch (template) {
                case "summary":
                case "default":
                    options["templateMapping"]["selected"] = getSummaryTemplate();
                    break;
                case "selected":
                    options["templateMapping"]["selected"] = getSelectionTemplate();
                    break;
                default:
                    addErrorAttribute(this, "invalid template, use summary or selected");
            }
        }
    
        return options;
    }
    
    /**
     * @private
     */
    function attachResizeObserver() {
        // against flickering
        this[resizeObserverSymbol] = new ResizeObserver((entries) => {
            if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
                try {
                    this[timerCallbackSymbol].touch();
                    return;
                } catch (e) {
                    delete this[timerCallbackSymbol];
                }
            }
    
            this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
                updatePopper.call(this);
                delete this[timerCallbackSymbol];
            });
        });
    
        requestAnimationFrame(() => {
    
            let parent = this.parentNode;
            while (!(parent instanceof HTMLElement) && parent !== null) {
                parent = parent.parentNode;
            }
    
            if (parent instanceof HTMLElement) {
                this[resizeObserverSymbol].observe(parent);
            }
    
    
        });
    
    }
    
    /**
     * @private
     */
    function disconnectResizeObserver() {
        if (this[resizeObserverSymbol] instanceof ResizeObserver) {
            this[resizeObserverSymbol].disconnect();
        }
    }
    
    /**
     * @private
     * @returns {string}
     */
    function getSelectionTemplate() {
        return `<div data-monster-role="selection" part="selection"
                     data-monster-insert="selection path:selection" role="search"
                ><input type="text" role="searchbox"
                        part="inline-filter" name="inline-filter"
                        data-monster-role="filter"
                        autocomplete="off"
                        tabindex="0"
                ><div data-monster-replace="path:messages.control"></div>
                </div>`;
    }
    
    /**
     * @private
     * @returns {string}
     */
    function getSummaryTemplate() {
        return `<div data-monster-role="selection" role="search" part="summary">
        <input type="text" role="searchbox"
               part="inline-filter" name="inline-filter"
               data-monster-role="filter"
               autocomplete="off"
               tabindex="0"
        >
        <div data-monster-replace="path:messages.selected"></div>    
    </div>`;
    }
    
    /**
     * @return {void}
     * @private
     */
    function parseSlotsToOptions() {
        let options = this.getOption("options");
        if (!isIterable(options)) {
            options = [];
        }
    
        let counter = 1;
        getSlottedElements.call(this, "div").forEach((node) => {
            let value = (counter++).toString();
            let visibility = "visible";
    
            if (node.hasAttribute("data-monster-value")) {
                value = node.getAttribute("data-monster-value");
            }
    
            let label = node.outerHTML;
    
            if (node.style.display === "none") {
                visibility = "hidden";
            }
    
            options.push({
                value,
                label,
                visibility,
            });
        });
    
        runAsOptionLengthChanged.call(this, options.length);
        this.setOption("options", options);
    }
    
    /**
     * wait until all options are finished rendering
     *
     * @private
     * @param {int} targetLength
     */
    function runAsOptionLengthChanged(targetLength) {
        const self = this;
    
        if (!self[optionsElementSymbol]) {
            return;
        }
    
        const callback = function (mutationsList, observer) {
            const run = false;
            for (const mutation of mutationsList) {
                if (mutation.type === "childList") {
                    const run = true;
                    break;
                }
            }
    
            if (run === true) {
                const nodes = self[optionsElementSymbol].querySelectorAll(
                    `div[${ATTRIBUTE_ROLE}=option]`,
                );
    
                if (nodes.length === targetLength) {
                    checkOptionState.call(self);
                    observer.disconnect();
                }
            }
        };
    
        const observer = new MutationObserver(callback);
        observer.observe(self[optionsElementSymbol], {
            attributes: false,
            childList: true,
            subtree: true,
        });
    }
    
    /**
     * @private
     * @param {*} value
     * @return {*}
     */
    function buildSelectionLabel(value) {
        const options = this.getOption("options");
    
        for (let i = 0; i < options.length; i++) {
            let o = options?.[i];
            let l, v, v2;
    
            if (this.getOption("features.useStrictValueComparison") === true) {
                v = value;
            } else {
                v = `${value}`;
            }
    
            if (isPrimitive(o) && o === value) {
                return o;
            } else if (!isObject(o)) {
                continue;
            }
    
            if (this.getOption("features.useStrictValueComparison") === true) {
                l = o?.["label"];
                v2 = o?.["value"];
            } else {
                l = `${o?.["label"]}`;
                v2 = `${o?.["value"]}`;
            }
    
            if (v2 === v) {
                return l;
            }
        }
    
        return undefined;
    }
    
    /**
     * @private
     * @param {*} value
     * @return {string}
     * @throws {Error} no value found
     */
    function getSelectionLabel(value) {
        const callback = this.getOption("formatter.selection");
        if (isFunction(callback)) {
            const label = callback.call(this, value);
            if (isString(label)) return label;
        }
    
        if (isString(value) || isInteger(value)) {
            return `${value}`;
        }
    
        return this.getOption("labels.cannot-be-loaded", value);
    }
    
    /**
     * @private
     * @param {Event} event
     */
    function handleToggleKeyboardEvents(event) {
        switch (event?.["code"]) {
            case "Escape":
                toggle.call(this);
                event.preventDefault();
                break;
            case "Space":
                toggle.call(this);
                event.preventDefault();
                break;
            case "ArrowDown":
                show.call(this);
                activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
                event.preventDefault();
                break;
            case "ArrowUp":
                hide.call(this);
                event.preventDefault();
                break;
        }
    }
    
    /**
     * @license AGPLv3
     * @since 1.15.0
     * @private
     * @this CustomElement
     */
    function initOptionObserver() {
        const self = this;
    
        self.attachObserver(
            new Observer(function () {
                new Processing(() => {
                    try {
                        self.updateI18n();
                    } catch (e) {
                        addErrorAttribute(self, e);
                        requestAnimationFrame(() => {
                            setStatusOrRemoveBadges.call(self, "error");
                        });
                    }
                    try {
                        areOptionsAvailableAndInit.call(self);
                    } catch (e) {
                        addErrorAttribute(self, e);
                        requestAnimationFrame(() => {
                            setStatusOrRemoveBadges.call(self, "error");
                        });
                    }
    
                    setSummaryAndControlText.call(self);
                }).run();
            }),
        );
    }
    
    /**
     * @private
     * @returns {Translations}
     */
    function getDefaultTranslation() {
        const translation = new Translations("en").assignTranslations(
            this.getOption("labels", {}),
        );
    
        try {
            const doc = getDocumentTranslations();
            translation.locale = doc.locale;
        } catch (e) {
        }
    
        return translation;
    }
    
    /**
     * @private
     * @return {string|*}
     */
    function setSummaryAndControlText() {
        const translations = getDefaultTranslation.call(this);
        const selections = this.getOption("selection");
    
        const text = translations.getPluralRuleText(
            "summary-text",
            selections.length,
            "",
        );
    
        const selectedText = new Formatter({
            count: String(selections.length),
        }).format(text);
    
        this.setOption("messages.selected", selectedText);
    
        const current = this.getOption("messages.control");
        const msg = this.getOption("labels.select-an-option");
    
        if (
            current === "" ||
            current === undefined ||
            current === msg ||
            current === null
        ) {
            if (selections.length === 0) {
                this.setOption("messages.control", msg);
            } else {
                this.setOption("messages.control", "");
            }
        }
    }
    
    /**
     * @private
     * @return {NodeList}
     */
    function getOptionElements() {
        return this[optionsElementSymbol].querySelectorAll(
            `[${ATTRIBUTE_ROLE}=option]`,
        );
    }
    
    /**
     * With the help of this filter callback, values can be filtered out. Only if the filter function returns true, the value is taken for the map.
     *
     * @callback function
     * @param {*} value Value
     * @param {string} key  Key
     * @see Monster.Data.buildMap
     */
    
    /**
     *
     * @callback Monster.Components.Form~formatterSelectionCallback
     * @param {*} value Value
     * @return {string|undefined}
     * @see Monster.Data.buildMap
     */
    
    /**
     * @private
     */
    function calcAndSetOptionsDimension() {
        const options = getOptionElements.call(this);
        const container = this[optionsElementSymbol];
        if (!(container instanceof HTMLElement && options instanceof NodeList)) {
            return;
        }
    
        let visible = 0;
        let optionHeight = 0;
        const max = this.getOption("showMaxOptions", 10);
    
        let scrollFlag = false;
        for (const [, option] of Object.entries(options)) {
            const computedStyle = getGlobal().getComputedStyle(option);
            if (computedStyle.display === "none") continue;
    
            let h = option.getBoundingClientRect().height;
            h += parseInt(computedStyle.getPropertyValue("margin-top"), 10);
            h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10);
            optionHeight += h;
    
            visible++;
    
            if (visible > max) {
                break;
            }
        }
    
        if (visible > max) {
            visible = max;
            scrollFlag = true;
        }
    
        if (visible === 0) {
            if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
                this.setOption(
                    "messages.emptyOptions",
                    this.getOption("labels.no-options-available"),
                );
            } else {
                this.setOption(
                    "messages.emptyOptions",
                    this.getOption("labels.no-options-found"),
                );
            }
            this[noOptionsAvailableElementSymbol].classList.remove("d-none");
        } else {
            this[noOptionsAvailableElementSymbol].classList.add("d-none");
        }
    
        const styles = getGlobal().getComputedStyle(this[optionsElementSymbol]);
        let padding = parseInt(styles.getPropertyValue("padding-top"), 10);
        padding += parseInt(styles.getPropertyValue("padding-bottom"), 10);
    
        let margin = parseInt(styles.getPropertyValue("margin-top"), 10);
        margin += parseInt(styles.getPropertyValue("margin-bottom"), 10);
    
        const containerHeight = optionHeight + padding + margin;
        container.style.height = `${containerHeight}px`;
    
        if (scrollFlag === true) {
            container.style.overflowY = "scroll";
        } else {
            container.style.overflowY = "auto";
        }
    
        const domRect = this[controlElementSymbol].getBoundingClientRect();
    
        this[popperElementSymbol].style.width = `${domRect.width}px`;
        container.style.overflowX = "auto";
    }
    
    /**
     * @private
     * @param {number} direction
     * @throws {Error} no shadow-root is defined
     */
    function activateCurrentOption(direction) {
        if (!this.shadowRoot) {
            throw new Error("no shadow-root is defined");
        }
    
        let focused = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`);
    
        if (
            !(focused instanceof HTMLElement) ||
            focused.matches("[data-monster-visibility=hidden]")
        ) {
            for (const [, e] of Object.entries(
                this.shadowRoot.querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`),
            )) {
                if (e.matches("[data-monster-visibility=visible]")) {
                    focused = e;
                    break;
                }
            }
        } else {
            if (direction === FOCUS_DIRECTION_DOWN) {
                while (focused.nextSibling) {
                    focused = focused.nextSibling;
    
                    if (
                        focused instanceof HTMLElement &&
                        focused.hasAttribute(ATTRIBUTE_ROLE) &&
                        focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
                        focused.matches("[data-monster-visibility=visible]") &&
                        focused.matches(":not([data-monster-filtered=true])")
                    ) {
                        break;
                    }
                }
            } else {
                let found = false;
                while (focused.previousSibling) {
                    focused = focused.previousSibling;
                    if (
                        focused instanceof HTMLElement &&
                        focused.hasAttribute(ATTRIBUTE_ROLE) &&
                        focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
                        focused.matches("[data-monster-visibility=visible]") &&
                        focused.matches(":not([data-monster-filtered=true])")
                    ) {
                        found = true;
                        break;
                    }
                }
                if (found === false) {
                    focusFilter.call(this);
                }
            }
        }
    
        new Processing(() => {
            if (focused instanceof HTMLElement) {
                this.shadowRoot
                    .querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`)
                    .forEach((e) => {
                        e.removeAttribute(`${ATTRIBUTE_PREFIX}focused`);
                    });
    
                focused.focus();
                focused.setAttribute(`${ATTRIBUTE_PREFIX}focused`, true);
            }
        })
            .run()
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    }
    
    /**
     * @private
     */
    function filterOptions() {
        new Processing(() => {
            let filterValue;
    
            switch (this.getOption("filter.position")) {
                case FILTER_POSITION_INLINE:
                    if (this[inlineFilterElementSymbol] instanceof HTMLElement) {
                        filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
                    } else {
                        return;
                    }
    
                    break;
                case FILTER_POSITION_POPPER:
                default:
                    if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
                        filterValue = this[popperFilterElementSymbol].value.toLowerCase();
                    } else {
                        return;
                    }
            }
    
            const options = this.getOption("options");
            for (const [i, option] of Object.entries(options)) {
                if (option.label.toLowerCase().indexOf(filterValue) === -1) {
                    this.setOption(`options.${i}.filtered`, "true");
                } else {
                    this.setOption(`options.${i}.filtered`, undefined);
                }
            }
        })
            .run()
            .then(() => {
                new Processing(100, () => {
                    calcAndSetOptionsDimension.call(this);
                    focusFilter.call(this);
                })
                    .run()
                    .catch((e) => {
                        addErrorAttribute(this, e);
                    });
            })
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    }
    
    /**
     * @private
     * @param {Event} event
     */
    function handleFilterKeyboardEvents(event) {
        const shiftKey = event?.["shiftKey"];
    
        switch (event?.["code"]) {
            case "Tab":
                activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
                event.preventDefault();
                break;
            case "Escape":
                toggle.call(this);
                event.preventDefault();
                break;
            case "Tab" && shiftKey === true:
            case "ArrowUp":
                activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
                event.preventDefault();
                break;
            case "Tab" && !shiftKey:
            case "ArrowDown":
                activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
                event.preventDefault();
                break;
            default:
                if (
                    this.getOption("features.lazyLoad") === true &&
                    this[lazyLoadDoneSymbol] !== true
                ) {
                    this.click();
                }
    
                handleFilterKeyEvents.call(this);
        }
    }
    
    /**
     * Method handleFilterKeyEvents is used to handle filter key events.
     * Debounce is used to prevent multiple calls.
     *
     * @function
     * @name handleFilterKeyEvents
     *
     * @private
     * @return {void} This method does not return anything.
     */
    function handleFilterKeyEvents() {
        if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) {
            try {
                this[keyFilterEventSymbol].touch();
                return;
            } catch (e) {
                delete this[keyFilterEventSymbol];
            }
        }
    
        this[keyFilterEventSymbol] = new DeadMansSwitch(200, () => {
            if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) {
                filterOptions.call(this);
            } else {
                filterFromRemote.call(this).catch((e) => {
                    addErrorAttribute(this, e);
                });
            }
    
            delete this[keyFilterEventSymbol];
        });
    }
    
    /**
     * @private
     */
    function filterFromRemote() {
        if (
            !(this[inlineFilterElementSymbol] instanceof HTMLElement) &&
            !(this[popperFilterElementSymbol] instanceof HTMLElement)
        ) {
            return;
        }
    
        show.call(this);
    
        const url = this.getOption("url");
        if (!url) {
            addErrorAttribute(this, "Missing URL for Remote Filter.");
            return;
        }
    
        let filterValue;
    
        switch (this.getOption("filter.position")) {
            case FILTER_POSITION_INLINE:
                if (this[inlineFilterElementSymbol] instanceof HTMLElement) {
                    filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
                }
    
                break;
            case FILTER_POSITION_POPPER:
            default:
                if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
                    filterValue = this[popperFilterElementSymbol].value.toLowerCase();
                }
        }
    
        return filterFromRemoteByValue.call(this, url, filterValue);
    }
    
    function formatURL(url, value) {
        if (value === undefined || value === null || value === "") {
            value = this.getOption("filter.defaultValue");
            if (value === undefined || value === null || value === "") {
                value = disabledRequestMarker.toString();
            }
        }
    
        const formatter = new Formatter({filter: encodeURI(value)});
        const openMarker = this.getOption("filter.marker.open");
        let closeMarker = this.getOption("filter.marker.close");
        if (!closeMarker) {
            closeMarker = openMarker;
        }
    
        if (openMarker && closeMarker) {
            formatter.setMarker(openMarker, closeMarker);
        }
    
        return formatter.format(url);
    }
    
    /**
     * @private
     * @param optionUrl
     * @param value
     * @returns {Promise<unknown>}
     */
    function filterFromRemoteByValue(optionUrl, value) {
        return new Processing(() => {
            let url = formatURL.call(this, optionUrl, value);
            if (url.indexOf(disabledRequestMarker.toString()) !== -1) {
                return;
            }
    
            fetchIt
                .call(this, url, {
                    disableHiding: true,
                })
                .then(() => {
                    checkOptionState.call(this);
                    show.call(this);
                })
                .catch((e) => {
                    throw e;
                });
        })
            .run()
            .catch((e) => {
                throw e;
            });
    }
    
    /**
     * @param {Event} event
     * @private
     */
    function handleOptionKeyboardEvents(event) {
        const shiftKey = event?.["shiftKey"];
    
        switch (event?.["code"]) {
            case "Escape":
                toggle.call(this);
                event.preventDefault();
                break;
            case "Enter":
            case "Space":
                const path = event.composedPath();
                const element = path?.[0];
                if (element instanceof HTMLElement) {
                    const input = element.getElementsByTagName("input");
                    if (!input) {
                        return;
                    }
                    fireEvent(input, "click");
                }
                event.preventDefault();
                break;
    
            case "Tab" && shiftKey === true:
            case "ArrowUp":
                activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
                event.preventDefault();
                break;
    
            case "Tab" && !shiftKey:
            case "ArrowLeft":
            case "ArrowRight":
                // handled by tree select
                break;
            case "ArrowDown":
                activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
                event.preventDefault();
                break;
            default:
                const p = event.composedPath();
                if (p?.[0] instanceof HTMLInputElement) {
                    return;
                }
                focusFilter.call(this);
                break;
        }
    }
    
    /**
     * @private
     * @return {string}
     */
    function getFilterMode() {
        switch (this.getOption("filter.mode")) {
            case FILTER_MODE_OPTIONS:
                return FILTER_MODE_OPTIONS;
            case FILTER_MODE_REMOTE:
                return FILTER_MODE_REMOTE;
            default:
                return FILTER_MODE_DISABLED;
        }
    }
    
    /**
     * @private
     */
    function blurFilter() {
        if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
            return;
        }
    
        if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
            return;
        }
    
        this[popperFilterContainerElementSymbol].classList.remove("active");
        this[popperFilterContainerElementSymbol].blur();
    
        this[inlineFilterElementSymbol].classList.remove("active");
        this[inlineFilterElementSymbol].blur();
    }
    
    /**
     * @private
     * @param focusOptions
     */
    function focusPopperFilter(focusOptions) {
        this[popperFilterContainerElementSymbol].classList.remove("d-none");
        this[popperFilterElementSymbol].classList.add("active");
        this[inlineFilterElementSymbol].classList.remove("active");
        this[inlineFilterElementSymbol].classList.add("d-none");
    
        if (!(this[popperFilterElementSymbol] instanceof HTMLElement)) {
            addErrorAttribute(this, "Missing Popper Filter Element.");
            return;
        }
    
        // visibility is set to visible, because focus() does not work on invisible elements
        // and the class definition is assigned later in the processing
        setTimeout(() => {
            if (focusOptions === undefined || focusOptions === null) {
                this[popperFilterElementSymbol].focus();
            } else {
                this[popperFilterElementSymbol].focus(focusOptions);
            }
        }, 100);
    }
    
    /**
     * @private
     * @param focusOptions
     */
    function focusInlineFilter(focusOptions) {
        const options = this.getOption("options");
        if (
            (!isArray(options) || options.length === 0) &&
            getFilterMode.call(this) !== FILTER_MODE_REMOTE
        ) {
            return;
        }
    
        this[popperFilterContainerElementSymbol].classList.add("d-none");
        this[inlineFilterElementSymbol].classList.add("active");
        this[inlineFilterElementSymbol].classList.remove("d-none");
    
        // visibility is set to visible, because focus() does not work on invisible elements
        // and the class definition is assigned later in the processing
        setTimeout(() => {
            if (focusOptions === undefined || focusOptions === null) {
                this[inlineFilterElementSymbol].focus();
            } else {
                this[inlineFilterElementSymbol].focus(focusOptions);
            }
        }, 100);
    }
    
    /**
     * @private
     */
    function focusFilter(focusOptions) {
        if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
            this[popperFilterContainerElementSymbol].classList.add("d-none");
            this[inlineFilterElementSymbol].classList.add("d-none");
            return;
        }
    
        if (this.getOption("filter.position") === FILTER_POSITION_INLINE) {
            return focusInlineFilter.call(this, focusOptions);
        }
    
        return focusPopperFilter.call(this, focusOptions);
    }
    
    /**
     * @private
     * @return {array}
     * @throws {Error} no shadow-root is defined
     * @throws {Error} unsupported type
     */
    function gatherState() {
        const type = this.getOption("type");
        if (["radio", "checkbox"].indexOf(type) === -1) {
            throw new Error("unsupported type");
        }
    
        if (!this.shadowRoot) {
            throw new Error("no shadow-root is defined");
        }
    
        const selection = [];
        const elements = this.shadowRoot.querySelectorAll(
            `input[type=${type}]:checked`,
        );
    
        for (const e of elements) {
            selection.push({
                label: getSelectionLabel.call(this, e.value),
                value: e.value,
            });
        }
    
        setSelection
            .call(this, selection)
            .then(() => {
            })
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    
        if (this.getOption("features.closeOnSelect") === true) {
            toggle.call(this);
        }
    
        return this;
    }
    
    /**
     * @private
     * @throws {Error} no shadow-root is defined
     * @throws {Error} unsupported type
     */
    function clearSelection() {
        const type = this.getOption("type");
        if (["radio", "checkbox"].indexOf(type) === -1) {
            throw new Error("unsupported type");
        }
    
        if (!this.shadowRoot) {
            throw new Error("no shadow-root is defined");
        }
    
        setSelection
            .call(this, [])
            .then(() => {
            })
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    }
    
    /**
     * @private
     */
    function areOptionsAvailableAndInit() {
        // prevent multiple calls
        if (this[areOptionsAvailableAndInitSymbol] === undefined) {
            this[areOptionsAvailableAndInitSymbol] = 0;
        }
    
        if (this[areOptionsAvailableAndInitSymbol] > 0) {
            this[areOptionsAvailableAndInitSymbol]--;
            return true;
        }
    
        this[areOptionsAvailableAndInitSymbol]++;
    
        const options = this.getOption("options");
    
        if (
            options === undefined ||
            options === null ||
            (isArray(options) && options.length === 0)
        ) {
            setStatusOrRemoveBadges.call(this, "empty");
    
            // hide.call(this);
    
            let msg = this.getOption("labels.no-options-available");
    
            if (
                this.getOption("url") !== null &&
                this.getOption("features.lazyLoad") === true &&
                this[lazyLoadDoneSymbol] !== true
            ) {
                msg = this.getOption("labels.click-to-load-options");
            }
    
            this.setOption("messages.control", msg);
            this.setOption("messages.summary", "");
    
            if (this.getOption("features.emptyValueIfNoOptions") === true) {
                this.value = "";
            }
            addErrorAttribute(this, "No options available.");
            return false;
        }
    
        const selections = this.getOption("selection");
        if (
            selections === undefined ||
            selections === null ||
            selections.length === 0
        ) {
            this.setOption(
                "messages.control",
                this.getOption("labels.select-an-option"),
            );
        } else {
            this.setOption("messages.control", "");
        }
    
        this.setOption("messages.summary", setSummaryAndControlText.call(this));
    
        let updated = false;
        let valueCounter = 1;
        for (const option of options) {
            if (option?.visibility === undefined) {
                option.visibility = "visible";
                updated = true;
            }
    
            if (option?.value === undefined && option?.label === undefined) {
                option.value = `${valueCounter++}`;
                option.label = option.value;
                updated = true;
                continue;
            }
    
            if (option?.value === undefined) {
                option.value = option.label;
                updated = true;
            }
    
            if (option?.label === undefined) {
                option.label = option.value;
                updated = true;
            }
        }
    
        if (updated) {
            this.setOption("options", options);
        }
    
        setStatusOrRemoveBadges.call(this);
    
        removeErrorAttribute(this, "No options available.");
        return true;
    }
    
    /**
     * @private
     * @throws {Error} no shadow-root is defined
     */
    function checkOptionState() {
        if (!this.shadowRoot) {
            throw new Error("no shadow-root is defined");
        }
    
        const elements = this.shadowRoot.querySelectorAll(
            `[${ATTRIBUTE_ROLE}=option] input`,
        );
    
        let selection = this.getOption("selection");
        if (!isArray(selection)) {
            selection = [];
        }
    
        const checkedValues = selection.map((a) => {
            return a.value;
        });
    
        for (const e of elements) {
            if (checkedValues.indexOf(e.value) !== -1) {
                if (e.checked !== true) e.checked = true;
            } else {
                if (e.checked !== false) e.checked = false;
            }
        }
    }
    
    /**
     * @private
     * @param {*} value
     * @return {Object}
     */
    function convertValueToSelection(value) {
        const selection = [];
    
        if (isString(value)) {
            value = value
                .split(",")
                .map((a) => {
                    return a.trim();
                })
                .filter((a) => {
                    return a !== "";
                });
        }
    
        if (isString(value) || isInteger(value)) {
            selection.push({
                label: getSelectionLabel.call(this, value),
                value: value,
            });
        } else if (isArray(value)) {
            for (const v of value) {
                selection.push({
                    label: getSelectionLabel.call(this, v),
                    value: v,
                });
            }
    
            value = value.join(",");
        } else {
            throw new Error("unsupported type");
        }
    
        return {
            selection: selection,
            value: value,
        };
    }
    
    /**
     * @private
     * @param {array} selection
     * @return {string}
     */
    function convertSelectionToValue(selection) {
        const value = [];
    
        if (isArray(selection)) {
            for (const obj of selection) {
                const v = obj?.["value"];
                if (v !== undefined) value.push(`${v}`);
            }
        }
    
        if (value.length === 0) {
            return "";
        } else if (value.length === 1) {
            const v = value.pop();
            if (v === undefined) return "";
            if (v === null) return "";
            return `${v}`;
        }
    
        return value.join(",");
    }
    
    /**
     * @private
     * @param {array} selection
     * @return {Promise}
     * @throws {Error} no shadow-root is defined
     */
    function setSelection(selection) {
        if (isString(selection) || isInteger(selection)) {
            const result = convertValueToSelection.call(this, selection);
            selection = result?.selection;
        } else if (selection === undefined || selection === null) {
            selection = [];
        }
    
        validateArray(selection);
    
        for (let i = 0; i < selection.length; i++) {
            let l = getSelectionLabel.call(this, selection[i].value);
            if (l === selection[i].value) {
                l = selection[i].label;
            }
    
            selection[i] = {
                label: l,
                value: selection[i].value,
            };
        }
    
        this.setOption("selection", selection);
        checkOptionState.call(this);
        setSummaryAndControlText.call(this);
    
        try {
            this?.setFormValue(this.value);
        } catch (e) {
            addErrorAttribute(this, e);
        }
    
        fireCustomEvent(this, "monster-selected", {
            selection,
        });
    
        fireEvent(this, "change");
    
        return new Processing(() => {
            const CLASSNAME = "selected";
    
            if (!this.shadowRoot) {
                throw new Error("no shadow-root is defined");
            }
    
            const notSelected = this.shadowRoot.querySelectorAll(":not(:checked)");
    
            if (notSelected) {
                notSelected.forEach((node) => {
                    const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
                    if (parent) {
                        parent.classList.remove(CLASSNAME);
                    }
                });
            }
    
            const selected = this.shadowRoot.querySelectorAll(":checked");
    
            if (selected) {
                selected.forEach((node) => {
                    const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
                    if (parent) {
                        parent.classList.add(CLASSNAME);
                    }
                });
            }
        })
            .run()
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    }
    
    /**
     * @private
     * @param {string} url
     * @return {Promise}
     * @throws {TypeError} the result cannot be parsed
     * @throws {TypeError} unsupported response
     */
    function fetchData(url) {
        const self = this;
        if (!url) url = this.getOption("url");
        if (!url) return Promise.resolve();
    
        const fetchOptions = this.getOption("fetch", {});
    
        let delayWatch = false;
    
        // if fetch short time, do not show loading badge, because of flickering
        requestAnimationFrame(() => {
            if (delayWatch === true) return;
            setStatusOrRemoveBadges.call(this, "loading");
            delayWatch = true;
        });
    
        url = formatURL.call(this, url);
    
        self[isLoadingSymbol] = true;
        const global = getGlobal();
        return global
            .fetch(url, fetchOptions)
            .then((response) => {
                self[isLoadingSymbol] = false;
                delayWatch = true;
                const contentType = response.headers.get("content-type");
                if (contentType && contentType.indexOf("application/json") !== -1) {
                    return response.text();
                }
    
                throw new TypeError(`unsupported response ${contentType}`);
            })
            .then((text) => {
                try {
                    return Promise.resolve(JSON.parse(String(text)));
                } catch (e) {
                    throw new TypeError("the result cannot be parsed, check the URL");
                }
            })
            .catch((e) => {
                self[isLoadingSymbol] = false;
                delayWatch = true;
                throw e;
            });
    }
    
    /**
     * @private
     */
    function hide() {
        this[popperElementSymbol].style.display = "none";
        setStatusOrRemoveBadges.call(this, "closed");
        removeAttributeToken(this[controlElementSymbol], "class", "open");
    }
    
    /**
     * @private
     */
    function show() {
        if (this.getOption("disabled", undefined) === true) {
            return;
        }
    
        if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
            return;
        }
    
        focusFilter.call(this);
    
        const lazyLoadFlag =
            this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true;
    
        if (lazyLoadFlag === true) {
            this[lazyLoadDoneSymbol] = true;
            setStatusOrRemoveBadges.call(this, "loading");
    
            new Processing(200, () => {
                this.fetch()
                    .then(() => {
                        checkOptionState.call(this);
                        requestAnimationFrame(() => {
                            show.call(this);
                        });
                    })
                    .catch((e) => {
                        addErrorAttribute(this, e);
                        setStatusOrRemoveBadges.call(this, "error");
                    });
            })
                .run()
                .catch((e) => {
                    addErrorAttribute(this, e);
                    setStatusOrRemoveBadges.call(this, "error");
                });
    
            return;
        }
    
        const hasPopperFilterFlag =
            this.getOption("filter.position") === FILTER_POSITION_POPPER &&
            getFilterMode.call(this) !== FILTER_MODE_DISABLED;
    
        const options = getOptionElements.call(this);
        if (options.length === 0 && hasPopperFilterFlag === false) {
            return;
        }
    
        this[popperElementSymbol].style.visibility = "hidden";
        this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
        setStatusOrRemoveBadges.call(this, "open");
    
        addAttributeToken(this[controlElementSymbol], "class", "open");
    
        new Processing(() => {
            calcAndSetOptionsDimension.call(this);
            focusFilter.call(this);
            this[popperElementSymbol].style.removeProperty("visibility");
            updatePopper.call(this);
        })
            .run()
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    }
    
    /**
     * @private
     */
    function toggle() {
        if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
            hide.call(this);
        } else {
            show.call(this);
        }
    }
    
    /**
     * @private
     * @fires monster-selection-removed
     * @fires monster-selection-cleared
     */
    function initEventHandler() {
        const self = this;
    
        /**
         * @param {Event} event
         */
        self[clearOptionEventHandler] = (event) => {
            const element = findTargetElementFromEvent(
                event,
                ATTRIBUTE_ROLE,
                "remove-badge",
            );
    
            if (element instanceof HTMLElement) {
                const badge = findClosestByAttribute(element, ATTRIBUTE_ROLE, "badge");
                if (badge instanceof HTMLElement) {
                    const value = badge.getAttribute(`${ATTRIBUTE_PREFIX}value`);
    
                    let selection = self.getOption("selection");
                    selection = selection.filter((b) => {
                        return value !== b.value;
                    });
    
                    setSelection
                        .call(self, selection)
                        .then(() => {
                            fireCustomEvent(self, "monster-selection-removed", {
                                value,
                            });
                        })
                        .catch((e) => {
                            addErrorAttribute(self, e);
                        });
                }
            }
        };
    
        /**
         * @param {Event} event
         */
        self[closeEventHandler] = (event) => {
            const path = event.composedPath();
    
            for (const [, element] of Object.entries(path)) {
                if (element === self) {
                    return;
                }
            }
            hide.call(self);
        };
    
        /**
         * @param {Event} event
         */
        self[inputEventHandler] = (event) => {
            const path = event.composedPath();
            const element = path?.[0];
    
            if (element instanceof HTMLElement) {
                if (
                    element.hasAttribute(ATTRIBUTE_ROLE) &&
                    element.getAttribute(ATTRIBUTE_ROLE) === "option-control"
                ) {
                    fireCustomEvent(self, "monster-change", {
                        type: event.type,
                        value: element.value,
                        checked: element.checked,
                    });
                } else if (
                    element.hasAttribute(ATTRIBUTE_ROLE) &&
                    element.getAttribute(ATTRIBUTE_ROLE) === "filter"
                ) {
                }
            }
        };
    
        /**
         * @param {Event} event
         */
        self[changeEventHandler] = (event) => {
            gatherState.call(self);
            fireCustomEvent(self, "monster-changed", event?.detail);
        };
    
        self[keyEventHandler] = (event) => {
            const path = event.composedPath();
            const element = path.shift();
    
            let role;
    
            if (element instanceof HTMLElement) {
                if (element.hasAttribute(ATTRIBUTE_ROLE)) {
                    role = element.getAttribute(ATTRIBUTE_ROLE);
                } else if (element === this) {
                    show.call(this);
                    // focusFilter.call(self);
                } else {
                    const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
                    if (e instanceof HTMLElement && e.hasAttribute(ATTRIBUTE_ROLE)) {
                        role = e.getAttribute(ATTRIBUTE_ROLE);
                    }
                }
            } else {
                return;
            }
    
            switch (role) {
                case "filter":
                    handleFilterKeyboardEvents.call(self, event);
                    break;
                case "option-label":
                case "option-control":
                case "option":
                    handleOptionKeyboardEvents.call(self, event);
                    break;
                case "control":
                case "toggle":
                    handleToggleKeyboardEvents.call(self, event);
                    break;
            }
        };
    
        const types = self.getOption("toggleEventType", ["click"]);
    
        for (const [, type] of Object.entries(types)) {
            self[controlElementSymbol]
                .querySelector(`[${ATTRIBUTE_ROLE}="container"]`)
                .addEventListener(type, function (event) {
                    const element = findTargetElementFromEvent(
                        event,
                        ATTRIBUTE_ROLE,
                        "remove-badge",
                    );
                    if (element instanceof HTMLElement) {
                        return;
                    }
    
                    toggle.call(self);
                });
    
            self[controlElementSymbol]
                .querySelector(`[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`)
                .addEventListener(type, function (event) {
                    if (self.getOption("disabled", undefined) === true) {
                        return;
                    }
    
                    const path = event.composedPath();
                    const element = path?.[0];
                    if (element instanceof HTMLElement) {
                        const control = element.closest(
                            `[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`,
                        );
                        if (control instanceof HTMLElement) {
                            if (control.classList.contains("clear")) {
                                clearSelection.call(self);
    
                                fireCustomEvent(self, "monster-selection-cleared", {});
                            } else {
                                const element = findTargetElementFromEvent(
                                    event,
                                    ATTRIBUTE_ROLE,
                                    "remove-badge",
                                );
                                if (element instanceof HTMLElement) {
                                    return;
                                }
    
                                toggle.call(self);
                            }
                        }
                    }
                });
    
            // badge, selection
            self.addEventListener(type, self[clearOptionEventHandler]);
        }
    
        self.addEventListener("monster-change", self[changeEventHandler]);
        self.addEventListener("input", self[inputEventHandler]);
        self.addEventListener("keydown", self[keyEventHandler]);
    
        return self;
    }
    
    /**
     * @private
     * @return {Select}
     */
    function setStatusOrRemoveBadges(suggestion) {
        requestAnimationFrame(() => {
            const selection = this.getOption("selection");
    
            const clearAllFlag =
                isArray(selection) &&
                selection.length > 0 &&
                this.getOption("features.clearAll") === true;
    
            const current = this.getOption("classes.statusOrRemoveBadge");
    
            if (suggestion === "error") {
                if (current !== "error") {
                    this.setOption("classes.statusOrRemoveBadge", "error");
                }
                return;
            }
    
            if (this[isLoadingSymbol] === true) {
                if (current !== "loading") {
                    this.setOption("classes.statusOrRemoveBadge", "loading");
                }
                return;
            }
    
            if (suggestion === "loading") {
                if (current !== "loading") {
                    this.setOption("classes.statusOrRemoveBadge", "loading");
                }
                return;
            }
    
            if (clearAllFlag) {
                if (current !== "clear") {
                    this.setOption("classes.statusOrRemoveBadge", "clear");
                }
                return;
            }
    
            if (this[controlElementSymbol].classList.contains("open")) {
                if (current !== "open") {
                    this.setOption("classes.statusOrRemoveBadge", "open");
                }
                return;
            }
    
            const options = this.getOption("options");
            if (
                options === undefined ||
                options === null ||
                (isArray(options) && options.length === 0)
            ) {
                if (current !== "empty") {
                    this.setOption("classes.statusOrRemoveBadge", "empty");
                }
                return;
            }
    
            if (suggestion) {
                if (current !== suggestion) {
                    this.setOption("classes.statusOrRemoveBadge", suggestion);
                }
                return;
            }
        });
    }
    
    /**
     * @private
     * @return {Select}
     * @throws {Error} no shadow-root is defined
     */
    function initControlReferences() {
        if (!this.shadowRoot) {
            throw new Error("no shadow-root is defined");
        }
    
        this[controlElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=control]`,
        );
        this[selectionElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=selection]`,
        );
        this[containerElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=container]`,
        );
        this[popperElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=popper]`,
        );
        this[inlineFilterElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`,
        );
        this[popperFilterElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=filter][name="popper-filter"]`,
        );
        this[popperFilterContainerElementSymbol] =
            this[popperFilterElementSymbol].parentElement;
        this[optionsElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=options]`,
        );
        this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}="no-options"]`,
        );
        this[statusOrRemoveBadgesElementSymbol] = this.shadowRoot.querySelector(
            `[${ATTRIBUTE_ROLE}=status-or-remove-badges]`,
        );
    }
    
    /**
     * @private
     */
    function updatePopper() {
        if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
            return;
        }
    
        if (this.getOption("disabled", false) === true) {
            return;
        }
    
        new Processing(() => {
            calcAndSetOptionsDimension.call(this);
            positionPopper.call(
                this,
                this[controlElementSymbol],
                this[popperElementSymbol],
                this.getOption("popper", {}),
            );
        })
            .run()
            .catch((e) => {
                addErrorAttribute(this, e);
            });
    
        return this;
    }
    
    /**
     * @private
     * @return {string}
     */
    function getTemplate() {
        // language=HTML
        return `
            <template id="options">
                <div data-monster-role="option" tabindex="-1"
                     data-monster-attributes="
                     data-monster-filtered path:options.filtered,
                     data-monster-visibility path:options.visibility">
                    <label part="option">
                        <input data-monster-role="option-control"
                               data-monster-attributes="
                type path:type,
                role path:role,
                value path:options | index:value, 
                name path:name, 
                part path:type | prefix:option- | suffix: form,
                class path:options.class 
                " tabindex="-1">
                        <div data-monster-replace="path:options | index:label"
                             part="option-label"></div>
                    </label>
                </div>
            </template>
    
            <template id="selection">
                <div data-monster-role="badge"
                     part="badge"
                     data-monster-attributes="
                     data-monster-value path:selection | index:value, 
                     class path:classes | index:badge, 
            part path:type | suffix:-option | prefix: form-" tabindex="-1">
                    <div data-monster-replace="path:selection | index:label" part="badge-label"
                         data-monster-role="badge-label"></div>
                    <div part="remove-badge" data-monster-select-this
                         data-monster-attributes="class path:features.clear | ?::hidden "
                         data-monster-role="remove-badge" tabindex="-1"></div>
                </div>
            </template>
    
            <slot class="hidden"></slot>
    
            <div data-monster-role="control" part="control" tabindex="0">
                <div data-monster-role="container">
                    \${selected}
                </div>
    
                <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
                    <div class="option-filter-control" role="search">
                        <input type="text" role="searchbox"
                               part="popper-filter" name="popper-filter"
                               data-monster-role="filter"
                               autocomplete="off"
                               tabindex="0">
                    </div>
                    <div part="content" class="flex" data-monster-replace="path:content">
                        <div part="options" data-monster-role="options" data-monster-insert="options path:options"
                             tabindex="-1"></div>
                    </div>
                    <div part="no-options" data-monster-role="no-options"
                         data-monster-replace="path:messages.emptyOptions"></div>
                </div>
                <div part="status-or-remove-badges" data-monster-role="status-or-remove-badges"
                     data-monster-attributes="class path:classes.statusOrRemoveBadge"></div>
            </div>
        `;
    }
    
    registerCustomElement(Select);