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
JavaScript
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