UNPKG

wj-elements

Version:

WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.

944 lines (943 loc) 42 kB
var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var _addedOptions, _htmlOptions, _Select_instances, htmlSelectedItem_fn; import WJElement from "./wje-element.js"; import Button from "./wje-button.js"; import "./wje-popup.js"; import { I as Icon } from "./icon-DY5AZ6xM.js"; import Label from "./wje-label.js"; import Chip from "./wje-chip.js"; import Input from "./wje-input.js"; import Option from "./wje-option.js"; import Options from "./wje-options.js"; import Checkbox from "./wje-checkbox.js"; import { P as Popup } from "./popup.element-Di4nHYij.js"; import { event } from "./event.js"; const styles = "/*\n[ WJ Select ]\n*/\n\n:host {\n margin-bottom: var(--wje-select-margin-bottom);\n width: 100%;\n display: block;\n [slot='arrow'] {\n transform: rotate(0deg);\n transition: all 0.2s ease-in;\n }\n}\n\n.native-select {\n position: relative;\n &.default {\n .wrapper {\n display: block;\n background-color: var(--wje-select-background);\n border-width: var(--wje-select-border-width);\n border-style: var(--wje-select-border-style);\n border-color: var(--wje-select-border-color);\n border-radius: var(--wje-select-border-radius);\n padding-inline: 0.5rem;\n padding-top: 0.125rem;\n padding-bottom: 0.125rem;\n }\n .input-wrapper {\n padding: 0;\n min-height: 28px;\n margin-top: -4px;\n }\n &.focused {\n wje-label {\n opacity: 0.67;\n font-size: 12px;\n letter-spacing: normal;\n }\n }\n wje-label {\n margin: 0;\n display: block;\n opacity: 1;\n cursor: text;\n transition: opacity 0.2s ease 0s;\n line-height: var(--wje-select-line-height);\n &.fade {\n opacity: 0.5;\n font-size: 12px;\n letter-spacing: normal;\n }\n }\n }\n &.standard {\n .wrapper {\n background-color: var(--wje-select-background);\n border-width: var(--wje-select-border-width);\n border-style: var(--wje-select-border-style);\n border-color: var(--wje-select-border-color);\n border-radius: var(--wje-select-border-radius);\n }\n .input-wrapper {\n background: transparent;\n width: 100%;\n }\n slot[name='error'] {\n position: static;\n\n background: transparent;\n padding: 0.25rem 0;\n left: auto;\n transform: none;\n color: var(--wje-input-color-invalid);\n font-size: 12px;\n line-height: normal;\n }\n }\n}\n\n.wrapper {\n display: flex;\n width: 100%;\n}\n\n.input-wrapper {\n display: grid;\n grid-template-columns: auto 1fr auto auto auto;\n align-items: center;\n background-color: var(--wje-select-background);\n min-height: 28px;\n color: var(--wje-select-color);\n line-height: var(--wje-select-line-height);\n appearance: none;\n padding: 2px 0.5rem;\n font-size: 14px !important;\n font-weight: normal;\n vertical-align: middle;\n input[readonly] {\n pointer-events: none;\n caret-color: transparent;\n }\n}\n\ninput {\n color: var(--wje-select-color);\n line-height: var(--wje-select-line-height);\n font-size: 14px !important;\n font-weight: 400;\n letter-spacing: 0.01em;\n border: medium;\n height: 25px;\n min-height: 25px;\n padding: 0;\n background: none;\n box-shadow: none;\n width: 100%;\n outline: 0;\n font-size: 14px !important;\n}\n\n::placeholder {\n opacity: 1;\n}\n\n:host [active] {\n .wrapper {\n border-radius: var(--wje-select-border-radius);\n }\n [slot='arrow'] {\n transform: rotate(180deg);\n transition: all 0.2s ease-in;\n }\n}\n\n.options-wrapper {\n border-width: var(--wje-select-options-border-width);\n border-style: var(--wje-select-options-border-style);\n border-color: var(--wje-select-options-border-color);\n border-radius: var(--wje-select-options-border-radius);\n margin-top: calc(0px - var(--wje-select-border-width));\n background: var(--wje-select-background);\n overflow: hidden;\n}\n\n.find {\n margin-block: var(--wje-select-find-margin-block);\n margin-inline: var(--wje-select-find-margin-inline);\n width: var(--wje-select-find-width);\n}\n\n.list {\n overflow: auto;\n height: 100%;\n}\n\n.options-wrapper:has(.find) .list {\n height: calc(100% - 32px - 0.5rem);\n}\n\n:host([multiple]) input {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n opacity: 0;\n}\n\n:host([multiple]) .chips {\n display: flex;\n flex: 1;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--wje-spacing-3x-small);\n}\n\n:host([disabled]) .input-wrapper {\n opacity: 0.5;\n pointer-events: none;\n cursor: not-allowed;\n}\n\n.counter {\n padding-inline: 0.5rem;\n}\n\nwje-chip {\n --wje-chip-margin: 0 0.25rem 0 0;\n}\n\nwje-button {\n --wje-padding-top: 0.25rem;\n --wje-padding-start: 0.25rem;\n --wje-padding-end: 0.25rem;\n --wje-padding-bottom: 0.25rem;\n --wje-button-margin-inline: 0 0.25rem;\n}\n\n.slot-start,\n.slot-end {\n &:not(:empty) {\n margin-right: 0.5rem;\n }\n}\n\nslot[name='error'] {\n display: none;\n}\n\n:host([invalid]) slot[name='error'] {\n display: block;\n}\n\nslot[name='error'] {\n display: none;\n position: absolute;\n max-width: 100%;\n min-width: auto;\n border-radius: 50px;\n background: black;\n padding: 0.25rem 0.5rem;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n color: white;\n font-size: var(--wje-font-size-small);\n width: max-content;\n line-height: normal;\n}"; class Select extends WJElement { /** * Constructor for the Select class. * @class * @description Initializes the Select component. * This constructor sets up the initial state of the component, including selected items, counter element, and internal element API. * It also tracks whether the select element was previously opened. * @class * @augments {WJElement} * @memberof Select */ constructor() { super(); __privateAdd(this, _Select_instances); __privateAdd(this, _addedOptions, []); __privateAdd(this, _htmlOptions, []); /** * An object representing component dependencies with their respective classes. * Each property in the object maps a custom component name (as a string key) * to its corresponding class or constructor. * @typedef {{[key: string]: Function}} Dependencies * @property {Function} 'wje-button' Represents the Button component class. * @property {Function} 'wje-popup' Represents the Popup component class. * @property {Function} 'wje-icon' Represents the Icon component class. * @property {Function} 'wje-label' Represents the Label component class. * @property {Function} 'wje-chip' Represents the Chip component class. * @property {Function} 'wje-input' Represents the Input component class. * @property {Function} 'wje-option' Represents the Option component class. * @property {Function} 'wje-options' Represents the Options component class. * @property {Function} 'wje-checkbox' Represents the Checkbox component class. */ __publicField(this, "dependencies", { "wje-button": Button, "wje-popup": Popup, "wje-icon": Icon, "wje-label": Label, "wje-chip": Chip, "wje-input": Input, "wje-option": Option, "wje-options": Options, "wje-checkbox": Checkbox }); __publicField(this, "className", "Select"); /** * Handles the option change event. * @param {Event} e The event. */ __publicField(this, "optionChange", (e) => { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); let allOptions = this.getAllOptions(); if (!this.hasAttribute("multiple")) { allOptions.forEach((option) => { if (option.value === e.target.value) { this.processClickedOption(option); } else { option.selected = false; } }); this.popup.hide(false); } else { this.processClickedOption(e.target, true); } this.selections(); this.validateSelect(); this.pristine = false; this.propagateValidation(); }); /** * Handles the selection and deselection of an option element. * @param {HTMLElement} option The option element that was clicked. * @param {boolean} [multiple] Indicates whether multiple selection is allowed. */ __publicField(this, "processClickedOption", (option, multiple = false) => { const isSelected = option.hasAttribute("selected"); option.selected = !isSelected; if (isSelected) { this.filterOutOption(option); } else { this.selectedOptions = multiple ? [...this.selectedOptions, option] : [option]; } }); /** * Filters out a specified option from the `selectedOptions` array. * This function removes an option from the `selectedOptions` array if its value * matches the value of the option provided as an argument. It allows for dynamically * updating the selected options by excluding the specified option. * @param {object} option The option to be removed from the `selectedOptions` array. * Should be an object containing a `value` property that is compared to the * `value` property of objects in the `selectedOptions` array. */ __publicField(this, "filterOutOption", (option) => { this.selectedOptions = this.selectedOptions.filter((sOption) => { return sOption.value !== option.value; }); }); /** * Handles the chip remove event. * @param {Event} e The event. */ __publicField(this, "removeChip", (e) => { e.target.parentNode.removeChild(e.target); this.processClickedOption(e.target.option, true); this.selections(); }); this._selected = []; this.counterEl = null; this.internals = this.attachInternals(); this._wasOppened = false; this.native = null; this.popup = null; this.labelElement = null; this.slotStart = null; this.slotEnd = null; this.input = null; this.optionsWrapper = null; this.chips = null; this.clear = null; this.list = null; this.selectedOptions = []; } /** * Setter for the value attribute. * @param {string} value The value to set. */ set value(value) { if (this.hasAttribute("multiple")) { const formData = new FormData(); value.forEach((v) => formData.append(this.name, v)); this.internals.setFormValue(formData); } else { this.internals.setFormValue(value); } } /** * Getter for the value attribute. * @returns {object} The value of the attribute. */ get value() { return this.selected; } set required(value) { if (value) { this.setAttribute("required", ""); } else { this.removeAttribute("required"); } } get required() { return this.hasAttribute("required"); } /** * Getter for the customErrorDisplay attribute. * @returns {boolean} Whether the attribute is present. */ get customErrorDisplay() { return this.hasAttribute("custom-error-display"); } /** * Getter for the validateOnChange attribute. * @returns {boolean} Whether the attribute is present. */ get validateOnChange() { return this.hasAttribute("validate-on-change"); } /** * Retrieves the value of the 'invalid' attribute. * This method checks if the 'invalid' attribute is present on the element. * @returns {boolean} Returns true if the 'invalid' attribute is present, otherwise false. */ get invalid() { return this.hasAttribute("invalid"); } /** * Sets the 'invalid' property of the element. * When set to a truthy value, the 'invalid' attribute is added to the element. * When set to a falsy value, the 'invalid' attribute is removed from the element. * @param {boolean} value A boolean indicating whether the element is invalid. */ set invalid(value) { if (value) { this.setAttribute("invalid", ""); } else { this.removeAttribute("invalid"); } } /** * Sets the maximum number of options allowed. * @param {string | number | null} value The value to set as the maximum number of options. * If null, the 'max-options' attribute will be removed. */ set maxOptions(value) { if (value) { this.setAttribute("max-options", value); } else { this.removeAttribute("max-options"); } } /** * Retrieves the maximum number of options allowed. * Parses the value of the 'max-options' attribute from the element and converts it to a number. * If the attribute is not present or cannot be converted to a valid number, defaults to 1. * @returns {number} The maximum number of options, or 0 if the attribute is not set or invalid. */ get maxOptions() { return +this.getAttribute("max-options") || 1; } /** * Getter for the form attribute. * @returns {HTMLFormElement} The form the input is associated with. */ get form() { return this.internals.form; } /** * Getter for the name attribute. * @returns {string} The name of the input. */ get name() { return this.getAttribute("name"); } /** * Getter for the type attribute. * @returns {string} The type of the input. */ get type() { return this.localName; } /** * Getter for the validity attribute. * @returns {ValidityState} The validity state of the input. */ get validity() { return this.internals.validity; } /** * Getter for the validationMessage attribute. * @returns {string} The validation message of the input. */ get validationMessage() { return this.internals.validationMessage; } /** * Getter for the willValidate attribute. * @returns {boolean} Whether the input will be validated. */ get willValidate() { return this.internals.willValidate; } /** * @summary Getter for the defaultValue attribute. * This method retrieves the 'value' attribute of the custom input element. * The 'value' attribute represents the default value of the input element. * If the 'value' attribute is not set, it returns an empty string. * @returns {string} The default value of the input element. */ get defaultValue() { return this.getAttribute("value") ?? ""; } /** * @summary Setter for the defaultValue attribute. * This method sets the 'value' attribute of the custom input element to the provided value. * The 'value' attribute represents the default value of the input element. * @param {string} value The value to set as the default value. */ set defaultValue(value) { this.setAttribute("value", value); } /** * Sets the label value. * @param {Array} value The selected value to set. */ set selected(value) { this._selected = value; } /** * Returns the selected value. * @returns {Array} The selected value. */ get selected() { return this.getSelected(); } /** * Retrieves the complete list of options available for the component. * The options are determined by combining elements from various sources, including loaded options, added options, and HTML-sourced options. * If a `wje-options` element is present within the component, its loaded options are included in the merged list. * In the absence of a `wje-options` element, duplicates among the added and HTML options are removed, retaining their order. * @returns {Array<object>} An array containing all the available options, combining the loaded, added, and HTML-based options, with duplicates removed where applicable. */ get options() { if (this.querySelector("wje-options")) { const allOptions = [...this.querySelector("wje-options").loadedOptions, ...__privateGet(this, _addedOptions), ...__privateGet(this, _htmlOptions)]; return allOptions; } else { const allOptions = [...__privateGet(this, _addedOptions), ...__privateGet(this, _htmlOptions)]; return Array.from( new Map(allOptions.reverse().map((obj) => [obj.value, obj])).values() ).reverse(); } } /** * Sets the trigger value. * @param {string} value The trigger value to set. */ set trigger(value) { this.setAttribute("trigger", value); } /** * Returns the trigger value. * @returns {string} The trigger value. */ get trigger() { return this.getAttribute("trigger") || "click"; } set offset(value) { this.setAttribute("offset", value); } get offset() { return this.getAttribute("offset") || "0"; } /** * Returns the CSS styles for the component. * @static * @returns {CSSStyleSheet} */ static get cssStyleSheet() { return styles; } /** * Returns the list of attributes to observe for changes. * @static * @returns {Array<string>} */ static get observedAttributes() { return ["active", "value", "disabled", "multiple", "label", "placeholder", "max-height", "max-options", "variant", "placement"]; } /** * Sets up the attributes for the component. */ setupAttributes() { this.isShadowRoot = "open"; } /** * Draws the component for the select. * @returns {DocumentFragment} */ draw() { let fragment = document.createDocumentFragment(); this.classList.add("wje-placement", this.placement ? "wje-" + this.placement : "wje-start"); let native = document.createElement("div"); native.setAttribute("part", "native"); native.classList.add("native-select", this.variant || "default"); let wrapper = document.createElement("div"); wrapper.classList.add("wrapper"); wrapper.setAttribute("slot", "anchor"); let label = document.createElement("wje-label"); label.setAttribute("part", "label"); label.innerText = this.label || ""; let inputWrapper = document.createElement("div"); inputWrapper.setAttribute("part", "input-wrapper"); inputWrapper.classList.add("input-wrapper"); let slotStart = document.createElement("div"); slotStart.classList.add("slot-start"); let input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("part", "input"); input.setAttribute("autocomplete", "off"); input.setAttribute("readonly", ""); input.setAttribute("placeholder", this.placeholder || ""); if (this.required) input.setAttribute("required", ""); let slotEnd = document.createElement("div"); slotEnd.classList.add("slot-end"); let arrow = document.createElement("wje-icon"); arrow.setAttribute("name", "chevron-down"); arrow.setAttribute("slot", "arrow"); let chips = document.createElement("div"); chips.classList.add("chips"); chips.innerText = this.placeholder || ""; let optionsWrapper = document.createElement("div"); optionsWrapper.setAttribute("part", "options-wrapper"); optionsWrapper.classList.add("options-wrapper"); optionsWrapper.style.setProperty("height", this.maxHeight || "auto"); let list = document.createElement("div"); list.classList.add("list"); let slot = document.createElement("slot"); let clear = document.createElement("wje-button"); clear.setAttribute("fill", "link"); clear.setAttribute("part", "clear"); clear.setAttribute("stop-propagation", ""); let clearIcon = document.createElement("wje-icon"); clearIcon.setAttribute("name", "x"); let error = document.createElement("div"); error.setAttribute("slot", "error"); let errorSlot = document.createElement("slot"); errorSlot.setAttribute("name", "error"); let popup = document.createElement("wje-popup"); popup.setAttribute("placement", "bottom-start"); popup.setAttribute("manual", ""); popup.setAttribute("size", ""); popup.setAttribute("part", "popup"); popup.setAttribute("offset", this.offset); if (this.hasAttribute("disabled")) popup.setAttribute("disabled", ""); if (this.variant === "standard") { if (this.hasAttribute("label")) native.appendChild(label); } else { wrapper.appendChild(label); } inputWrapper.append(slotStart); inputWrapper.append(input); clear.append(clearIcon); if (this.hasAttribute("multiple")) inputWrapper.append(chips); if (this.hasAttribute("clearable")) inputWrapper.append(clear); inputWrapper.appendChild(slotEnd); inputWrapper.appendChild(arrow); list.appendChild(slot); if (this.hasAttribute("find")) { let find = document.createElement("wje-input"); find.setAttribute("variant", "standard"); find.setAttribute("placeholder", "Hľadať"); find.setAttribute("part", "find"); find.clearable = true; find.classList.add("find"); optionsWrapper.appendChild(find); this.findEl = find; } if (this.hasAttribute("lazy")) { event.addListener(popup, "wje-popup:show", null, (e) => { if (this._wasOppened) return; this._wasOppened = true; const optionsElement = this.querySelector("wje-options"); optionsElement.setAttribute("lazy", ""); optionsElement.setAttribute("attached", ""); }); } else { const optionsElement = this.querySelector("wje-options"); optionsElement == null ? void 0 : optionsElement.setAttribute("attached", ""); } optionsWrapper.append(list); wrapper.append(inputWrapper); popup.append(wrapper); popup.append(optionsWrapper); if (this.trigger === "click") popup.setAttribute("manual", ""); this.append(error); native.append(popup); native.append(errorSlot); this.native = native; this.popup = popup; this.labelElement = label; this.slotStart = slotStart; this.slotEnd = slotEnd; this.input = input; this.optionsWrapper = optionsWrapper; this.chips = chips; this.clear = clear; this.list = list; fragment.appendChild(native); return fragment; } /** * Performs actions and binds events after the component's markup and state are initialized. * Actions include setting up event listeners, managing option elements, handling focus and blur behaviors, * synchronizing the selected options, and managing a find functionality for filtering options. * @returns {void} Does not return a value. The method operates by updating the state and behavior of the component. */ afterDraw() { var _a, _b; this.validateSelect(); if (this.hasAttribute("invalid")) { this.showInvalidMessage(); } (_a = this.getAllOptions()) == null ? void 0 : _a.forEach((option) => { this.optionCheckSlot(option); }); __privateSet(this, _htmlOptions, Array.from(this.querySelectorAll(":scope > wje-option")).map((option) => { return { value: option.value, text: option.textContent.trim() }; })); this.input.addEventListener("focus", (e) => { var _a2; (_a2 = this.labelElement) == null ? void 0 : _a2.classList.add("fade"); this.native.classList.add("focused"); }); this.input.addEventListener("blur", (e) => { var _a2; this.native.classList.remove("focused"); if (!e.target.value) (_a2 = this.labelElement) == null ? void 0 : _a2.classList.remove("fade"); }); this.input.addEventListener("input", (e) => { this.propagateValidation(); }); this.addEventListener("wje-option:change", this.optionChange); this.addEventListener("wje-select:invalid", (e) => { this.invalid = true; this.pristine = false; this.showInvalidMessage(); if (this.customErrorDisplay) { e.preventDefault(); } }); (_b = this.clear) == null ? void 0 : _b.addEventListener("wje-button:click", (e) => { e.preventDefault(); e.stopPropagation(); this.selectedOptions = []; this.getAllOptions().forEach((option) => { option.selected = false; }); this.selections(); e.stopPropagation(); }); this.selectedOptions = this.getSelectedOptions(); this.selections(true); this.list.addEventListener("wje-options:load", (e) => { this.selectedOptions.forEach((option) => { this.getAllOptions().forEach((el) => { if (el.value === option.value) { el.selected = true; } }); }); this.list.scrollTo(0, 0); }); if (this.hasAttribute("find") && this.findEl instanceof HTMLElement) { event.addListener(this.findEl, "keyup", "", (e) => { const optionsElement = this.querySelector("wje-options"); if (optionsElement && optionsElement.hasAttribute("lazy")) { optionsElement.setAttribute("search", e.target.value); } else { let value = e.target.value.trim().toLowerCase(); this.getAllOptions().forEach((option) => { if (option.textContent.trim().toLowerCase().includes(value)) option.style.display = "block"; else option.style.display = "none"; }); } }); } } /** * Returns all the options as HTML. * @returns {NodeList} The options as HTML. */ getAllOptions() { return this.querySelectorAll("wje-option"); } /** * Returns the selected options as HTML. * @returns {NodeList} The selected options as HTML. */ getSelectedOptions() { return Array.from(this.querySelectorAll("wje-option[selected]")); } /** * Returns the selected options. * @returns {Array} The selected options. */ getSelected() { return this.selectedOptions.map((option) => { return { value: option.value, text: __privateMethod(this, _Select_instances, htmlSelectedItem_fn).call(this, option.value) // option.textContent.trim(), }; }); } /** * Handles the selection change. * @param {Element[]} options The option that changed. * @param {number} length The length of the selected options. */ selectionChanged(options = null, length = 0) { var _a, _b; if (this.hasAttribute("multiple")) { this.value = this.selectedOptions.map((el) => el.value).reverse(); if (this.placeholder && length === 0) { this.chips.innerHTML = this.placeholder; this.input.value = ""; } else { if (options !== null) Array.from(options).slice(0, +this.maxOptions).forEach((option) => this.chips.appendChild(this.getChip(option))); if (this.counterEl instanceof HTMLElement || !this.maxOptions || length > +this.maxOptions) { this.counter(); } } } else { let option = options == null ? void 0 : options.at(0); let value = (option && __privateMethod(this, _Select_instances, htmlSelectedItem_fn).call(this, option.value)) ?? ""; this.value = (_b = (_a = this.selectedOptions) == null ? void 0 : _a.map((el) => el.value)) == null ? void 0 : _b.at(0); this.input.value = value; if (option && option instanceof HTMLElement) { this.slotStart.innerHTML = ""; let optionSlotStart = option == null ? void 0 : option.querySelector("wje-option > [slot=start]"); if (optionSlotStart) { this.slotStart.appendChild(optionSlotStart.cloneNode(true)); } this.slotEnd.innerHTML = ""; let optionSlotEnd = option == null ? void 0 : option.querySelector("wje-option > [slot=end]"); if (optionSlotEnd) { this.slotEnd.appendChild(optionSlotEnd.cloneNode(true)); } } } } /** * Updates the selected options and their corresponding chips. * @param {boolean} [silence] Determines whether to suppress the "wje-select:change" event. * @returns {void} * @description * This method fetches the currently selected options and updates the `selectedOptions` array. * It clears and rebuilds the chips representing the selected items in the UI. * If the number of selected options reaches the maximum allowed (`maxOptions`), it stops updating the counter. * Optionally, it dispatches a custom event when the selection changes unless `silence` is set to `true`. * //@fires wje-select:change - Dispatched when the selection changes, unless `silence` is `true`. * @example * // Call the method and allow event dispatch * selections(); * @example * // Call the method without dispatching the event * selections(true); */ selections(silence = false) { if (this.selectedOptions.length >= +this.maxOptions) { this.counterEl = null; } if (this.chips) { this.chips.innerHTML = ""; } if (this.selectedOptions.length > 0) { this.selectionChanged(this.selectedOptions, this.selectedOptions.length); } else { this.selectionChanged(); } if (silence) return; event.dispatchCustomEvent(this, "wje-select:change"); } /** * Manages the display of a counter element to indicate the number of items exceeding the maximum allowed options. * - If the number of selected items equals the maximum allowed, the counter element is removed. * - If the counter element doesn't exist and the number of items exceeds the maximum, it is created and updated. */ counter() { if (this.counterEl && this.value.length === +this.maxOptions) { this.counterEl.remove(); this.counterEl = null; return; } if (!this.counterEl) { this.counterEl = document.createElement("span"); this.counterEl.classList.add("counter"); this.chips.appendChild(this.counterEl); } this.counterEl.innerText = `+${this.value.length - +this.maxOptions}`; } /** * Returns a chip element. * @param {Element} option The option to get the chip for. * @returns {Element} The chip element. */ getChip(option) { let chip = document.createElement("wje-chip"); chip.size = "small"; chip.removable = true; chip.round = true; chip.addEventListener("wje:chip-remove", this.removeChip); chip.option = option; let label = document.createElement("wje-label"); label.innerText = __privateMethod(this, _Select_instances, htmlSelectedItem_fn).call(this, option.value); chip.appendChild(label); return chip; } /** * Generates an HTML option element based on the provided item and mapping. * @param {object} item The item to generate the option for. * @param {object} [map] The mapping object that specifies the properties of the item to use for the option's value and text. * @param {string} [map.value] The property of the item to use for the option's value. * @param {string} [map.text] The property of the item to use for the option's text. * @returns {HTMLElement} The generated HTML option element. */ htmlOption(item, map = { value: "value", text: "text" }) { let option = document.createElement("wje-option"); if (item[map.value] === null) { console.warn(`The item ${JSON.stringify(item)} does not have the property ${map.value}`); } if (item[map.text] === null) { console.warn(`The item ${JSON.stringify(item)} does not have the property ${map.text}`); } option.setAttribute("value", item[map.value] ?? ""); option.innerText = item[map.text] ?? ""; __privateGet(this, _addedOptions).push({ [map.value]: item[map.value], [map.text]: item[map.text] }); return option; } /** * Adds an option to the select element. * @param {any} optionData The data for the option to be added. * @param {boolean} [silent] Whether to suppress any events triggered by the addition of the option. * @param {object} [map] The mapping object specifying the properties of the option data to be used for the value and text of the option. */ addOption(optionData, silent = false, map = { value: "value", text: "text" }) { if (!optionData) return; const optionsElement = this.querySelector("wje-options"); if (optionsElement) { optionsElement.addOption(optionData, silent, map); return; } let option = this.htmlOption(optionData, map); this.appendChild(option); } /** * Adds options to the select element. * @param {Array | object} optionsData The options data to be added. Can be an array of objects or a single object. * @param {boolean} [silent] Indicates whether to trigger events when adding options. Default is false. * @param {object} [map] The mapping object that specifies the properties of the options data object. Default is { value: "value", text: "text" }. */ addOptions(optionsData, silent = false, map = { value: "value", text: "text" }) { if (!Array.isArray(optionsData)) { this.addOption(optionsData, silent, map); } else { const optionsElement = this.querySelector("wje-options"); if (optionsElement) { optionsElement.addOptions(optionsData, silent, map); return; } optionsData.forEach((item) => { this.addOption(item, silent, map); }); } } /** * Selects an option with the specified value. * @param {string} value The value of the option to be selected. * @param {boolean} [silent] Whether to suppress firing events. */ selectOption(value, silent = false) { if (!value) return; let option = this.querySelector(`wje-option[value="${value}"]`); if (option) { this.processClickedOption(option, this.hasAttribute("multiple")); } if (this.drawingStatus > this.drawingStatuses.START) this.selections(silent); } /** * Selects one or multiple options in the select element. * @param {Array|any} values The value(s) of the option(s) to be selected. * @param {boolean} [silent] Whether to trigger the change event or not. */ selectOptions(values, silent = false) { if (!Array.isArray(values)) { this.selectOption(values, silent); } else { values.forEach((value) => { this.selectOption(value, silent); }); } } /** * Returns the provided item. * @param {any} item The item to be returned. * @returns {any} The same item that was passed as input. */ htmlSelectedItem(item) { return item; } /** * Clones and appends an icon from a template with slot "check" to the given option element. * @param {HTMLElement} option The target option element where the "check" icon will be added. * @returns {void} */ optionCheckSlot(option) { var _a; let icon = (_a = this.querySelector("template")) == null ? void 0 : _a.content.querySelector(`[slot="check"]`); if (!icon) { console.warn(`Icon with slot "check" was not found.`); return; } let iconClone = icon.cloneNode(true); option.append(iconClone); } /** * Validates the selection of options in the select element. * Checks if the element is required and no option is selected, * in which case it sets a validation error with a custom message. * If the element passes validation, it clears any existing validation errors. * * @return {void} Does not return a value. */ validateSelect() { if (this.required && this.selectedOptions.length === 0) { const msg = this.getAttribute("message-required") || "Zvoľte možnosť"; this.internals.setValidity({ valueMissing: true }, msg); } else { this.internals.setValidity({}); } } /** * Checks and updates the validation state of the component based on its current properties. * If the component is invalid and a custom error display is enabled, it dispatches an 'invalid' event. * @returns {void} This method does not return a value. */ propagateValidation() { this.invalid = !this.pristine && !this.validity.valid; if (this.invalid) { event.dispatchCustomEvent(this, "wje-select:invalid"); } } showInvalidMessage() { { const slot = this.querySelector("[slot='error']"); let errorMessageEL = slot.querySelector("[error-message]"); if (!errorMessageEL) { const error = document.createElement("div"); error.setAttribute("error-message", ""); slot.append(error); errorMessageEL = error; } errorMessageEL.textContent = this.internals.validationMessage; } console.log(`Invalid input: ${this.internals.validationMessage}`); } /** * Lifecycle callback invoked when the custom element becomes associated with a form element. * @param {HTMLFormElement} form The form element the custom element is associated with. * @returns {void} */ formAssociatedCallback(form) { if (form) { form.addEventListener("submit", () => { this.validateSelect(); this.propagateValidation(); }); } } /** * The formResetCallback method is a built-in lifecycle callback that gets called when a form gets reset. * This method is responsible for resetting the value of the custom input element to its default value. * It also resets the form value and validity state in the form internals. * @function */ formResetCallback() { this.value = this.defaultValue; this.internals.setFormValue(this.defaultValue); this.internals.setValidity({}); } /** * The formStateRestoreCallback method is a built-in lifecycle callback that gets called when the state of a form-associated custom element is restored. * This method is responsible for restoring the value of the custom input element to its saved state. * It also restores the form value and validity state in the form internals to their saved states. * @param {object} state The saved state of the custom input element. * @function */ formStateRestoreCallback(state) { this.value = state.value; this.internals.setFormValue(state.value); this.internals.setValidity({}); } /** * The formStateSaveCallback method is a built-in lifecycle callback that gets called when the state of a form-associated custom element is saved. * This method is responsible for saving the value of the custom input element. * @returns {object} The saved state of the custom input element. * @function */ formStateSaveCallback() { return { value: this.value }; } /** * The formDisabledCallback method is a built-in lifecycle callback that gets called when the disabled state of a form-associated custom element changes. * This method is not implemented yet. * @param {boolean} disabled The new disabled state of the custom input element. * @function */ formDisabledCallback(disabled) { var _a; console.warn("formDisabledCallback not implemented yet"); (_a = this.native) == null ? void 0 : _a.classList.toggle("disabled", disabled); this.toggleAttribute("disabled", disabled); } /** * Checks if all elements in the `elements` array are present in the `options` array based on their `option` property. * @param {Array} elements The array of elements to check. Each element should have an `option` property. * @param {Array} options The array of options to verify against. * @returns {boolean} Returns true if all elements in the `elements` array are found within the `options` array, otherwise returns false. */ areAllElementsInOptions(elements, options) { if (elements.length === 0) return false; return elements.every( (el) => options.some((opt) => JSON.stringify(opt) === JSON.stringify(el.option)) ); } } _addedOptions = new WeakMap(); _htmlOptions = new WeakMap(); _Select_instances = new WeakSet(); /** * Processes the provided item to retrieve its corresponding value and text * based on the configuration of `wje-options`, then updates and returns * the selected item's HTML representation. * @param {any} item The input item for which the value and text are determined. * @returns {string} The HTML representation of the selected item's value. */ htmlSelectedItem_fn = function(item) { var _a, _b, _c; const keyValue = ((_a = this.querySelector("wje-options")) == null ? void 0 : _a.itemValue) ?? "value"; const textValue = ((_b = this.querySelector("wje-options")) == null ? void 0 : _b.itemText) ?? "text"; const value = ((_c = this.options.find((option) => option[keyValue] === item)) == null ? void 0 : _c[textValue]) ?? ""; return this.htmlSelectedItem(value); }; /** * Whether the input is associated with a form. * @type {boolean} */ __publicField(Select, "formAssociated", true); Select.define("wje-select", Select); export { Select as default }; //# sourceMappingURL=wje-select.js.map