UNPKG

@postnord/web-components

Version:
1,407 lines 59.5 kB
/*!
 * Built with Stencil
 * By PostNord.
 */
import { h, Host, Mixin } from "@stencil/core";
import { animateHeightFactory } from "../../../globals/mixins/index";
import { translations } from "./translations";
import { close, alert_exclamation_circle, chevron_down } from "pn-design-assets/pn-assets/icons.js";
import { uuidv4, awaitTopbar, ripple, en, getMenuWidth, getTotalHeightOffset } from "../../../index";
/**
 * The `pn-multiselect` gets its options via javascript.
 *
 * Native HTML does not accept arrays of objects. Most frameworks solves this automatically (Vue, react, etc...),
 * but keep this in mind if you use this component outside of a framework environment.
 *
 * @nativeChange The `pn-multiselect` is built with `input[type=checkbox]` elements, so the `change` event works. However, we recommend the customEvents described above.
 *
 * @slot helpertext - You can use this slot instead of the prop `helpertext`. Recommended, only if you need to include additional HTML markup. Such as a `pn-text-link`. Use a `span` element to wrap the text and link. {@since v7.25.0}
 * @slot error - You can use this slot instead of the prop `error`. Recommended, only if you need to include additional HTML markup. Such as a `pn-text-link`. Use a `span` element to wrap the text and link. {@since v7.25.0}
 *
 * @see {@link https://portal.postnord.com/web-components/?path=/docs/components-input-multiselect--docs#pn-multiselect%20(options) Options documentation}
 */
export class PnMultiselect extends Mixin(animateHeightFactory) {
    constructor() {
        super();
    }
    id = `pn-multiselect-${uuidv4()}`;
    idLegend = `${this.id}-legend`;
    idButton = `${this.id}-button`;
    idOptions = `${this.id}-options`;
    idHelper = `${this.id}-text`;
    idError = `${this.id}-error`;
    idChips = `${this.id}-chips`;
    idSr = `${this.id}-sr`;
    elGroup;
    elButton;
    elInput;
    elList;
    elChips;
    srTimer;
    hostElement;
    isSearching = false;
    /** Layout state. */
    open = false;
    upwards = false;
    srMessage;
    /** Label placed above the select */
    label;
    /** Display a helper text underneath the select */
    helpertext;
    /** Manually set the language, not needed if you have the pnTopbar available */
    language = undefined;
    /** HTML name of the checkbox elements. Used for each checkbox inside the multiselect. @category Native attributes */
    name;
    /** HTML name of the input element. @category Native attributes*/
    selectName;
    /**
     * This is what will be shown on load if no value is used.
     * The `placeholder` will override the default text used if you have the `search` prop active.
     * @category Native attributes
     */
    placeholder;
    /** Set the select as required. @category Native attributes */
    required = false;
    /** Disable the select. @category Native attributes */
    disabled = false;
    /**
     * The array of `PnMultiselectOption` objects.
     * @see {@link PnMultiselectOption} for the option object properties.
     * @category Features
     **/
    options;
    /** Set the date picker label as compact. If used, the `placeholder` will no longer be displayed. @since v7.21.0 @category Features */
    compact = false;
    /** Display an icon to the left of the select input @category Features */
    icon;
    /**
     * Adds a "Select all" option into the list.
     * If you use the search feature at the same time, clicking this option will only toggle the options found.
     * @see {@link search}
     * @category Features
     **/
    selectAll = false;
    /**
     * Set a custom value for the "Select all value" option.
     * @see {@link selectAll}
     * @category Features
     */
    allValue = 'pn_multiselect_all';
    /**
     * Allow the user to search among the options.
     * The selected options will now display underneath the multiselect element.
     *
     * @category Features
     **/
    search = false;
    /** Set the search query of the multiselect.
     *
     * @see {@link search}
     * @category Features
     */
    searchQuery = '';
    /**
     * Decide how many items should be shown before ellipsis. Requires the `search` prop to work.
     *
     * @see {@link search}
     * @category Features
     **/
    itemCount = 5;
    /**
     * The component will automatically set the max height of the dropdown list on its own.
     * It takes the `pn-topbar` into account and will open in the direction that fits best.
     * Use this prop to override this behaviour and use a custom max-height.
     * @category Features
     **/
    maxHeight;
    /**
     * Force the dropdown to always open upwards.
     * @category Features
     **/
    top = false;
    /**
     * Force the dropdown to always open downwards.
     * @category Features
     **/
    bottom = false;
    /** Trigger the invalid state. @category Features */
    invalid = false;
    /** Display an error message and trigger the invalid state. @category Features */
    error;
    /** Set a custom ID for the select. @since v7.25.0 @category HTML attributes */
    pnId;
    /**
     * Provide the label via an aria attribute.
     * We strongly recommend you use the `label` prop instead.
     * @since v7.25.0
     * @category HTML attributes
     */
    pnAriaLabel;
    /**
     * Provide the label from another element via its ID.
     * We strongly recommend you use the `label` prop instead.
     * @since v7.25.0
     * @category HTML attributes
     */
    pnAriaLabelledby;
    /** Select HTML id @deprecated Use `pn-id` instead. @category HTML attributes */
    selectId;
    handleOpen() {
        if (this.open) {
            this.addGlobalEventListeners();
            this.handleDirection();
            this.handleMaxHeight();
            this.handleOffset();
        }
        else {
            this.removeGlobalEventListeners();
        }
        this.dropdownHandler();
    }
    handleSearch() {
        if (!this.search)
            this.options = [
                ...this.options.map(option => {
                    const item = {
                        ...option,
                        hide: false,
                    };
                    if (item?.group?.length)
                        item.group.map(opt => ({ ...opt, hide: false }));
                    return item;
                }),
            ];
    }
    handleSearchQuery() {
        if (this.search && typeof this.searchQuery === 'string')
            this.handleInputSearch();
    }
    handleSelectId() {
        const id = this.getId();
        this.idLegend = `${id}-legend`;
        this.idButton = `${id}-button`;
        this.idOptions = `${id}-options`;
        this.idHelper = `${id}-text`;
        this.idError = `${id}-error`;
        this.idChips = `${id}-chips`;
        this.idSr = `${id}-sr`;
    }
    /**
     * If the select is open and you resize the window.
     * Remove all css props and disable the animations entierly.
     **/
    handleResize() {
        if (!this.open)
            return;
        this.toggleOpen(false);
        this.elGroup.style.removeProperty('--pn-select-max-height');
        this.elGroup.style.removeProperty('--pn-select-options-offset');
    }
    /** Dispatched everytime the multiselect is opened or closed. */
    toggleSelect;
    /** This event contains the entire options array with the new props. Dispatched everytime you make changes to any option. */
    allOptions;
    /** Dispatched when you toggle an option. Includes all the props of the option. */
    selectedOption;
    /**
     * This event is dispatched when the user toggles the "Choose all options" box.
     * Also triggers when you click the "Select {number} options" if you are performing a search at the same time.
     **/
    selectedAllOptions;
    /**
     * Contains the search text and the options found for that query.
     * Dispacthed everytime you change the search query.
     **/
    searchInput;
    async componentWillLoad() {
        if (this.language === undefined)
            await awaitTopbar(this.hostElement);
    }
    componentDidLoad() {
        if (!!this.searchQuery?.length)
            this.handleSearchQuery();
    }
    getId() {
        return this.pnId || this.selectId || this.id;
    }
    hasHelperText() {
        return this.helpertext?.length > 0 || this.checkSlottedHelper();
    }
    /** If any `error` text is present, either via prop/slot. */
    hasErrorMessage() {
        return this.error?.length > 0 || this.checkSlottedError();
    }
    /** If any `error` is active, either via the prop `invalid` or `error` prop/slot. */
    hasError() {
        return this.hasErrorMessage() || this.invalid;
    }
    checkSlottedHelper() {
        return !!this.hostElement.querySelector('[slot=helpertext]');
    }
    checkSlottedError() {
        return !!this.hostElement.querySelector('[slot=error]');
    }
    hideHelpertext() {
        return this.hasErrorMessage() || !this.hasHelperText();
    }
    hideError() {
        return !this.hasErrorMessage();
    }
    globalEvents = (event) => {
        const target = event.target;
        if (!this.hostElement.contains(target) || event?.key === 'Escape') {
            this.toggleOpen(false);
        }
    };
    addGlobalEventListeners() {
        const root = this.hostElement.getRootNode();
        root.addEventListener('click', this.globalEvents);
    }
    removeGlobalEventListeners() {
        const root = this.hostElement.getRootNode();
        root.removeEventListener('click', this.globalEvents);
    }
    dropdownHandler() {
        if (this.open)
            this.openDropdown(this.elList);
        else
            this.closeDropdown(this.elList);
    }
    emitEvents(option, all) {
        this.allOptions.emit(this.options);
        if (option)
            return this.selectedOption.emit(option);
        const data = {
            checked: all,
            searching: this.isSearching,
        };
        if (this.search && this.optionsTotal() !== this.optionsSearch().length) {
            data.query = this.searchQuery;
            data.found = this.optionsSearch();
        }
        this.selectedAllOptions.emit(data);
    }
    optionsChecked() {
        const list = this.options?.reduce((sum, item) => {
            const subgroup = item.group ? [...item.group.filter(({ checked }) => checked)] : [];
            if (item.checked)
                sum.push(item);
            if (subgroup?.length)
                sum.push(...subgroup);
            return sum;
        }, []);
        return list;
    }
    optionsTotal() {
        return this.options?.reduce((sum, item) => sum + (item?.group?.length ? item.group.length + 1 : 1), 0);
    }
    optionsIndex(val) {
        const findNested = ({ group = [] }) => group?.findIndex(({ value }) => value === val);
        const indexGroup = this.options.findIndex(option => option.value === val || findNested(option) !== -1);
        const indexNested = findNested(this.options?.[indexGroup]);
        return {
            indexGroup,
            indexNested,
        };
    }
    optionsCheckedTotal() {
        return this.optionsChecked()?.length;
    }
    optionsCheckedPreview() {
        return this.optionsChecked()?.slice(0, this.itemCount);
    }
    optionsCheckedLabels() {
        return this.optionsChecked()
            ?.map(({ label }) => label)
            ?.join(', ')
            ?.trim();
    }
    optionsSearch() {
        const list = this.options?.reduce((sum, item) => {
            const subgroup = item.group ? [...item.group.filter(({ hide }) => !hide)] : [];
            if (!item.hide)
                sum.push(item);
            if (subgroup?.length)
                sum.push(...subgroup);
            return sum;
        }, []);
        return list;
    }
    noResults() {
        return this.optionsSearch()?.length === 0;
    }
    isIndeterminate(groupIndex) {
        const option = this.options?.[groupIndex];
        if (option?.group?.length) {
            const all = option.group.every(({ checked }) => checked);
            const none = option.group.every(({ checked }) => !checked);
            return !(all || none);
        }
        return false;
    }
    optionSelect({ val, checked, chip }) {
        if (val === this.allValue)
            return this.optionSelectAll(checked);
        const options = this.options;
        const { indexGroup, indexNested } = this.optionsIndex(val);
        const group = options[indexGroup]?.group;
        const nested = group?.[indexNested];
        if (nested?.value) {
            options[indexGroup].group[indexNested].checked = checked;
            const allSiblingsChecked = group?.every(({ checked }) => checked);
            if (allSiblingsChecked)
                options[indexGroup].checked = true;
            else
                options[indexGroup].checked = false;
        }
        else {
            options[indexGroup].checked = checked;
            if (group?.length)
                options[indexGroup].group = group?.map(opt => ({ ...opt, checked: checked }));
        }
        const option = indexNested === -1 ? options[indexGroup] : options[indexGroup].group[indexNested];
        this.emitEvents(option);
        this.options = [...options];
        if (typeof chip === 'number') {
            this.handleSrMesssage(`${this.translate('REMOVED')} ${option.label}`);
            requestAnimationFrame(() => {
                const index = chip === this.optionsCheckedTotal() ? chip - 1 : chip;
                const btn = Array.from(this.elChips.children)?.[index];
                btn?.querySelector('button').focus({ preventScroll: true });
            });
        }
    }
    optionSelectAll(checked) {
        this.options = [
            ...this.options.map(option => {
                const opt = {
                    ...option,
                    checked: this.isSearching ? (option.hide ? option.checked : checked) : checked,
                };
                if (opt.group)
                    opt.group = [
                        ...opt.group.map(item => ({
                            ...item,
                            checked: this.isSearching ? (item.hide ? item.checked : checked) : checked,
                        })),
                    ];
                const allChecked = opt?.group?.every(({ checked }) => checked);
                if (opt?.group?.length)
                    opt.checked = allChecked;
                return opt;
            }),
        ];
        this.emitEvents(null, checked);
    }
    optionSelected(val) {
        return !!this.optionsChecked()?.find(({ value, checked }) => value === val && checked);
    }
    translate(prop) {
        const text = translations?.[prop]?.[this.language || en];
        if (text.includes('{number}'))
            return text.replace('{number}', this.optionsSearch().length);
        return text;
    }
    getRect(element) {
        return element?.getBoundingClientRect();
    }
    ripple({ clientX, clientY, target }) {
        const isKeyboard = clientX === 0 && clientY === 0;
        const element = target.nextElementSibling;
        const { x, width, y, top } = this.getRect(element);
        const clientCor = isKeyboard ? { clientX: x + width - 24, clientY: y - top } : { clientX, clientY };
        ripple(clientCor, element);
    }
    handleSrMesssage(text) {
        this.srMessage = text;
        clearTimeout(this.srTimer);
        this.srTimer = setTimeout(() => (this.srMessage = null), 2000);
    }
    handleDirection() {
        if (this.top)
            return (this.upwards = true);
        if (this.bottom)
            return (this.upwards = false);
        const { bottom, top } = this.getRect(this.elInput);
        const oneEm = 16;
        /*
         * Take the inner window height and subtract the elements bottom and 1em (16px)
         * If the max height of this calculation is less than the space above the element, open it upwards.
         * Do another calculation with the top value - 1em.
         */
        const offsetTop = window.innerHeight - bottom - oneEm;
        const offsetBottom = top - oneEm;
        /** Always point downwards, unless the space to the bottom is less than half of the top offset space. */
        this.upwards = offsetBottom / 2 > offsetTop;
    }
    handleMaxHeight() {
        if (this.maxHeight)
            return this.elGroup.style.setProperty('--pn-select-max-height', this.maxHeight);
        const { bottom, top } = this.getRect(this.elInput);
        const inputHeight = this.upwards ? top : bottom;
        const offsetTop = getTotalHeightOffset();
        const offset = this.upwards ? offsetTop + 16 : 16;
        const maxHeight = this.upwards ? inputHeight - offset : window.innerHeight - inputHeight - offset;
        this.elGroup.style.setProperty('--pn-select-max-height', `${Math.ceil(maxHeight)}px`);
    }
    handleOffset() {
        this.elGroup.style.removeProperty('--pn-select-options-offset');
        requestAnimationFrame(() => {
            const { left, right } = this.getRect(this.elList);
            const menuWidth = getMenuWidth();
            const leftBoundary = menuWidth + 8;
            const rightBoundary = window.innerWidth - 8;
            let offset = 0;
            if (left < leftBoundary)
                offset = leftBoundary - left;
            else if (right > rightBoundary)
                offset = rightBoundary - right;
            if (offset !== 0)
                this.elGroup.style.setProperty('--pn-select-options-offset', `${Math.floor(offset)}px`);
        });
    }
    setFocus() {
        if (this.search)
            this.elInput.focus({ preventScroll: true });
        else
            this.elButton.focus({ preventScroll: true });
    }
    toggleOpen(state) {
        if (typeof state === 'boolean' && state === this.open)
            return;
        this.open = state ?? !this.open;
        this.toggleSelect.emit({ open: this.open });
    }
    handleInputKeyboard(event) {
        event.stopImmediatePropagation();
        const { code } = event;
        if (code === 'Escape')
            return this.toggleOpen(false);
        if (!this.open && code.match(/^(Space|Enter)$|Arrow|^Key.*$/))
            this.toggleOpen();
    }
    setSearchQuery(value) {
        if (!this.open)
            this.toggleOpen(true);
        this.searchQuery = value;
    }
    handleInputSearch() {
        if (this.isEmpty())
            return;
        const term = this.searchQuery;
        const performSearch = ({ label, helpertext, value }) => {
            return `${label} ${helpertext} ${value}`.trim().toLowerCase().includes(term.toLowerCase());
        };
        const options = this.options.map(option => {
            const foundNested = option?.group?.map(item => ({ ...item, hide: !performSearch(item) })) || [];
            const found = performSearch(option) || foundNested?.some(({ hide }) => !hide);
            option.hide = !found;
            if (option?.group?.length)
                option.group = [...foundNested];
            return option;
        });
        this.isSearching = term !== '';
        this.options = [...options];
        this.searchInput.emit({ query: term, found: this.optionsSearch() });
    }
    getListItem(index) {
        const { value } = this.options[index];
        return this.elList.querySelector(`.pn-multiselect-option-input[value='${value}']`);
    }
    getOptionIndex(val, triggers, code) {
        const total = this.optionsTotal() - 1;
        if (code === 'End')
            return total;
        if (code === 'Home')
            return 0;
        const valIndex = this.options.findIndex(({ value }) => value === val);
        const count = triggers.find(item => typeof item === 'number');
        const index = valIndex + count;
        if (index >= total)
            return total;
        if (index <= 0)
            return 0;
        return index;
    }
    checkboxNav(e, val) {
        const { code } = e;
        const arrowUp = code === 'ArrowUp' && -1;
        const arrowDown = code === 'ArrowDown' && 1;
        const pageUp = code === 'PageUp' && -10;
        const pageDown = code === 'PageDown' && 10;
        const home = code === 'Home' && 0;
        const end = code === 'End' && this.optionsTotal() - 1;
        const tab = code === 'Tab';
        const space = code === 'Space';
        const enter = code === 'Enter';
        const escape = code === 'Escape';
        const triggers = [arrowUp, arrowDown, pageUp, pageDown, home, end, tab, space, enter, escape];
        if (!triggers.some(item => typeof item === 'number' || item))
            return;
        if (tab || space || enter)
            return;
        e.stopImmediatePropagation();
        e.preventDefault();
        if (escape) {
            this.setFocus();
            return this.toggleOpen(false);
        }
        const item = this.getOptionIndex(val, triggers, code);
        return this.getListItem(item)?.focus();
    }
    handleBlur(event) {
        const target = event.target;
        const related = event.relatedTarget;
        const jumpingToChips = (related || target)?.className === 'pn-multiselect-chip-button';
        if (jumpingToChips)
            this.toggleOpen(false);
    }
    handleLabel() {
        if (this.disabled)
            return;
        this.setFocus();
    }
    /** Check if there are no options in the list. */
    isEmpty() {
        return (this.options?.length || 0) === 0;
    }
    showSelectAll() {
        return this.selectAll && !this.isEmpty() && !this.noResults();
    }
    /** Check if the empty/nothing found should be visible. */
    showEmptyOption() {
        return (this.isSearching && this.noResults()) || this.isEmpty();
    }
    getPlaceholder() {
        const autoTranslation = this.search ? 'SEARCH' : 'SELECT_AN_OPTION';
        return this.placeholder || this.translate(autoTranslation);
    }
    handleChange({ target }) {
        const { value, checked } = target;
        this.optionSelect({ val: value, checked });
    }
    /** Display the "X more selected options" text. */
    additonalOptions() {
        const count = this.optionsCheckedTotal() - this.itemCount;
        const single = `MORE_OPTION${count === 1 ? '' : 'S'}`;
        return `${count} ${this.translate(single)}`;
    }
    displayLabel() {
        return !!this.label;
    }
    getAriaLabel() {
        return !this.displayLabel() && !this.pnAriaLabelledby ? this.pnAriaLabel : null;
    }
    /**
     * If you use a label (technically a legend), use the `idLegend` to point to it.
     * If not, make sure there is no pnAriaLabel and use pnAriaLabelledby.
     * This is to make sure there are no double label/labelledby.
     */
    getAriaLabelledby() {
        return this.displayLabel() ? this.idLegend : !this.pnAriaLabel ? this.pnAriaLabelledby : null;
    }
    describedBy() {
        const list = [];
        if (this.search)
            list.push(this.idChips);
        if (this.srMessage)
            list.push(this.idSr);
        if (this.hasHelperText() && !this.hasErrorMessage())
            list.push(this.idHelper);
        if (this.hasErrorMessage())
            list.push(this.idError);
        if (list.length)
            return list.join(' ');
        return null;
    }
    /**
     * This is to prevent a rare issue with rendering flag icons.
     * Flags use a set ID to identify gradient colors. If the icon is rendered while the dropdown is closed,
     * the ID is not unique and the icon will not be rendered for other pn-icons using it.
     * This is a temp fix to address the root issue which is how the flags SVG styling is built.
     */
    renderItemIcon(icon, hide) {
        return (this.open || this.isMoving()) && !!icon && !hide;
    }
    renderOption({ label, value, checked, helpertext, icon, id = `${this.getId()}-${value || label}`, invalid, disabled, hide, group, }, indexGroup, indexNested) {
        return (h("li", { class: "pn-multiselect-option", key: id, hidden: hide }, h("input", { type: "checkbox", id: id, class: "pn-multiselect-option-input", checked: checked ?? this.optionSelected(value), indeterminate: indexNested === undefined && this.isIndeterminate(indexGroup), name: this.name, value: value, disabled: this.disabled || disabled, required: this.required, "aria-invalid": invalid ? 'true' : null, "aria-describedby": helpertext ? `${id}-helper` : null, onClick: (event) => this.ripple(event), onKeyDown: (event) => this.checkboxNav(event, value), onBlur: (event) => this.handleBlur(event) }), h("div", { class: "pn-multiselect-option-content" }, this.renderItemIcon(icon, hide) && h("pn-icon", { icon: icon, color: "blue900" }), h("div", { class: "pn-multiselect-option-text" }, h("label", { class: "pn-multiselect-option-label", htmlFor: id }, h("span", null, label)), helpertext && (h("p", { class: "pn-multiselect-option-helper", id: `${id}-helper` }, helpertext))), h("div", { class: "pn-multiselect-option-checkbox" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none" }, h("polyline", { class: "pn-multiselect-option-checkbox-checkmark-path", points: "4,12 9,17 20,6", "stroke-width": "3" }), h("polyline", { class: "pn-multiselect-option-checkbox-indeterminate-path", points: "4,12 20,12", "stroke-width": "3" })))), group?.length && (h("ul", { class: "pn-multiselect-optgroup" }, group?.map((option, i) => this.renderOption(option, indexGroup, i))))));
    }
    renderOptions() {
        return (h("ul", { id: this.idOptions, class: "pn-multiselect-options", "data-open": this.open, "data-upwards": this.upwards, onChange: (event) => this.handleChange(event), style: { height: '0px' }, ref: el => (this.elList = el) }, this.showSelectAll() &&
            this.renderOption({
                label: this.translate(`SELECT_${this.isSearching ? 'FOUND' : 'ALL'}_OPTIONS`),
                value: this.allValue,
                checked: this.optionsTotal() === this.optionsCheckedTotal(),
            }), this.options?.map((option, index) => this.renderOption(option, index)), this.showEmptyOption() && (h("li", { class: "pn-multiselect-option" }, h("div", { class: "pn-multiselect-option-content", role: "alert" }, h("div", { class: "pn-multiselect-option-text" }, h("span", { class: "pn-multiselect-option-label" }, this.translate(this.isEmpty() ? 'NO_OPTIONS' : 'NO_SEARCH_RESULTS'))))))));
    }
    renderChips() {
        return (h("ul", { id: this.idChips, class: "pn-multiselect-chips", "aria-label": this.translate('SELECTED_OPTIONS'), ref: el => (this.elChips = el) }, this.optionsCheckedPreview()?.map(({ label, value, icon }, index) => (h("li", { class: "pn-multiselect-chip", "aria-setsize": this.optionsCheckedTotal(), "aria-posinset": index + 1, key: `${label}-${value}` }, !!icon && h("pn-icon", { icon: icon, small: true }), h("span", { class: "pn-multiselect-chip-label" }, label), h("button", { type: "button", class: "pn-multiselect-chip-button", "aria-label": `${this.translate('REMOVE')} ${label}`, onClick: () => this.optionSelect({ val: value, checked: false, chip: index }) }, h("pn-icon", { icon: close, small: true, color: "blue700" }))))), this.optionsCheckedTotal() > this.itemCount && (h("li", { class: "pn-multiselect-chip", "data-count": true }, h("span", { class: "pn-multiselect-chip-label" }, "+ ", this.additonalOptions())))));
    }
    renderLabel(compactLabel = false) {
        if (!this.label)
            return null;
        const Tag = compactLabel ? 'div' : 'legend';
        const id = compactLabel ? null : this.idLegend;
        const attrs = compactLabel ? { inert: true } : { onClick: () => this.handleLabel() };
        return (h(Tag, { id: id, class: { 'pn-multiselect-label': true, 'pn-multiselect-sr-only': this.compact && !compactLabel }, "data-compact": this.compact, ...attrs }, h("span", null, this.label), !!this.optionsChecked()?.length && (h("span", null, this.optionsCheckedTotal(), "/", this.optionsTotal()))));
    }
    render() {
        return (h(Host, { key: 'a0ecbc763c7fe2e742a2737a13ccb25049ca5d2c' }, h("fieldset", { key: 'e34a6c4a098b8509985300c6101f28810a2624e5', class: "pn-multiselect", "data-icon": !!this.icon, "data-error": this.hasError(), disabled: this.disabled }, this.renderLabel(), h("div", { key: '4af7e657d83cd4eb9d1f7aef7c41e91c204e8b49', class: "pn-multiselect-group", ref: el => (this.elGroup = el) }, h("div", { key: 'f5d0ab6e61dfac0c19a3ba86230c3da6e14efde5', class: "pn-multiselect-input" }, !!this.icon && h("pn-icon", { key: '2bb3a5906b9624c14b912dae42ab0f266be28708', class: "pn-multiselect-icon", "data-custom": true, icon: this.icon }), h("input", { key: '40bf05bdcdfc7d20d11c7d463863727e5d6aff2e', tabindex: this.search ? null : '-1', type: this.search ? 'search' : 'input', id: this.getId(), class: "pn-multiselect-element", value: this.search ? this.searchQuery : this.optionsCheckedLabels(), name: this.selectName, placeholder: this.getPlaceholder(), required: this.search ? null : this.required, "aria-label": this.getAriaLabel(), "aria-labelledby": this.getAriaLabelledby(), "aria-describedby": this.describedBy(), "aria-controls": `${this.idOptions} ${this.search ? this.idChips : ''}`, "aria-invalid": this.hasError()?.toString(), disabled: this.disabled, readonly: !this.search, "data-compact": this.compact, onClick: () => (this.search && this.open ? null : this.toggleOpen()), onKeyDown: e => this.handleInputKeyboard(e), onBlur: e => this.handleBlur(e), onInput: e => this.search && this.setSearchQuery(e.target.value), ref: el => (this.elInput = el) }), this.compact && this.renderLabel(true), this.hasError() && (h("pn-icon", { key: 'cb667952d2f8c5d2d651781f15ff74b6b775e042', class: "pn-multiselect-icon", "data-error": true, icon: alert_exclamation_circle, color: "warning" })), h("button", { key: '94b05fea39ded60eab60763fdb84a9b711ae8162', id: this.idButton, type: "button", class: "pn-multiselect-button", "aria-label": this.translate(`BUTTON_${this.open ? 'CLOSE' : 'OPEN'}`), "aria-describedby": this.search ? null : this.getId(), "aria-controls": this.idOptions, "aria-expanded": this.open.toString(), onClick: () => this.toggleOpen(), ref: el => (this.elButton = el) }, h("pn-icon", { key: 'eeaafee94ca176871092ceeb80d963fccc31dcec', class: "pn-multiselect-icon", icon: chevron_down, color: "blue700" }))), this.renderOptions()), h("p", { key: '2b769d32ed6692b99a19c125c5177eb375095e68', id: this.idHelper, class: "pn-multiselect-text", hidden: this.hideHelpertext() }, h("span", { key: 'dc43a1985211d85923e3085d44cb2b368e2582a4' }, this.helpertext), h("slot", { key: 'f0f4cd86c528644a133f7c9db51251a77265e15f', name: "helpertext" })), h("p", { key: '718171d4d2c510d70c562a188ef25eacf60ea039', id: this.idError, class: "pn-multiselect-text", role: "alert", hidden: this.hideError() }, h("span", { key: '2b0cfd1042189fb60d96e3d43001f3ff6794cb4e' }, this.error), h("slot", { key: 'e381d819a3de5c550816309f097ae37c713b604e', name: "error" })), this.search && this.renderChips(), h("slot", { key: '6f7731063585b04cc851a5790ec77fff209baa03' }), this.search && (h("p", { key: 'f4df41d21ebc5219a86fbc90947285635df86d13', id: this.idSr, class: "pn-multiselect-sr-only", role: "alert", "aria-live": "assertive" }, this.srMessage && h("span", { key: '6d24d915b81ef53a1eb210d3c7d0a85184ea1e88' }, this.srMessage))))));
    }
    static get is() { return "pn-multiselect"; }
    static get originalStyleUrls() {
        return {
            "$": ["pn-multiselect.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["pn-multiselect.css"]
        };
    }
    static get properties() {
        return {
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Label placed above the select"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "label"
            },
            "helpertext": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Display a helper text underneath the select"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "helpertext"
            },
            "language": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "PnLanguages",
                    "resolved": "\"\" | \"da\" | \"en\" | \"fi\" | \"no\" | \"sv\"",
                    "references": {
                        "PnLanguages": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnLanguages",
                            "referenceLocation": "PnLanguages"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Manually set the language, not needed if you have the pnTopbar available"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "language",
                "defaultValue": "undefined"
            },
            "name": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Native attributes"
                        }],
                    "text": "HTML name of the checkbox elements. Used for each checkbox inside the multiselect."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "name"
            },
            "selectName": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Native attributes"
                        }],
                    "text": "HTML name of the input element."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "select-name"
            },
            "placeholder": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Native attributes"
                        }],
                    "text": "This is what will be shown on load if no value is used.\nThe `placeholder` will override the default text used if you have the `search` prop active."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "placeholder"
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Native attributes"
                        }],
                    "text": "Set the select as required."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "required",
                "defaultValue": "false"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Native attributes"
                        }],
                    "text": "Disable the select."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "disabled",
                "defaultValue": "false"
            },
            "options": {
                "type": "unknown",
                "mutable": true,
                "complexType": {
                    "original": "PnMultiselectOption[]",
                    "resolved": "PnMultiselectOption[]",
                    "references": {
                        "PnMultiselectOption": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnMultiselectOption",
                            "referenceLocation": "PnMultiselectOption"
                        }
                    }
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "see",
                            "text": "{@link PnMultiselectOption } for the option object properties."
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "The array of `PnMultiselectOption` objects."
                },
                "getter": false,
                "setter": false
            },
            "compact": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "since",
                            "text": "v7.21.0"
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Set the date picker label as compact. If used, the `placeholder` will no longer be displayed."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "compact",
                "defaultValue": "false"
            },
            "icon": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Display an icon to the left of the select input"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "icon"
            },
            "selectAll": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "see",
                            "text": "{@link search }"
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Adds a \"Select all\" option into the list.\nIf you use the search feature at the same time, clicking this option will only toggle the options found."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "select-all",
                "defaultValue": "false"
            },
            "allValue": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "see",
                            "text": "{@link selectAll }"
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Set a custom value for the \"Select all value\" option."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "all-value",
                "defaultValue": "'pn_multiselect_all'"
            },
            "search": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Allow the user to search among the options.\nThe selected options will now display underneath the multiselect element."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "search",
                "defaultValue": "false"
            },
            "searchQuery": {
                "type": "string",
                "mutable": true,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "see",
                            "text": "{@link search }"
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Set the search query of the multiselect."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "search-query",
                "defaultValue": "''"
            },
            "itemCount": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "see",
                            "text": "{@link search }"
                        }, {
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Decide how many items should be shown before ellipsis. Requires the `search` prop to work."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "item-count",
                "defaultValue": "5"
            },
            "maxHeight": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "The component will automatically set the max height of the dropdown list on its own.\nIt takes the `pn-topbar` into account and will open in the direction that fits best.\nUse this prop to override this behaviour and use a custom max-height."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "max-height"
            },
            "top": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Force the dropdown to always open upwards."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "top",
                "defaultValue": "false"
            },
            "bottom": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Force the dropdown to always open downwards."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "bottom",
                "defaultValue": "false"
            },
            "invalid": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Trigger the invalid state."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "invalid",
                "defaultValue": "false"
            },
            "error": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "category",
                            "text": "Features"
                        }],
                    "text": "Display an error message and trigger the invalid state."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "error"
            },
            "pnId": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "since",
                            "text": "v7.25.0"
                        }, {
                            "name": "category",
                            "text": "HTML attributes"
                        }],
                    "text": "Set a custom ID for the select."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "pn-id"
            },
            "pnAriaLabel": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "since",
                            "text": "v7.25.0"
                        }, {
                            "name": "category",
                            "text": "HTML attributes"
                        }],
                    "text": "Provide the label via an aria attribute.\nWe strongly recommend you use the `label` prop instead."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "pn-aria-label"
            },
            "pnAriaLabelledby": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "since",
                            "text": "v7.25.0"
                        }, {
                            "name": "category",
                            "text": "HTML attributes"
                        }],
                    "text": "Provide the label from another element via its ID.\nWe strongly recommend you use the `label` prop instead."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "pn-aria-labelledby"
            },
            "selectId": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "deprecated",
                            "text": "Use `pn-id` instead."
                        }, {
                            "name": "category",
                            "text": "HTML attributes"
                        }],
                    "text": "Select HTML id"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "select-id"
            }
        };
    }
    static get states() {
        return {
            "isSearching": {},
            "open": {},
            "upwards": {},
            "srMessage": {}
        };
    }
    static get events() {
        return [{
                "method": "toggleSelect",
                "name": "toggleSelect",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Dispatched everytime the multiselect is opened or closed."
                },
                "complexType": {
                    "original": "{ open: boolean }",
                    "resolved": "{ open: boolean; }",
                    "references": {}
                }
            }, {
                "method": "allOptions",
                "name": "allOptions",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "This event contains the entire options array with the new props. Dispatched everytime you make changes to any option."
                },
                "complexType": {
                    "original": "PnMultiselectOption[]",
                    "resolved": "PnMultiselectOption[]",
                    "references": {
                        "PnMultiselectOption": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnMultiselectOption",
                            "referenceLocation": "PnMultiselectOption"
                        }
                    }
                }
            }, {
                "method": "selectedOption",
                "name": "selectedOption",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Dispatched when you toggle an option. Includes all the props of the option."
                },
                "complexType": {
                    "original": "PnMultiselectOption",
                    "resolved": "PnMultiselectOption",
                    "references": {
                        "PnMultiselectOption": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnMultiselectOption",
                            "referenceLocation": "PnMultiselectOption"
                        }
                    }
                }
            }, {
                "method": "selectedAllOptions",
                "name": "selectedAllOptions",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "This event is dispatched when the user toggles the \"Choose all options\" box.\nAlso triggers when you click the \"Select {number} options\" if you are performing a search at the same time."
                },
                "complexType": {
                    "original": "{\n    checked: boolean;\n    searching: boolean;\n    query?: string;\n    found?: PnMultiselectOption[];\n  }",
                    "resolved": "{ checked: boolean; searching: boolean; query?: string; found?: PnMultiselectOption[]; }",
                    "references": {
                        "PnMultiselectOption": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnMultiselectOption",
                            "referenceLocation": "PnMultiselectOption"
                        }
                    }
                }
            }, {
                "method": "searchInput",
                "name": "searchInput",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Contains the search text and the options found for that query.\nDispacthed everytime you change the search query."
                },
                "complexType": {
                    "original": "{\n    query: string;\n    found?: PnMultiselectOption[];\n  }",
                    "resolved": "{ query: string; found?: PnMultiselectOption[]; }",
                    "references": {
                        "PnMultiselectOption": {
                            "location": "import",
                            "path": "@/index",
                            "id": "src/index.ts::PnMultiselectOption",
                            "referenceLocation": "PnMultiselectOption"
                        }
                    }
                }
            }];
    }
    static get elementRef() { return "hostElement"; }
    static get watchers() {
        return [{
                "propName": "open",
                "methodName": "handleOpen"
            }, {
                "propName": "search",
                "methodName": "handleSearch"
            }, {
                "propName": "searchQuery",
                "methodName": "handleSearchQuery"
            }, {
                "propName": "selectId",
                "methodName": "handleSelectId"
            }, {
                "propName": "pnId",
                "methodName": "handleSelectId",
                "handlerOptions": {
                    "immediate": true
                }
            }];
    }
    static get listeners() {
        return [{
                "name": "resize",
                "method": "handleResize",
                "target": "window",
                "capture": false,
                "passive": true
            }];
    }
}