diff --git a/development/issues/open/322.html b/development/issues/open/322.html new file mode 100644 index 0000000000000000000000000000000000000000..f940ec28495430ad6731747578c709cf4a00f1a3 --- /dev/null +++ b/development/issues/open/322.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> + <title>investigate select with int and empty value #322</title> + <script src="./322.mjs" type="module"></script> +</head> +<body> + <h1>investigate select with int and empty value #322</h1> + <p></p> + <ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/322">Issue #322</a></li> + <li><a href="/">Back to overview</a></li> + </ul> + <main style="margin-left:500px;width: 500px; border: 1px solid black; overflow: auto;"> + + <h2>Root Item</h2> + + <monster-select + data-monster-bind="path:data.masterRootitemIID" + data-monster-bind-type="int" + data-monster-option-empty-equivalents="0" + data-monster-option-features-emptyValueIfNoOptions="true" + value="0" + + + data-monster-option-type="radio" + data-monster-option-filter-mode="remote" + data-monster-option-filter-position="popper" + + data-monster-option-url="/issue-316.json?q={filter}" + data-monster-option-lookup-url="/api/commerce/item/{filter}" + data-monster-option-mapping-selector="dataset.*" + data-monster-option-mapping-labeltemplate="${name} (${iid})" + data-monster-option-mapping-valuetemplate="${iid}" + > + </monster-select> + +<!-- <monster-select data-monster-option-filter-mode="remote"--> +<!-- data-monster-option-filter-position="inline"--> +<!-- data-monster-option-mapping-total="sys.total"--> +<!-- data-monster-option-mapping-selector="dataset.*"--> +<!-- data-monster-option-filter-defaultvalue=""--> +<!-- data-monster-option-remoteinfo-url="/issue-316.json?total"--> +<!-- data-monster-option-placeholder-filter="You can search and it will load when you change the filter, not the value listed here."--> +<!-- data-monster-option-filter-defaultoptionsurl="/issue-322.json"--> +<!-- data-monster-option-url="/issue-316.json?q={filter}"--> +<!-- data-monster-option-mapping-labeltemplate="${name}"--> +<!-- data-monster-option-mapping-valuetemplate="${id}">--> +<!-- </monster-select>--> + + </main> +</body> +</html> diff --git a/development/issues/open/322.mjs b/development/issues/open/322.mjs new file mode 100644 index 0000000000000000000000000000000000000000..55d65f2c8193f2e5829691f36d5f24a9a52ae7e4 --- /dev/null +++ b/development/issues/open/322.mjs @@ -0,0 +1,15 @@ +/** +* @file development/issues/open/322.mjs +* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/322 +* @description investigate select with int and empty value +* @issue 322 +*/ + +import "../../../source/components/style/property.pcss"; +import "../../../source/components/style/link.pcss"; +import "../../../source/components/style/color.pcss"; +import "../../../source/components/style/theme.pcss"; +import "../../../source/components/style/normalize.pcss"; +import "../../../source/components/style/typography.pcss"; +import "../../../source/components/form/select.mjs"; + diff --git a/development/mock/issue-322.js b/development/mock/issue-322.js new file mode 100644 index 0000000000000000000000000000000000000000..7905ce2f8245d0fc1d5c94797d1a58be9a3f350c --- /dev/null +++ b/development/mock/issue-322.js @@ -0,0 +1,289 @@ +const json = + `{ + "dataset": { + "a25c3d99-e94b-43b9-c38f-0b20f5f645e2": { + "iid": 502, + "name": "Versand", + "masterNameLocale": [], + "masterNumber": "Versand", + "masterSKU": "", + "masterNotice": "", + "masterDescription": "", + "masterRootitemIID": 0, + "masterRootitemCombine": "0", + "masterProductfamily1": "", + "masterProductfamily2": "", + "masterProductfamilyCombinde": "1", + "masterFollowingIID": "", + "active": false, + "variants": { + "__UNIQUE__": "" + }, + "variantsData": [ + { + "decription": "", + "number": "", + "text": { + "de": "", + "en": "" + }, + "hide": false, + "properties": null + } + ], + "packingUnits": [], + "salesAddon": 0, + "salesSetIID": "a:0:{}", + "salesChannel": [], + "salesStart": null, + "salesEnd": null, + "salesType": 30, + "salesCountry": [ + 0 + ], + "salesCountryMode": 0, + "salesAnnouncementOrderGroupGID": [ + 0 + ], + "salesAnnouncementOrderFlag": "0", + "salesAnnouncementOrderFrom": null, + "salesAnnouncementOrderTo": null, + "salesAnnouncementPriceFlag": "0", + "salesAnnouncementOrderTimegroupFlag": "0", + "salesPosition": 0, + "salesCompaniesRestriction": [], + "salesCompaniesAssignment": 0, + "salesCompaniesNoExport": "0", + "salesCompaniesShopFlag": "0", + "salesGroup": 100, + "salesQuantityRestriction": "", + "salesQuantityRestrictAmountPerOrder": 0, + "salesAffiliatesRestriction": [ + 0 + ], + "salesDisabledpayment": [], + "catalogingKeywords": [], + "catalogingDepartmentDID1": 0, + "catalogingDepartmentDID2": 0, + "catalogingBrandBID1": 0, + "catalogingBrandBID2": 0, + "catalogingGender": "", + "catalogingCategoryCID1": 0, + "catalogingCategoryCID2": 0, + "catalogingCategoryCID3": 0, + "catalogingCategoryCID4": [], + "catalogingCollectionCLID1": 0, + "catalogingCollectionCLID2": [ + 0 + ], + "catalogingAssignmentAAID1": 0, + "catalogingAssignmentABID2": [], + "catalogingAssignmentACID3": [], + "catalogingRasterRID1": 0, + "catalogingRasterRID2": [], + "catalogingImagesource": "", + "catalogingAudienceAUID1": 0, + "catalogingAudienceAUID2": [], + "catalogingSeasonSAID": 0, + "catalogingStyleSID1": 0, + "catalogingStyleSID2": 0, + "catalogingStyleSID3": 0, + "catalogingStyleSID4": [], + "specificationCOID1": 0, + "specificationCOID2": 0, + "specificationCOID3": 0, + "specificationCOID4": [], + "specificationWidth": 0, + "specificationWidthNormalizationFactor": null, + "specificationWidthViewNormalizationFactor": null, + "specificationHeight": 0, + "specificationHeightNormalizationFactor": null, + "specificationHeightViewNormalizationFactor": null, + "specificationDepth": 0, + "specificationDepthNormalizationFactor": null, + "specificationDepthViewNormalizationFactor": null, + "specificationWeight": 0, + "specificationWeightNormalizationFactor": null, + "specificationWeightViewNormalizationFactor": null, + "specificationWeightNet": 0, + "specificationWeightNetNormalizationFactor": null, + "specificationWeightNetViewNormalizationFactor": null, + "specificationMaterial": "a:1:{s:2:\\"de\\";s:0:\\"\\";}", + "specificationVolume": 0, + "specificationVolumeNormalizationFactor": null, + "specificationVolumeViewNormalizationFactor": null, + "specificationMaterialMAID": 0, + "specificationBasepriceReference": 0, + "sourcingPceControlSerialnumber": "0", + "sourcingPceControlChargenumber": "0", + "sourcingStockInventoryObligation": "0", + "sourcingDistributor1UID": 0, + "sourcingDistributor1Name": "", + "sourcingDistributor1Deliverytime": "0", + "sourcingDistributor1Number": "0", + "sourcingDistributor1DeliverytimeUnit": 0, + "sourcingDistributor1OrderPlugin": "", + "sourcingDistributor1ErpID": "", + "sourcingDistributor1ErpName": "", + "sourcingDistributor1ErpNumber": "", + "sourcingDistributor2UID": 0, + "sourcingDistributor2Name": "", + "sourcingDistributor2Deliverytime": "0", + "sourcingDistributor2Number": "0", + "sourcingDistributor2DeliverytimeUnit": 0, + "sourcingDistributor2OrderPlugin": "", + "sourcingDistributor2ErpID": "", + "sourcingDistributor2ErpName": "", + "sourcingDistributor2ErpNumber": "", + "sourcingDistributor3UID": 0, + "sourcingDistributor3Name": "", + "sourcingDistributor3Deliverytime": "", + "sourcingDistributor3Number": "", + "sourcingDistributor3DeliverytimeUnit": 0, + "sourcingDistributor3OrderPlugin": "", + "sourcingDistributor3ErpID": "", + "sourcingDistributor3ErpName": "", + "sourcingDistributor3ErpNumber": "", + "sourcingStockFlag": 16, + "sourcingStockMinCount": 0, + "sourcingStockEstimatedBalance": 0, + "shippingIncoterms": "", + "shippingNotOnPicklist": "0", + "shippingStoretype": 0, + "shippingHideOnDocument": "0", + "shippingPackaging": [], + "shippingPrePackagingUnit": 0, + "shippingPreventFromCosts": "0", + "manufacturerUID": 0, + "manufacturerName": "", + "manufacturerItemUrl": "", + "manufacturerColor": "", + "manufacturerOriginCountry": "", + "orderPreorder": 0, + "financesVat": { + "de": 0, + "gb": 0, + "fr": 0, + "at": 0, + "ch": 0, + "be": 0, + "cz": 0, + "gr": 0, + "hr": 0, + "it": 0, + "lu": 0, + "mc": 0, + "nl": 0, + "sk": 0, + "us": 0, + "sv": 0 + }, + "financesTaxcode": { + "de": "", + "gb": "", + "fr": "", + "at": "", + "ch": "", + "be": "", + "cz": "", + "gr": "", + "hr": "", + "it": "", + "lu": "", + "mc": "", + "nl": "", + "sk": "", + "us": "", + "sv": "" + }, + "financesBought": 0, + "financesDiscountable": 0, + "financesRevenueInland": "", + "financesRevenueAbroad": "", + "financesRevenueEU": "", + "financesRevenueEUID": "", + "financesCostInland": "", + "financesCostAbroad": "", + "financesCostEU": "", + "financesCostEUID": "", + "financesCustomsTariffNumber": "", + "financesCustomsTariffDescription1": "", + "financesCustomsTariffDescription2": "", + "sourcingStockEdiDeactivateSKUCheck": "", + "marketingAlsobought": "", + "marketingCommendation1": "", + "marketingCommendation2": "", + "marketingCommendation3": "", + "marketingCommendation4": "", + "marketingCommendation5": "", + "marketingCommendation6": "", + "marketingFlag": 0, + "marketingExtraUrl1": "", + "marketingExtraUrl2": "", + "marketingExtraPage": "", + "salesAnnouncementRestrictionGroupGID": "0", + "erpID": "", + "erpName": "", + "erpNumber": "", + "erpLastUpdate": "2025-04-10T07:12:53", + "erpCreation": null, + "archived": true, + "localStrings": { + "salesType": "Systemintern", + "salesGroup": "Versandkosten" + }, + "property": { + "dataset": [] + } + } + }, + "sys": { + "pagination": { + "total": 1, + "currentPage": 1, + "objectsPerPage": 20, + "offset": 0, + "nextOffset": null, + "prevOffset": null, + "pages": 1 + }, + "message": "200 OK", + "code": 200 + } +}`; + + +// check if json is valid +JSON.parse(json) + +// check if json is valid + +export default [ + { + url: '/issue-322.json', + method: 'get', + rawResponse: async (req, res) => { + res.setHeader('Content-Type', 'application/json') + res.statusCode = 200 + setTimeout(function() { + res.end(json) + }, 10); + }, + }, + + { + url: '/issue-322', + method: 'post', + rawResponse: async (req, res) => { + res.setHeader('Content-Type', 'application/json') + res.statusCode = 400 + + setTimeout(function() { + res.end(json400Error) + }, 10); + }, + } + + + +]; \ No newline at end of file diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs index f116da6ef76acb4f59d4b6b5121fcd077b1a9775..797aa12788d334d9ac45e8d3f293347734a21535 100644 --- a/source/components/form/select.mjs +++ b/source/components/form/select.mjs @@ -12,61 +12,61 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { instanceSymbol, internalSymbol } from "../../constants.mjs"; -import { buildMap, build as buildValue } from "../../data/buildmap.mjs"; +import {instanceSymbol, internalSymbol} from "../../constants.mjs"; +import {buildMap, build as buildValue} from "../../data/buildmap.mjs"; import { - addAttributeToken, - containsAttributeToken, - findClosestByAttribute, - removeAttributeToken, + addAttributeToken, + containsAttributeToken, + findClosestByAttribute, + removeAttributeToken, } from "../../dom/attributes.mjs"; -import { ATTRIBUTE_PREFIX, ATTRIBUTE_ROLE } from "../../dom/constants.mjs"; -import { CustomControl } from "../../dom/customcontrol.mjs"; +import {ATTRIBUTE_PREFIX, ATTRIBUTE_ROLE} from "../../dom/constants.mjs"; +import {CustomControl} from "../../dom/customcontrol.mjs"; import { - assembleMethodSymbol, - getSlottedElements, - registerCustomElement, + assembleMethodSymbol, + getSlottedElements, + registerCustomElement, } from "../../dom/customelement.mjs"; -import { addErrorAttribute, removeErrorAttribute } from "../../dom/error.mjs"; +import {addErrorAttribute, removeErrorAttribute} from "../../dom/error.mjs"; import { - findTargetElementFromEvent, - fireCustomEvent, - fireEvent, + findTargetElementFromEvent, + fireCustomEvent, + fireEvent, } from "../../dom/events.mjs"; -import { getLocaleOfDocument } from "../../dom/locale.mjs"; -import { getDocument } from "../../dom/util.mjs"; +import {getLocaleOfDocument} from "../../dom/locale.mjs"; +import {getDocument} from "../../dom/util.mjs"; import { - getDocumentTranslations, - Translations, + getDocumentTranslations, + Translations, } from "../../i18n/translations.mjs"; -import { Formatter } from "../../text/formatter.mjs"; -import { getGlobal } from "../../types/global.mjs"; -import { ID } from "../../types/id.mjs"; +import {Formatter} from "../../text/formatter.mjs"; +import {getGlobal} from "../../types/global.mjs"; +import {ID} from "../../types/id.mjs"; import { - isArray, - isFunction, - isInteger, - isIterable, - isObject, - isPrimitive, - isString, + isArray, + isFunction, + isInteger, + isIterable, + isObject, + isPrimitive, + isString, } from "../../types/is.mjs"; -import { Observer } from "../../types/observer.mjs"; -import { ProxyObserver } from "../../types/proxyobserver.mjs"; -import { validateArray, validateString } from "../../types/validate.mjs"; -import { DeadMansSwitch } from "../../util/deadmansswitch.mjs"; -import { Processing } from "../../util/processing.mjs"; -import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs"; -import { SelectStyleSheet } from "./stylesheet/select.mjs"; -import { positionPopper } from "./util/floating-ui.mjs"; -import { Pathfinder } from "../../data/pathfinder.mjs"; -import { TokenList } from "../../types/tokenlist.mjs"; +import {Observer} from "../../types/observer.mjs"; +import {ProxyObserver} from "../../types/proxyobserver.mjs"; +import {validateArray, validateString} from "../../types/validate.mjs"; +import {DeadMansSwitch} from "../../util/deadmansswitch.mjs"; +import {Processing} from "../../util/processing.mjs"; +import {STYLE_DISPLAY_MODE_BLOCK} from "./constants.mjs"; +import {SelectStyleSheet} from "./stylesheet/select.mjs"; +import {positionPopper} from "./util/floating-ui.mjs"; +import {Pathfinder} from "../../data/pathfinder.mjs"; +import {TokenList} from "../../types/tokenlist.mjs"; export { - getSelectionTemplate, - getSummaryTemplate, - popperElementSymbol, - Select, + getSelectionTemplate, + getSummaryTemplate, + popperElementSymbol, + Select, }; /** @@ -187,7 +187,7 @@ const popperFilterElementSymbol = Symbol("popperFilterElement"); * @type {Symbol} */ const popperFilterContainerElementSymbol = Symbol( - "popperFilterContainerElement", + "popperFilterContainerElement", ); /** @@ -245,7 +245,7 @@ const cleanupOptionsListSymbol = Symbol("cleanupOptionsList"); * @type {symbol} */ const debounceOptionsMutationObserverSymbol = Symbol( - "debounceOptionsMutationObserver", + "debounceOptionsMutationObserver", ); /** @@ -313,468 +313,479 @@ const FILTER_POSITION_INLINE = "inline"; * @fires monster-selection-cleared */ class Select extends CustomControl { - /** - * - */ - constructor() { - super(); - initOptionObserver.call(this); - } - - /** - * This method is called by the `instanceof` operator. - * @return {Symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/form/select@@instance"); - } - - /** - * The current selection of the Select - * - * ``` - * e = document.querySelector('monster-select'); - * console.log(e.value) - * // ↦ 1 - * // ↦ ['1','2'] - * ``` - * - * @return {string} - */ - get value() { - return convertSelectionToValue.call(this, this.getOption("selection")); - } - - /** - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * Set selection - * - * ``` - * e = document.querySelector('monster-select'); - * e.value=1 - * ``` - * - * @property {string|array} value - * @throws {Error} unsupported type - * @fires monster-selected this event is fired when the selection is set - */ - set value(value) { - const result = convertValueToSelection.call(this, value); - - setSelection - .call(this, result.selection) - .then(() => {}) - .catch((e) => { - addErrorAttribute(this, e); - }); - } - - /** - * To set the options via the HTML tag, the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {string[]} toggleEventType Array of DOM event names (e.g. ["click","touch"]) to toggle the dropdown. - * @property {boolean} delegatesFocus Whether the element delegates focus to its internal control (e.g. the filter input). - * @property {Array<Object>} options Array of option objects {label,value,visibility?,data?} for static option list. - * @property {string|string[]} selection Initial selected value(s) as string, comma-separated string, or array of strings. - * @property {number} showMaxOptions Maximum visible options before the dropdown scrolls. - * @property {"radio"|"checkbox"} type Selection mode: "radio" for single, "checkbox" for multiple. - * @property {string} name Name of the hidden form field for form submission. - * @property {string|null} url URL to dynamically fetch options via HTTP when opening or filtering. - * @property {Object} lookup Configuration for lookup requests. - * @property {string} lookup.url URL template with ${filter} placeholder to fetch only selected entries on init when `url` is set and either `features.lazyLoad` or `filter.mode==="remote"`. - * @property {boolean} lookup.grouping Group lookup requests: true to fetch all selected values in one request, false to fetch each individually. - * @property {string} fetch.redirect Fetch redirect mode (e.g. "error"). - * @property {string} fetch.method HTTP method for fetching options (e.g. "GET"). - * @property {string} fetch.mode Fetch mode (e.g. "same-origin"). - * @property {string} fetch.credentials Credentials policy for fetch (e.g. "same-origin"). - * @property {Object.<string,string>} fetch.headers HTTP headers for fetch requests. - * @property {string} labels.cannot-be-loaded Message when options cannot be loaded. - * @property {string} labels.no-options-available Message when no static options are available. - * @property {string} labels.click-to-load-options Message prompting user to click to load options when `features.lazyLoad` is enabled. - * @property {string} labels.select-an-option Placeholder text when no selection is made. - * @property {string} labels.no-options Message when neither slots nor fetched options exist. - * @property {string} labels.no-options-found Message when filter yields no matching options. - * @property {string} labels.summary-text.zero Plural template for zero selected entries (e.g. "No entries were selected"). - * @property {string} labels.summary-text.one Plural template for one selected entry. - * @property {string} labels.summary-text.other Plural template for multiple selected entries. - * @property {boolean} features.clearAll Show a "clear all" badge to reset selection. - * @property {boolean} features.clear Show remove icon on individual selection badges. - * @property {boolean} features.lazyLoad Lazy-load options on first open (initial fetch on show and triggers `lookup.url` preload; automatically disabled if `filter.mode==="remote"`). - * @property {boolean} features.closeOnSelect Automatically close dropdown after selection. - * @property {boolean} features.emptyValueIfNoOptions Set value to empty when no options are available. - * @property {boolean} features.storeFetchedData Persist raw fetched data for later retrieval via `getLastFetchedData()`. - * @property {boolean} features.useStrictValueComparison Use strict (`===`) comparison when matching option values. - * @property {boolean} features.showRemoteInfo When the filter mode is set to "remote," display a badge indicating the possibility of additional remote options. - * @property {Object} remoteInfo Configuration for remote info badge. - * @property {string} remoteInfo.path Path to the remote info badge. - * @property {string} remoteInfo.url URL for total count of options when `filter.mode==="remote"` is set. - * @property {Object} placeholder Placeholder text for the control. - * @property {string} placeholder.filter Placeholder text for filter input. - * @property {string|null} filter.defaultValue Default filter value for remote requests; if unset or empty, disabled marker prevents request. - * @property {"options"|"remote"|"disabled"} filter.mode Client-side ("options"), server-side ("remote"; disables `features.lazyLoad`), or disabled filtering. - * @property {"inline"|"popper"} filter.position Position of filter input: inline within control or inside popper dropdown. - * @property {string} filter.marker.open Opening marker for embedding filter value in `filter.mode==="remote"` URLs. - * @property {string} filter.marker.close Closing marker for embedding filter value in URLs. - * @property {string|null} filter.defaultOptionsUrl URL for default options when `filter.mode==="remote"` is set and no filter value is provided. - * @property {string} templates.main HTML template string for rendering options and selection badges. - * @property {string} templateMapping.selected Template variant for selected items (e.g. badge vs summary view). - * @property {string} popper.placement Popper.js placement strategy for dropdown (e.g. "bottom"). - * @property {Array<string|Object>} popper.middleware Popper.js middleware or offset configurations. - * @property {string} mapping.selector Data path or selector to identify entries in imported data. - * @property {string} mapping.labelTemplate Template for option labels using placeholders like `${name}`. - * @property {string} mapping.valueTemplate Template for option values using placeholders like `${name}`. - * @property {Function} mapping.filter Optional callback to filter imported map entries before building `options[]`. - * @property {string} empty.defaultValueRadio Default radio-value when no selection exists. - * @property {Array} empty.defaultValueCheckbox Default checkbox-values array when no selection exists. - * @property {Array} empty.equivalents Values considered empty (e.g. `undefined`, `null`, `""`, `NaN`) and normalized to defaults. - * @property {Function} formatter.selection Callback `(value)=>string` to format the display label of each selected value. - * @property {Object} classes CSS classes for styling. - * @property {string} classes.badge CSS class for the selection badge. - * @property {string} classes.statusOrRemoveBadge CSS class for the status or remove badge. - * @property {string} classes.remoteInfo CSS class for the remote info badge. - * @property {string} classes.noOptions CSS class for the no options available message. - * @property {number|null} total Total number of options available. - */ - get defaults() { - return Object.assign( - {}, - super.defaults, - { - toggleEventType: ["click", "touch"], - delegatesFocus: false, - options: [], - selection: [], - showMaxOptions: 10, - type: "radio", - name: new ID("s").toString(), - features: { - clearAll: true, - clear: true, - lazyLoad: false, - closeOnSelect: false, - emptyValueIfNoOptions: false, - storeFetchedData: false, - useStrictValueComparison: false, - showRemoteInfo: true, - }, - placeholder: { - filter: "", - }, - url: null, - - remoteInfo: { - path: null, - url: null, - }, - - lookup: { - url: null, - grouping: false, - }, - - labels: getTranslations(), - messages: { - control: null, - selected: null, - emptyOptions: null, - total: null, - }, - fetch: { - redirect: "error", - method: "GET", - mode: "same-origin", - credentials: "same-origin", - headers: { - accept: "application/json", - }, - }, - filter: { - defaultValue: null, - mode: FILTER_MODE_DISABLED, - position: FILTER_POSITION_INLINE, - marker: { - open: "{", - close: "}", - }, - defaultOptionsUrl: null, - }, - classes: { - badge: "monster-badge-primary", - statusOrRemoveBadge: "empty", - remoteInfo: "monster-margin-start-4 monster-margin-top-4", - noOptions: "monster-margin-top-4 monster-margin-start-4", - }, - mapping: { - selector: "*", - labelTemplate: "", - valueTemplate: "", - filter: null, - total: null, - }, - empty: { - defaultValueRadio: "", - defaultValueCheckbox: [], - equivalents: [undefined, null, "", NaN], - }, - formatter: { - selection: buildSelectionLabel, - }, - templates: { - main: getTemplate(), - }, - templateMapping: { - /** with the attribute `data-monster-selected-template` the template for the selected options can be defined. */ - selected: getSelectionTemplate(), - }, - - total: null, - - popper: { - placement: "bottom", - middleware: ["flip", "offset:1"], - }, - }, - initOptionsFromArguments.call(this), - ); - } - - /** - * @return {Select} - */ - [assembleMethodSymbol]() { - const self = this; - super[assembleMethodSymbol](); - - initControlReferences.call(self); - initEventHandler.call(self); - - let lazyLoadFlag = self.getOption("features.lazyLoad", false); - const remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE; - - initTotal.call(self); - - if (getFilterMode.call(this) === FILTER_MODE_REMOTE) { - self.setOption("features.lazyLoad", false); - lazyLoadFlag = false; - } - - if (self.hasAttribute("value")) { - new Processing(10, () => { - const oldValue = self.value; - const newValue = self.getAttribute("value"); - if (oldValue !== newValue) { - self.value = newValue; - } - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - } - - if (self.getOption("url") !== null) { - if (lazyLoadFlag || remoteFilterFlag) { - lookupSelection.call(self); - } else { - self - .fetch() - .then(() => {}) - .catch((e) => { - addErrorAttribute(self, e); - }); - } - } - - setTimeout(() => { - let lastValue = self.value; - self[internalSymbol].attachObserver( - new Observer(function () { - if (isObject(this) && this instanceof ProxyObserver) { - const n = this.getSubject()?.options?.value; - - if (lastValue !== n && n !== undefined) { - lastValue = n; - setSelection - .call(self, n) - .then(() => {}) - .catch((e) => { - addErrorAttribute(self, e); - }); - } - } - }), - ); - - areOptionsAvailableAndInit.call(self); - }, 0); - - return this; - } - - /** - * - * @return {*} - * @throws {Error} storeFetchedData is not enabled - * @since 3.66.0 - */ - getLastFetchedData() { - if (this.getOption("features.storeFetchedData") === false) { - throw new Error("storeFetchedData is not enabled"); - } - - return this?.[lastFetchedDataSymbol]; - } - - /** - * The Button.click() method simulates a click on the internal button element. - * - * @since 3.27.0 - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} - */ - click() { - if (this.getOption("disabled") === true) { - return; - } - - toggle.call(this); - } - - /** - * The Button.focus() method sets focus on the internal button element. - * - * @since 3.27.0 - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} - */ - focus(options) { - if (this.getOption("disabled") === true) { - return; - } - - new Processing(() => { - gatherState.call(this); - focusFilter.call(this, options); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - } - - /** - * The Button.blur() method removes focus from the internal button element. - * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur - */ - blur() { - new Processing(() => { - gatherState.call(this); - blurFilter.call(this); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - } - - /** - * If no url is specified, the options are taken from the Component itself. - * - * @param {string|URL} url URL to fetch the options - * @return {Promise} - */ - fetch(url) { - return fetchIt.call(this, url); - } - - /** - * @return {void} - */ - connectedCallback() { - super.connectedCallback(); - const document = getDocument(); - - for (const [, type] of Object.entries(["click", "touch"])) { - // close on outside ui-events - document.addEventListener(type, this[closeEventHandler]); - } - - parseSlotsToOptions.call(this); - attachResizeObserver.call(this); - updatePopper.call(this); - - new Processing(() => { - gatherState.call(this); - focusFilter.call(this); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - } - - /** - * @return {void} - */ - disconnectedCallback() { - super.disconnectedCallback(); - const document = getDocument(); - - // close on outside ui-events - for (const [, type] of Object.entries(["click", "touch"])) { - document.removeEventListener(type, this[closeEventHandler]); - } - - disconnectResizeObserver.call(this); - } - - /** - * Import Select Options from dataset - * Not to be confused with the control defaults/options - * - * @param {array|object|Map|Set} data - * @return {Select} - * @throws {Error} map is not iterable - * @throws {Error} missing label configuration - * @fires monster-options-set this event is fired when the options are set - */ - importOptions(data) { - this[cleanupOptionsListSymbol] = true; - return importOptionsIntern.call(this, data); - } - - /** - * @private - * @return {Select} - */ - calcAndSetOptionsDimension() { - calcAndSetOptionsDimension.call(this); - return this; - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-select"; - } - - /** - * - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [SelectStyleSheet]; - } + /** + * + */ + constructor() { + super(); + initOptionObserver.call(this); + } + + /** + * This method is called by the `instanceof` operator. + * @return {Symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/form/select@@instance"); + } + + /** + * The current selection of the Select + * + * ``` + * e = document.querySelector('monster-select'); + * console.log(e.value) + * // ↦ 1 + * // ↦ ['1','2'] + * ``` + * + * @return {string} + */ + get value() { + return convertSelectionToValue.call(this, this.getOption("selection")); + } + + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} + * @return {boolean} + */ + static get formAssociated() { + return true; + } + + /** + * Set selection + * + * ``` + * e = document.querySelector('monster-select'); + * e.value=1 + * ``` + * + * @property {string|array} value + * @throws {Error} unsupported type + * @fires monster-selected this event is fired when the selection is set + */ + set value(value) { + const result = convertValueToSelection.call(this, value); + + setSelection + .call(this, result.selection) + .then(() => { + }) + .catch((e) => { + addErrorAttribute(this, e); + }); + } + + /** + * To set the options via the HTML tag, the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {string[]} toggleEventType Array of DOM event names (e.g. ["click","touch"]) to toggle the dropdown. + * @property {boolean} delegatesFocus Whether the element delegates focus to its internal control (e.g. the filter input). + * @property {Array<Object>} options Array of option objects {label,value,visibility?,data?} for static option list. + * @property {string|string[]} selection Initial selected value(s) as string, comma-separated string, or array of strings. + * @property {number} showMaxOptions Maximum visible options before the dropdown scrolls. + * @property {"radio"|"checkbox"} type Selection mode: "radio" for single, "checkbox" for multiple. + * @property {string} name Name of the hidden form field for form submission. + * @property {string|null} url URL to dynamically fetch options via HTTP when opening or filtering. + * @property {Object} lookup Configuration for lookup requests. + * @property {string} lookup.url URL template with ${filter} placeholder to fetch only selected entries on init when `url` is set and either `features.lazyLoad` or `filter.mode==="remote"`. + * @property {boolean} lookup.grouping Group lookup requests: true to fetch all selected values in one request, false to fetch each individually. + * @property {string} fetch.redirect Fetch redirect mode (e.g. "error"). + * @property {string} fetch.method HTTP method for fetching options (e.g. "GET"). + * @property {string} fetch.mode Fetch mode (e.g. "same-origin"). + * @property {string} fetch.credentials Credentials policy for fetch (e.g. "same-origin"). + * @property {Object.<string,string>} fetch.headers HTTP headers for fetch requests. + * @property {string} labels.cannot-be-loaded Message when options cannot be loaded. + * @property {string} labels.no-options-available Message when no static options are available. + * @property {string} labels.click-to-load-options Message prompting user to click to load options when `features.lazyLoad` is enabled. + * @property {string} labels.select-an-option Placeholder text when no selection is made. + * @property {string} labels.no-options Message when neither slots nor fetched options exist. + * @property {string} labels.no-options-found Message when filter yields no matching options. + * @property {string} labels.summary-text.zero Plural template for zero selected entries (e.g. "No entries were selected"). + * @property {string} labels.summary-text.one Plural template for one selected entry. + * @property {string} labels.summary-text.other Plural template for multiple selected entries. + * @property {boolean} features.clearAll Show a "clear all" badge to reset selection. + * @property {boolean} features.clear Show remove icon on individual selection badges. + * @property {boolean} features.lazyLoad Lazy-load options on first open (initial fetch on show and triggers `lookup.url` preload; automatically disabled if `filter.mode==="remote"`). + * @property {boolean} features.closeOnSelect Automatically close dropdown after selection. + * @property {boolean} features.emptyValueIfNoOptions Set value to empty when no options are available. + * @property {boolean} features.storeFetchedData Persist raw fetched data for later retrieval via `getLastFetchedData()`. + * @property {boolean} features.useStrictValueComparison Use strict (`===`) comparison when matching option values. + * @property {boolean} features.showRemoteInfo When the filter mode is set to "remote," display a badge indicating the possibility of additional remote options. + * @property {Object} remoteInfo Configuration for remote info badge. + * @property {string} remoteInfo.path Path to the remote info badge. + * @property {string} remoteInfo.url URL for total count of options when `filter.mode==="remote"` is set. + * @property {Object} placeholder Placeholder text for the control. + * @property {string} placeholder.filter Placeholder text for filter input. + * @property {string|null} filter.defaultValue Default filter value for remote requests; if unset or empty, disabled marker prevents request. + * @property {"options"|"remote"|"disabled"} filter.mode Client-side ("options"), server-side ("remote"; disables `features.lazyLoad`), or disabled filtering. + * @property {"inline"|"popper"} filter.position Position of filter input: inline within control or inside popper dropdown. + * @property {string} filter.marker.open Opening marker for embedding filter value in `filter.mode==="remote"` URLs. + * @property {string} filter.marker.close Closing marker for embedding filter value in URLs. + * @property {string|null} filter.defaultOptionsUrl URL for default options when `filter.mode==="remote"` is set and no filter value is provided. + * @property {string} templates.main HTML template string for rendering options and selection badges. + * @property {string} templateMapping.selected Template variant for selected items (e.g. badge vs summary view). + * @property {string} popper.placement Popper.js placement strategy for dropdown (e.g. "bottom"). + * @property {Array<string|Object>} popper.middleware Popper.js middleware or offset configurations. + * @property {string} mapping.selector Data path or selector to identify entries in imported data. + * @property {string} mapping.labelTemplate Template for option labels using placeholders like `${name}`. + * @property {string} mapping.valueTemplate Template for option values using placeholders like `${name}`. + * @property {Function} mapping.filter Optional callback to filter imported map entries before building `options[]`. + * @property {string} empty.defaultValueRadio Default radio-value when no selection exists. + * @property {Array} empty.defaultValueCheckbox Default checkbox-values array when no selection exists. + * @property {Array} empty.equivalents Values considered empty (e.g. `undefined`, `null`, `""`, `NaN`) and normalized to defaults. + * @property {Function} formatter.selection Callback `(value)=>string` to format the display label of each selected value. + * @property {Object} classes CSS classes for styling. + * @property {string} classes.badge CSS class for the selection badge. + * @property {string} classes.statusOrRemoveBadge CSS class for the status or remove badge. + * @property {string} classes.remoteInfo CSS class for the remote info badge. + * @property {string} classes.noOptions CSS class for the no options available message. + * @property {number|null} total Total number of options available. + */ + get defaults() { + return Object.assign( + {}, + super.defaults, + { + toggleEventType: ["click", "touch"], + delegatesFocus: false, + options: [], + selection: [], + showMaxOptions: 10, + type: "radio", + name: new ID("s").toString(), + features: { + clearAll: true, + clear: true, + lazyLoad: false, + closeOnSelect: false, + emptyValueIfNoOptions: false, + storeFetchedData: false, + useStrictValueComparison: false, + showRemoteInfo: true, + }, + placeholder: { + filter: "", + }, + url: null, + + remoteInfo: { + path: null, + url: null, + }, + + lookup: { + url: null, + grouping: false, + }, + + labels: getTranslations(), + messages: { + control: null, + selected: null, + emptyOptions: null, + total: null, + }, + fetch: { + redirect: "error", + method: "GET", + mode: "same-origin", + credentials: "same-origin", + headers: { + accept: "application/json", + }, + }, + filter: { + defaultValue: null, + mode: FILTER_MODE_DISABLED, + position: FILTER_POSITION_INLINE, + marker: { + open: "{", + close: "}", + }, + defaultOptionsUrl: null, + }, + classes: { + badge: "monster-badge-primary", + statusOrRemoveBadge: "empty", + remoteInfo: "monster-margin-start-4 monster-margin-top-4", + noOptions: "monster-margin-top-4 monster-margin-start-4", + }, + mapping: { + selector: "*", + labelTemplate: "", + valueTemplate: "", + filter: null, + total: null, + }, + empty: { + defaultValueRadio: "", + defaultValueCheckbox: [], + equivalents: [undefined, null, "", NaN], + }, + formatter: { + selection: buildSelectionLabel, + }, + templates: { + main: getTemplate(), + }, + templateMapping: { + /** with the attribute `data-monster-selected-template` the template for the selected options can be defined. */ + selected: getSelectionTemplate(), + }, + + total: null, + + popper: { + placement: "bottom", + middleware: ["flip", "offset:1"], + }, + }, + initOptionsFromArguments.call(this), + ); + } + + /** + * @return {Select} + */ + [assembleMethodSymbol]() { + const self = this; + super[assembleMethodSymbol](); + + initControlReferences.call(self); + initEventHandler.call(self); + + let lazyLoadFlag = self.getOption("features.lazyLoad", false); + const remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE; + + initTotal.call(self); + + if (getFilterMode.call(this) === FILTER_MODE_REMOTE) { + self.setOption("features.lazyLoad", false); + lazyLoadFlag = false; + } + + if (self.hasAttribute("value")) { + new Processing(10, () => { + const oldValue = self.value; + const newValue = self.getAttribute("value"); + if (oldValue !== newValue) { + self.value = newValue; + } + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + } + + if (self.getOption("url") !== null) { + if (lazyLoadFlag || remoteFilterFlag) { + lookupSelection.call(self); + } else { + self + .fetch() + .then(() => { + }) + .catch((e) => { + addErrorAttribute(self, e); + }); + } + } + + setTimeout(() => { + let lastValue = self.value; + self[internalSymbol].attachObserver( + new Observer(function () { + if (isObject(this) && this instanceof ProxyObserver) { + const n = this.getSubject()?.options?.value; + + if (lastValue !== n && n !== undefined) { + lastValue = n; + setSelection + .call(self, n) + .then(() => { + }) + .catch((e) => { + addErrorAttribute(self, e); + }); + } + } + }), + ); + + areOptionsAvailableAndInit.call(self); + }, 0); + + return this; + } + + /** + * + * @return {*} + * @throws {Error} storeFetchedData is not enabled + * @since 3.66.0 + */ + getLastFetchedData() { + if (this.getOption("features.storeFetchedData") === false) { + throw new Error("storeFetchedData is not enabled"); + } + + return this?.[lastFetchedDataSymbol]; + } + + /** + * The Button.click() method simulates a click on the internal button element. + * + * @since 3.27.0 + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} + */ + click() { + if (this.getOption("disabled") === true) { + return; + } + + toggle.call(this); + } + + /** + * The Button.focus() method sets focus on the internal button element. + * + * @since 3.27.0 + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} + */ + focus(options) { + if (this.getOption("disabled") === true) { + return; + } + + new Processing(() => { + gatherState.call(this); + focusFilter.call(this, options); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + } + + /** + * The Button.blur() method removes focus from the internal button element. + * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur + */ + blur() { + new Processing(() => { + gatherState.call(this); + blurFilter.call(this); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + } + + /** + * If no url is specified, the options are taken from the Component itself. + * + * @param {string|URL} url URL to fetch the options + * @return {Promise} + */ + fetch(url) { + try { + const result = fetchIt.call(this, url); + if (result instanceof Promise) { + return result; + } + } catch (e) { + addErrorAttribute(this, e); + return Promise.reject(e); + } + } + + /** + * @return {void} + */ + connectedCallback() { + super.connectedCallback(); + const document = getDocument(); + + for (const [, type] of Object.entries(["click", "touch"])) { + // close on outside ui-events + document.addEventListener(type, this[closeEventHandler]); + } + + parseSlotsToOptions.call(this); + attachResizeObserver.call(this); + updatePopper.call(this); + + new Processing(() => { + gatherState.call(this); + focusFilter.call(this); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + } + + /** + * @return {void} + */ + disconnectedCallback() { + super.disconnectedCallback(); + const document = getDocument(); + + // close on outside ui-events + for (const [, type] of Object.entries(["click", "touch"])) { + document.removeEventListener(type, this[closeEventHandler]); + } + + disconnectResizeObserver.call(this); + } + + /** + * Import Select Options from dataset + * Not to be confused with the control defaults/options + * + * @param {array|object|Map|Set} data + * @return {Select} + * @throws {Error} map is not iterable + * @throws {Error} missing label configuration + * @fires monster-options-set this event is fired when the options are set + */ + importOptions(data) { + this[cleanupOptionsListSymbol] = true; + return importOptionsIntern.call(this, data); + } + + /** + * @private + * @return {Select} + */ + calcAndSetOptionsDimension() { + calcAndSetOptionsDimension.call(this); + return this; + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-select"; + } + + /** + * + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [SelectStyleSheet]; + } } /** @@ -783,104 +794,105 @@ class Select extends CustomControl { * @returns {any} */ function importOptionsIntern(data) { - const self = this; - const mappingOptions = this.getOption("mapping", {}); - const selector = mappingOptions?.["selector"]; - const labelTemplate = mappingOptions?.["labelTemplate"]; - const valueTemplate = mappingOptions?.["valueTemplate"]; - let filter = mappingOptions?.["filter"]; - - let flag = false; - if (labelTemplate === "") { - addErrorAttribute(this, "empty label template"); - flag = true; - } - - if (valueTemplate === "") { - addErrorAttribute(this, "empty value template"); - flag = true; - } - - if (flag === true) { - throw new Error("missing label configuration"); - } - - if (isString(filter)) { - if (0 === filter.indexOf("run:")) { - const code = filter.replace("run:", ""); - filter = (m, v, k) => { - const fkt = new Function("m", "v", "k", "control", code); - return fkt(m, v, k, self); - }; - } else if (0 === filter.indexOf("call:")) { - const parts = filter.split(":"); - parts.shift(); // remove prefix - const fkt = parts.shift(); - - switch (fkt) { - case "filterValueOfAttribute": - const attribute = parts.shift(); - const attrValue = self.getAttribute(attribute); - - filter = (m, v, k) => { - const mm = buildValue(m, valueTemplate); - return mm != attrValue; // no type check, no !== - }; - break; - - default: - addErrorAttribute(this, new Error(`Unknown filter function ${fkt}`)); - } - } - } - - const map = buildMap(data, selector, labelTemplate, valueTemplate, filter); - - let options = []; - if (this[cleanupOptionsListSymbol] !== true) { - options = this.getOption("options", []); - } - - if (!isIterable(map)) { - throw new Error("map is not iterable"); - } - - const visibility = "visible"; - - map.forEach((label, value) => { - for (const option of options) { - if (option.value === value) { - option.label = label; - option.visibility = visibility; - option.data = map.get(value); - return; - } - } - - options.push({ - value, - label, - visibility, - data: map.get(value), - }); - }); - - this.setOption("options", options); - - fireCustomEvent(this, "monster-options-set", { - options, - }); - - setTimeout(() => { - setSelection - .call(this, this.getOption("selection")) - .then(() => {}) - .catch((e) => { - addErrorAttribute(this, e); - }); - }, 10); - - return this; + const self = this; + const mappingOptions = this.getOption("mapping", {}); + const selector = mappingOptions?.["selector"]; + const labelTemplate = mappingOptions?.["labelTemplate"]; + const valueTemplate = mappingOptions?.["valueTemplate"]; + let filter = mappingOptions?.["filter"]; + + let flag = false; + if (labelTemplate === "") { + addErrorAttribute(this, "empty label template"); + flag = true; + } + + if (valueTemplate === "") { + addErrorAttribute(this, "empty value template"); + flag = true; + } + + if (flag === true) { + throw new Error("missing label configuration"); + } + + if (isString(filter)) { + if (0 === filter.indexOf("run:")) { + const code = filter.replace("run:", ""); + filter = (m, v, k) => { + const fkt = new Function("m", "v", "k", "control", code); + return fkt(m, v, k, self); + }; + } else if (0 === filter.indexOf("call:")) { + const parts = filter.split(":"); + parts.shift(); // remove prefix + const fkt = parts.shift(); + + switch (fkt) { + case "filterValueOfAttribute": + const attribute = parts.shift(); + const attrValue = self.getAttribute(attribute); + + filter = (m, v, k) => { + const mm = buildValue(m, valueTemplate); + return mm != attrValue; // no type check, no !== + }; + break; + + default: + addErrorAttribute(this, new Error(`Unknown filter function ${fkt}`)); + } + } + } + + const map = buildMap(data, selector, labelTemplate, valueTemplate, filter); + + let options = []; + if (this[cleanupOptionsListSymbol] !== true) { + options = this.getOption("options", []); + } + + if (!isIterable(map)) { + throw new Error("map is not iterable"); + } + + const visibility = "visible"; + + map.forEach((label, value) => { + for (const option of options) { + if (option.value === value) { + option.label = label; + option.visibility = visibility; + option.data = map.get(value); + return; + } + } + + options.push({ + value, + label, + visibility, + data: map.get(value), + }); + }); + + this.setOption("options", options); + + fireCustomEvent(this, "monster-options-set", { + options, + }); + + setTimeout(() => { + setSelection + .call(this, this.getOption("selection")) + .then(() => { + }) + .catch((e) => { + addErrorAttribute(this, e); + }); + }, 10); + + return this; } /** @@ -888,528 +900,528 @@ function importOptionsIntern(data) { * @returns {object} */ function getTranslations() { - const locale = getLocaleOfDocument(); - switch (locale.language) { - case "de": - return { - "cannot-be-loaded": "Kann nicht geladen werden", - "no-options-available": "Keine Auswahl verfügbar.", - "click-to-load-options": "Klicken, um Auswahl zu laden.", - "select-an-option": "Bitte Auswahl treffen", - "summary-text": { - zero: "Keine Auswahl getroffen", - one: '<span class="monster-badge-primary-pill">1</span> Auswahl getroffen', - other: - '<span class="monster-badge-primary-pill">${count}</span> Auswahlen getroffen', - }, - "no-options": - '<span class="monster-badge-error-pill">Leider gibt es keine Auswahlmöglichkeiten in der Liste.</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Keine Auswahlmöglichkeiten verfügbar. Bitte ändern Sie den Filter.</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Es sind keine weiteren Auswahlmöglichkeiten verfügbar.</span>', - one: '<span class="monster-badge-primary-pill">Es ist 1 weitere Auswahlmöglichkeit verfügbar.</span>', - other: - '<span class="monster-badge-primary-pill">Es sind ${count} weitere Auswahlmöglichkeiten verfügbar.</span>', - }, - }; - - case "es": - return { - "cannot-be-loaded": "No se puede cargar", - "no-options-available": "No hay opciones disponibles.", - "click-to-load-options": "Haga clic para cargar opciones.", - "select-an-option": "Seleccione una opción", - "summary-text": { - zero: "No se seleccionaron entradas", - one: '<span class="monster-badge-primary-pill">1</span> entrada seleccionada', - other: - '<span class="monster-badge-primary-pill">${count}</span> entradas seleccionadas', - }, - "no-options": - '<span class="monster-badge-error-pill">Desafortunadamente, no hay opciones disponibles en la lista.</span>', - "no-options-found": - '<span class="monster-badge-error-pill">No hay opciones disponibles en la lista. Por favor, modifique el filtro.</span>', - total: { - zero: '<span class="monster-badge-primary-pill">No hay entradas adicionales disponibles.</span>', - one: '<span class="monster-badge-primary-pill">1 entrada adicional está disponible.</span>', - other: - '<span class="monster-badge-primary-pill">${count} entradas adicionales están disponibles.</span>', - }, - }; - - case "zh": - return { - "cannot-be-loaded": "无法加载", - "no-options-available": "没有可用选项。", - "click-to-load-options": "点击以加载选项。", - "select-an-option": "选择一个选项", - "summary-text": { - zero: "未选择任何条目", - one: '<span class="monster-badge-primary-pill">1</span> 个条目已选择', - other: - '<span class="monster-badge-primary-pill">${count}</span> 个条目已选择', - }, - "no-options": - '<span class="monster-badge-error-pill">很抱歉,列表中没有可用选项。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">列表中没有可用选项。请修改筛选条件。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">没有更多条目可用。</span>', - one: '<span class="monster-badge-primary-pill">还有 1 个可用条目。</span>', - other: - '<span class="monster-badge-primary-pill">还有 ${count} 个可用条目。</span>', - }, - }; - - case "hi": - return { - "cannot-be-loaded": "लोड नहीं किया जा सकता", - "no-options-available": "कोई विकल्प उपलब्ध नहीं है।", - "click-to-load-options": "विकल्प लोड करने के लिए क्लिक करें।", - "select-an-option": "एक विकल्प चुनें", - "summary-text": { - zero: "कोई प्रविष्टि चयनित नहीं", - one: '<span class="monster-badge-primary-pill">1</span> प्रविष्टि चयनित', - other: - '<span class="monster-badge-primary-pill">${count}</span> प्रविष्टियाँ चयनित', - }, - "no-options": - '<span class="monster-badge-error-pill">क्षमा करें, सूची में कोई विकल्प उपलब्ध नहीं है।</span>', - "no-options-found": - '<span class="monster-badge-error-pill">सूची में कोई विकल्प उपलब्ध नहीं है। कृपया फ़िल्टर बदलें।</span>', - total: { - zero: '<span class="monster-badge-primary-pill">कोई अतिरिक्त प्रविष्टि उपलब्ध नहीं है।</span>', - one: '<span class="monster-badge-primary-pill">1 अतिरिक्त प्रविष्टि उपलब्ध है।</span>', - other: - '<span class="monster-badge-primary-pill">${count} अतिरिक्त प्रविष्टियाँ उपलब्ध हैं।</span>', - }, - }; - - case "bn": - return { - "cannot-be-loaded": "লোড করা যায়নি", - "no-options-available": "কোন বিকল্প উপলব্ধ নেই।", - "click-to-load-options": "বিকল্প লোড করতে ক্লিক করুন।", - "select-an-option": "একটি বিকল্প নির্বাচন করুন", - "summary-text": { - zero: "কোন এন্ট্রি নির্বাচিত হয়নি", - one: '<span class="monster-badge-primary-pill">1</span> এন্ট্রি নির্বাচিত', - other: - '<span class="monster-badge-primary-pill">${count}</span> এন্ট্রি নির্বাচিত', - }, - "no-options": - '<span class="monster-badge-error-pill">দুঃখিত, তালিকায় কোন বিকল্প পাওয়া যায়নি।</span>', - "no-options-found": - '<span class="monster-badge-error-pill">তালিকায় কোন বিকল্প পাওয়া যায়নি। দয়া করে ফিল্টার পরিবর্তন করুন।</span>', - total: { - zero: '<span class="monster-badge-primary-pill">আর কোনো এন্ট্রি উপলব্ধ নেই।</span>', - one: '<span class="monster-badge-primary-pill">1 অতিরিক্ত এন্ট্রি উপলব্ধ।</span>', - other: - '<span class="monster-badge-primary-pill">${count} অতিরিক্ত এন্ট্রি উপলব্ধ।</span>', - }, - }; - - case "pt": - return { - "cannot-be-loaded": "Não é possível carregar", - "no-options-available": "Nenhuma opção disponível.", - "click-to-load-options": "Clique para carregar opções.", - "select-an-option": "Selecione uma opção", - "summary-text": { - zero: "Nenhuma entrada selecionada", - one: '<span class="monster-badge-primary-pill">1</span> entrada selecionada', - other: - '<span class="monster-badge-primary-pill">${count}</span> entradas selecionadas', - }, - "no-options": - '<span class="monster-badge-error-pill">Infelizmente, não há opções disponíveis na lista.</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Nenhuma opção disponível na lista. Considere modificar o filtro.</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Não há entradas adicionais disponíveis.</span>', - one: '<span class="monster-badge-primary-pill">1 entrada adicional está disponível.</span>', - other: - '<span class="monster-badge-primary-pill">${count} entradas adicionais estão disponíveis.</span>', - }, - }; - - case "ru": - return { - "cannot-be-loaded": "Не удалось загрузить", - "no-options-available": "Нет доступных вариантов.", - "click-to-load-options": "Нажмите, чтобы загрузить варианты.", - "select-an-option": "Выберите вариант", - "summary-text": { - zero: "Нет выбранных записей", - one: '<span class="monster-badge-primary-pill">1</span> запись выбрана', - other: - '<span class="monster-badge-primary-pill">${count}</span> записей выбрано', - }, - "no-options": - '<span class="monster-badge-error-pill">К сожалению, в списке нет доступных вариантов.</span>', - "no-options-found": - '<span class="monster-badge-error-pill">В списке нет доступных вариантов. Пожалуйста, измените фильтр.</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Дополнительных записей нет.</span>', - one: '<span class="monster-badge-primary-pill">Доступна 1 дополнительная запись.</span>', - other: - '<span class="monster-badge-primary-pill">${count} дополнительных записей доступны.</span>', - }, - }; - - case "ja": - return { - "cannot-be-loaded": "読み込めません", - "no-options-available": "利用可能なオプションがありません。", - "click-to-load-options": "クリックしてオプションを読み込む。", - "select-an-option": "オプションを選択", - "summary-text": { - zero: "選択された項目はありません", - one: '<span class="monster-badge-primary-pill">1</span> 件選択されました', - other: - '<span class="monster-badge-primary-pill">${count}</span> 件選択されました', - }, - "no-options": - '<span class="monster-badge-error-pill">申し訳ありませんが、リストに利用可能なオプションがありません。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">リストに利用可能なオプションがありません。フィルターを変更してください。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">追加の項目はありません。</span>', - one: '<span class="monster-badge-primary-pill">1 件の追加項目があります。</span>', - other: - '<span class="monster-badge-primary-pill">${count} 件の追加項目があります。</span>', - }, - }; - - case "pa": - return { - "cannot-be-loaded": "ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ", - "no-options-available": "ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ।", - "click-to-load-options": "ਚੋਣਾਂ ਲੋਡ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ।", - "select-an-option": "ਇੱਕ ਚੋਣ ਚੁਣੋ", - "summary-text": { - zero: "ਕੋਈ ਐਂਟਰੀ ਚੁਣੀ ਨਹੀਂ ਗਈ", - one: '<span class="monster-badge-primary-pill">1</span> ਐਂਟਰੀ ਚੁਣੀ ਗਈ', - other: - '<span class="monster-badge-primary-pill">${count}</span> ਐਂਟਰੀਆਂ ਚੁਣੀਆਂ ਗਈਆਂ', - }, - "no-options": - '<span class="monster-badge-error-pill">ਮਾਫ ਕਰਨਾ, ਸੂਚੀ ਵਿੱਚ ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।</span>', - "no-options-found": - '<span class="monster-badge-error-pill">ਸੂਚੀ ਵਿੱਚ ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਫਿਲਟਰ ਬਦਲੋ।</span>', - total: { - zero: '<span class="monster-badge-primary-pill">ਕੋਈ ਹੋਰ ਐਂਟਰੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ.</span>', - one: '<span class="monster-badge-primary-pill">1 ਵਾਧੂ ਐਂਟਰੀ ਉਪਲਬਧ ਹੈ.</span>', - other: - '<span class="monster-badge-primary-pill">${count} ਵਾਧੂ ਐਂਟਰੀਆਂ ਉਪਲਬਧ ਹਨ.</span>', - }, - }; - - case "mr": - return { - "cannot-be-loaded": "लोड केले जाऊ शकत नाही", - "no-options-available": "कोणतीही पर्याय उपलब्ध नाहीत。", - "click-to-load-options": "पर्याय लोड करण्यासाठी क्लिक करा。", - "select-an-option": "एक पर्याय निवडा", - "summary-text": { - zero: "कोणीही नोंद निवडलेली नाही", - one: '<span class="monster-badge-primary-pill">1</span> नोंद निवडली', - other: - '<span class="monster-badge-primary-pill">${count}</span> नोंदी निवडल्या', - }, - "no-options": - '<span class="monster-badge-error-pill">क्षमस्व, यादीमध्ये कोणतीही पर्याय उपलब्ध नाहीत。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">यादीमध्ये कोणतेही पर्याय उपलब्ध नाहीत। कृपया फिल्टर बदला。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">आणखी कोणतीही नोंद उपलब्ध नाही。</span>', - one: '<span class="monster-badge-primary-pill">1 अतिरिक्त नोंद उपलब्ध आहे。</span>', - other: - '<span class="monster-badge-primary-pill">${count} अतिरिक्त नोंदी उपलब्ध आहेत。</span>', - }, - }; - - case "it": - return { - "cannot-be-loaded": "Non può essere caricato", - "no-options-available": "Nessuna opzione disponibile。", - "click-to-load-options": "Clicca per caricare le opzioni。", - "select-an-option": "Seleziona un'opzione", - "summary-text": { - zero: "Nessuna voce selezionata", - one: '<span class="monster-badge-primary-pill">1</span> voce selezionata', - other: - '<span class="monster-badge-primary-pill">${count}</span> voci selezionate', - }, - "no-options": - '<span class="monster-badge-error-pill">Purtroppo, non ci sono opzioni disponibili nella lista。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Nessuna opzione disponibile nella lista。Si prega di modificare il filtro。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Non ci sono altre voci disponibili。</span>', - one: '<span class="monster-badge-primary-pill">C\'è 1 voce aggiuntiva disponibile。</span>', - other: - '<span class="monster-badge-primary-pill">Ci sono ${count} voci aggiuntive disponibili。</span>', - }, - }; - - case "nl": - return { - "cannot-be-loaded": "Kan niet worden geladen", - "no-options-available": "Geen opties beschikbaar。", - "click-to-load-options": "Klik om opties te laden。", - "select-an-option": "Selecteer een optie", - "summary-text": { - zero: "Er zijn geen items geselecteerd", - one: '<span class="monster-badge-primary-pill">1</span> item geselecteerd', - other: - '<span class="monster-badge-primary-pill">${count}</span> items geselecteerd', - }, - "no-options": - '<span class="monster-badge-error-pill">Helaas zijn er geen opties beschikbaar in de lijst。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Geen opties beschikbaar in de lijst。Overweeg het filter aan te passen。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Er zijn geen extra items beschikbaar。</span>', - one: '<span class="monster-badge-primary-pill">1 extra item is beschikbaar。</span>', - other: - '<span class="monster-badge-primary-pill">${count} extra items zijn beschikbaar。</span>', - }, - }; - - case "sv": - return { - "cannot-be-loaded": "Kan inte laddas", - "no-options-available": "Inga alternativ tillgängliga。", - "click-to-load-options": "Klicka för att ladda alternativ。", - "select-an-option": "Välj ett alternativ", - "summary-text": { - zero: "Inga poster valdes", - one: '<span class="monster-badge-primary-pill">1</span> post valdes', - other: - '<span class="monster-badge-primary-pill">${count}</span> poster valdes', - }, - "no-options": - '<span class="monster-badge-error-pill">Tyvärr finns det inga alternativ tillgängliga i listan。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Inga alternativ finns tillgängliga i listan。Överväg att modifiera filtret。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Det finns inga fler poster tillgängliga。</span>', - one: '<span class="monster-badge-primary-pill">Det finns 1 ytterligare post tillgänglig。</span>', - other: - '<span class="monster-badge-primary-pill">Det finns ${count} ytterligare poster tillgängliga。</span>', - }, - }; - - case "pl": - return { - "cannot-be-loaded": "Nie można załadować", - "no-options-available": "Brak dostępnych opcji。", - "click-to-load-options": "Kliknij, aby załadować opcje。", - "select-an-option": "Wybierz opcję", - "summary-text": { - zero: "Nie wybrano żadnych wpisów", - one: '<span class="monster-badge-primary-pill">1</span> wpis został wybrany', - other: - '<span class="monster-badge-primary-pill">${count}</span> wpisy zostały wybrane', - }, - "no-options": - '<span class="monster-badge-error-pill">Niestety, nie ma dostępnych opcji na liście。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Brak dostępnych opcji na liście。Rozważ zmianę filtra。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Nie ma więcej dostępnych wpisów。</span>', - one: '<span class="monster-badge-primary-pill">Jest 1 dodatkowy wpis dostępny。</span>', - other: - '<span class="monster-badge-primary-pill">Jest ${count} dodatkowych wpisów dostępnych。</span>', - }, - }; - - case "da": - return { - "cannot-be-loaded": "Kan ikke indlæses", - "no-options-available": "Ingen muligheder tilgængelige。", - "click-to-load-options": "Klik for at indlæse muligheder。", - "select-an-option": "Vælg en mulighed", - "summary-text": { - zero: "Ingen indlæg blev valgt", - one: '<span class="monster-badge-primary-pill">1</span> indlæg blev valgt', - other: - '<span class="monster-badge-primary-pill">${count}</span> indlæg blev valgt', - }, - "no-options": - '<span class="monster-badge-error-pill">Desværre er der ingen muligheder tilgængelige på listen。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Ingen muligheder tilgængelige på listen。Overvej at ændre filteret。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Der er ingen yderligere poster tilgængelige。</span>', - one: '<span class="monster-badge-primary-pill">Der er 1 yderligere post tilgængelig。</span>', - other: - '<span class="monster-badge-primary-pill">Der er ${count} yderligere poster tilgængelige。</span>', - }, - }; - - case "fi": - return { - "cannot-be-loaded": "Ei voi ladata", - "no-options-available": "Ei vaihtoehtoja saatavilla。", - "click-to-load-options": "Napsauta ladataksesi vaihtoehtoja。", - "select-an-option": "Valitse vaihtoehto", - "summary-text": { - zero: "Ei valittuja kohteita", - one: '<span class="monster-badge-primary-pill">1</span> kohde valittu', - other: - '<span class="monster-badge-primary-pill">${count}</span> kohdetta valittu', - }, - "no-options": - '<span class="monster-badge-error-pill">Valitettavasti listalla ei ole vaihtoehtoja saatavilla。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Listalla ei ole vaihtoehtoja saatavilla。Harkitse suodattimen muuttamista。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Lisäkohteita ei ole saatavilla。</span>', - one: '<span class="monster-badge-primary-pill">1 lisäkohde on saatavilla。</span>', - other: - '<span class="monster-badge-primary-pill">${count} lisäkohdetta on saatavilla。</span>', - }, - }; - - case "no": - return { - "cannot-be-loaded": "Kan ikke lastes", - "no-options-available": "Ingen alternativer tilgjengelig。", - "click-to-load-options": "Klikk for å laste alternativer。", - "select-an-option": "Velg et alternativ", - "summary-text": { - zero: "Ingen oppføringer ble valgt", - one: '<span class="monster-badge-primary-pill">1</span> oppføring valgt', - other: - '<span class="monster-badge-primary-pill">${count}</span> oppføringer valgt', - }, - "no-options": - '<span class="monster-badge-error-pill">Dessverre er det ingen alternativer tilgjengelig i listen。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">Ingen alternativer tilgjengelig på listen。Vurder å endre filteret。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Det er ingen flere poster tilgjengelige。</span>', - one: '<span class="monster-badge-primary-pill">Det er 1 ytterligere post tilgjengelig。</span>', - other: - '<span class="monster-badge-primary-pill">Det er ${count} ytterligere poster tilgjengelig。</span>', - }, - }; - - case "cs": - return { - "cannot-be-loaded": "Nelze načíst", - "no-options-available": "Žádné možnosti nejsou k dispozici。", - "click-to-load-options": "Klikněte pro načtení možností。", - "select-an-option": "Vyberte možnost", - "summary-text": { - zero: "Žádné položky nebyly vybrány", - one: '<span class="monster-badge-primary-pill">1</span> položka vybrána', - other: - '<span class="monster-badge-primary-pill">${count}</span> položky vybrány', - }, - "no-options": - '<span class="monster-badge-error-pill">Bohužel nejsou k dispozici žádné možnosti v seznamu。</span>', - "no-options-found": - '<span class="monster-badge-error-pill">V seznamu nejsou k dispozici žádné možnosti。Zvažte změnu filtru。</span>', - total: { - zero: '<span class="monster-badge-primary-pill">Žádné další položky nejsou k dispozici。</span>', - one: '<span class="monster-badge-primary-pill">Je k dispozici 1 další položka。</span>', - other: - '<span class="monster-badge-primary-pill">K dispozici je ${count} dalších položek。</span>', - }, - }; - - default: - // Fallback to English if locale.language is unrecognized - return { - "cannot-be-loaded": "Cannot be loaded", - "no-options-available": "No options available.", - "click-to-load-options": "Click to load options.", - "select-an-option": "Select an option", - "summary-text": { - zero: "No entries were selected", - one: '<span class="monster-badge-primary-pill">1</span> entry was selected', - other: - '<span class="monster-badge-primary-pill">${count}</span> entries were selected', - }, - "no-options": - '<span class="monster-badge-error-pill">Unfortunately, there are no options available in the list.</span>', - "no-options-found": - '<span class="monster-badge-error-pill">No options are available in the list. Please consider modifying the filter.</span>', - total: { - zero: '<span class="monster-badge-primary-pill">No additional entries are available.</span>', - one: '<span class="monster-badge-primary-pill">1 additional entry is available.</span>', - other: - '<span class="monster-badge-primary-pill">${count} additional entries are available.</span>', - }, - }; - } + const locale = getLocaleOfDocument(); + switch (locale.language) { + case "de": + return { + "cannot-be-loaded": "Kann nicht geladen werden", + "no-options-available": "Keine Auswahl verfügbar.", + "click-to-load-options": "Klicken, um Auswahl zu laden.", + "select-an-option": "Bitte Auswahl treffen", + "summary-text": { + zero: "Keine Auswahl getroffen", + one: '<span class="monster-badge-primary-pill">1</span> Auswahl getroffen', + other: + '<span class="monster-badge-primary-pill">${count}</span> Auswahlen getroffen', + }, + "no-options": + '<span class="monster-badge-error-pill">Leider gibt es keine Auswahlmöglichkeiten in der Liste.</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Keine Auswahlmöglichkeiten verfügbar. Bitte ändern Sie den Filter.</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Es sind keine weiteren Auswahlmöglichkeiten verfügbar.</span>', + one: '<span class="monster-badge-primary-pill">Es ist 1 weitere Auswahlmöglichkeit verfügbar.</span>', + other: + '<span class="monster-badge-primary-pill">Es sind ${count} weitere Auswahlmöglichkeiten verfügbar.</span>', + }, + }; + + case "es": + return { + "cannot-be-loaded": "No se puede cargar", + "no-options-available": "No hay opciones disponibles.", + "click-to-load-options": "Haga clic para cargar opciones.", + "select-an-option": "Seleccione una opción", + "summary-text": { + zero: "No se seleccionaron entradas", + one: '<span class="monster-badge-primary-pill">1</span> entrada seleccionada', + other: + '<span class="monster-badge-primary-pill">${count}</span> entradas seleccionadas', + }, + "no-options": + '<span class="monster-badge-error-pill">Desafortunadamente, no hay opciones disponibles en la lista.</span>', + "no-options-found": + '<span class="monster-badge-error-pill">No hay opciones disponibles en la lista. Por favor, modifique el filtro.</span>', + total: { + zero: '<span class="monster-badge-primary-pill">No hay entradas adicionales disponibles.</span>', + one: '<span class="monster-badge-primary-pill">1 entrada adicional está disponible.</span>', + other: + '<span class="monster-badge-primary-pill">${count} entradas adicionales están disponibles.</span>', + }, + }; + + case "zh": + return { + "cannot-be-loaded": "无法加载", + "no-options-available": "没有可用选项。", + "click-to-load-options": "点击以加载选项。", + "select-an-option": "选择一个选项", + "summary-text": { + zero: "未选择任何条目", + one: '<span class="monster-badge-primary-pill">1</span> 个条目已选择', + other: + '<span class="monster-badge-primary-pill">${count}</span> 个条目已选择', + }, + "no-options": + '<span class="monster-badge-error-pill">很抱歉,列表中没有可用选项。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">列表中没有可用选项。请修改筛选条件。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">没有更多条目可用。</span>', + one: '<span class="monster-badge-primary-pill">还有 1 个可用条目。</span>', + other: + '<span class="monster-badge-primary-pill">还有 ${count} 个可用条目。</span>', + }, + }; + + case "hi": + return { + "cannot-be-loaded": "लोड नहीं किया जा सकता", + "no-options-available": "कोई विकल्प उपलब्ध नहीं है।", + "click-to-load-options": "विकल्प लोड करने के लिए क्लिक करें।", + "select-an-option": "एक विकल्प चुनें", + "summary-text": { + zero: "कोई प्रविष्टि चयनित नहीं", + one: '<span class="monster-badge-primary-pill">1</span> प्रविष्टि चयनित', + other: + '<span class="monster-badge-primary-pill">${count}</span> प्रविष्टियाँ चयनित', + }, + "no-options": + '<span class="monster-badge-error-pill">क्षमा करें, सूची में कोई विकल्प उपलब्ध नहीं है।</span>', + "no-options-found": + '<span class="monster-badge-error-pill">सूची में कोई विकल्प उपलब्ध नहीं है। कृपया फ़िल्टर बदलें।</span>', + total: { + zero: '<span class="monster-badge-primary-pill">कोई अतिरिक्त प्रविष्टि उपलब्ध नहीं है।</span>', + one: '<span class="monster-badge-primary-pill">1 अतिरिक्त प्रविष्टि उपलब्ध है।</span>', + other: + '<span class="monster-badge-primary-pill">${count} अतिरिक्त प्रविष्टियाँ उपलब्ध हैं।</span>', + }, + }; + + case "bn": + return { + "cannot-be-loaded": "লোড করা যায়নি", + "no-options-available": "কোন বিকল্প উপলব্ধ নেই।", + "click-to-load-options": "বিকল্প লোড করতে ক্লিক করুন।", + "select-an-option": "একটি বিকল্প নির্বাচন করুন", + "summary-text": { + zero: "কোন এন্ট্রি নির্বাচিত হয়নি", + one: '<span class="monster-badge-primary-pill">1</span> এন্ট্রি নির্বাচিত', + other: + '<span class="monster-badge-primary-pill">${count}</span> এন্ট্রি নির্বাচিত', + }, + "no-options": + '<span class="monster-badge-error-pill">দুঃখিত, তালিকায় কোন বিকল্প পাওয়া যায়নি।</span>', + "no-options-found": + '<span class="monster-badge-error-pill">তালিকায় কোন বিকল্প পাওয়া যায়নি। দয়া করে ফিল্টার পরিবর্তন করুন।</span>', + total: { + zero: '<span class="monster-badge-primary-pill">আর কোনো এন্ট্রি উপলব্ধ নেই।</span>', + one: '<span class="monster-badge-primary-pill">1 অতিরিক্ত এন্ট্রি উপলব্ধ।</span>', + other: + '<span class="monster-badge-primary-pill">${count} অতিরিক্ত এন্ট্রি উপলব্ধ।</span>', + }, + }; + + case "pt": + return { + "cannot-be-loaded": "Não é possível carregar", + "no-options-available": "Nenhuma opção disponível.", + "click-to-load-options": "Clique para carregar opções.", + "select-an-option": "Selecione uma opção", + "summary-text": { + zero: "Nenhuma entrada selecionada", + one: '<span class="monster-badge-primary-pill">1</span> entrada selecionada', + other: + '<span class="monster-badge-primary-pill">${count}</span> entradas selecionadas', + }, + "no-options": + '<span class="monster-badge-error-pill">Infelizmente, não há opções disponíveis na lista.</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Nenhuma opção disponível na lista. Considere modificar o filtro.</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Não há entradas adicionais disponíveis.</span>', + one: '<span class="monster-badge-primary-pill">1 entrada adicional está disponível.</span>', + other: + '<span class="monster-badge-primary-pill">${count} entradas adicionais estão disponíveis.</span>', + }, + }; + + case "ru": + return { + "cannot-be-loaded": "Не удалось загрузить", + "no-options-available": "Нет доступных вариантов.", + "click-to-load-options": "Нажмите, чтобы загрузить варианты.", + "select-an-option": "Выберите вариант", + "summary-text": { + zero: "Нет выбранных записей", + one: '<span class="monster-badge-primary-pill">1</span> запись выбрана', + other: + '<span class="monster-badge-primary-pill">${count}</span> записей выбрано', + }, + "no-options": + '<span class="monster-badge-error-pill">К сожалению, в списке нет доступных вариантов.</span>', + "no-options-found": + '<span class="monster-badge-error-pill">В списке нет доступных вариантов. Пожалуйста, измените фильтр.</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Дополнительных записей нет.</span>', + one: '<span class="monster-badge-primary-pill">Доступна 1 дополнительная запись.</span>', + other: + '<span class="monster-badge-primary-pill">${count} дополнительных записей доступны.</span>', + }, + }; + + case "ja": + return { + "cannot-be-loaded": "読み込めません", + "no-options-available": "利用可能なオプションがありません。", + "click-to-load-options": "クリックしてオプションを読み込む。", + "select-an-option": "オプションを選択", + "summary-text": { + zero: "選択された項目はありません", + one: '<span class="monster-badge-primary-pill">1</span> 件選択されました', + other: + '<span class="monster-badge-primary-pill">${count}</span> 件選択されました', + }, + "no-options": + '<span class="monster-badge-error-pill">申し訳ありませんが、リストに利用可能なオプションがありません。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">リストに利用可能なオプションがありません。フィルターを変更してください。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">追加の項目はありません。</span>', + one: '<span class="monster-badge-primary-pill">1 件の追加項目があります。</span>', + other: + '<span class="monster-badge-primary-pill">${count} 件の追加項目があります。</span>', + }, + }; + + case "pa": + return { + "cannot-be-loaded": "ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ", + "no-options-available": "ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ।", + "click-to-load-options": "ਚੋਣਾਂ ਲੋਡ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ।", + "select-an-option": "ਇੱਕ ਚੋਣ ਚੁਣੋ", + "summary-text": { + zero: "ਕੋਈ ਐਂਟਰੀ ਚੁਣੀ ਨਹੀਂ ਗਈ", + one: '<span class="monster-badge-primary-pill">1</span> ਐਂਟਰੀ ਚੁਣੀ ਗਈ', + other: + '<span class="monster-badge-primary-pill">${count}</span> ਐਂਟਰੀਆਂ ਚੁਣੀਆਂ ਗਈਆਂ', + }, + "no-options": + '<span class="monster-badge-error-pill">ਮਾਫ ਕਰਨਾ, ਸੂਚੀ ਵਿੱਚ ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।</span>', + "no-options-found": + '<span class="monster-badge-error-pill">ਸੂਚੀ ਵਿੱਚ ਕੋਈ ਚੋਣ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਫਿਲਟਰ ਬਦਲੋ।</span>', + total: { + zero: '<span class="monster-badge-primary-pill">ਕੋਈ ਹੋਰ ਐਂਟਰੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ.</span>', + one: '<span class="monster-badge-primary-pill">1 ਵਾਧੂ ਐਂਟਰੀ ਉਪਲਬਧ ਹੈ.</span>', + other: + '<span class="monster-badge-primary-pill">${count} ਵਾਧੂ ਐਂਟਰੀਆਂ ਉਪਲਬਧ ਹਨ.</span>', + }, + }; + + case "mr": + return { + "cannot-be-loaded": "लोड केले जाऊ शकत नाही", + "no-options-available": "कोणतीही पर्याय उपलब्ध नाहीत。", + "click-to-load-options": "पर्याय लोड करण्यासाठी क्लिक करा。", + "select-an-option": "एक पर्याय निवडा", + "summary-text": { + zero: "कोणीही नोंद निवडलेली नाही", + one: '<span class="monster-badge-primary-pill">1</span> नोंद निवडली', + other: + '<span class="monster-badge-primary-pill">${count}</span> नोंदी निवडल्या', + }, + "no-options": + '<span class="monster-badge-error-pill">क्षमस्व, यादीमध्ये कोणतीही पर्याय उपलब्ध नाहीत。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">यादीमध्ये कोणतेही पर्याय उपलब्ध नाहीत। कृपया फिल्टर बदला。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">आणखी कोणतीही नोंद उपलब्ध नाही。</span>', + one: '<span class="monster-badge-primary-pill">1 अतिरिक्त नोंद उपलब्ध आहे。</span>', + other: + '<span class="monster-badge-primary-pill">${count} अतिरिक्त नोंदी उपलब्ध आहेत。</span>', + }, + }; + + case "it": + return { + "cannot-be-loaded": "Non può essere caricato", + "no-options-available": "Nessuna opzione disponibile。", + "click-to-load-options": "Clicca per caricare le opzioni。", + "select-an-option": "Seleziona un'opzione", + "summary-text": { + zero: "Nessuna voce selezionata", + one: '<span class="monster-badge-primary-pill">1</span> voce selezionata', + other: + '<span class="monster-badge-primary-pill">${count}</span> voci selezionate', + }, + "no-options": + '<span class="monster-badge-error-pill">Purtroppo, non ci sono opzioni disponibili nella lista。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Nessuna opzione disponibile nella lista。Si prega di modificare il filtro。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Non ci sono altre voci disponibili。</span>', + one: '<span class="monster-badge-primary-pill">C\'è 1 voce aggiuntiva disponibile。</span>', + other: + '<span class="monster-badge-primary-pill">Ci sono ${count} voci aggiuntive disponibili。</span>', + }, + }; + + case "nl": + return { + "cannot-be-loaded": "Kan niet worden geladen", + "no-options-available": "Geen opties beschikbaar。", + "click-to-load-options": "Klik om opties te laden。", + "select-an-option": "Selecteer een optie", + "summary-text": { + zero: "Er zijn geen items geselecteerd", + one: '<span class="monster-badge-primary-pill">1</span> item geselecteerd', + other: + '<span class="monster-badge-primary-pill">${count}</span> items geselecteerd', + }, + "no-options": + '<span class="monster-badge-error-pill">Helaas zijn er geen opties beschikbaar in de lijst。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Geen opties beschikbaar in de lijst。Overweeg het filter aan te passen。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Er zijn geen extra items beschikbaar。</span>', + one: '<span class="monster-badge-primary-pill">1 extra item is beschikbaar。</span>', + other: + '<span class="monster-badge-primary-pill">${count} extra items zijn beschikbaar。</span>', + }, + }; + + case "sv": + return { + "cannot-be-loaded": "Kan inte laddas", + "no-options-available": "Inga alternativ tillgängliga。", + "click-to-load-options": "Klicka för att ladda alternativ。", + "select-an-option": "Välj ett alternativ", + "summary-text": { + zero: "Inga poster valdes", + one: '<span class="monster-badge-primary-pill">1</span> post valdes', + other: + '<span class="monster-badge-primary-pill">${count}</span> poster valdes', + }, + "no-options": + '<span class="monster-badge-error-pill">Tyvärr finns det inga alternativ tillgängliga i listan。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Inga alternativ finns tillgängliga i listan。Överväg att modifiera filtret。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Det finns inga fler poster tillgängliga。</span>', + one: '<span class="monster-badge-primary-pill">Det finns 1 ytterligare post tillgänglig。</span>', + other: + '<span class="monster-badge-primary-pill">Det finns ${count} ytterligare poster tillgängliga。</span>', + }, + }; + + case "pl": + return { + "cannot-be-loaded": "Nie można załadować", + "no-options-available": "Brak dostępnych opcji。", + "click-to-load-options": "Kliknij, aby załadować opcje。", + "select-an-option": "Wybierz opcję", + "summary-text": { + zero: "Nie wybrano żadnych wpisów", + one: '<span class="monster-badge-primary-pill">1</span> wpis został wybrany', + other: + '<span class="monster-badge-primary-pill">${count}</span> wpisy zostały wybrane', + }, + "no-options": + '<span class="monster-badge-error-pill">Niestety, nie ma dostępnych opcji na liście。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Brak dostępnych opcji na liście。Rozważ zmianę filtra。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Nie ma więcej dostępnych wpisów。</span>', + one: '<span class="monster-badge-primary-pill">Jest 1 dodatkowy wpis dostępny。</span>', + other: + '<span class="monster-badge-primary-pill">Jest ${count} dodatkowych wpisów dostępnych。</span>', + }, + }; + + case "da": + return { + "cannot-be-loaded": "Kan ikke indlæses", + "no-options-available": "Ingen muligheder tilgængelige。", + "click-to-load-options": "Klik for at indlæse muligheder。", + "select-an-option": "Vælg en mulighed", + "summary-text": { + zero: "Ingen indlæg blev valgt", + one: '<span class="monster-badge-primary-pill">1</span> indlæg blev valgt', + other: + '<span class="monster-badge-primary-pill">${count}</span> indlæg blev valgt', + }, + "no-options": + '<span class="monster-badge-error-pill">Desværre er der ingen muligheder tilgængelige på listen。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Ingen muligheder tilgængelige på listen。Overvej at ændre filteret。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Der er ingen yderligere poster tilgængelige。</span>', + one: '<span class="monster-badge-primary-pill">Der er 1 yderligere post tilgængelig。</span>', + other: + '<span class="monster-badge-primary-pill">Der er ${count} yderligere poster tilgængelige。</span>', + }, + }; + + case "fi": + return { + "cannot-be-loaded": "Ei voi ladata", + "no-options-available": "Ei vaihtoehtoja saatavilla。", + "click-to-load-options": "Napsauta ladataksesi vaihtoehtoja。", + "select-an-option": "Valitse vaihtoehto", + "summary-text": { + zero: "Ei valittuja kohteita", + one: '<span class="monster-badge-primary-pill">1</span> kohde valittu', + other: + '<span class="monster-badge-primary-pill">${count}</span> kohdetta valittu', + }, + "no-options": + '<span class="monster-badge-error-pill">Valitettavasti listalla ei ole vaihtoehtoja saatavilla。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Listalla ei ole vaihtoehtoja saatavilla。Harkitse suodattimen muuttamista。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Lisäkohteita ei ole saatavilla。</span>', + one: '<span class="monster-badge-primary-pill">1 lisäkohde on saatavilla。</span>', + other: + '<span class="monster-badge-primary-pill">${count} lisäkohdetta on saatavilla。</span>', + }, + }; + + case "no": + return { + "cannot-be-loaded": "Kan ikke lastes", + "no-options-available": "Ingen alternativer tilgjengelig。", + "click-to-load-options": "Klikk for å laste alternativer。", + "select-an-option": "Velg et alternativ", + "summary-text": { + zero: "Ingen oppføringer ble valgt", + one: '<span class="monster-badge-primary-pill">1</span> oppføring valgt', + other: + '<span class="monster-badge-primary-pill">${count}</span> oppføringer valgt', + }, + "no-options": + '<span class="monster-badge-error-pill">Dessverre er det ingen alternativer tilgjengelig i listen。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">Ingen alternativer tilgjengelig på listen。Vurder å endre filteret。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Det er ingen flere poster tilgjengelige。</span>', + one: '<span class="monster-badge-primary-pill">Det er 1 ytterligere post tilgjengelig。</span>', + other: + '<span class="monster-badge-primary-pill">Det er ${count} ytterligere poster tilgjengelig。</span>', + }, + }; + + case "cs": + return { + "cannot-be-loaded": "Nelze načíst", + "no-options-available": "Žádné možnosti nejsou k dispozici。", + "click-to-load-options": "Klikněte pro načtení možností。", + "select-an-option": "Vyberte možnost", + "summary-text": { + zero: "Žádné položky nebyly vybrány", + one: '<span class="monster-badge-primary-pill">1</span> položka vybrána', + other: + '<span class="monster-badge-primary-pill">${count}</span> položky vybrány', + }, + "no-options": + '<span class="monster-badge-error-pill">Bohužel nejsou k dispozici žádné možnosti v seznamu。</span>', + "no-options-found": + '<span class="monster-badge-error-pill">V seznamu nejsou k dispozici žádné možnosti。Zvažte změnu filtru。</span>', + total: { + zero: '<span class="monster-badge-primary-pill">Žádné další položky nejsou k dispozici。</span>', + one: '<span class="monster-badge-primary-pill">Je k dispozici 1 další položka。</span>', + other: + '<span class="monster-badge-primary-pill">K dispozici je ${count} dalších položek。</span>', + }, + }; + + default: + // Fallback to English if locale.language is unrecognized + return { + "cannot-be-loaded": "Cannot be loaded", + "no-options-available": "No options available.", + "click-to-load-options": "Click to load options.", + "select-an-option": "Select an option", + "summary-text": { + zero: "No entries were selected", + one: '<span class="monster-badge-primary-pill">1</span> entry was selected', + other: + '<span class="monster-badge-primary-pill">${count}</span> entries were selected', + }, + "no-options": + '<span class="monster-badge-error-pill">Unfortunately, there are no options available in the list.</span>', + "no-options-found": + '<span class="monster-badge-error-pill">No options are available in the list. Please consider modifying the filter.</span>', + total: { + zero: '<span class="monster-badge-primary-pill">No additional entries are available.</span>', + one: '<span class="monster-badge-primary-pill">1 additional entry is available.</span>', + other: + '<span class="monster-badge-primary-pill">${count} additional entries are available.</span>', + }, + }; + } } /** * @private */ function lookupSelection() { - const self = this; - - const observer = new IntersectionObserver((entries, obs) => { - for (const entry of entries) { - if (entry.isIntersecting) { - - obs.disconnect(); // Only observe once - - setTimeout(() => { - const selection = self.getOption("selection"); - if (selection.length === 0) { - return; - } - - if (self[isLoadingSymbol] === true) { - return; - } - - if (self[lazyLoadDoneSymbol] === true) { - return; - } - - let url = self.getOption("url"); - const lookupUrl = self.getOption("lookup.url"); - if (lookupUrl !== null) { - url = lookupUrl; - } - - self[cleanupOptionsListSymbol] = false; - - if (self.getOption("lookup.grouping") === true) { - filterFromRemoteByValue - .call( - self, - url, - selection.map((s) => s?.["value"]), - ) - .catch((e) => { - addErrorAttribute(self, e); - }); - return; - } - - for (const s of selection) { - if (s?.["value"]) { - filterFromRemoteByValue.call(self, url, s["value"]).catch((e) => { - addErrorAttribute(self, e); - }); - } - } - }, 100); - } - } - }, { threshold: 0.1 }); - - // Beobachte das Element selbst (dieses Element muss im DOM sein) - observer.observe(self); + const self = this; + + const observer = new IntersectionObserver((entries, obs) => { + for (const entry of entries) { + if (entry.isIntersecting) { + + obs.disconnect(); // Only observe once + + setTimeout(() => { + const selection = self.getOption("selection"); + if (selection.length === 0) { + return; + } + + if (self[isLoadingSymbol] === true) { + return; + } + + if (self[lazyLoadDoneSymbol] === true) { + return; + } + + let url = self.getOption("url"); + const lookupUrl = self.getOption("lookup.url"); + if (lookupUrl !== null) { + url = lookupUrl; + } + + self[cleanupOptionsListSymbol] = false; + + if (self.getOption("lookup.grouping") === true) { + filterFromRemoteByValue + .call( + self, + url, + selection.map((s) => s?.["value"]), + ) + .catch((e) => { + addErrorAttribute(self, e); + }); + return; + } + + for (const s of selection) { + if (s?.["value"]) { + filterFromRemoteByValue.call(self, url, s["value"]).catch((e) => { + addErrorAttribute(self, e); + }); + } + } + }, 100); + } + } + }, {threshold: 0.1}); + + // Beobachte das Element selbst (dieses Element muss im DOM sein) + observer.observe(self); } @@ -1420,82 +1432,79 @@ function lookupSelection() { * @returns {Promise<never>|Promise<unknown>} */ function fetchIt(url, controlOptions) { - const self = this; - - if (url instanceof URL) { - url = url.toString(); - } - - if (url !== undefined && url !== null) { - url = validateString(url); - } else { - url = this.getOption("url"); - if (url === null) { - return Promise.reject(new Error("No url defined")); - } - } - - return new Promise((resolve, reject) => { - setStatusOrRemoveBadges.call(this, "loading"); - - new Processing(10, () => { - fetchData - .call(this, url) - .then((map) => { - if ( - isObject(map) || - isArray(map) || - map instanceof Set || - map instanceof Map - ) { - try { - importOptionsIntern.call(self, map); - } catch (e) { - setStatusOrRemoveBadges.call(this, "error"); - reject(e); - return; - } - - this[lastFetchedDataSymbol] = map; - - let result; - const selection = this.getOption("selection"); - let newValue = []; - if (selection) { - newValue = selection; - } else if (this.hasAttribute("value")) { - newValue = this.getAttribute("value"); - } - - result = setSelection.call(this, newValue); - - queueMicrotask(() => { - checkOptionState.call(this); - setTotalText.call(this); - updatePopper.call(this); - setStatusOrRemoveBadges.call(this, "closed"); - - resolve(result); - }); - - return; - } - - setStatusOrRemoveBadges.call(this, "error"); - reject(new Error("invalid response")); - }) - .catch((e) => { - setStatusOrRemoveBadges.call(this, "error"); - reject(e); - }); - }) - .run() - .catch((e) => { - setStatusOrRemoveBadges.call(this, "error"); - addErrorAttribute(this, e); - reject(e); - }); - }); + const self = this; + + if (url instanceof URL) { + url = url.toString(); + } + + if (url !== undefined && url !== null) { + url = validateString(url); + } else { + url = this.getOption("url"); + if (url === null) { + return Promise.reject(new Error("No url defined")); + } + } + + return new Promise((resolve, reject) => { + setStatusOrRemoveBadges.call(this, "loading"); + + new Processing(10, () => { + fetchData + .call(this, url) + .then((map) => { + if ( + isObject(map) || + isArray(map) || + map instanceof Set || + map instanceof Map + ) { + try { + importOptionsIntern.call(self, map); + } catch (e) { + setStatusOrRemoveBadges.call(this, "error"); + reject(e); + return; + } + + this[lastFetchedDataSymbol] = map; + + let result; + const selection = this.getOption("selection"); + let newValue = []; + if (selection) { + newValue = selection; + } else if (this.hasAttribute("value")) { + newValue = this.getAttribute("value"); + } + + result = setSelection.call(this, newValue); + + queueMicrotask(() => { + checkOptionState.call(this); + setTotalText.call(this); + updatePopper.call(this); + setStatusOrRemoveBadges.call(this, "closed"); + + resolve(result); + }); + + return; + } + + setStatusOrRemoveBadges.call(this, "error"); + reject(new Error("invalid response")); + }).catch((e) => { + setStatusOrRemoveBadges.call(this, "error"); + reject(e); + }); + }).run().catch((e) => { + setStatusOrRemoveBadges.call(this, "error"); + addErrorAttribute(this, e); + reject(e); + }); + }); } /** @@ -1510,66 +1519,66 @@ function fetchIt(url, controlOptions) { * @return {object} */ function initOptionsFromArguments() { - const options = {}; - - const template = this.getAttribute("data-monster-selected-template"); - if (isString(template)) { - if (!options["templateMapping"]) options["templateMapping"] = {}; - - switch (template) { - case "summary": - case "default": - options["templateMapping"]["selected"] = getSummaryTemplate(); - break; - case "selected": - options["templateMapping"]["selected"] = getSelectionTemplate(); - break; - default: - addErrorAttribute(this, "invalid template, use summary or selected"); - } - } - - return options; + const options = {}; + + const template = this.getAttribute("data-monster-selected-template"); + if (isString(template)) { + if (!options["templateMapping"]) options["templateMapping"] = {}; + + switch (template) { + case "summary": + case "default": + options["templateMapping"]["selected"] = getSummaryTemplate(); + break; + case "selected": + options["templateMapping"]["selected"] = getSelectionTemplate(); + break; + default: + addErrorAttribute(this, "invalid template, use summary or selected"); + } + } + + return options; } /** * @private */ function attachResizeObserver() { - // against flickering - this[resizeObserverSymbol] = new ResizeObserver((entries) => { - if (this[timerCallbackSymbol] instanceof DeadMansSwitch) { - try { - this[timerCallbackSymbol].touch(); - return; - } catch (e) { - delete this[timerCallbackSymbol]; - } - } - - this[timerCallbackSymbol] = new DeadMansSwitch(200, () => { - updatePopper.call(this); - delete this[timerCallbackSymbol]; - }); - }); - - let parent = this.parentNode; - while (!(parent instanceof HTMLElement) && parent !== null) { - parent = parent.parentNode; - } - - if (parent instanceof HTMLElement) { - this[resizeObserverSymbol].observe(parent); - } + // against flickering + this[resizeObserverSymbol] = new ResizeObserver((entries) => { + if (this[timerCallbackSymbol] instanceof DeadMansSwitch) { + try { + this[timerCallbackSymbol].touch(); + return; + } catch (e) { + delete this[timerCallbackSymbol]; + } + } + + this[timerCallbackSymbol] = new DeadMansSwitch(200, () => { + updatePopper.call(this); + delete this[timerCallbackSymbol]; + }); + }); + + let parent = this.parentNode; + while (!(parent instanceof HTMLElement) && parent !== null) { + parent = parent.parentNode; + } + + if (parent instanceof HTMLElement) { + this[resizeObserverSymbol].observe(parent); + } } /** * @private */ function disconnectResizeObserver() { - if (this[resizeObserverSymbol] instanceof ResizeObserver) { - this[resizeObserverSymbol].disconnect(); - } + if (this[resizeObserverSymbol] instanceof ResizeObserver) { + this[resizeObserverSymbol].disconnect(); + } } /** @@ -1577,7 +1586,7 @@ function disconnectResizeObserver() { * @returns {string} */ function getSelectionTemplate() { - return `<div data-monster-role="selection" part="selection" + return `<div data-monster-role="selection" part="selection" data-monster-insert="selection path:selection" role="search" ><input type="text" role="searchbox" part="inline-filter" name="inline-filter" @@ -1594,7 +1603,7 @@ function getSelectionTemplate() { * @returns {string} */ function getSummaryTemplate() { - return `<div data-monster-role="selection" role="search" part="summary"> + return `<div data-monster-role="selection" role="search" part="summary"> <input type="text" role="searchbox" data-monster-attributes="placeholder path:placeholder.filter" part="inline-filter" name="inline-filter" @@ -1611,34 +1620,34 @@ function getSummaryTemplate() { * @private */ function parseSlotsToOptions() { - let options = this.getOption("options"); - if (!isIterable(options)) { - options = []; - } - - let counter = 1; - getSlottedElements.call(this, "div").forEach((node) => { - let value = (counter++).toString(); - let visibility = "visible"; - - if (node.hasAttribute("data-monster-value")) { - value = node.getAttribute("data-monster-value"); - } - - let label = node.outerHTML; - - if (node.style.display === "none") { - visibility = "hidden"; - } - - options.push({ - value, - label, - visibility, - }); - }); - - this.setOption("options", options); + let options = this.getOption("options"); + if (!isIterable(options)) { + options = []; + } + + let counter = 1; + getSlottedElements.call(this, "div").forEach((node) => { + let value = (counter++).toString(); + let visibility = "visible"; + + if (node.hasAttribute("data-monster-value")) { + value = node.getAttribute("data-monster-value"); + } + + let label = node.outerHTML; + + if (node.style.display === "none") { + visibility = "hidden"; + } + + options.push({ + value, + label, + visibility, + }); + }); + + this.setOption("options", options); } /** @@ -1647,38 +1656,38 @@ function parseSlotsToOptions() { * @return {*} */ function buildSelectionLabel(value) { - const options = this.getOption("options"); - - for (let i = 0; i < options.length; i++) { - let o = options?.[i]; - let l, v, v2; - - if (this.getOption("features.useStrictValueComparison") === true) { - v = value; - } else { - v = `${value}`; - } - - if (isPrimitive(o) && o === value) { - return o; - } else if (!isObject(o)) { - continue; - } - - if (this.getOption("features.useStrictValueComparison") === true) { - l = o?.["label"]; - v2 = o?.["value"]; - } else { - l = `${o?.["label"]}`; - v2 = `${o?.["value"]}`; - } - - if (v2 === v) { - return l; - } - } - - return undefined; + const options = this.getOption("options"); + + for (let i = 0; i < options.length; i++) { + let o = options?.[i]; + let l, v, v2; + + if (this.getOption("features.useStrictValueComparison") === true) { + v = value; + } else { + v = `${value}`; + } + + if (isPrimitive(o) && o === value) { + return o; + } else if (!isObject(o)) { + continue; + } + + if (this.getOption("features.useStrictValueComparison") === true) { + l = o?.["label"]; + v2 = o?.["value"]; + } else { + l = `${o?.["label"]}`; + v2 = `${o?.["value"]}`; + } + + if (v2 === v) { + return l; + } + } + + return undefined; } /** @@ -1688,17 +1697,17 @@ function buildSelectionLabel(value) { * @throws {Error} no value found */ function getSelectionLabel(value) { - const callback = this.getOption("formatter.selection"); - if (isFunction(callback)) { - const label = callback.call(this, value); - if (isString(label)) return label; - } + const callback = this.getOption("formatter.selection"); + if (isFunction(callback)) { + const label = callback.call(this, value); + if (isString(label)) return label; + } - if (isString(value) || isInteger(value)) { - return `${value}`; - } + if (isString(value) || isInteger(value)) { + return `${value}`; + } - return this.getOption("labels.cannot-be-loaded", value); + return this.getOption("labels.cannot-be-loaded", value); } /** @@ -1706,25 +1715,25 @@ function getSelectionLabel(value) { * @param {Event} event */ function handleToggleKeyboardEvents(event) { - switch (event?.["code"]) { - case "Escape": - toggle.call(this); - event.preventDefault(); - break; - case "Space": - toggle.call(this); - event.preventDefault(); - break; - case "ArrowDown": - show.call(this); - activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); - event.preventDefault(); - break; - case "ArrowUp": - hide.call(this); - event.preventDefault(); - break; - } + switch (event?.["code"]) { + case "Escape": + toggle.call(this); + event.preventDefault(); + break; + case "Space": + toggle.call(this); + event.preventDefault(); + break; + case "ArrowDown": + show.call(this); + activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); + event.preventDefault(); + break; + case "ArrowUp": + hide.call(this); + event.preventDefault(); + break; + } } /** @@ -1734,28 +1743,28 @@ function handleToggleKeyboardEvents(event) { * @this CustomElement */ function initOptionObserver() { - const self = this; - - self.attachObserver( - new Observer(function () { - new Processing(() => { - try { - self.updateI18n(); - } catch (e) { - addErrorAttribute(self, e); - setStatusOrRemoveBadges.call(self, "error"); - } - try { - areOptionsAvailableAndInit.call(self); - } catch (e) { - addErrorAttribute(self, e); - setStatusOrRemoveBadges.call(self, "error"); - } - - setSummaryAndControlText.call(self); - }).run(); - }), - ); + const self = this; + + self.attachObserver( + new Observer(function () { + new Processing(() => { + try { + self.updateI18n(); + } catch (e) { + addErrorAttribute(self, e); + setStatusOrRemoveBadges.call(self, "error"); + } + try { + areOptionsAvailableAndInit.call(self); + } catch (e) { + addErrorAttribute(self, e); + setStatusOrRemoveBadges.call(self, "error"); + } + + setSummaryAndControlText.call(self); + }).run(); + }), + ); } /** @@ -1763,52 +1772,53 @@ function initOptionObserver() { * @returns {Translations} */ function getDefaultTranslation() { - const translation = new Translations("en").assignTranslations( - this.getOption("labels", {}), - ); + const translation = new Translations("en").assignTranslations( + this.getOption("labels", {}), + ); - try { - const doc = getDocumentTranslations(); - translation.locale = doc.locale; - } catch (e) {} + try { + const doc = getDocumentTranslations(); + translation.locale = doc.locale; + } catch (e) { + } - return translation; + return translation; } /** * @private */ function setTotalText() { - if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { - return; - } + if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { + return; + } - if (this.getOption("features.showRemoteInfo") !== true) { - return; - } + if (this.getOption("features.showRemoteInfo") !== true) { + return; + } - const count = this.getOption("options").length; - const total = Number.parseInt(this.getOption("total")); + const count = this.getOption("options").length; + const total = Number.parseInt(this.getOption("total")); - if (Number.isNaN(total)) { - this.setOption("messages.total", ""); - return; - } + if (Number.isNaN(total)) { + this.setOption("messages.total", ""); + return; + } - const translations = getDefaultTranslation.call(this); - const text = translations.getPluralRuleText("total", total, ""); + const translations = getDefaultTranslation.call(this); + const text = translations.getPluralRuleText("total", total, ""); - const diff = total - count; - if (diff <= 0) { - this.setOption("messages.total", ""); - return; - } + const diff = total - count; + if (diff <= 0) { + this.setOption("messages.total", ""); + return; + } - const selectedText = new Formatter({ - count: String(diff), - }).format(text); + const selectedText = new Formatter({ + count: String(diff), + }).format(text); - this.setOption("messages.total", selectedText); + this.setOption("messages.total", selectedText); } /** @@ -1816,36 +1826,36 @@ function setTotalText() { * @return {string|*} */ function setSummaryAndControlText() { - const translations = getDefaultTranslation.call(this); - const selections = this.getOption("selection"); - - const text = translations.getPluralRuleText( - "summary-text", - selections.length, - "", - ); - - const selectedText = new Formatter({ - count: String(selections.length), - }).format(text); - - this.setOption("messages.selected", selectedText); - - const current = this.getOption("messages.control"); - const msg = this.getOption("labels.select-an-option"); - - if ( - current === "" || - current === undefined || - current === msg || - current === null - ) { - if (selections.length === 0) { - this.setOption("messages.control", msg); - } else { - this.setOption("messages.control", ""); - } - } + const translations = getDefaultTranslation.call(this); + const selections = this.getOption("selection"); + + const text = translations.getPluralRuleText( + "summary-text", + selections.length, + "", + ); + + const selectedText = new Formatter({ + count: String(selections.length), + }).format(text); + + this.setOption("messages.selected", selectedText); + + const current = this.getOption("messages.control"); + const msg = this.getOption("labels.select-an-option"); + + if ( + current === "" || + current === undefined || + current === msg || + current === null + ) { + if (selections.length === 0) { + this.setOption("messages.control", msg); + } else { + this.setOption("messages.control", ""); + } + } } /** @@ -1853,9 +1863,9 @@ function setSummaryAndControlText() { * @return {NodeList} */ function getOptionElements() { - return this[optionsElementSymbol].querySelectorAll( - `[${ATTRIBUTE_ROLE}=option]`, - ); + return this[optionsElementSymbol].querySelectorAll( + `[${ATTRIBUTE_ROLE}=option]`, + ); } /** @@ -1879,80 +1889,80 @@ function getOptionElements() { * @private */ function calcAndSetOptionsDimension() { - const options = getOptionElements.call(this); - const container = this[optionsElementSymbol]; - if (!(container instanceof HTMLElement && options instanceof NodeList)) { - return; - } - - let visible = 0; - let optionHeight = 0; - const max = this.getOption("showMaxOptions", 10); - - let scrollFlag = false; - for (const [, option] of Object.entries(options)) { - const computedStyle = getGlobal().getComputedStyle(option); - if (computedStyle.display === "none") continue; - - let h = option.getBoundingClientRect().height; - h += parseInt(computedStyle.getPropertyValue("margin-top"), 10); - h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10); - optionHeight += h; - - visible++; - - if (visible > max) { - break; - } - } - - if (visible > max) { - visible = max; - scrollFlag = true; - } - - if (visible === 0) { - if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { - this.setOption( - "messages.emptyOptions", - this.getOption("labels.no-options-available"), - ); - } else { - this.setOption( - "messages.emptyOptions", - this.getOption("labels.no-options-found"), - ); - } - - let classes = new TokenList(this.getOption("classes.noOptions")); - classes.remove("d-none"); - this.setOption("classes.noOptions", classes.toString()); - } else { - let classes = new TokenList(this.getOption("classes.noOptions")); - classes.add("d-none"); - this.setOption("classes.noOptions", classes.toString()); - } - - const styles = getGlobal().getComputedStyle(this[optionsElementSymbol]); - let padding = parseInt(styles.getPropertyValue("padding-top"), 10); - padding += parseInt(styles.getPropertyValue("padding-bottom"), 10); - - let margin = parseInt(styles.getPropertyValue("margin-top"), 10); - margin += parseInt(styles.getPropertyValue("margin-bottom"), 10); - - const containerHeight = optionHeight + padding + margin; - container.style.height = `${containerHeight}px`; - - if (scrollFlag === true) { - container.style.overflowY = "scroll"; - } else { - container.style.overflowY = "auto"; - } - - const domRect = this[controlElementSymbol].getBoundingClientRect(); - - this[popperElementSymbol].style.width = `${domRect.width}px`; - container.style.overflowX = "auto"; + const options = getOptionElements.call(this); + const container = this[optionsElementSymbol]; + if (!(container instanceof HTMLElement && options instanceof NodeList)) { + return; + } + + let visible = 0; + let optionHeight = 0; + const max = this.getOption("showMaxOptions", 10); + + let scrollFlag = false; + for (const [, option] of Object.entries(options)) { + const computedStyle = getGlobal().getComputedStyle(option); + if (computedStyle.display === "none") continue; + + let h = option.getBoundingClientRect().height; + h += parseInt(computedStyle.getPropertyValue("margin-top"), 10); + h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10); + optionHeight += h; + + visible++; + + if (visible > max) { + break; + } + } + + if (visible > max) { + visible = max; + scrollFlag = true; + } + + if (visible === 0) { + if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { + this.setOption( + "messages.emptyOptions", + this.getOption("labels.no-options-available"), + ); + } else { + this.setOption( + "messages.emptyOptions", + this.getOption("labels.no-options-found"), + ); + } + + let classes = new TokenList(this.getOption("classes.noOptions")); + classes.remove("d-none"); + this.setOption("classes.noOptions", classes.toString()); + } else { + let classes = new TokenList(this.getOption("classes.noOptions")); + classes.add("d-none"); + this.setOption("classes.noOptions", classes.toString()); + } + + const styles = getGlobal().getComputedStyle(this[optionsElementSymbol]); + let padding = parseInt(styles.getPropertyValue("padding-top"), 10); + padding += parseInt(styles.getPropertyValue("padding-bottom"), 10); + + let margin = parseInt(styles.getPropertyValue("margin-top"), 10); + margin += parseInt(styles.getPropertyValue("margin-bottom"), 10); + + const containerHeight = optionHeight + padding + margin; + container.style.height = `${containerHeight}px`; + + if (scrollFlag === true) { + container.style.overflowY = "scroll"; + } else { + container.style.overflowY = "auto"; + } + + const domRect = this[controlElementSymbol].getBoundingClientRect(); + + this[popperElementSymbol].style.width = `${domRect.width}px`; + container.style.overflowX = "auto"; } /** @@ -1961,126 +1971,126 @@ function calcAndSetOptionsDimension() { * @throws {Error} no shadow-root is defined */ function activateCurrentOption(direction) { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - let focused = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`); - - if ( - !(focused instanceof HTMLElement) || - focused.matches("[data-monster-visibility=hidden]") - ) { - for (const [, e] of Object.entries( - this.shadowRoot.querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`), - )) { - if (e.matches("[data-monster-visibility=visible]")) { - focused = e; - break; - } - } - } else { - if (direction === FOCUS_DIRECTION_DOWN) { - while (focused.nextSibling) { - focused = focused.nextSibling; - - if ( - focused instanceof HTMLElement && - focused.hasAttribute(ATTRIBUTE_ROLE) && - focused.getAttribute(ATTRIBUTE_ROLE) === "option" && - focused.matches("[data-monster-visibility=visible]") && - focused.matches(":not([data-monster-filtered=true])") - ) { - break; - } - } - } else { - let found = false; - while (focused.previousSibling) { - focused = focused.previousSibling; - if ( - focused instanceof HTMLElement && - focused.hasAttribute(ATTRIBUTE_ROLE) && - focused.getAttribute(ATTRIBUTE_ROLE) === "option" && - focused.matches("[data-monster-visibility=visible]") && - focused.matches(":not([data-monster-filtered=true])") - ) { - found = true; - break; - } - } - if (found === false) { - focusFilter.call(this); - } - } - } - - new Processing(() => { - if (focused instanceof HTMLElement) { - this.shadowRoot - .querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`) - .forEach((e) => { - e.removeAttribute(`${ATTRIBUTE_PREFIX}focused`); - }); - - focused.focus(); - focused.setAttribute(`${ATTRIBUTE_PREFIX}focused`, true); - } - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + let focused = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`); + + if ( + !(focused instanceof HTMLElement) || + focused.matches("[data-monster-visibility=hidden]") + ) { + for (const [, e] of Object.entries( + this.shadowRoot.querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`), + )) { + if (e.matches("[data-monster-visibility=visible]")) { + focused = e; + break; + } + } + } else { + if (direction === FOCUS_DIRECTION_DOWN) { + while (focused.nextSibling) { + focused = focused.nextSibling; + + if ( + focused instanceof HTMLElement && + focused.hasAttribute(ATTRIBUTE_ROLE) && + focused.getAttribute(ATTRIBUTE_ROLE) === "option" && + focused.matches("[data-monster-visibility=visible]") && + focused.matches(":not([data-monster-filtered=true])") + ) { + break; + } + } + } else { + let found = false; + while (focused.previousSibling) { + focused = focused.previousSibling; + if ( + focused instanceof HTMLElement && + focused.hasAttribute(ATTRIBUTE_ROLE) && + focused.getAttribute(ATTRIBUTE_ROLE) === "option" && + focused.matches("[data-monster-visibility=visible]") && + focused.matches(":not([data-monster-filtered=true])") + ) { + found = true; + break; + } + } + if (found === false) { + focusFilter.call(this); + } + } + } + + new Processing(() => { + if (focused instanceof HTMLElement) { + this.shadowRoot + .querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`) + .forEach((e) => { + e.removeAttribute(`${ATTRIBUTE_PREFIX}focused`); + }); + + focused.focus(); + focused.setAttribute(`${ATTRIBUTE_PREFIX}focused`, true); + } + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); } /** * @private */ function filterOptions() { - new Processing(() => { - let filterValue; - - switch (this.getOption("filter.position")) { - case FILTER_POSITION_INLINE: - if (this[inlineFilterElementSymbol] instanceof HTMLElement) { - filterValue = this[inlineFilterElementSymbol].value.toLowerCase(); - } else { - return; - } - - break; - case FILTER_POSITION_POPPER: - default: - if (this[popperFilterElementSymbol] instanceof HTMLInputElement) { - filterValue = this[popperFilterElementSymbol].value.toLowerCase(); - } else { - return; - } - } - - const options = this.getOption("options"); - for (const [i, option] of Object.entries(options)) { - if (option.label.toLowerCase().indexOf(filterValue) === -1) { - this.setOption(`options.${i}.filtered`, "true"); - } else { - this.setOption(`options.${i}.filtered`, undefined); - } - } - }) - .run() - .then(() => { - new Processing(100, () => { - calcAndSetOptionsDimension.call(this); - focusFilter.call(this); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - }) - .catch((e) => { - addErrorAttribute(this, e); - }); + new Processing(() => { + let filterValue; + + switch (this.getOption("filter.position")) { + case FILTER_POSITION_INLINE: + if (this[inlineFilterElementSymbol] instanceof HTMLElement) { + filterValue = this[inlineFilterElementSymbol].value.toLowerCase(); + } else { + return; + } + + break; + case FILTER_POSITION_POPPER: + default: + if (this[popperFilterElementSymbol] instanceof HTMLInputElement) { + filterValue = this[popperFilterElementSymbol].value.toLowerCase(); + } else { + return; + } + } + + const options = this.getOption("options"); + for (const [i, option] of Object.entries(options)) { + if (option.label.toLowerCase().indexOf(filterValue) === -1) { + this.setOption(`options.${i}.filtered`, "true"); + } else { + this.setOption(`options.${i}.filtered`, undefined); + } + } + }) + .run() + .then(() => { + new Processing(100, () => { + calcAndSetOptionsDimension.call(this); + focusFilter.call(this); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + }) + .catch((e) => { + addErrorAttribute(this, e); + }); } /** @@ -2088,37 +2098,37 @@ function filterOptions() { * @param {Event} event */ function handleFilterKeyboardEvents(event) { - const shiftKey = event?.["shiftKey"]; - - switch (event?.["code"]) { - case "Tab": - activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); - event.preventDefault(); - break; - case "Escape": - toggle.call(this); - event.preventDefault(); - break; - case "Tab" && shiftKey === true: - case "ArrowUp": - activateCurrentOption.call(this, FOCUS_DIRECTION_UP); - event.preventDefault(); - break; - case "Tab" && !shiftKey: - case "ArrowDown": - activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); - event.preventDefault(); - break; - default: - if ( - this.getOption("features.lazyLoad") === true && - this[lazyLoadDoneSymbol] !== true - ) { - this.click(); - } - - handleFilterKeyEvents.call(this); - } + const shiftKey = event?.["shiftKey"]; + + switch (event?.["code"]) { + case "Tab": + activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); + event.preventDefault(); + break; + case "Escape": + toggle.call(this); + event.preventDefault(); + break; + case "Tab" && shiftKey === true: + case "ArrowUp": + activateCurrentOption.call(this, FOCUS_DIRECTION_UP); + event.preventDefault(); + break; + case "Tab" && !shiftKey: + case "ArrowDown": + activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); + event.preventDefault(); + break; + default: + if ( + this.getOption("features.lazyLoad") === true && + this[lazyLoadDoneSymbol] !== true + ) { + this.click(); + } + + handleFilterKeyEvents.call(this); + } } /** @@ -2132,68 +2142,68 @@ function handleFilterKeyboardEvents(event) { * @return {void} This method does not return anything. */ function handleFilterKeyEvents() { - if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) { - try { - this[keyFilterEventSymbol].touch(); - return; - } catch (e) { - delete this[keyFilterEventSymbol]; - } - } - - this[keyFilterEventSymbol] = new DeadMansSwitch(200, () => { - if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { - filterOptions.call(this); - } else { - this[cleanupOptionsListSymbol] = true; - - filterFromRemote.call(this).catch((e) => { - addErrorAttribute(this, e); - }); - } - - delete this[keyFilterEventSymbol]; - }); + if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) { + try { + this[keyFilterEventSymbol].touch(); + return; + } catch (e) { + delete this[keyFilterEventSymbol]; + } + } + + this[keyFilterEventSymbol] = new DeadMansSwitch(200, () => { + if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { + filterOptions.call(this); + } else { + this[cleanupOptionsListSymbol] = true; + + filterFromRemote.call(this).catch((e) => { + addErrorAttribute(this, e); + }); + } + + delete this[keyFilterEventSymbol]; + }); } /** * @private */ function filterFromRemote() { - if ( - !(this[inlineFilterElementSymbol] instanceof HTMLElement) && - !(this[popperFilterElementSymbol] instanceof HTMLElement) - ) { - return Promise.reject(new Error("Missing Filter Element.")); - } - - show.call(this); - - const url = this.getOption("url"); - if (!url) { - addErrorAttribute(this, "Missing URL for Remote Filter."); - return Promise.reject(new Error("Missing URL for Remote Filter.")); - } - - let filterValue; - let showFlag = false; - - switch (this.getOption("filter.position")) { - case FILTER_POSITION_INLINE: - if (this[inlineFilterElementSymbol] instanceof HTMLElement) { - filterValue = this[inlineFilterElementSymbol].value.toLowerCase(); - } - showFlag = true; - - break; - case FILTER_POSITION_POPPER: - default: - if (this[popperFilterElementSymbol] instanceof HTMLInputElement) { - filterValue = this[popperFilterElementSymbol].value.toLowerCase(); - } - } - - return filterFromRemoteByValue.call(this, url, filterValue, showFlag); + if ( + !(this[inlineFilterElementSymbol] instanceof HTMLElement) && + !(this[popperFilterElementSymbol] instanceof HTMLElement) + ) { + return Promise.reject(new Error("Missing Filter Element.")); + } + + show.call(this); + + const url = this.getOption("url"); + if (!url) { + addErrorAttribute(this, "Missing URL for Remote Filter."); + return Promise.reject(new Error("Missing URL for Remote Filter.")); + } + + let filterValue; + let showFlag = false; + + switch (this.getOption("filter.position")) { + case FILTER_POSITION_INLINE: + if (this[inlineFilterElementSymbol] instanceof HTMLElement) { + filterValue = this[inlineFilterElementSymbol].value.toLowerCase(); + } + showFlag = true; + + break; + case FILTER_POSITION_POPPER: + default: + if (this[popperFilterElementSymbol] instanceof HTMLInputElement) { + filterValue = this[popperFilterElementSymbol].value.toLowerCase(); + } + } + + return filterFromRemoteByValue.call(this, url, filterValue, showFlag); } /** @@ -2203,25 +2213,25 @@ function filterFromRemote() { * @returns {string} */ function formatURL(url, value) { - if (value === undefined || value === null || value === "") { - value = this.getOption("filter.defaultValue"); - if (value === undefined || value === null) { - value = disabledRequestMarker.toString(); - } - } - - const formatter = new Formatter({ filter: encodeURI(value) }); - const openMarker = this.getOption("filter.marker.open"); - let closeMarker = this.getOption("filter.marker.close"); - if (!closeMarker) { - closeMarker = openMarker; - } - - if (openMarker && closeMarker) { - formatter.setMarker(openMarker, closeMarker); - } - - return formatter.format(url); + if (value === undefined || value === null || value === "") { + value = this.getOption("filter.defaultValue"); + if (value === undefined || value === null) { + value = disabledRequestMarker.toString(); + } + } + + const formatter = new Formatter({filter: encodeURI(value)}); + const openMarker = this.getOption("filter.marker.open"); + let closeMarker = this.getOption("filter.marker.close"); + if (!closeMarker) { + closeMarker = openMarker; + } + + if (openMarker && closeMarker) { + formatter.setMarker(openMarker, closeMarker); + } + + return formatter.format(url); } /** @@ -2233,30 +2243,29 @@ function formatURL(url, value) { * @return {string} The formatted URL with the applied filters and markers. */ function filterFromRemoteByValue(optionUrl, value, openPopper) { - return new Processing(() => { - let url = formatURL.call(this, optionUrl, value); - if (url.indexOf(disabledRequestMarker.toString()) !== -1) { - return; - } - - fetchIt - .call(this, url, { - disableHiding: true, - }) - .then(() => { - checkOptionState.call(this); - if (openPopper === true) { - show.call(this); - } - }) - .catch((e) => { - throw e; - }); - }) - .run() - .catch((e) => { - throw e; - }); + return new Processing(() => { + let url = formatURL.call(this, optionUrl, value); + if (url.indexOf(disabledRequestMarker.toString()) !== -1) { + return; + } + + fetchIt + .call(this, url, { + disableHiding: true, + }) + .then(() => { + checkOptionState.call(this); + if (openPopper === true) { + show.call(this); + } + }) + .catch((e) => { + addErrorAttribute(this, e); + setStatusOrRemoveBadges.call(this, "error"); + }); + }).run().catch((e) => { + throw e; + }); } /** @@ -2264,50 +2273,50 @@ function filterFromRemoteByValue(optionUrl, value, openPopper) { * @private */ function handleOptionKeyboardEvents(event) { - const shiftKey = event?.["shiftKey"]; - - switch (event?.["code"]) { - case "Escape": - toggle.call(this); - event.preventDefault(); - break; - case "Enter": - case "Space": - const path = event.composedPath(); - const element = path?.[0]; - if (element instanceof HTMLElement) { - const input = element.getElementsByTagName("input"); - if (!input) { - return; - } - fireEvent(input, "click"); - } - event.preventDefault(); - break; - - case "Tab" && shiftKey === true: - case "ArrowUp": - activateCurrentOption.call(this, FOCUS_DIRECTION_UP); - event.preventDefault(); - break; - - case "Tab" && !shiftKey: - case "ArrowLeft": - case "ArrowRight": - // handled by tree select - break; - case "ArrowDown": - activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); - event.preventDefault(); - break; - default: - const p = event.composedPath(); - if (p?.[0] instanceof HTMLInputElement) { - return; - } - focusFilter.call(this); - break; - } + const shiftKey = event?.["shiftKey"]; + + switch (event?.["code"]) { + case "Escape": + toggle.call(this); + event.preventDefault(); + break; + case "Enter": + case "Space": + const path = event.composedPath(); + const element = path?.[0]; + if (element instanceof HTMLElement) { + const input = element.getElementsByTagName("input"); + if (!input) { + return; + } + fireEvent(input, "click"); + } + event.preventDefault(); + break; + + case "Tab" && shiftKey === true: + case "ArrowUp": + activateCurrentOption.call(this, FOCUS_DIRECTION_UP); + event.preventDefault(); + break; + + case "Tab" && !shiftKey: + case "ArrowLeft": + case "ArrowRight": + // handled by tree select + break; + case "ArrowDown": + activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN); + event.preventDefault(); + break; + default: + const p = event.composedPath(); + if (p?.[0] instanceof HTMLInputElement) { + return; + } + focusFilter.call(this); + break; + } } /** @@ -2315,33 +2324,33 @@ function handleOptionKeyboardEvents(event) { * @return {string} */ function getFilterMode() { - switch (this.getOption("filter.mode")) { - case FILTER_MODE_OPTIONS: - return FILTER_MODE_OPTIONS; - case FILTER_MODE_REMOTE: - return FILTER_MODE_REMOTE; - default: - return FILTER_MODE_DISABLED; - } + switch (this.getOption("filter.mode")) { + case FILTER_MODE_OPTIONS: + return FILTER_MODE_OPTIONS; + case FILTER_MODE_REMOTE: + return FILTER_MODE_REMOTE; + default: + return FILTER_MODE_DISABLED; + } } /** * @private */ function blurFilter() { - if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) { - return; - } + if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) { + return; + } - if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { - return; - } + if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { + return; + } - this[popperFilterContainerElementSymbol].classList.remove("active"); - this[popperFilterContainerElementSymbol].blur(); + this[popperFilterContainerElementSymbol].classList.remove("active"); + this[popperFilterContainerElementSymbol].blur(); - this[inlineFilterElementSymbol].classList.remove("active"); - this[inlineFilterElementSymbol].blur(); + this[inlineFilterElementSymbol].classList.remove("active"); + this[inlineFilterElementSymbol].blur(); } /** @@ -2349,25 +2358,25 @@ function blurFilter() { * @param focusOptions */ function focusPopperFilter(focusOptions) { - this[popperFilterContainerElementSymbol].classList.remove("d-none"); - this[popperFilterElementSymbol].classList.add("active"); - this[inlineFilterElementSymbol].classList.remove("active"); - this[inlineFilterElementSymbol].classList.add("d-none"); - - if (!(this[popperFilterElementSymbol] instanceof HTMLElement)) { - addErrorAttribute(this, "Missing Popper Filter Element."); - return; - } - - // visibility is set to visible, because focus() does not work on invisible elements - // and the class definition is assigned later in the processing - setTimeout(() => { - if (focusOptions === undefined || focusOptions === null) { - this[popperFilterElementSymbol].focus(); - } else { - this[popperFilterElementSymbol].focus(focusOptions); - } - }, 100); + this[popperFilterContainerElementSymbol].classList.remove("d-none"); + this[popperFilterElementSymbol].classList.add("active"); + this[inlineFilterElementSymbol].classList.remove("active"); + this[inlineFilterElementSymbol].classList.add("d-none"); + + if (!(this[popperFilterElementSymbol] instanceof HTMLElement)) { + addErrorAttribute(this, "Missing Popper Filter Element."); + return; + } + + // visibility is set to visible, because focus() does not work on invisible elements + // and the class definition is assigned later in the processing + setTimeout(() => { + if (focusOptions === undefined || focusOptions === null) { + this[popperFilterElementSymbol].focus(); + } else { + this[popperFilterElementSymbol].focus(focusOptions); + } + }, 100); } /** @@ -2375,44 +2384,44 @@ function focusPopperFilter(focusOptions) { * @param focusOptions */ function focusInlineFilter(focusOptions) { - const options = this.getOption("options"); - if ( - (!isArray(options) || options.length === 0) && - getFilterMode.call(this) !== FILTER_MODE_REMOTE - ) { - return; - } - - this[popperFilterContainerElementSymbol].classList.add("d-none"); - this[inlineFilterElementSymbol].classList.add("active"); - this[inlineFilterElementSymbol].classList.remove("d-none"); - - // visibility is set to visible, because focus() does not work on invisible elements - // and the class definition is assigned later in the processing - setTimeout(() => { - if (focusOptions === undefined || focusOptions === null) { - this[inlineFilterElementSymbol].focus(); - } else { - this[inlineFilterElementSymbol].focus(focusOptions); - } - }, 100); + const options = this.getOption("options"); + if ( + (!isArray(options) || options.length === 0) && + getFilterMode.call(this) !== FILTER_MODE_REMOTE + ) { + return; + } + + this[popperFilterContainerElementSymbol].classList.add("d-none"); + this[inlineFilterElementSymbol].classList.add("active"); + this[inlineFilterElementSymbol].classList.remove("d-none"); + + // visibility is set to visible, because focus() does not work on invisible elements + // and the class definition is assigned later in the processing + setTimeout(() => { + if (focusOptions === undefined || focusOptions === null) { + this[inlineFilterElementSymbol].focus(); + } else { + this[inlineFilterElementSymbol].focus(focusOptions); + } + }, 100); } /** * @private */ function focusFilter(focusOptions) { - if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { - this[popperFilterContainerElementSymbol].classList.add("d-none"); - this[inlineFilterElementSymbol].classList.add("d-none"); - return; - } + if (getFilterMode.call(this) === FILTER_MODE_DISABLED) { + this[popperFilterContainerElementSymbol].classList.add("d-none"); + this[inlineFilterElementSymbol].classList.add("d-none"); + return; + } - if (this.getOption("filter.position") === FILTER_POSITION_INLINE) { - return focusInlineFilter.call(this, focusOptions); - } + if (this.getOption("filter.position") === FILTER_POSITION_INLINE) { + return focusInlineFilter.call(this, focusOptions); + } - return focusPopperFilter.call(this, focusOptions); + return focusPopperFilter.call(this, focusOptions); } /** @@ -2422,39 +2431,40 @@ function focusFilter(focusOptions) { * @throws {Error} unsupported type */ function gatherState() { - const type = this.getOption("type"); - if (["radio", "checkbox"].indexOf(type) === -1) { - throw new Error("unsupported type"); - } - - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - const selection = []; - const elements = this.shadowRoot.querySelectorAll( - `input[type=${type}]:checked`, - ); - - for (const e of elements) { - selection.push({ - label: getSelectionLabel.call(this, e.value), - value: e.value, - }); - } - - setSelection - .call(this, selection) - .then(() => {}) - .catch((e) => { - addErrorAttribute(this, e); - }); - - if (this.getOption("features.closeOnSelect") === true) { - hide.call(this); - } - - return this; + const type = this.getOption("type"); + if (["radio", "checkbox"].indexOf(type) === -1) { + throw new Error("unsupported type"); + } + + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + const selection = []; + const elements = this.shadowRoot.querySelectorAll( + `input[type=${type}]:checked`, + ); + + for (const e of elements) { + selection.push({ + label: getSelectionLabel.call(this, e.value), + value: e.value, + }); + } + + setSelection + .call(this, selection) + .then(() => { + }) + .catch((e) => { + addErrorAttribute(this, e); + }); + + if (this.getOption("features.closeOnSelect") === true) { + hide.call(this); + } + + return this; } /** @@ -2463,21 +2473,22 @@ function gatherState() { * @throws {Error} unsupported type */ function clearSelection() { - const type = this.getOption("type"); - if (["radio", "checkbox"].indexOf(type) === -1) { - throw new Error("unsupported type"); - } - - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - setSelection - .call(this, []) - .then(() => {}) - .catch((e) => { - addErrorAttribute(this, e); - }); + const type = this.getOption("type"); + if (["radio", "checkbox"].indexOf(type) === -1) { + throw new Error("unsupported type"); + } + + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + setSelection + .call(this, []) + .then(() => { + }) + .catch((e) => { + addErrorAttribute(this, e); + }); } const optionAvailableDeadManSymbol = Symbol("optionAvailableDeadManSymbol"); @@ -2486,127 +2497,127 @@ const optionAvailableDeadManSymbol = Symbol("optionAvailableDeadManSymbol"); * @private */ function areOptionsAvailableAndInit() { - // against flickering - if (this[optionAvailableDeadManSymbol] instanceof DeadMansSwitch) { - try { - this[optionAvailableDeadManSymbol].touch(); - return; - } catch (e) { - delete this[optionAvailableDeadManSymbol]; - } - } - - this[optionAvailableDeadManSymbol] = new DeadMansSwitch(200, () => { - areOptionsAvailableAndInitInternal.call(this); - delete this[timerCallbackSymbol]; - }); + // against flickering + if (this[optionAvailableDeadManSymbol] instanceof DeadMansSwitch) { + try { + this[optionAvailableDeadManSymbol].touch(); + return; + } catch (e) { + delete this[optionAvailableDeadManSymbol]; + } + } + + this[optionAvailableDeadManSymbol] = new DeadMansSwitch(200, () => { + areOptionsAvailableAndInitInternal.call(this); + delete this[timerCallbackSymbol]; + }); } function areOptionsAvailableAndInitInternal() { - // prevent multiple calls - if (this[areOptionsAvailableAndInitSymbol] === undefined) { - this[areOptionsAvailableAndInitSymbol] = 0; - } - - if (this[areOptionsAvailableAndInitSymbol] > 0) { - this[areOptionsAvailableAndInitSymbol]--; - return true; - } - - this[areOptionsAvailableAndInitSymbol]++; - - const options = this.getOption("options"); - - if ( - options === undefined || - options === null || - (isArray(options) && options.length === 0) - ) { - setStatusOrRemoveBadges.call(this, "empty"); - - let msg = this.getOption("labels.no-options-available"); - - if ( - this.getOption("url") !== null && - this.getOption("features.lazyLoad") === true && - this[lazyLoadDoneSymbol] !== true - ) { - msg = this.getOption("labels.click-to-load-options"); - } - - if (this.getOption("filter.mode") === FILTER_MODE_REMOTE) { - msg = ""; - } else { - this.setOption("filter.defaultValue", undefined); - } - - if ( - containsAttributeToken(this[controlElementSymbol], "class", "open") === - true - ) { - msg = ""; - } - - this.setOption("messages.control", msg); - this.setOption("messages.summary", ""); - - if (this.getOption("features.emptyValueIfNoOptions") === true) { - this.value = ""; - } - addErrorAttribute(this, "No options available."); - return false; - } - - const selections = this.getOption("selection"); - if ( - selections === undefined || - selections === null || - selections.length === 0 - ) { - this.setOption( - "messages.control", - this.getOption("labels.select-an-option"), - ); - } else { - this.setOption("messages.control", ""); - } - - this.setOption("messages.summary", setSummaryAndControlText.call(this)); - - let updated = false; - let valueCounter = 1; - for (const option of options) { - if (option?.visibility === undefined) { - option.visibility = "visible"; - updated = true; - } - - if (option?.value === undefined && option?.label === undefined) { - option.value = `${valueCounter++}`; - option.label = option.value; - updated = true; - continue; - } - - if (option?.value === undefined) { - option.value = option.label; - updated = true; - } - - if (option?.label === undefined) { - option.label = option.value; - updated = true; - } - } - - if (updated) { - this.setOption("options", options); - } - - setStatusOrRemoveBadges.call(this); - - removeErrorAttribute(this, "No options available."); - return true; + // prevent multiple calls + if (this[areOptionsAvailableAndInitSymbol] === undefined) { + this[areOptionsAvailableAndInitSymbol] = 0; + } + + if (this[areOptionsAvailableAndInitSymbol] > 0) { + this[areOptionsAvailableAndInitSymbol]--; + return true; + } + + this[areOptionsAvailableAndInitSymbol]++; + + const options = this.getOption("options"); + + if ( + options === undefined || + options === null || + (isArray(options) && options.length === 0) + ) { + setStatusOrRemoveBadges.call(this, "empty"); + + let msg = this.getOption("labels.no-options-available"); + + if ( + this.getOption("url") !== null && + this.getOption("features.lazyLoad") === true && + this[lazyLoadDoneSymbol] !== true + ) { + msg = this.getOption("labels.click-to-load-options"); + } + + if (this.getOption("filter.mode") === FILTER_MODE_REMOTE) { + msg = ""; + } else { + this.setOption("filter.defaultValue", undefined); + } + + if ( + containsAttributeToken(this[controlElementSymbol], "class", "open") === + true + ) { + msg = ""; + } + + this.setOption("messages.control", msg); + this.setOption("messages.summary", ""); + + if (this.getOption("features.emptyValueIfNoOptions") === true) { + this.value = ""; + } + addErrorAttribute(this, "No options available."); + return false; + } + + const selections = this.getOption("selection"); + if ( + selections === undefined || + selections === null || + selections.length === 0 + ) { + this.setOption( + "messages.control", + this.getOption("labels.select-an-option"), + ); + } else { + this.setOption("messages.control", ""); + } + + this.setOption("messages.summary", setSummaryAndControlText.call(this)); + + let updated = false; + let valueCounter = 1; + for (const option of options) { + if (option?.visibility === undefined) { + option.visibility = "visible"; + updated = true; + } + + if (option?.value === undefined && option?.label === undefined) { + option.value = `${valueCounter++}`; + option.label = option.value; + updated = true; + continue; + } + + if (option?.value === undefined) { + option.value = option.label; + updated = true; + } + + if (option?.label === undefined) { + option.label = option.value; + updated = true; + } + } + + if (updated) { + this.setOption("options", options); + } + + setStatusOrRemoveBadges.call(this); + + removeErrorAttribute(this, "No options available."); + return true; } /** @@ -2614,30 +2625,30 @@ function areOptionsAvailableAndInitInternal() { * @throws {Error} no shadow-root is defined */ function checkOptionState() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - const elements = this.shadowRoot.querySelectorAll( - `[${ATTRIBUTE_ROLE}=option] input`, - ); - - let selection = this.getOption("selection"); - if (!isArray(selection)) { - selection = []; - } - - const checkedValues = selection.map((a) => { - return a.value; - }); - - for (const e of elements) { - if (checkedValues.indexOf(e.value) !== -1) { - if (e.checked !== true) e.checked = true; - } else { - if (e.checked !== false) e.checked = false; - } - } + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + const elements = this.shadowRoot.querySelectorAll( + `[${ATTRIBUTE_ROLE}=option] input`, + ); + + let selection = this.getOption("selection"); + if (!isArray(selection)) { + selection = []; + } + + const checkedValues = selection.map((a) => { + return a.value; + }); + + for (const e of elements) { + if (checkedValues.indexOf(e.value) !== -1) { + if (e.checked !== true) e.checked = true; + } else { + if (e.checked !== false) e.checked = false; + } + } } /** @@ -2646,41 +2657,41 @@ function checkOptionState() { * @return {Object} */ function convertValueToSelection(value) { - const selection = []; - - if (isString(value)) { - value = value - .split(",") - .map((a) => { - return a.trim(); - }) - .filter((a) => { - return a !== ""; - }); - } - - if (isString(value) || isInteger(value)) { - selection.push({ - label: getSelectionLabel.call(this, value), - value: value, - }); - } else if (isArray(value)) { - for (const v of value) { - selection.push({ - label: getSelectionLabel.call(this, v), - value: v, - }); - } - - value = value.join(","); - } else { - throw new Error("unsupported type"); - } - - return { - selection: selection, - value: value, - }; + const selection = []; + + if (isString(value)) { + value = value + .split(",") + .map((a) => { + return a.trim(); + }) + .filter((a) => { + return a !== ""; + }); + } + + if (isString(value) || isInteger(value)) { + selection.push({ + label: getSelectionLabel.call(this, value), + value: value, + }); + } else if (isArray(value)) { + for (const v of value) { + selection.push({ + label: getSelectionLabel.call(this, v), + value: v, + }); + } + + value = value.join(","); + } else { + throw new Error("unsupported type"); + } + + return { + selection: selection, + value: value, + }; } /** @@ -2689,25 +2700,25 @@ function convertValueToSelection(value) { * @return {string} */ function convertSelectionToValue(selection) { - const value = []; - - if (isArray(selection)) { - for (const obj of selection) { - const v = obj?.["value"]; - if (v !== undefined) value.push(`${v}`); - } - } - - if (value.length === 0) { - return ""; - } else if (value.length === 1) { - const v = value.pop(); - if (v === undefined) return ""; - if (v === null) return ""; - return `${v}`; - } - - return value.join(","); + const value = []; + + if (isArray(selection)) { + for (const obj of selection) { + const v = obj?.["value"]; + if (v !== undefined) value.push(`${v}`); + } + } + + if (value.length === 0) { + return ""; + } else if (value.length === 1) { + const v = value.pop(); + if (v === undefined) return ""; + if (v === null) return ""; + return `${v}`; + } + + return value.join(","); } /** @@ -2716,15 +2727,16 @@ function convertSelectionToValue(selection) { * @returns {boolean} */ function isValueIsEmpty(value) { - let equivalents = this.getOption("empty.equivalents"); - if (!isArray(equivalents)) { - if (equivalents === undefined) { - return false; - } - equivalents = [equivalents]; - } - - return equivalents.indexOf(value) !== -1; + debugger + let equivalents = this.getOption("empty.equivalents"); + if (!isArray(equivalents)) { + if (equivalents === undefined) { + return false; + } + equivalents = [equivalents]; + } + + return equivalents.indexOf(value) !== -1; } /** @@ -2733,18 +2745,18 @@ function isValueIsEmpty(value) { * @returns {*} */ function isValueIsEmptyThenGetNormalize(value) { - let emptyDefault = null; - if (this.getOption("type") === "checkbox") { - emptyDefault = this.getOption("empty.defaultValueCheckbox"); - } else { - emptyDefault = this.getOption("empty.defaultValueRadio"); - } - - if (isValueIsEmpty.call(this, value)) { - return emptyDefault; - } - - return value; + let emptyDefault = null; + if (this.getOption("type") === "checkbox") { + emptyDefault = this.getOption("empty.defaultValueCheckbox"); + } else { + emptyDefault = this.getOption("empty.defaultValueRadio"); + } + + if (isValueIsEmpty.call(this, value)) { + return emptyDefault; + } + + return value; } /** @@ -2753,97 +2765,97 @@ function isValueIsEmptyThenGetNormalize(value) { * @returns {Promise<unknown | void>} */ function setSelection(selection) { - const self = this; - - selection = isValueIsEmptyThenGetNormalize.call(this, selection); - - if (isString(selection) || isInteger(selection)) { - const result = convertValueToSelection.call(this, selection); - selection = result?.selection; - } - - validateArray(selection); - - let resultSelection = []; - for (let i = 0; i < selection.length; i++) { - if (isValueIsEmpty.call(this, selection[i].value)) { - continue; - } - - let l = getSelectionLabel.call(this, selection[i].value); - if (l === selection[i].value) { - l = selection[i].label; - } - - resultSelection.push({ - label: l, - value: selection[i].value, - }); - } - - selection = resultSelection; - - this.setOption("selection", selection); - - checkOptionState.call(this); - setSummaryAndControlText.call(this); - - try { - this?.setFormValue(this.value); - } catch (e) { - addErrorAttribute(this, e); - } - - fireCustomEvent(this, "monster-selected", { - selection, - }); - - fireEvent(this, "change"); // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/291 - - if (this[runLookupOnceSymbol] !== true && selection.length > 0) { - this[runLookupOnceSymbol] = true; - - const lazyLoadFlag = - this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true; - const remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE; - if (lazyLoadFlag || remoteFilterFlag) { - lookupSelection.call(self); - } - } - - return new Processing(() => { - const CLASSNAME = "selected"; - - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - const notSelected = this.shadowRoot.querySelectorAll(":not(:checked)"); - - if (notSelected) { - notSelected.forEach((node) => { - const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`); - if (parent) { - parent.classList.remove(CLASSNAME); - } - }); - } - - const selected = this.shadowRoot.querySelectorAll(":checked"); - - if (selected) { - selected.forEach((node) => { - const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`); - if (parent) { - parent.classList.add(CLASSNAME); - } - }); - } - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); + const self = this; + + selection = isValueIsEmptyThenGetNormalize.call(this, selection); + + if (isString(selection) || isInteger(selection)) { + const result = convertValueToSelection.call(this, selection); + selection = result?.selection; + } + + validateArray(selection); + + let resultSelection = []; + for (let i = 0; i < selection.length; i++) { + if (isValueIsEmpty.call(this, selection[i].value)) { + continue; + } + + let l = getSelectionLabel.call(this, selection[i].value); + if (l === selection[i].value) { + l = selection[i].label; + } + + resultSelection.push({ + label: l, + value: selection[i].value, + }); + } + + selection = resultSelection; + + this.setOption("selection", selection); + + checkOptionState.call(this); + setSummaryAndControlText.call(this); + + try { + this?.setFormValue(this.value); + } catch (e) { + addErrorAttribute(this, e); + } + + fireCustomEvent(this, "monster-selected", { + selection, + }); + + fireEvent(this, "change"); // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/291 + + if (this[runLookupOnceSymbol] !== true && selection.length > 0) { + this[runLookupOnceSymbol] = true; + + const lazyLoadFlag = + this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true; + const remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE; + if (lazyLoadFlag || remoteFilterFlag) { + lookupSelection.call(self); + } + } + + return new Processing(() => { + const CLASSNAME = "selected"; + + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + const notSelected = this.shadowRoot.querySelectorAll(":not(:checked)"); + + if (notSelected) { + notSelected.forEach((node) => { + const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`); + if (parent) { + parent.classList.remove(CLASSNAME); + } + }); + } + + const selected = this.shadowRoot.querySelectorAll(":checked"); + + if (selected) { + selected.forEach((node) => { + const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`); + if (parent) { + parent.classList.add(CLASSNAME); + } + }); + } + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); } /** @@ -2854,220 +2866,219 @@ function setSelection(selection) { * @throws {TypeError} unsupported response */ function fetchData(url) { - const self = this; - if (!url) url = this.getOption("url"); - if (!url) return Promise.resolve(); - - const fetchOptions = this.getOption("fetch", {}); - - let delayWatch = false; - - // if fetch short time, do not show loading badge, because of flickering - requestAnimationFrame(() => { - if (delayWatch === true) return; - setStatusOrRemoveBadges.call(this, "loading"); - delayWatch = true; - }); - - url = formatURL.call(this, url); - - self[isLoadingSymbol] = true; - const global = getGlobal(); - return global - .fetch(url, fetchOptions) - .then((response) => { - self[isLoadingSymbol] = false; - delayWatch = true; - - if (!(response.status >= 200 && response.status < 300)) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - return response.text(); - } - - throw new TypeError(`unsupported response ${contentType}`); - }) - .then((text) => { - try { - return Promise.resolve(JSON.parse(String(text))); - } catch (e) { - throw new TypeError("the result cannot be parsed, check the URL"); - } - }) - .catch((e) => { - self[isLoadingSymbol] = false; - delayWatch = true; - throw e; - }); + const self = this; + if (!url) url = this.getOption("url"); + if (!url) return Promise.resolve(); + + const fetchOptions = this.getOption("fetch", {}); + + let delayWatch = false; + + // if fetch short time, do not show loading badge, because of flickering + requestAnimationFrame(() => { + if (delayWatch === true) return; + setStatusOrRemoveBadges.call(this, "loading"); + delayWatch = true; + }); + + url = formatURL.call(this, url); + + self[isLoadingSymbol] = true; + const global = getGlobal(); + return global + .fetch(url, fetchOptions) + .then((response) => { + self[isLoadingSymbol] = false; + delayWatch = true; + + if (!(response.status >= 200 && response.status < 300)) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + return response.text(); + } + + throw new TypeError(`unsupported response ${contentType}`); + }) + .then((text) => { + try { + return Promise.resolve(JSON.parse(String(text))); + } catch (e) { + throw new TypeError("the result cannot be parsed, check the URL"); + } + }) + .catch((e) => { + self[isLoadingSymbol] = false; + delayWatch = true; + throw e; + }); } /** * @private */ function hide() { - this[popperElementSymbol].style.display = "none"; - setStatusOrRemoveBadges.call(this, "closed"); - removeAttributeToken(this[controlElementSymbol], "class", "open"); + this[popperElementSymbol].style.display = "none"; + setStatusOrRemoveBadges.call(this, "closed"); + removeAttributeToken(this[controlElementSymbol], "class", "open"); } /** * @private */ function show() { - if (this.getOption("disabled", undefined) === true) { - return; - } - - if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) { - return; - } - - setTotalText.call(this); - - focusFilter.call(this); - - const lazyLoadFlag = - this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true; - - if (lazyLoadFlag === true) { - this[lazyLoadDoneSymbol] = true; - setStatusOrRemoveBadges.call(this, "loading"); - - new Processing(200, () => { - this.fetch() - .then(() => { - checkOptionState.call(this); - requestAnimationFrame(() => { - show.call(this); - }); - }) - .catch((e) => { - addErrorAttribute(this, e); - setStatusOrRemoveBadges.call(this, "error"); - }); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - setStatusOrRemoveBadges.call(this, "error"); - }); - - return; - } - - initDefaultOptionsFromUrl.call(this); - - const hasPopperFilterFlag = - this.getOption("filter.position") === FILTER_POSITION_POPPER && - getFilterMode.call(this) !== FILTER_MODE_DISABLED; - - const options = getOptionElements.call(this); - if (options.length === 0 && hasPopperFilterFlag === false) { - return; - } - - this[popperElementSymbol].style.visibility = "hidden"; - this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK; - setStatusOrRemoveBadges.call(this, "open"); - - addAttributeToken(this[controlElementSymbol], "class", "open"); - - new Processing(() => { - calcAndSetOptionsDimension.call(this); - focusFilter.call(this); - this[popperElementSymbol].style.removeProperty("visibility"); - updatePopper.call(this); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); + if (this.getOption("disabled", undefined) === true) { + return; + } + + if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) { + return; + } + + setTotalText.call(this); + + focusFilter.call(this); + + const lazyLoadFlag = + this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true; + + if (lazyLoadFlag === true) { + this[lazyLoadDoneSymbol] = true; + setStatusOrRemoveBadges.call(this, "loading"); + + new Processing(200, () => { + this.fetch() + .then(() => { + checkOptionState.call(this); + requestAnimationFrame(() => { + show.call(this); + }); + }) + .catch((e) => { + addErrorAttribute(this, e); + setStatusOrRemoveBadges.call(this, "error"); + }); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + setStatusOrRemoveBadges.call(this, "error"); + }); + + return; + } + + initDefaultOptionsFromUrl.call(this); + + const hasPopperFilterFlag = + this.getOption("filter.position") === FILTER_POSITION_POPPER && + getFilterMode.call(this) !== FILTER_MODE_DISABLED; + + const options = getOptionElements.call(this); + if (options.length === 0 && hasPopperFilterFlag === false) { + return; + } + + this[popperElementSymbol].style.visibility = "hidden"; + this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK; + setStatusOrRemoveBadges.call(this, "open"); + + addAttributeToken(this[controlElementSymbol], "class", "open"); + + new Processing(() => { + calcAndSetOptionsDimension.call(this); + focusFilter.call(this); + this[popperElementSymbol].style.removeProperty("visibility"); + updatePopper.call(this); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); } function initDefaultOptionsFromUrl() { - const url = this.getOption("filter.defaultOptionsUrl"); - if (!url) { - return; - } - - this.setOption("filter.defaultOptionsUrl", null); - - fetchData - .call(this, url) - .then((data) => { - this[cleanupOptionsListSymbol] = false; - importOptionsIntern.call(this, data); - setStatusOrRemoveBadges.call(this, "open"); - initTotal.call(this, data); - }) - .catch((e) => { - addErrorAttribute(this, e); - setStatusOrRemoveBadges.call(this, "error"); - }); + const url = this.getOption("filter.defaultOptionsUrl"); + if (!url) { + return; + } + + this.setOption("filter.defaultOptionsUrl", null); + + fetchData + .call(this, url) + .then((data) => { + this[cleanupOptionsListSymbol] = false; + importOptionsIntern.call(this, data); + setStatusOrRemoveBadges.call(this, "open"); + initTotal.call(this, data); + }).catch((e) => { + addErrorAttribute(this, e); + setStatusOrRemoveBadges.call(this, "error"); + }); } /** * @private */ function initTotal() { - if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { - return; - } - - const url = this.getOption("remoteInfo.url"); - const mapping = this.getOption("mapping.total"); - - if (!isString(mapping) || !isString(url)) { - return; - } - - getGlobal() - .fetch(url) - .then((response) => { - if (!response.ok) { - // Improved status checking using `response.ok` - addErrorAttribute( - this, - `HTTP error status: ${response.status} - ${response.statusText}`, - ); - return; - } - - return response.text(); - }) - .then((text) => { - try { - const data = JSON.parse(String(text)); - const pathfinder = new Pathfinder(data); - const total = pathfinder.getVia(mapping); - - if (!isInteger(total)) { - addErrorAttribute(this, "total is not an integer"); - return; - } - - this.setOption("total", total); - } catch (e) { - addErrorAttribute(this, e); - } - }) - .catch((e) => { - addErrorAttribute(this, e); - }); + if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) { + return; + } + + const url = this.getOption("remoteInfo.url"); + const mapping = this.getOption("mapping.total"); + + if (!isString(mapping) || !isString(url)) { + return; + } + + getGlobal() + .fetch(url) + .then((response) => { + if (!response.ok) { + // Improved status checking using `response.ok` + addErrorAttribute( + this, + `HTTP error status: ${response.status} - ${response.statusText}`, + ); + return; + } + + return response.text(); + }) + .then((text) => { + try { + const data = JSON.parse(String(text)); + const pathfinder = new Pathfinder(data); + const total = pathfinder.getVia(mapping); + + if (!isInteger(total)) { + addErrorAttribute(this, "total is not an integer"); + return; + } + + this.setOption("total", total); + } catch (e) { + addErrorAttribute(this, e); + } + }) + .catch((e) => { + addErrorAttribute(this, e); + }); } /** * @private */ function toggle() { - if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) { - hide.call(this); - } else { - show.call(this); - } + if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) { + hide.call(this); + } else { + show.call(this); + } } /** @@ -3076,217 +3087,217 @@ function toggle() { * @fires monster-selection-cleared */ function initEventHandler() { - const self = this; - - /** - * @param {Event} event - */ - self[clearOptionEventHandler] = (event) => { - const element = findTargetElementFromEvent( - event, - ATTRIBUTE_ROLE, - "remove-badge", - ); - - if (element instanceof HTMLElement) { - const badge = findClosestByAttribute(element, ATTRIBUTE_ROLE, "badge"); - if (badge instanceof HTMLElement) { - const value = badge.getAttribute(`${ATTRIBUTE_PREFIX}value`); - - let selection = self.getOption("selection"); - selection = selection.filter((b) => { - return value !== b.value; - }); - - setSelection - .call(self, selection) - .then(() => { - fireCustomEvent(self, "monster-selection-removed", { - value, - }); - }) - .catch((e) => { - addErrorAttribute(self, e); - }); - } - } - }; - - /** - * @param {Event} event - */ - self[closeEventHandler] = (event) => { - const path = event.composedPath(); - - for (const [, element] of Object.entries(path)) { - if (element === self) { - return; - } - } - hide.call(self); - }; - - /** - * @param {Event} event - */ - self[inputEventHandler] = (event) => { - const path = event.composedPath(); - const element = path?.[0]; - - if (element instanceof HTMLElement) { - if ( - element.hasAttribute(ATTRIBUTE_ROLE) && - element.getAttribute(ATTRIBUTE_ROLE) === "option-control" - ) { - fireCustomEvent(self, "monster-change", { - type: event.type, - value: element.value, - checked: element.checked, - }); - } else if ( - element.hasAttribute(ATTRIBUTE_ROLE) && - element.getAttribute(ATTRIBUTE_ROLE) === "filter" - ) { - } - } - }; - - /** - * @param {Event} event - */ - self[changeEventHandler] = (event) => { - gatherState.call(self); - fireCustomEvent(self, "monster-changed", event?.detail); - }; - - self[keyEventHandler] = (event) => { - const path = event.composedPath(); - const element = path.shift(); - - let role; - - if (element instanceof HTMLElement) { - if (element.hasAttribute(ATTRIBUTE_ROLE)) { - role = element.getAttribute(ATTRIBUTE_ROLE); - } else if (element === this) { - show.call(this); - // focusFilter.call(self); - } else { - const e = element.closest(`[${ATTRIBUTE_ROLE}]`); - if (e instanceof HTMLElement && e.hasAttribute(ATTRIBUTE_ROLE)) { - role = e.getAttribute(ATTRIBUTE_ROLE); - } - } - } else { - return; - } - - switch (role) { - case "filter": - handleFilterKeyboardEvents.call(self, event); - break; - case "option-label": - case "option-control": - case "option": - handleOptionKeyboardEvents.call(self, event); - break; - case "control": - case "toggle": - handleToggleKeyboardEvents.call(self, event); - break; - } - }; - - const types = self.getOption("toggleEventType", ["click"]); - - for (const [, type] of Object.entries(types)) { - self[controlElementSymbol] - .querySelector(`[${ATTRIBUTE_ROLE}="container"]`) - .addEventListener(type, function (event) { - const element = findTargetElementFromEvent( - event, - ATTRIBUTE_ROLE, - "remove-badge", - ); - - if (element instanceof HTMLElement) { - return; - } - - toggle.call(self); - }); - - self[controlElementSymbol] - .querySelector(`[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`) - .addEventListener(type, function (event) { - if (self.getOption("disabled", undefined) === true) { - return; - } - - const path = event.composedPath(); - const element = path?.[0]; - if (element instanceof HTMLElement) { - const control = element.closest( - `[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`, - ); - if (control instanceof HTMLElement) { - if (control.classList.contains("clear")) { - clearSelection.call(self); - - fireCustomEvent(self, "monster-selection-cleared", {}); - } else { - const element = findTargetElementFromEvent( - event, - ATTRIBUTE_ROLE, - "remove-badge", - ); - if (element instanceof HTMLElement) { - return; - } - - toggle.call(self); - } - } - } - }); - - // badge, selection - self.addEventListener(type, self[clearOptionEventHandler]); - } - - self.addEventListener("monster-change", self[changeEventHandler]); - self.addEventListener("input", self[inputEventHandler]); - self.addEventListener("keydown", self[keyEventHandler]); - - const callback = () => { - if (this[debounceOptionsMutationObserverSymbol] instanceof DeadMansSwitch) { - try { - this[debounceOptionsMutationObserverSymbol].touch(); - return; - } catch (e) { - delete this[debounceOptionsMutationObserverSymbol]; - } - } - - this[debounceOptionsMutationObserverSymbol] = new DeadMansSwitch( - 100, - () => { - checkOptionState.call(self); - calcAndSetOptionsDimension.call(self); - updatePopper.call(self); - delete this[debounceOptionsMutationObserverSymbol]; - }, - ); - }; - - const observer = new MutationObserver(callback); - observer.observe(self[optionsElementSymbol], { - attributes: false, - childList: true, - subtree: true, - }); - - return self; + const self = this; + + /** + * @param {Event} event + */ + self[clearOptionEventHandler] = (event) => { + const element = findTargetElementFromEvent( + event, + ATTRIBUTE_ROLE, + "remove-badge", + ); + + if (element instanceof HTMLElement) { + const badge = findClosestByAttribute(element, ATTRIBUTE_ROLE, "badge"); + if (badge instanceof HTMLElement) { + const value = badge.getAttribute(`${ATTRIBUTE_PREFIX}value`); + + let selection = self.getOption("selection"); + selection = selection.filter((b) => { + return value !== b.value; + }); + + setSelection + .call(self, selection) + .then(() => { + fireCustomEvent(self, "monster-selection-removed", { + value, + }); + }) + .catch((e) => { + addErrorAttribute(self, e); + }); + } + } + }; + + /** + * @param {Event} event + */ + self[closeEventHandler] = (event) => { + const path = event.composedPath(); + + for (const [, element] of Object.entries(path)) { + if (element === self) { + return; + } + } + hide.call(self); + }; + + /** + * @param {Event} event + */ + self[inputEventHandler] = (event) => { + const path = event.composedPath(); + const element = path?.[0]; + + if (element instanceof HTMLElement) { + if ( + element.hasAttribute(ATTRIBUTE_ROLE) && + element.getAttribute(ATTRIBUTE_ROLE) === "option-control" + ) { + fireCustomEvent(self, "monster-change", { + type: event.type, + value: element.value, + checked: element.checked, + }); + } else if ( + element.hasAttribute(ATTRIBUTE_ROLE) && + element.getAttribute(ATTRIBUTE_ROLE) === "filter" + ) { + } + } + }; + + /** + * @param {Event} event + */ + self[changeEventHandler] = (event) => { + gatherState.call(self); + fireCustomEvent(self, "monster-changed", event?.detail); + }; + + self[keyEventHandler] = (event) => { + const path = event.composedPath(); + const element = path.shift(); + + let role; + + if (element instanceof HTMLElement) { + if (element.hasAttribute(ATTRIBUTE_ROLE)) { + role = element.getAttribute(ATTRIBUTE_ROLE); + } else if (element === this) { + show.call(this); + // focusFilter.call(self); + } else { + const e = element.closest(`[${ATTRIBUTE_ROLE}]`); + if (e instanceof HTMLElement && e.hasAttribute(ATTRIBUTE_ROLE)) { + role = e.getAttribute(ATTRIBUTE_ROLE); + } + } + } else { + return; + } + + switch (role) { + case "filter": + handleFilterKeyboardEvents.call(self, event); + break; + case "option-label": + case "option-control": + case "option": + handleOptionKeyboardEvents.call(self, event); + break; + case "control": + case "toggle": + handleToggleKeyboardEvents.call(self, event); + break; + } + }; + + const types = self.getOption("toggleEventType", ["click"]); + + for (const [, type] of Object.entries(types)) { + self[controlElementSymbol] + .querySelector(`[${ATTRIBUTE_ROLE}="container"]`) + .addEventListener(type, function (event) { + const element = findTargetElementFromEvent( + event, + ATTRIBUTE_ROLE, + "remove-badge", + ); + + if (element instanceof HTMLElement) { + return; + } + + toggle.call(self); + }); + + self[controlElementSymbol] + .querySelector(`[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`) + .addEventListener(type, function (event) { + if (self.getOption("disabled", undefined) === true) { + return; + } + + const path = event.composedPath(); + const element = path?.[0]; + if (element instanceof HTMLElement) { + const control = element.closest( + `[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`, + ); + if (control instanceof HTMLElement) { + if (control.classList.contains("clear")) { + clearSelection.call(self); + + fireCustomEvent(self, "monster-selection-cleared", {}); + } else { + const element = findTargetElementFromEvent( + event, + ATTRIBUTE_ROLE, + "remove-badge", + ); + if (element instanceof HTMLElement) { + return; + } + + toggle.call(self); + } + } + } + }); + + // badge, selection + self.addEventListener(type, self[clearOptionEventHandler]); + } + + self.addEventListener("monster-change", self[changeEventHandler]); + self.addEventListener("input", self[inputEventHandler]); + self.addEventListener("keydown", self[keyEventHandler]); + + const callback = () => { + if (this[debounceOptionsMutationObserverSymbol] instanceof DeadMansSwitch) { + try { + this[debounceOptionsMutationObserverSymbol].touch(); + return; + } catch (e) { + delete this[debounceOptionsMutationObserverSymbol]; + } + } + + this[debounceOptionsMutationObserverSymbol] = new DeadMansSwitch( + 100, + () => { + checkOptionState.call(self); + calcAndSetOptionsDimension.call(self); + updatePopper.call(self); + delete this[debounceOptionsMutationObserverSymbol]; + }, + ); + }; + + const observer = new MutationObserver(callback); + observer.observe(self[optionsElementSymbol], { + attributes: false, + childList: true, + subtree: true, + }); + + return self; } /** @@ -3294,69 +3305,69 @@ function initEventHandler() { * @return {Select} */ function setStatusOrRemoveBadges(suggestion) { - requestAnimationFrame(() => { - const selection = this.getOption("selection"); - - const clearAllFlag = - isArray(selection) && - selection.length > 0 && - this.getOption("features.clearAll") === true; - - const current = this.getOption("classes.statusOrRemoveBadge"); - - if (suggestion === "error") { - if (current !== "error") { - this.setOption("classes.statusOrRemoveBadge", "error"); - } - return; - } - - if (this[isLoadingSymbol] === true) { - if (current !== "loading") { - this.setOption("classes.statusOrRemoveBadge", "loading"); - } - return; - } - - if (suggestion === "loading") { - if (current !== "loading") { - this.setOption("classes.statusOrRemoveBadge", "loading"); - } - return; - } - - if (this[controlElementSymbol].classList.contains("open")) { - if (current !== "open") { - this.setOption("classes.statusOrRemoveBadge", "open"); - } - return; - } - - if (clearAllFlag) { - if (current !== "clear") { - this.setOption("classes.statusOrRemoveBadge", "clear"); - } - return; - } - - const options = this.getOption("options"); - if ( - options === undefined || - options === null || - (isArray(options) && options.length === 0) - ) { - if (current !== "empty") { - this.setOption("classes.statusOrRemoveBadge", "empty"); - } - return; - } - - if (suggestion) { - if (current !== suggestion) { - this.setOption("classes.statusOrRemoveBadge", suggestion); - } - } - }); + requestAnimationFrame(() => { + const selection = this.getOption("selection"); + + const clearAllFlag = + isArray(selection) && + selection.length > 0 && + this.getOption("features.clearAll") === true; + + const current = this.getOption("classes.statusOrRemoveBadge"); + + if (suggestion === "error") { + if (current !== "error") { + this.setOption("classes.statusOrRemoveBadge", "error"); + } + return; + } + + if (this[isLoadingSymbol] === true) { + if (current !== "loading") { + this.setOption("classes.statusOrRemoveBadge", "loading"); + } + return; + } + + if (suggestion === "loading") { + if (current !== "loading") { + this.setOption("classes.statusOrRemoveBadge", "loading"); + } + return; + } + + if (this[controlElementSymbol].classList.contains("open")) { + if (current !== "open") { + this.setOption("classes.statusOrRemoveBadge", "open"); + } + return; + } + + if (clearAllFlag) { + if (current !== "clear") { + this.setOption("classes.statusOrRemoveBadge", "clear"); + } + return; + } + + const options = this.getOption("options"); + if ( + options === undefined || + options === null || + (isArray(options) && options.length === 0) + ) { + if (current !== "empty") { + this.setOption("classes.statusOrRemoveBadge", "empty"); + } + return; + } + + if (suggestion) { + if (current !== suggestion) { + this.setOption("classes.statusOrRemoveBadge", suggestion); + } + } + }); } /** @@ -3365,72 +3376,72 @@ function setStatusOrRemoveBadges(suggestion) { * @throws {Error} no shadow-root is defined */ function initControlReferences() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - this[controlElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=control]`, - ); - this[selectionElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=selection]`, - ); - this[containerElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=container]`, - ); - this[popperElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=popper]`, - ); - this[inlineFilterElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`, - ); - this[popperFilterElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=filter][name="popper-filter"]`, - ); - this[popperFilterContainerElementSymbol] = - this[popperFilterElementSymbol].parentElement; - this[optionsElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=options]`, - ); - this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="no-options"]`, - ); - this[statusOrRemoveBadgesElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=status-or-remove-badges]`, - ); - - this[remoteInfoElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=remote-info]`, - ); + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + this[controlElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=control]`, + ); + this[selectionElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=selection]`, + ); + this[containerElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=container]`, + ); + this[popperElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=popper]`, + ); + this[inlineFilterElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`, + ); + this[popperFilterElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=filter][name="popper-filter"]`, + ); + this[popperFilterContainerElementSymbol] = + this[popperFilterElementSymbol].parentElement; + this[optionsElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=options]`, + ); + this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="no-options"]`, + ); + this[statusOrRemoveBadgesElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=status-or-remove-badges]`, + ); + + this[remoteInfoElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=remote-info]`, + ); } /** * @private */ function updatePopper() { - if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) { - return; - } - - if (this.getOption("disabled", false) === true) { - return; - } - - new Processing(() => { - calcAndSetOptionsDimension.call(this); - positionPopper.call( - this, - this[controlElementSymbol], - this[popperElementSymbol], - this.getOption("popper", {}), - ); - }) - .run() - .catch((e) => { - addErrorAttribute(this, e); - }); - - return this; + if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) { + return; + } + + if (this.getOption("disabled", false) === true) { + return; + } + + new Processing(() => { + calcAndSetOptionsDimension.call(this); + positionPopper.call( + this, + this[controlElementSymbol], + this[popperElementSymbol], + this.getOption("popper", {}), + ); + }) + .run() + .catch((e) => { + addErrorAttribute(this, e); + }); + + return this; } /** @@ -3438,8 +3449,8 @@ function updatePopper() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <template id="options"> <div data-monster-role="option" tabindex="-1" data-monster-attributes="