UNPKG

@postnord/web-components

Version:
1,180 lines 51.4 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, 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. * * @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`; 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; /** * 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. */ placeholder; /** HTML name of the checkbox elements. Used for each checkbox inside the multiselect. */ name; /** Select HTML id */ selectId = this.id; /** HTML name of the input element.*/ selectName; /** Display an icon to the left of the select input */ icon; /** Set the date picker label as compact. If used, the `placeholder` will no longer be displayed. @since v7.21.0 */ compact = false; /** Manually set the language, not needed if you have the pnTopbar available */ language = null; /** * Array of options. * @hide true **/ options; /** * 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; /** * Use a custom max-height for the dropdown list. * @description 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; /** Set the select as required. @category Validation */ required = false; /** Disable the select. @category Validation */ disabled = false; /** Trigger the invalid state. @category Validation */ invalid = false; /** Display an error message and trigger the invalid state. @category Validation */ error; handleOpen() { if (this.open) { this.addGlobalEventListeners(); this.handleDirection(); this.handleMaxHeight(); this.handleWidth(); } 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.selectId; this.idLegend = `${id}-legend`; this.idButton = `${id}-button`; this.idOptions = `${id}-options`; this.idHelper = `${id}-text`; 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.elList.style.removeProperty('--pn-select-options-width'); this.elList.style.removeProperty('--pn-select-max-height'); } /** 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() { this.handleSelectId(); if (this.language === null) await awaitTopbar(this.hostElement); } componentDidLoad() { if (!!this.searchQuery?.length) this.handleSearchQuery(); } globalEvents = (event) => { const target = event.target; const isWithinCalendar = target?.closest(this.hostElement.localName); if (!isWithinCalendar) 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`); } handleWidth() { const { width } = this.getRect(this.elInput); this.elGroup.style.setProperty('--pn-select-options-width', `${Math.ceil(width)}px`); } setFocus() { if (this.search) this.elInput.focus({ preventScroll: true }); else this.elButton.focus({ preventScroll: true }); } hasError() { return this.invalid || !!this.error; } hasMessage() { return !!this.helpertext || !!this.error; } 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)}`; } describedBy() { const list = [this.search && this.idChips, this.srMessage && this.idSr, !!this.helpertext && this.idHelper].filter(Boolean); if (list.length) return list.join(' '); return null; } renderOption({ label, value, checked, helpertext, icon, id = `${this.selectId}-${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" }, !!icon && 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 }, index) => (h("li", { class: "pn-multiselect-chip", "aria-setsize": this.optionsCheckedTotal(), "aria-posinset": index + 1, key: `${label}-${value}` }, 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: '55af777ec6dd7165b4d16bdad6ec784fe81b8f20' }, h("fieldset", { key: '41a5e9a45b3460a96676d606c458a062efe6ab80', class: "pn-multiselect", "data-icon": !!this.icon, "data-error": this.hasError(), disabled: this.disabled }, this.renderLabel(), h("div", { key: 'fb6874e8d17a70ec0e1e21bf3474575b01c3e669', class: "pn-multiselect-group", ref: el => (this.elGroup = el) }, h("div", { key: 'ab6aa995337f5a80e2026d98fe69399571249c24', class: "pn-multiselect-input" }, !!this.icon && h("pn-icon", { key: '0d9495e8864e266a6ababc752781b6e9c75595c3', class: "pn-multiselect-icon", "data-custom": true, icon: this.icon }), h("input", { key: '6b5adbc27ab64b9cdb7f53a99d2f4cd9a2185e47', tabindex: this.search ? null : '-1', type: this.search ? 'search' : 'input', id: this.selectId, class: "pn-multiselect-element", value: this.search ? this.searchQuery : this.optionsCheckedLabels(), name: this.selectName, placeholder: this.getPlaceholder(), required: this.search ? null : this.required, "aria-labelledby": this.idLegend, "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: '36e6f1e307758cdec5f9eda59413b8a0c28af78c', class: "pn-multiselect-icon", "data-error": true, icon: alert_exclamation_circle, color: "warning" })), h("button", { key: '6197b7545b8646fecbd5a12e540e3011303f660c', id: this.idButton, type: "button", class: "pn-multiselect-button", "aria-label": this.translate(`BUTTON_${this.open ? 'CLOSE' : 'OPEN'}`), "aria-describedby": this.search ? null : this.selectId, "aria-controls": this.idOptions, "aria-expanded": this.open.toString(), onClick: () => this.toggleOpen(), ref: el => (this.elButton = el) }, h("pn-icon", { key: 'f9ddba2705b7782606897f6374f5963833bde76e', class: "pn-multiselect-icon", icon: chevron_down, color: "blue700" }))), this.renderOptions()), this.hasMessage() && (h("p", { key: 'd9fb544f999b9d779362f13090e9653bbd22fd4e', id: this.idHelper, class: "pn-multiselect-description", role: !!this.error ? 'alert' : null }, h("span", { key: 'ed1426909307379ba22c06a6870ad867f5a7cdad' }, this.error || this.helpertext))), this.search && this.renderChips(), h("slot", { key: 'd0bc44a9160fa4151e6f74c05fa3102189710454' }), this.search && (h("p", { key: '0d8c04c19227c1461a18ccc21a9fd7412a2f6ee1', id: this.idSr, class: "pn-multiselect-sr-only", role: "alert", "aria-live": "assertive" }, this.srMessage && h("span", { key: '610fc47f9a032c6e8931745e6b6d0b418eadd5ee' }, 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": true, "optional": false, "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" }, "placeholder": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "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" }, "name": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "HTML name of the checkbox elements. Used for each checkbox inside the multiselect." }, "getter": false, "setter": false, "reflect": false, "attribute": "name" }, "selectId": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Select HTML id" }, "getter": false, "setter": false, "reflect": false, "attribute": "select-id", "defaultValue": "this.id" }, "selectName": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "HTML name of the input element." }, "getter": false, "setter": false, "reflect": false, "attribute": "select-name" }, "icon": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Display an icon to the left of the select input" }, "getter": false, "setter": false, "reflect": false, "attribute": "icon" }, "compact": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "since", "text": "v7.21.0" }], "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" }, "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": "null" }, "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": "hide", "text": "true" }], "text": "Array of options." }, "getter": false, "setter": false }, "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": "description", "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." }, { "name": "category", "text": "Features" }], "text": "Use a custom max-height for the dropdown list." }, "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" }, "required": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Validation" }], "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": "Validation" }], "text": "Disable the select." }, "getter": false, "setter": false, "reflect": false, "attribute": "disabled", "defaultValue": "false" }, "invalid": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "category", "text": "Validation" }], "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": "Validation" }], "text": "Display an error message and trigger the invalid state." }, "getter": false, "setter": false, "reflect": false, "attribute": "error" } }; } 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 cha