@schukai/monster 3.120.0 → 3.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,13 +12,11 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol } from "../../constants.mjs";
16
- import { internalSymbol } from "../../constants.mjs";
15
+ import { instanceSymbol, internalSymbol } from "../../constants.mjs";
17
16
  import { buildMap, build as buildValue } from "../../data/buildmap.mjs";
18
- import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
19
- import { positionPopper } from "./util/floating-ui.mjs";
20
17
  import {
21
18
  addAttributeToken,
19
+ containsAttributeToken,
22
20
  findClosestByAttribute,
23
21
  removeAttributeToken,
24
22
  } from "../../dom/attributes.mjs";
@@ -29,12 +27,18 @@ import {
29
27
  getSlottedElements,
30
28
  registerCustomElement,
31
29
  } from "../../dom/customelement.mjs";
30
+ import { addErrorAttribute, removeErrorAttribute } from "../../dom/error.mjs";
32
31
  import {
33
32
  findTargetElementFromEvent,
34
33
  fireCustomEvent,
35
34
  fireEvent,
36
35
  } from "../../dom/events.mjs";
36
+ import { getLocaleOfDocument } from "../../dom/locale.mjs";
37
37
  import { getDocument } from "../../dom/util.mjs";
38
+ import {
39
+ getDocumentTranslations,
40
+ Translations,
41
+ } from "../../i18n/translations.mjs";
38
42
  import { Formatter } from "../../text/formatter.mjs";
39
43
  import { getGlobal } from "../../types/global.mjs";
40
44
  import { ID } from "../../types/id.mjs";
@@ -50,21 +54,17 @@ import {
50
54
  import { Observer } from "../../types/observer.mjs";
51
55
  import { ProxyObserver } from "../../types/proxyobserver.mjs";
52
56
  import { validateArray, validateString } from "../../types/validate.mjs";
57
+ import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
53
58
  import { Processing } from "../../util/processing.mjs";
54
59
  import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
55
60
  import { SelectStyleSheet } from "./stylesheet/select.mjs";
56
- import {
57
- getDocumentTranslations,
58
- Translations,
59
- } from "../../i18n/translations.mjs";
60
- import { getLocaleOfDocument } from "../../dom/locale.mjs";
61
- import { addErrorAttribute, removeErrorAttribute } from "../../dom/error.mjs";
61
+ import { positionPopper } from "./util/floating-ui.mjs";
62
62
 
63
63
  export {
64
- Select,
65
- popperElementSymbol,
66
- getSummaryTemplate,
67
64
  getSelectionTemplate,
65
+ getSummaryTemplate,
66
+ popperElementSymbol,
67
+ Select,
68
68
  };
69
69
 
70
70
  /**
@@ -227,6 +227,20 @@ const disabledRequestMarker = Symbol("@@disabledRequestMarker");
227
227
  */
228
228
  const runLookupOnceSymbol = Symbol("runLookupOnce");
229
229
 
230
+ /**
231
+ * @private
232
+ * @type {symbol}
233
+ */
234
+ const cleanupOptionsListSymbol = Symbol("cleanupOptionsList");
235
+
236
+ /**
237
+ * @private
238
+ * @type {symbol}
239
+ */
240
+ const debounceOptionsMutationObserverSymbol = Symbol(
241
+ "debounceOptionsMutationObserver",
242
+ );
243
+
230
244
  /**
231
245
  * @private
232
246
  * @type {number}
@@ -361,59 +375,55 @@ class Select extends CustomControl {
361
375
  *
362
376
  * The individual configuration values can be found in the table.
363
377
  *
364
- * @property {Object} toggleEventType List of event types to be observed for opening the dropdown
365
- * @property {boolean} delegatesFocus lorem [see mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus)
366
- * @property {Object[]} options Selection of key identifier pairs available for selection and displayed in the dropdown.
367
- * @property {string} options[].label
368
- * @property {string} options[].value
369
- * @property {string} options[].visibility hidden or visible
370
- * @property {Array} selection Selected options
371
- * @property {Integer} showMaxOptions Maximum number of visible options before a scroll bar should be displayed.
372
- * @property {string} type Multiple (checkbox) or single selection (radio)
373
- * @property {string} name Name of the form field
374
- * @property {string} url Load options from server per url
375
- * @property {object} lookup Load options from server per url
376
- * @property {string} lookup.url Load options from server per url, the playceholder ${filter} is replaced with the value of the selected option
377
- * @property {boolean} lookup.grouping Load all selected options from server per url at once (true) or one by one (false)
378
- * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
379
- * @property {String} fetch.redirect
380
- * @property {String} fetch.method
381
- * @property {String} fetch.mode
382
- * @property {String} fetch.credentials
383
- * @property {Object} fetch.headers
384
- * @property {Object} labels
385
- * @property {string} labels.cannot-be-loaded cannot be loaded
386
- * @property {string} labels.no-options-available no options available
387
- * @property {string} labels.select-an-option select an option
388
- * @property {string} labels.no-option no option in the list, maybe you have to change the filter
389
- * @property {Object} features List with features
390
- * @property {Boolean} features.clearAll Display of a delete button to delete the entire selection
391
- * @property {Boolean} features.clear Display of a delete key for deleting the specific selection
392
- * @property {Boolean} features.lazyLoad Load options when first opening the dropdown. (Hint; lazylLoad is not supported with remote filter)
393
- * @property {Boolean} features.closeOnSelect Close the dropdown when an option is selected (since 3.54.0)
394
- * @property {Boolean} features.emptyValueIfNoOptions If no options are available, the selection is set to an empty array
395
- * @property {Boolean} features.storeFetchedData Store fetched data in the object
396
- * @property {Boolean} features.useStrictValueComparison Use strict value comparison for the selection
397
- * @property {string} filter.defaultValue Default filter value, if the filter is empty, if the default value is null, then no request is made
398
- * @property {Boolean} filter.mode Filter mode, values: options, remote, disabled (Hint; lazylLoad is not supported with remote filter, if you use remote filter, the lazyLoad is disabled)
399
- * @property {Object} templates Template definitions
400
- * @property {string} templates.main Main template
401
- * @property {string} templateMapping Mapping of the template placeholders
402
- * @property {string} templateMapping.selected Selected Template
403
- * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
404
- * @property {string} popper.placement PopperJS placement
405
- * @property {Object[]} modifiers PopperJS placement
406
- * @property {Object} mapping
407
- * @property {String} mapping.selector Path to select the appropriate entries
408
- * @property {String} mapping.labelTemplate template with the label placeholders in the form ${name}, where name is the key (**)
409
- * @property {String} mapping.valueTemplate template with the value placeholders in the form ${name}, where name is the key
410
- * @property {function|undefined} mapping.filter Filtering of values via a function
411
- * @property {Object} empty
412
- * @property {String} empty.defaultValueRadio Default value if you use radio buttons
413
- * @property {Array} empty.defaultValueCheckbox Default value if you use checkboxes
414
- * @property {Array} empty.equivalents Equivalents for empty values
415
- * @property {Object} formatter
416
- * @property {function|undefined} formatter.selection format selection label
378
+ * @property {string[]} toggleEventType Array of DOM event names (e.g. ["click","touch"]) to toggle the dropdown.
379
+ * @property {boolean} delegatesFocus Whether the element delegates focus to its internal control (e.g. the filter input).
380
+ * @property {Array<Object>} options Array of option objects {label,value,visibility?,data?} for static option list.
381
+ * @property {string|string[]} selection Initial selected value(s) as string, comma-separated string, or array of strings.
382
+ * @property {number} showMaxOptions Maximum visible options before the dropdown scrolls.
383
+ * @property {"radio"|"checkbox"} type Selection mode: "radio" for single, "checkbox" for multiple.
384
+ * @property {string} name Name of the hidden form field for form submission.
385
+ * @property {string|null} url URL to dynamically fetch options via HTTP when opening or filtering.
386
+ * @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"`.
387
+ * @property {boolean} lookup.grouping Group lookup requests: true to fetch all selected values in one request, false to fetch each individually.
388
+ * @property {string} fetch.redirect Fetch redirect mode (e.g. "error").
389
+ * @property {string} fetch.method HTTP method for fetching options (e.g. "GET").
390
+ * @property {string} fetch.mode Fetch mode (e.g. "same-origin").
391
+ * @property {string} fetch.credentials Credentials policy for fetch (e.g. "same-origin").
392
+ * @property {Object.<string,string>} fetch.headers HTTP headers for fetch requests.
393
+ * @property {string} labels.cannot-be-loaded Message when options cannot be loaded.
394
+ * @property {string} labels.no-options-available Message when no static options are available.
395
+ * @property {string} labels.click-to-load-options Message prompting user to click to load options when `features.lazyLoad` is enabled.
396
+ * @property {string} labels.select-an-option Placeholder text when no selection is made.
397
+ * @property {string} labels.no-options Message when neither slots nor fetched options exist.
398
+ * @property {string} labels.no-options-found Message when filter yields no matching options.
399
+ * @property {string} labels.summary-text.zero Plural template for zero selected entries (e.g. "No entries were selected").
400
+ * @property {string} labels.summary-text.one Plural template for one selected entry.
401
+ * @property {string} labels.summary-text.other Plural template for multiple selected entries.
402
+ * @property {boolean} features.clearAll Show a "clear all" badge to reset selection.
403
+ * @property {boolean} features.clear Show remove icon on individual selection badges.
404
+ * @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"`).
405
+ * @property {boolean} features.closeOnSelect Automatically close dropdown after selection.
406
+ * @property {boolean} features.emptyValueIfNoOptions Set value to empty when no options are available.
407
+ * @property {boolean} features.storeFetchedData Persist raw fetched data for later retrieval via `getLastFetchedData()`.
408
+ * @property {boolean} features.useStrictValueComparison Use strict (`===`) comparison when matching option values.
409
+ * @property {string|null} filter.defaultValue Default filter value for remote requests; if unset or empty, disabled marker prevents request.
410
+ * @property {"options"|"remote"|"disabled"} filter.mode Client-side ("options"), server-side ("remote"; disables `features.lazyLoad`), or disabled filtering.
411
+ * @property {"inline"|"popper"} filter.position Position of filter input: inline within control or inside popper dropdown.
412
+ * @property {string} filter.marker.open Opening marker for embedding filter value in `filter.mode==="remote"` URLs.
413
+ * @property {string} filter.marker.close Closing marker for embedding filter value in URLs.
414
+ * @property {string|null} filter.defaultOptionsUrl URL for default options when `filter.mode==="remote"` is set and no filter value is provided.
415
+ * @property {string} templates.main HTML template string for rendering options and selection badges.
416
+ * @property {string} templateMapping.selected Template variant for selected items (e.g. badge vs summary view).
417
+ * @property {string} popper.placement Popper.js placement strategy for dropdown (e.g. "bottom").
418
+ * @property {Array<string|Object>} popper.middleware Popper.js middleware or offset configurations.
419
+ * @property {string} mapping.selector Data path or selector to identify entries in imported data.
420
+ * @property {string} mapping.labelTemplate Template for option labels using placeholders like `${name}`.
421
+ * @property {string} mapping.valueTemplate Template for option values using placeholders like `${name}`.
422
+ * @property {Function} mapping.filter Optional callback to filter imported map entries before building `options[]`.
423
+ * @property {string} empty.defaultValueRadio Default radio-value when no selection exists.
424
+ * @property {Array} empty.defaultValueCheckbox Default checkbox-values array when no selection exists.
425
+ * @property {Array} empty.equivalents Values considered empty (e.g. `undefined`, `null`, `""`, `NaN`) and normalized to defaults.
426
+ * @property {Function} formatter.selection Callback `(value)=>string` to format the display label of each selected value.
417
427
  */
418
428
  get defaults() {
419
429
  return Object.assign(
@@ -464,6 +474,7 @@ class Select extends CustomControl {
464
474
  open: "{",
465
475
  close: "}",
466
476
  },
477
+ defaultOptionsUrl: null,
467
478
  },
468
479
  classes: {
469
480
  badge: "monster-badge-primary",
@@ -514,11 +525,8 @@ class Select extends CustomControl {
514
525
  let remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE;
515
526
 
516
527
  if (getFilterMode.call(this) === FILTER_MODE_REMOTE) {
517
- self.getOption("features.lazyLoad", false);
518
- if (lazyLoadFlag === true) {
519
- addErrorAttribute(this, "lazyLoad is not supported with remote filter");
520
- lazyLoadFlag = false;
521
- }
528
+ self.setOption("features.lazyLoad", false);
529
+ lazyLoadFlag = false;
522
530
  }
523
531
 
524
532
  if (self.hasAttribute("value")) {
@@ -697,95 +705,8 @@ class Select extends CustomControl {
697
705
  * @fires monster-options-set this event is fired when the options are set
698
706
  */
699
707
  importOptions(data) {
700
- const self = this;
701
- const mappingOptions = this.getOption("mapping", {});
702
- const selector = mappingOptions?.["selector"];
703
- const labelTemplate = mappingOptions?.["labelTemplate"];
704
- const valueTemplate = mappingOptions?.["valueTemplate"];
705
- let filter = mappingOptions?.["filter"];
706
-
707
- let flag = false;
708
- if (labelTemplate === "") {
709
- addErrorAttribute(this, "empty label template");
710
- flag = true;
711
- }
712
-
713
- if (valueTemplate === "") {
714
- addErrorAttribute(this, "empty value template");
715
- flag = true;
716
- }
717
-
718
- if (flag === true) {
719
- throw new Error("missing label configuration");
720
- }
721
- if (isString(filter)) {
722
- if (0 === filter.indexOf("run:")) {
723
- const code = filter.replace("run:", "");
724
- filter = (m, v, k) => {
725
- const fkt = new Function("m", "v", "k", "control", code);
726
- return fkt(m, v, k, self);
727
- };
728
- } else if (0 === filter.indexOf("call:")) {
729
- const parts = filter.split(":");
730
- parts.shift(); // remove prefix
731
- const fkt = parts.shift();
732
-
733
- switch (fkt) {
734
- case "filterValueOfAttribute":
735
- const attribute = parts.shift();
736
- const attrValue = self.getAttribute(attribute);
737
-
738
- filter = (m, v, k) => {
739
- const mm = buildValue(m, valueTemplate);
740
- return mm != attrValue; // no type check, no !==
741
- };
742
- break;
743
-
744
- default:
745
- addErrorAttribute(
746
- this,
747
- new Error(`Unknown filter function ${fkt}`),
748
- );
749
- }
750
- }
751
- }
752
-
753
- const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
754
-
755
- const options = [];
756
-
757
- if (!isIterable(map)) {
758
- throw new Error("map is not iterable");
759
- }
760
-
761
- const visibility = "visible";
762
-
763
- map.forEach((label, value) => {
764
- options.push({
765
- value,
766
- label,
767
- visibility,
768
- data: map.get(value),
769
- });
770
- });
771
-
772
- runAsOptionLengthChanged.call(this, map.size);
773
- this.setOption("options", options);
774
-
775
- fireCustomEvent(this, "monster-options-set", {
776
- options,
777
- });
778
-
779
- setTimeout(() => {
780
- setSelection
781
- .call(this, this.getOption("selection"))
782
- .then(() => {})
783
- .catch((e) => {
784
- addErrorAttribute(this, e);
785
- });
786
- }, 10);
787
-
788
- return this;
708
+ this[cleanupOptionsListSymbol] = true;
709
+ return importOptionsIntern.call(this, data);
789
710
  }
790
711
 
791
712
  /**
@@ -814,6 +735,102 @@ class Select extends CustomControl {
814
735
  }
815
736
  }
816
737
 
738
+ /**
739
+ * @private
740
+ * @param data
741
+ * @returns {any}
742
+ */
743
+ function importOptionsIntern(data) {
744
+ const self = this;
745
+ const mappingOptions = this.getOption("mapping", {});
746
+ const selector = mappingOptions?.["selector"];
747
+ const labelTemplate = mappingOptions?.["labelTemplate"];
748
+ const valueTemplate = mappingOptions?.["valueTemplate"];
749
+ let filter = mappingOptions?.["filter"];
750
+
751
+ let flag = false;
752
+ if (labelTemplate === "") {
753
+ addErrorAttribute(this, "empty label template");
754
+ flag = true;
755
+ }
756
+
757
+ if (valueTemplate === "") {
758
+ addErrorAttribute(this, "empty value template");
759
+ flag = true;
760
+ }
761
+
762
+ if (flag === true) {
763
+ throw new Error("missing label configuration");
764
+ }
765
+ if (isString(filter)) {
766
+ if (0 === filter.indexOf("run:")) {
767
+ const code = filter.replace("run:", "");
768
+ filter = (m, v, k) => {
769
+ const fkt = new Function("m", "v", "k", "control", code);
770
+ return fkt(m, v, k, self);
771
+ };
772
+ } else if (0 === filter.indexOf("call:")) {
773
+ const parts = filter.split(":");
774
+ parts.shift(); // remove prefix
775
+ const fkt = parts.shift();
776
+
777
+ switch (fkt) {
778
+ case "filterValueOfAttribute":
779
+ const attribute = parts.shift();
780
+ const attrValue = self.getAttribute(attribute);
781
+
782
+ filter = (m, v, k) => {
783
+ const mm = buildValue(m, valueTemplate);
784
+ return mm != attrValue; // no type check, no !==
785
+ };
786
+ break;
787
+
788
+ default:
789
+ addErrorAttribute(this, new Error(`Unknown filter function ${fkt}`));
790
+ }
791
+ }
792
+ }
793
+
794
+ const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
795
+
796
+ let options = [];
797
+ if (this[cleanupOptionsListSymbol] !== true) {
798
+ options = this.getOption("options", []);
799
+ }
800
+
801
+ if (!isIterable(map)) {
802
+ throw new Error("map is not iterable");
803
+ }
804
+
805
+ const visibility = "visible";
806
+
807
+ map.forEach((label, value) => {
808
+ options.push({
809
+ value,
810
+ label,
811
+ visibility,
812
+ data: map.get(value),
813
+ });
814
+ });
815
+
816
+ this.setOption("options", options);
817
+
818
+ fireCustomEvent(this, "monster-options-set", {
819
+ options,
820
+ });
821
+
822
+ setTimeout(() => {
823
+ setSelection
824
+ .call(this, this.getOption("selection"))
825
+ .then(() => {})
826
+ .catch((e) => {
827
+ addErrorAttribute(this, e);
828
+ });
829
+ }, 10);
830
+
831
+ return this;
832
+ }
833
+
817
834
  /**
818
835
  * @private
819
836
  * @returns {object}
@@ -1003,6 +1020,8 @@ function lookupSelection() {
1003
1020
  url = lookupUrl;
1004
1021
  }
1005
1022
 
1023
+ this[cleanupOptionsListSymbol] = false;
1024
+
1006
1025
  if (this.getOption("lookup.grouping") === true) {
1007
1026
  filterFromRemoteByValue
1008
1027
  .call(
@@ -1027,6 +1046,8 @@ function lookupSelection() {
1027
1046
  }
1028
1047
 
1029
1048
  function fetchIt(url, controlOptions) {
1049
+ const self = this;
1050
+
1030
1051
  if (url instanceof URL) {
1031
1052
  url = url.toString();
1032
1053
  }
@@ -1054,7 +1075,7 @@ function fetchIt(url, controlOptions) {
1054
1075
  map instanceof Map
1055
1076
  ) {
1056
1077
  try {
1057
- this.importOptions(map);
1078
+ importOptionsIntern.call(self, map);
1058
1079
  } catch (e) {
1059
1080
  setStatusOrRemoveBadges.call(this, "error");
1060
1081
  reject(e);
@@ -1074,7 +1095,7 @@ function fetchIt(url, controlOptions) {
1074
1095
 
1075
1096
  result = setSelection.call(this, newValue);
1076
1097
 
1077
- requestAnimationFrame(() => {
1098
+ queueMicrotask(() => {
1078
1099
  checkOptionState.call(this);
1079
1100
  setStatusOrRemoveBadges.call(this, "closed");
1080
1101
  updatePopper.call(this);
@@ -1203,7 +1224,7 @@ function getSummaryTemplate() {
1203
1224
  autocomplete="off"
1204
1225
  tabindex="0"
1205
1226
  >
1206
- <div data-monster-replace="path:messages.selected"></div>
1227
+ <div data-monster-replace="path:messages.selected"></div>
1207
1228
  </div>`;
1208
1229
  }
1209
1230
 
@@ -1239,52 +1260,9 @@ function parseSlotsToOptions() {
1239
1260
  });
1240
1261
  });
1241
1262
 
1242
- runAsOptionLengthChanged.call(this, options.length);
1243
1263
  this.setOption("options", options);
1244
1264
  }
1245
1265
 
1246
- /**
1247
- * wait until all options are finished rendering
1248
- *
1249
- * @private
1250
- * @param {int} targetLength
1251
- */
1252
- function runAsOptionLengthChanged(targetLength) {
1253
- const self = this;
1254
-
1255
- if (!self[optionsElementSymbol]) {
1256
- return;
1257
- }
1258
-
1259
- const callback = function (mutationsList, observer) {
1260
- const run = false;
1261
- for (const mutation of mutationsList) {
1262
- if (mutation.type === "childList") {
1263
- const run = true;
1264
- break;
1265
- }
1266
- }
1267
-
1268
- if (run === true) {
1269
- const nodes = self[optionsElementSymbol].querySelectorAll(
1270
- `div[${ATTRIBUTE_ROLE}=option]`,
1271
- );
1272
-
1273
- if (nodes.length === targetLength) {
1274
- checkOptionState.call(self);
1275
- observer.disconnect();
1276
- }
1277
- }
1278
- };
1279
-
1280
- const observer = new MutationObserver(callback);
1281
- observer.observe(self[optionsElementSymbol], {
1282
- attributes: false,
1283
- childList: true,
1284
- subtree: true,
1285
- });
1286
- }
1287
-
1288
1266
  /**
1289
1267
  * @private
1290
1268
  * @param {*} value
@@ -1748,6 +1726,8 @@ function handleFilterKeyEvents() {
1748
1726
  if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) {
1749
1727
  filterOptions.call(this);
1750
1728
  } else {
1729
+ this[cleanupOptionsListSymbol] = true;
1730
+
1751
1731
  filterFromRemote.call(this).catch((e) => {
1752
1732
  addErrorAttribute(this, e);
1753
1733
  });
@@ -2075,10 +2055,29 @@ function clearSelection() {
2075
2055
  });
2076
2056
  }
2077
2057
 
2058
+ const optionAvailableDeadManSymbol = Symbol("optionAvailableDeadManSymbol");
2059
+
2078
2060
  /**
2079
2061
  * @private
2080
2062
  */
2081
2063
  function areOptionsAvailableAndInit() {
2064
+ // against flickering
2065
+ if (this[optionAvailableDeadManSymbol] instanceof DeadMansSwitch) {
2066
+ try {
2067
+ this[optionAvailableDeadManSymbol].touch();
2068
+ return;
2069
+ } catch (e) {
2070
+ delete this[optionAvailableDeadManSymbol];
2071
+ }
2072
+ }
2073
+
2074
+ this[optionAvailableDeadManSymbol] = new DeadMansSwitch(200, () => {
2075
+ areOptionsAvailableAndInitInternal.call(this);
2076
+ delete this[timerCallbackSymbol];
2077
+ });
2078
+ }
2079
+
2080
+ function areOptionsAvailableAndInitInternal() {
2082
2081
  // prevent multiple calls
2083
2082
  if (this[areOptionsAvailableAndInitSymbol] === undefined) {
2084
2083
  this[areOptionsAvailableAndInitSymbol] = 0;
@@ -2112,6 +2111,17 @@ function areOptionsAvailableAndInit() {
2112
2111
  msg = this.getOption("labels.click-to-load-options");
2113
2112
  }
2114
2113
 
2114
+ if (this.getOption("filter.mode") === FILTER_MODE_REMOTE) {
2115
+ msg = "";
2116
+ }
2117
+
2118
+ if (
2119
+ containsAttributeToken(this[controlElementSymbol], "class", "open") ===
2120
+ true
2121
+ ) {
2122
+ msg = "";
2123
+ }
2124
+
2115
2125
  this.setOption("messages.control", msg);
2116
2126
  this.setOption("messages.summary", "");
2117
2127
 
@@ -2443,6 +2453,11 @@ function fetchData(url) {
2443
2453
  .then((response) => {
2444
2454
  self[isLoadingSymbol] = false;
2445
2455
  delayWatch = true;
2456
+
2457
+ if (response.status >= 200 && response.status < 300) {
2458
+ throw new Error(`HTTP error! status: ${response.status}`);
2459
+ }
2460
+
2446
2461
  const contentType = response.headers.get("content-type");
2447
2462
  if (contentType && contentType.indexOf("application/json") !== -1) {
2448
2463
  return response.text();
@@ -2516,6 +2531,8 @@ function show() {
2516
2531
  return;
2517
2532
  }
2518
2533
 
2534
+ initDefaultOptionsFromUrl.call(this);
2535
+
2519
2536
  const hasPopperFilterFlag =
2520
2537
  this.getOption("filter.position") === FILTER_POSITION_POPPER &&
2521
2538
  getFilterMode.call(this) !== FILTER_MODE_DISABLED;
@@ -2543,6 +2560,27 @@ function show() {
2543
2560
  });
2544
2561
  }
2545
2562
 
2563
+ function initDefaultOptionsFromUrl() {
2564
+ const url = this.getOption("filter.defaultOptionsUrl");
2565
+ if (!url) {
2566
+ return;
2567
+ }
2568
+
2569
+ this.setOption("filter.defaultOptionsUrl", null);
2570
+
2571
+ fetchData
2572
+ .call(this, url)
2573
+ .then((data) => {
2574
+ this[cleanupOptionsListSymbol] = false;
2575
+ importOptionsIntern.call(this, data);
2576
+ setStatusOrRemoveBadges.call(this, "open");
2577
+ })
2578
+ .catch((e) => {
2579
+ addErrorAttribute(this, e);
2580
+ setStatusOrRemoveBadges.call(this, "error");
2581
+ });
2582
+ }
2583
+
2546
2584
  /**
2547
2585
  * @private
2548
2586
  */
@@ -2692,6 +2730,7 @@ function initEventHandler() {
2692
2730
  ATTRIBUTE_ROLE,
2693
2731
  "remove-badge",
2694
2732
  );
2733
+
2695
2734
  if (element instanceof HTMLElement) {
2696
2735
  return;
2697
2736
  }
@@ -2741,6 +2780,34 @@ function initEventHandler() {
2741
2780
  self.addEventListener("input", self[inputEventHandler]);
2742
2781
  self.addEventListener("keydown", self[keyEventHandler]);
2743
2782
 
2783
+ const callback = () => {
2784
+ if (this[debounceOptionsMutationObserverSymbol] instanceof DeadMansSwitch) {
2785
+ try {
2786
+ this[debounceOptionsMutationObserverSymbol].touch();
2787
+ return;
2788
+ } catch (e) {
2789
+ delete this[debounceOptionsMutationObserverSymbol];
2790
+ }
2791
+ }
2792
+
2793
+ this[debounceOptionsMutationObserverSymbol] = new DeadMansSwitch(
2794
+ 100,
2795
+ () => {
2796
+ checkOptionState.call(self);
2797
+ calcAndSetOptionsDimension.call(self);
2798
+ updatePopper.call(self);
2799
+ delete this[debounceOptionsMutationObserverSymbol];
2800
+ },
2801
+ );
2802
+ };
2803
+
2804
+ const observer = new MutationObserver(callback);
2805
+ observer.observe(self[optionsElementSymbol], {
2806
+ attributes: false,
2807
+ childList: true,
2808
+ subtree: true,
2809
+ });
2810
+
2744
2811
  return self;
2745
2812
  }
2746
2813
 
@@ -2780,16 +2847,16 @@ function setStatusOrRemoveBadges(suggestion) {
2780
2847
  return;
2781
2848
  }
2782
2849
 
2783
- if (clearAllFlag) {
2784
- if (current !== "clear") {
2785
- this.setOption("classes.statusOrRemoveBadge", "clear");
2850
+ if (this[controlElementSymbol].classList.contains("open")) {
2851
+ if (current !== "open") {
2852
+ this.setOption("classes.statusOrRemoveBadge", "open");
2786
2853
  }
2787
2854
  return;
2788
2855
  }
2789
2856
 
2790
- if (this[controlElementSymbol].classList.contains("open")) {
2791
- if (current !== "open") {
2792
- this.setOption("classes.statusOrRemoveBadge", "open");
2857
+ if (clearAllFlag) {
2858
+ if (current !== "clear") {
2859
+ this.setOption("classes.statusOrRemoveBadge", "clear");
2793
2860
  }
2794
2861
  return;
2795
2862
  }
@@ -2902,10 +2969,10 @@ function getTemplate() {
2902
2969
  data-monster-attributes="
2903
2970
  type path:type,
2904
2971
  role path:role,
2905
- value path:options.value,
2906
- name path:name,
2972
+ value path:options.value,
2973
+ name path:name,
2907
2974
  part path:type | prefix:option- | suffix: form,
2908
- class path:options.class
2975
+ class path:options.class
2909
2976
  " tabindex="-1">
2910
2977
  <div data-monster-replace="path:options.label"
2911
2978
  part="option-label"></div>
@@ -2917,8 +2984,8 @@ function getTemplate() {
2917
2984
  <div data-monster-role="badge"
2918
2985
  part="badge"
2919
2986
  data-monster-attributes="
2920
- data-monster-value path:selection | index:value,
2921
- class path:classes | index:badge,
2987
+ data-monster-value path:selection | index:value,
2988
+ class path:classes | index:badge,
2922
2989
  part path:type | suffix:-option | prefix: form-" tabindex="-1">
2923
2990
  <div data-monster-replace="path:selection | index:label" part="badge-label"
2924
2991
  data-monster-role="badge-label"></div>