@schukai/monster 4.136.4 → 4.136.6

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.
@@ -41,6 +41,7 @@ import { getLocaleOfDocument } from "../../dom/locale.mjs";
41
41
  import {
42
42
  findElementWithSelectorUpwards,
43
43
  getDocument,
44
+ getWindow,
44
45
  } from "../../dom/util.mjs";
45
46
  import {
46
47
  getDocumentTranslations,
@@ -69,6 +70,8 @@ import {
69
70
  isPositionedPopperOpen,
70
71
  openPositionedPopper,
71
72
  positionPopper,
73
+ resolveClippingBoundaryElement,
74
+ resolveParentPopperContentBoundary,
72
75
  } from "./util/floating-ui.mjs";
73
76
  import { Pathfinder } from "../../data/pathfinder.mjs";
74
77
  import { TokenList } from "../../types/tokenlist.mjs";
@@ -76,9 +79,14 @@ import { TokenList } from "../../types/tokenlist.mjs";
76
79
  import "../datatable/pagination.mjs";
77
80
 
78
81
  export {
82
+ getDefaultSelectPopperPositionProfile,
79
83
  getSelectionTemplate,
80
84
  getSummaryTemplate,
81
85
  popperElementSymbol,
86
+ resolveSelectPopperWidthConstraints,
87
+ resolveSelectListDimension,
88
+ resolveSelectVisibleRect,
89
+ resolveSelectViewportMetrics,
82
90
  Select,
83
91
  };
84
92
 
@@ -129,6 +137,14 @@ const clearOptionEventHandler = Symbol("clearOptionEventHandler");
129
137
  * @type {Symbol}
130
138
  */
131
139
  const resizeObserverSymbol = Symbol("resizeObserver");
140
+ const resizeObserverFrameSymbol = Symbol("resizeObserverFrame");
141
+ const visualViewportResizeHandlerSymbol = Symbol("visualViewportResizeHandler");
142
+ const visualViewportScrollHandlerSymbol = Symbol("visualViewportScrollHandler");
143
+ const visibilityChangeHandlerSymbol = Symbol("visibilityChangeHandler");
144
+ const windowResizeHandlerSymbol = Symbol("windowResizeHandler");
145
+ const windowOrientationChangeHandlerSymbol = Symbol(
146
+ "windowOrientationChangeHandler",
147
+ );
132
148
 
133
149
  /**
134
150
  * local symbol
@@ -246,21 +262,17 @@ const remoteInfoElementSymbol = Symbol("remoteInfoElement");
246
262
  const areOptionsAvailableAndInitSymbol = Symbol("@@areOptionsAvailableAndInit");
247
263
 
248
264
  /**
265
+ * Internal sentinel used to suppress a remote request after URL formatting.
266
+ *
267
+ * This is currently inserted only when `filter.defaultValue` resolves to
268
+ * `undefined` or `null`. An empty string is treated as a real filter value and
269
+ * therefore still produces a request.
270
+ *
249
271
  * @private
250
272
  * @type {symbol}
251
273
  */
252
274
  const disabledRequestMarker = Symbol("@@disabledRequestMarker");
253
275
 
254
- /**
255
- * @private
256
- * @type {symbol}
257
- */
258
- const runLookupOnceSymbol = Symbol("runLookupOnce");
259
-
260
- /**
261
- * @private
262
- * @type {symbol}
263
- */
264
276
  const cleanupOptionsListSymbol = Symbol("cleanupOptionsList");
265
277
  const optionsVersionSymbol = Symbol("optionsVersion");
266
278
  const pendingSelectionSymbol = Symbol("pendingSelection");
@@ -285,16 +297,6 @@ const debounceOptionsMutationObserverSymbol = Symbol(
285
297
  */
286
298
  const currentPageSymbol = Symbol("currentPage");
287
299
 
288
- /**
289
- * @private
290
- * @type {symbol}
291
- */
292
- const remoteFilterFirstOpendSymbol = Symbol("remoteFilterFirstOpend");
293
-
294
- /**
295
- * @private
296
- * @type {symbol}
297
- */
298
300
  const lookupCacheSymbol = Symbol("lookupCache");
299
301
 
300
302
  /**
@@ -302,6 +304,7 @@ const lookupCacheSymbol = Symbol("lookupCache");
302
304
  * @type {symbol}
303
305
  */
304
306
  const lookupInProgressSymbol = Symbol("lookupInProgress");
307
+ const unresolvedSelectionValuesSymbol = Symbol("unresolvedSelectionValues");
305
308
  const fetchRequestVersionSymbol = Symbol("fetchRequestVersion");
306
309
 
307
310
  /**
@@ -343,6 +346,9 @@ const FILTER_POSITION_POPPER = "popper";
343
346
  * @type {string}
344
347
  */
345
348
  const FILTER_POSITION_INLINE = "inline";
349
+ const SELECT_MIN_POPPER_WIDTH = 240;
350
+ const SELECT_MAX_POPPER_HEIGHT = 500;
351
+ const SELECT_VIEWPORT_PADDING = 12;
346
352
 
347
353
  /**
348
354
  * A select control that can be used to select o
@@ -380,6 +386,7 @@ class Select extends CustomControl {
380
386
  this[currentPageSymbol] = 1;
381
387
  this[lookupCacheSymbol] = new Map();
382
388
  this[lookupInProgressSymbol] = new Map();
389
+ this[unresolvedSelectionValuesSymbol] = new Set();
383
390
  this[optionsMapSymbol] = new Map();
384
391
  this[closeOnSelectAutoSymbol] = true;
385
392
  initOptionObserver.call(this);
@@ -462,8 +469,8 @@ class Select extends CustomControl {
462
469
  * @property {string} name - Name of the hidden form field for form submission.
463
470
  * @property {string|null} url - URL to dynamically fetch options via HTTP when opening or filtering.
464
471
  * @property {number|null} total - Total number of available options, useful for pagination with remote data.
465
- * @property {Object} lookup - Configuration for fetching initially selected values.
466
- * @property {string|null} lookup.url - URL template with a `${filter}` placeholder to look up selected entries on initialization. Used when `url` is set and either `features.lazyLoad` is active or `filter.mode` is `"remote"`.
472
+ * @property {Object} lookup - Configuration for hydrating already selected values.
473
+ * @property {string|null} lookup.url - URL template with a `${filter}` placeholder to look up selected entries. Prefer a stable value-based lookup endpoint. This is used for initial hydration and for resolving labels of externally assigned selections that are not yet present in the local option map.
467
474
  * @property {boolean} lookup.grouping - If `true`, all selected values are fetched in a single request; otherwise, a separate request is sent for each value.
468
475
  * @property {Object} fetch - Configuration for HTTP requests via `fetch`.
469
476
  * @property {string} fetch.redirect - Fetch redirect mode (e.g., "error", "follow").
@@ -472,7 +479,7 @@ class Select extends CustomControl {
472
479
  * @property {string} fetch.credentials - Credentials policy for fetch (e.g., "include", "same-origin").
473
480
  * @property {Object.<string, string>} fetch.headers - HTTP headers to be sent with every request.
474
481
  * @property {Object} labels - Text labels for various states and UI elements.
475
- * @property {string} labels.cannot-be-loaded - Message displayed when options cannot be loaded.
482
+ * @property {string} labels.cannot-be-loaded - Message displayed when options cannot be loaded. Unresolved selected values keep their raw key instead of replacing it with this label.
476
483
  * @property {string} labels.no-options-available - Message displayed when no static options are provided.
477
484
  * @property {string} labels.click-to-load-options - Prompt to load options when `features.lazyLoad` is enabled.
478
485
  * @property {string} labels.select-an-option - Placeholder text when no selection has been made.
@@ -495,10 +502,10 @@ class Select extends CustomControl {
495
502
  * @property {Object} placeholder - Placeholder texts for input fields.
496
503
  * @property {string} placeholder.filter - Placeholder text for the filter input field.
497
504
  * @property {Object} filter - Configuration for the filtering functionality.
498
- * @property {string|null} filter.defaultValue - Default filter value for remote requests. An empty string will prevent the initial request.
505
+ * @property {string|null} filter.defaultValue - Default filter value for remote requests. In the current implementation only `undefined` or `null` suppress the request by inserting an internal disabled marker. An empty string `""` is still formatted into the URL and therefore does not prevent the request.
499
506
  * @property {"options"|"remote"|"disabled"} filter.mode - Filter mode: `"options"` (client-side), `"remote"` (server-side, `lazyLoad` is ignored), or `"disabled"`.
500
507
  * @property {"inline"|"popper"} filter.position - Position of the filter input: `"inline"` (inside the control) or `"popper"` (inside the dropdown).
501
- * @property {string|null} filter.defaultOptionsUrl - URL to load an initial list of options when `filter.mode` is `"remote"` and no filter value has been entered.
508
+ * @property {string|null} filter.defaultOptionsUrl - URL to load options when `filter.mode` is `"remote"` and no filter value has been entered. This is used as the empty-filter source both on open and after the filter is cleared again.
502
509
  * @property {Object} filter.marker - Markers for embedding the filter value into the `url` for server-side filtering.
503
510
  * @property {string} filter.marker.open - Opening marker (e.g., `{`).
504
511
  * @property {string} filter.marker.close - Closing marker (e.g., `}`).
@@ -526,6 +533,7 @@ class Select extends CustomControl {
526
533
  * @property {Function} formatter.selection - Callback `(value, option) => string` to format the display text of selected values.
527
534
  * @property {Object} classes - CSS classes for various elements.
528
535
  * @property {string} classes.badge - CSS class for selection badges.
536
+ * @property {string} classes.badgeUnresolved - CSS class used for selection badges whose value could not be hydrated. By default this swaps the badge into a warning style while keeping the raw key visible.
529
537
  * @property {string} classes.statusOrRemoveBadge - CSS class for the status or remove badge.
530
538
  * @property {string} classes.remoteInfo - CSS class for the remote info badge.
531
539
  * @property {string} classes.noOptions - CSS class for the "no options" message.
@@ -607,6 +615,7 @@ class Select extends CustomControl {
607
615
 
608
616
  classes: {
609
617
  badge: "monster-badge-primary",
618
+ badgeUnresolved: "monster-badge-warning",
610
619
  statusOrRemoveBadge: "empty",
611
620
  remoteInfo: "monster-margin-start-4 monster-margin-top-4",
612
621
  noOptions: "monster-margin-top-4 monster-margin-start-4",
@@ -663,6 +672,7 @@ class Select extends CustomControl {
663
672
  // Clear the lookup cache
664
673
  this[lookupCacheSymbol].clear();
665
674
  this[lookupInProgressSymbol].clear();
675
+ this[unresolvedSelectionValuesSymbol].clear();
666
676
 
667
677
  setSelection
668
678
  .call(this, null)
@@ -684,7 +694,6 @@ class Select extends CustomControl {
684
694
  resetErrorAttribute(this);
685
695
 
686
696
  this[lazyLoadDoneSymbol] = false;
687
- this[runLookupOnceSymbol] = false;
688
697
 
689
698
  checkOptionState.call(this);
690
699
  calcAndSetOptionsDimension.call(this);
@@ -735,7 +744,9 @@ class Select extends CustomControl {
735
744
 
736
745
  if (self.getOption("url") !== null) {
737
746
  if (lazyLoadFlag || remoteFilterFlag) {
738
- lookupSelection.call(self);
747
+ if (self.getOption("lookup.url")) {
748
+ lookupSelection.call(self);
749
+ }
739
750
  } else {
740
751
  self
741
752
  .fetch()
@@ -1758,9 +1769,12 @@ function getTranslations() {
1758
1769
  }
1759
1770
 
1760
1771
  /**
1761
- * @private
1762
- */
1763
- /**
1772
+ * Hydrates the current selection once the component becomes visible.
1773
+ *
1774
+ * This path is used for initial hydration when remote or lazy-loaded options
1775
+ * are not yet present locally. It is intentionally limited to `lookup.url`
1776
+ * and does not fall back to the main remote option source.
1777
+ *
1764
1778
  * @private
1765
1779
  */
1766
1780
  function lookupSelection() {
@@ -1799,7 +1813,10 @@ function runSelectionLookupWhenVisible(self) {
1799
1813
  return;
1800
1814
  }
1801
1815
 
1802
- let url = self.getOption("lookup.url") || self.getOption("url");
1816
+ const url = self.getOption("lookup.url");
1817
+ if (!url) {
1818
+ return;
1819
+ }
1803
1820
  self[cleanupOptionsListSymbol] = false;
1804
1821
 
1805
1822
  if (self.getOption("lookup.grouping") === true) {
@@ -1975,21 +1992,8 @@ function initOptionsFromArguments() {
1975
1992
  * @private
1976
1993
  */
1977
1994
  function attachResizeObserver() {
1978
- // against flickering
1979
- this[resizeObserverSymbol] = new ResizeObserver((entries) => {
1980
- if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
1981
- try {
1982
- this[timerCallbackSymbol].touch();
1983
- return;
1984
- } catch (e) {
1985
- delete this[timerCallbackSymbol];
1986
- }
1987
- }
1988
-
1989
- this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
1990
- updatePopper.call(this);
1991
- delete this[timerCallbackSymbol];
1992
- });
1995
+ this[resizeObserverSymbol] = new ResizeObserver(() => {
1996
+ scheduleResizeObserverPopperUpdate.call(this);
1993
1997
  });
1994
1998
 
1995
1999
  let parent = this.parentNode;
@@ -2000,6 +2004,70 @@ function attachResizeObserver() {
2000
2004
  if (parent instanceof HTMLElement) {
2001
2005
  this[resizeObserverSymbol].observe(parent);
2002
2006
  }
2007
+
2008
+ for (const element of [
2009
+ this,
2010
+ this[controlElementSymbol],
2011
+ this[popperElementSymbol],
2012
+ this[popperFilterContainerElementSymbol],
2013
+ this[optionsElementSymbol],
2014
+ this[paginationElementSymbol],
2015
+ ]) {
2016
+ if (element instanceof HTMLElement) {
2017
+ this[resizeObserverSymbol].observe(element);
2018
+ }
2019
+ }
2020
+
2021
+ const boundaryElement = resolveClippingBoundaryElement(
2022
+ this[controlElementSymbol],
2023
+ this[popperElementSymbol],
2024
+ );
2025
+ if (boundaryElement instanceof HTMLElement) {
2026
+ this[resizeObserverSymbol].observe(boundaryElement);
2027
+ }
2028
+
2029
+ const viewport = getGlobal().visualViewport;
2030
+ const windowObject = getWindow();
2031
+ this[visualViewportResizeHandlerSymbol] = () => {
2032
+ updatePopper.call(this);
2033
+ };
2034
+ this[visualViewportScrollHandlerSymbol] = () => {
2035
+ updatePopper.call(this);
2036
+ };
2037
+ this[visibilityChangeHandlerSymbol] = () => {
2038
+ if (document.visibilityState === "visible") {
2039
+ updatePopper.call(this);
2040
+ }
2041
+ };
2042
+ this[windowResizeHandlerSymbol] = () => {
2043
+ updatePopper.call(this);
2044
+ };
2045
+ this[windowOrientationChangeHandlerSymbol] = () => {
2046
+ updatePopper.call(this);
2047
+ };
2048
+
2049
+ if (viewport) {
2050
+ viewport.addEventListener(
2051
+ "resize",
2052
+ this[visualViewportResizeHandlerSymbol],
2053
+ );
2054
+ viewport.addEventListener(
2055
+ "scroll",
2056
+ this[visualViewportScrollHandlerSymbol],
2057
+ );
2058
+ }
2059
+
2060
+ document.addEventListener(
2061
+ "visibilitychange",
2062
+ this[visibilityChangeHandlerSymbol],
2063
+ );
2064
+ if (windowObject?.addEventListener instanceof Function) {
2065
+ windowObject.addEventListener("resize", this[windowResizeHandlerSymbol]);
2066
+ windowObject.addEventListener(
2067
+ "orientationchange",
2068
+ this[windowOrientationChangeHandlerSymbol],
2069
+ );
2070
+ }
2003
2071
  }
2004
2072
 
2005
2073
  /**
@@ -2009,6 +2077,83 @@ function disconnectResizeObserver() {
2009
2077
  if (this[resizeObserverSymbol] instanceof ResizeObserver) {
2010
2078
  this[resizeObserverSymbol].disconnect();
2011
2079
  }
2080
+ cancelScheduledResizeObserverPopperUpdate.call(this);
2081
+
2082
+ const viewport = getGlobal().visualViewport;
2083
+ if (
2084
+ viewport &&
2085
+ this[visualViewportResizeHandlerSymbol] instanceof Function
2086
+ ) {
2087
+ viewport.removeEventListener(
2088
+ "resize",
2089
+ this[visualViewportResizeHandlerSymbol],
2090
+ );
2091
+ }
2092
+ if (
2093
+ viewport &&
2094
+ this[visualViewportScrollHandlerSymbol] instanceof Function
2095
+ ) {
2096
+ viewport.removeEventListener(
2097
+ "scroll",
2098
+ this[visualViewportScrollHandlerSymbol],
2099
+ );
2100
+ }
2101
+ if (this[visibilityChangeHandlerSymbol] instanceof Function) {
2102
+ document.removeEventListener(
2103
+ "visibilitychange",
2104
+ this[visibilityChangeHandlerSymbol],
2105
+ );
2106
+ }
2107
+ const windowObject = getWindow();
2108
+ if (
2109
+ windowObject?.removeEventListener instanceof Function &&
2110
+ this[windowResizeHandlerSymbol] instanceof Function
2111
+ ) {
2112
+ windowObject.removeEventListener("resize", this[windowResizeHandlerSymbol]);
2113
+ }
2114
+ if (
2115
+ windowObject?.removeEventListener instanceof Function &&
2116
+ this[windowOrientationChangeHandlerSymbol] instanceof Function
2117
+ ) {
2118
+ windowObject.removeEventListener(
2119
+ "orientationchange",
2120
+ this[windowOrientationChangeHandlerSymbol],
2121
+ );
2122
+ }
2123
+ }
2124
+
2125
+ function scheduleResizeObserverPopperUpdate() {
2126
+ const globalObject = getGlobal();
2127
+ if (typeof this[resizeObserverFrameSymbol] === "number") {
2128
+ return;
2129
+ }
2130
+
2131
+ const schedule =
2132
+ globalObject?.requestAnimationFrame instanceof Function
2133
+ ? globalObject.requestAnimationFrame.bind(globalObject)
2134
+ : (callback) => {
2135
+ return globalObject.setTimeout(callback, 16);
2136
+ };
2137
+
2138
+ this[resizeObserverFrameSymbol] = schedule(() => {
2139
+ delete this[resizeObserverFrameSymbol];
2140
+ updatePopper.call(this);
2141
+ });
2142
+ }
2143
+
2144
+ function cancelScheduledResizeObserverPopperUpdate() {
2145
+ const globalObject = getGlobal();
2146
+ const frameId = this[resizeObserverFrameSymbol];
2147
+ if (typeof frameId !== "number") {
2148
+ return;
2149
+ }
2150
+
2151
+ if (globalObject?.cancelAnimationFrame instanceof Function) {
2152
+ globalObject.cancelAnimationFrame(frameId);
2153
+ } else {
2154
+ globalObject.clearTimeout(frameId);
2155
+ }
2156
+ delete this[resizeObserverFrameSymbol];
2012
2157
  }
2013
2158
 
2014
2159
  /**
@@ -2091,22 +2236,120 @@ function parseSlotsToOptions() {
2091
2236
  * @return {*}
2092
2237
  */
2093
2238
  function buildSelectionLabel(value) {
2094
- // First, check the lookup cache.
2095
- if (this[lookupCacheSymbol].has(value)) {
2096
- return this[lookupCacheSymbol].get(value);
2097
- }
2098
-
2099
2239
  const strict = this.getOption("features.useStrictValueComparison") === true;
2100
2240
  const map = this[optionsMapSymbol];
2101
2241
  const key = strict ? value : String(value);
2102
2242
 
2103
2243
  if (map && map.has(key)) {
2244
+ if (clearUnresolvedSelectionValue.call(this, value)) {
2245
+ this[lookupCacheSymbol].delete(value);
2246
+ }
2104
2247
  return map.get(key);
2105
2248
  }
2106
2249
 
2250
+ if (this[lookupCacheSymbol].has(value)) {
2251
+ return this[lookupCacheSymbol].get(value);
2252
+ }
2253
+
2107
2254
  return undefined;
2108
2255
  }
2109
2256
 
2257
+ /**
2258
+ * @private
2259
+ * @param {*} value
2260
+ * @returns {*}
2261
+ */
2262
+ function getSelectionStateKey(value) {
2263
+ return this.getOption("features.useStrictValueComparison") === true
2264
+ ? value
2265
+ : String(value);
2266
+ }
2267
+
2268
+ /**
2269
+ * @private
2270
+ * @param {*} value
2271
+ * @returns {boolean}
2272
+ */
2273
+ function markSelectionAsUnresolved(value) {
2274
+ const key = getSelectionStateKey.call(this, value);
2275
+ if (this[unresolvedSelectionValuesSymbol].has(key)) {
2276
+ return false;
2277
+ }
2278
+
2279
+ this[unresolvedSelectionValuesSymbol].add(key);
2280
+ return true;
2281
+ }
2282
+
2283
+ /**
2284
+ * @private
2285
+ * @param {*} value
2286
+ * @returns {boolean}
2287
+ */
2288
+ function clearUnresolvedSelectionValue(value) {
2289
+ const key = getSelectionStateKey.call(this, value);
2290
+ return this[unresolvedSelectionValuesSymbol].delete(key);
2291
+ }
2292
+
2293
+ /**
2294
+ * @private
2295
+ * @param {*} value
2296
+ * @returns {boolean}
2297
+ */
2298
+ function isSelectionValueUnresolved(value) {
2299
+ const key = getSelectionStateKey.call(this, value);
2300
+ return this[unresolvedSelectionValuesSymbol].has(key);
2301
+ }
2302
+
2303
+ /**
2304
+ * @private
2305
+ * @param {*} value
2306
+ * @returns {string}
2307
+ */
2308
+ function getSelectionBadgeClass(value) {
2309
+ const classes = new TokenList(this.getOption("classes.badge"));
2310
+ if (!isSelectionValueUnresolved.call(this, value)) {
2311
+ return classes.toString();
2312
+ }
2313
+
2314
+ const unresolvedClasses = new TokenList(
2315
+ this.getOption("classes.badgeUnresolved"),
2316
+ );
2317
+ for (const token of classes.entries()) {
2318
+ if (token.startsWith("monster-badge-")) {
2319
+ classes.remove(token);
2320
+ }
2321
+ }
2322
+
2323
+ classes.add(unresolvedClasses.entries());
2324
+ return classes.toString();
2325
+ }
2326
+
2327
+ /**
2328
+ * @private
2329
+ * @param {*} value
2330
+ * @param {string} [preferredLabel]
2331
+ * @returns {{class: string, label: string, unresolved: boolean, value: *}}
2332
+ */
2333
+ function buildSelectionItem(value, preferredLabel) {
2334
+ let label = getSelectionLabel.call(this, value);
2335
+ const unresolved = isSelectionValueUnresolved.call(this, value);
2336
+ if (
2337
+ !unresolved &&
2338
+ `${label}` === `${value}` &&
2339
+ isString(preferredLabel) &&
2340
+ preferredLabel.length > 0
2341
+ ) {
2342
+ label = preferredLabel;
2343
+ }
2344
+
2345
+ return {
2346
+ label,
2347
+ value,
2348
+ class: getSelectionBadgeClass.call(this, value),
2349
+ unresolved,
2350
+ };
2351
+ }
2352
+
2110
2353
  /**
2111
2354
  * @private
2112
2355
  * @param {string} value The value to look up.
@@ -2132,6 +2375,7 @@ async function lookupValueAndCache(value) {
2132
2375
 
2133
2376
  let hasError = false;
2134
2377
  let found = false;
2378
+ let refreshSelection = false;
2135
2379
  try {
2136
2380
  this[lookupInProgressSymbol].set(value, true);
2137
2381
 
@@ -2163,28 +2407,47 @@ async function lookupValueAndCache(value) {
2163
2407
  }
2164
2408
 
2165
2409
  // The lookup might return more than the requested value, so we cache all of them.
2166
- if (!this[lookupCacheSymbol].has(itemValue)) {
2410
+ if (this[lookupCacheSymbol].get(itemValue) !== itemLabel) {
2167
2411
  this[lookupCacheSymbol].set(itemValue, itemLabel);
2168
- if (`${itemValue}` === `${value}`) {
2169
- found = true;
2170
- }
2412
+ refreshSelection = true;
2413
+ }
2414
+
2415
+ if (clearUnresolvedSelectionValue.call(this, itemValue)) {
2416
+ refreshSelection = true;
2417
+ }
2418
+
2419
+ if (`${itemValue}` === `${value}`) {
2420
+ found = true;
2171
2421
  }
2172
2422
  }
2173
2423
 
2174
2424
  if (!found && !this[lookupCacheSymbol].has(value)) {
2175
- const fallback = this.getOption("labels.cannot-be-loaded", value);
2176
- this[lookupCacheSymbol].set(value, `${fallback}`);
2425
+ this[lookupCacheSymbol].set(value, `${value}`);
2426
+ refreshSelection = true;
2177
2427
  }
2178
2428
 
2179
- // If the specific value was found, trigger a re-render of the selection.
2180
- if (found) {
2181
- await setSelection.call(this, this.getOption("selection"));
2429
+ if (!found && markSelectionAsUnresolved.call(this, value)) {
2430
+ refreshSelection = true;
2182
2431
  }
2183
2432
  } catch (e) {
2184
2433
  hasError = true;
2185
2434
  addErrorAttribute(this, e);
2435
+
2436
+ if (!this[lookupCacheSymbol].has(value)) {
2437
+ this[lookupCacheSymbol].set(value, `${value}`);
2438
+ refreshSelection = true;
2439
+ }
2440
+
2441
+ if (markSelectionAsUnresolved.call(this, value)) {
2442
+ refreshSelection = true;
2443
+ }
2186
2444
  } finally {
2187
2445
  this[lookupInProgressSymbol].delete(value);
2446
+
2447
+ if (refreshSelection) {
2448
+ await setSelection.call(this, this.getOption("selection"));
2449
+ }
2450
+
2188
2451
  if (hasError) {
2189
2452
  setStatusOrRemoveBadges.call(this, "error");
2190
2453
  } else {
@@ -2468,10 +2731,9 @@ function calcAndSetOptionsDimension() {
2468
2731
  }
2469
2732
 
2470
2733
  let visible = 0;
2471
- let optionHeight = 0;
2734
+ const visibleOptionHeights = [];
2472
2735
  const max = this.getOption("showMaxOptions", 10);
2473
2736
 
2474
- let scrollFlag = false;
2475
2737
  for (const [, option] of Object.entries(options)) {
2476
2738
  const computedStyle = getGlobal().getComputedStyle(option);
2477
2739
  if (computedStyle.display === "none") continue;
@@ -2479,18 +2741,8 @@ function calcAndSetOptionsDimension() {
2479
2741
  let h = option.getBoundingClientRect().height;
2480
2742
  h += parseInt(computedStyle.getPropertyValue("margin-top"), 10);
2481
2743
  h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10);
2482
- optionHeight += h;
2483
-
2744
+ visibleOptionHeights.push(h);
2484
2745
  visible++;
2485
-
2486
- if (visible > max) {
2487
- break;
2488
- }
2489
- }
2490
-
2491
- if (visible > max) {
2492
- visible = max;
2493
- scrollFlag = true;
2494
2746
  }
2495
2747
 
2496
2748
  if (visible === 0) {
@@ -2535,61 +2787,310 @@ function calcAndSetOptionsDimension() {
2535
2787
  let margin = parseInt(styles.getPropertyValue("margin-top"), 10);
2536
2788
  margin += parseInt(styles.getPropertyValue("margin-bottom"), 10);
2537
2789
 
2538
- let containerHeight = optionHeight + padding + margin;
2539
- const maxViewportHeight = Math.floor(getVisibleHeightLimit(this) / 3);
2540
- if (containerHeight > maxViewportHeight) {
2541
- containerHeight = maxViewportHeight;
2542
- }
2790
+ const geometry = getSelectPopperGeometry.call(this);
2791
+ const listDimension = resolveSelectListDimension({
2792
+ visibleOptionHeights,
2793
+ maxVisibleOptions: max,
2794
+ availableHeight: geometry.availableHeight,
2795
+ padding,
2796
+ margin,
2797
+ });
2798
+ const widthConstraints = resolveSelectPopperWidthConstraints({
2799
+ controlWidth: this[controlElementSymbol].getBoundingClientRect().width,
2800
+ availableWidth: geometry.availableWidth,
2801
+ });
2543
2802
 
2544
- container.style.height = `${containerHeight}px`;
2803
+ container.style.height =
2804
+ listDimension.desiredHeight > 0 ? `${listDimension.desiredHeight}px` : "0px";
2805
+ container.style.maxHeight =
2806
+ listDimension.maxHeight > 0 ? `${listDimension.maxHeight}px` : "0px";
2807
+ container.style.overflowY = listDimension.overflowY;
2545
2808
 
2546
- if (scrollFlag === true) {
2547
- container.style.overflowY = "scroll";
2809
+ this[popperElementSymbol].dataset.monsterPreferredWidth = `${Math.ceil(
2810
+ widthConstraints.preferredWidth,
2811
+ )}`;
2812
+ if (
2813
+ Number.isFinite(widthConstraints.maxWidth) &&
2814
+ widthConstraints.maxWidth > 0
2815
+ ) {
2816
+ this[popperElementSymbol].dataset.monsterPreferredMaxWidth = `${Math.ceil(
2817
+ widthConstraints.maxWidth,
2818
+ )}`;
2819
+ this[popperElementSymbol].style.maxWidth = `${Math.ceil(
2820
+ widthConstraints.maxWidth,
2821
+ )}px`;
2548
2822
  } else {
2549
- container.style.overflowY = "auto";
2823
+ delete this[popperElementSymbol].dataset.monsterPreferredMaxWidth;
2824
+ this[popperElementSymbol].style.removeProperty("maxWidth");
2550
2825
  }
2826
+ this[popperElementSymbol].style.width = "";
2827
+ this[popperElementSymbol].style.removeProperty("minWidth");
2828
+ this[popperElementSymbol].style.maxHeight = `${Math.ceil(
2829
+ Math.min(geometry.availableHeight, SELECT_MAX_POPPER_HEIGHT),
2830
+ )}px`;
2831
+ container.style.overflowX = "hidden";
2551
2832
 
2552
- const domRect = this[controlElementSymbol].getBoundingClientRect();
2833
+ if (content instanceof HTMLElement) {
2834
+ content.style.overflow = "hidden";
2835
+ content.style.maxHeight =
2836
+ listDimension.maxHeight > 0 ? `${listDimension.maxHeight}px` : "";
2837
+ }
2838
+ }
2553
2839
 
2554
- this[popperElementSymbol].dataset.monsterPreferredWidth = `${Math.ceil(
2555
- domRect.width,
2556
- )}`;
2557
- this[popperElementSymbol].style.width = `${domRect.width}px`;
2558
- this[popperElementSymbol].style.minWidth = `${domRect.width}px`;
2559
- container.style.overflowX = "auto";
2840
+ function resolveSelectPopperPreferredWidth(controlWidth) {
2841
+ return Math.max(Math.ceil(controlWidth), SELECT_MIN_POPPER_WIDTH);
2842
+ }
2560
2843
 
2561
- if (content instanceof HTMLElement) {
2562
- content.style.overflowX = "hidden";
2563
- content.style.overflowY = "hidden";
2844
+ function resolveSelectPopperWidthConstraints({
2845
+ controlWidth = 0,
2846
+ availableWidth = 0,
2847
+ }) {
2848
+ const preferredWidth = resolveSelectPopperPreferredWidth(controlWidth);
2849
+ const maxWidth = Math.max(0, availableWidth);
2850
+
2851
+ return {
2852
+ preferredWidth,
2853
+ maxWidth: maxWidth > 0 ? maxWidth : Infinity,
2854
+ };
2855
+ }
2856
+
2857
+ function resolveSelectListDimension({
2858
+ visibleOptionHeights = [],
2859
+ maxVisibleOptions = 10,
2860
+ availableHeight,
2861
+ padding = 0,
2862
+ margin = 0,
2863
+ }) {
2864
+ const visibleHeights = visibleOptionHeights.filter((height) => {
2865
+ return Number.isFinite(height) && height > 0;
2866
+ });
2867
+ const naturalListHeight =
2868
+ visibleHeights
2869
+ .slice(0, Math.max(0, maxVisibleOptions))
2870
+ .reduce((sum, height) => {
2871
+ return sum + height;
2872
+ }, 0) +
2873
+ padding +
2874
+ margin;
2875
+ const fullListHeight =
2876
+ visibleHeights.reduce((sum, height) => {
2877
+ return sum + height;
2878
+ }, 0) +
2879
+ padding +
2880
+ margin;
2881
+ const maxHeight = resolveSelectListMaxHeight({
2882
+ availableHeight,
2883
+ });
2884
+ const desiredHeight = Math.min(
2885
+ maxHeight,
2886
+ fullListHeight > 0 ? naturalListHeight : 0,
2887
+ );
2888
+
2889
+ return {
2890
+ desiredHeight,
2891
+ fullHeight: fullListHeight,
2892
+ maxHeight,
2893
+ overflowY: fullListHeight > desiredHeight ? "auto" : "hidden",
2894
+ };
2895
+ }
2896
+
2897
+ function resolveSelectListMaxHeight(geometry) {
2898
+ if (!geometry || !Number.isFinite(geometry.availableHeight)) {
2899
+ return SELECT_MAX_POPPER_HEIGHT;
2564
2900
  }
2901
+
2902
+ return Math.max(
2903
+ 0,
2904
+ Math.min(geometry.availableHeight, SELECT_MAX_POPPER_HEIGHT),
2905
+ );
2565
2906
  }
2566
2907
 
2567
- /**
2568
- * @private
2569
- */
2570
- function getVisibleHeightLimit(startEl) {
2571
- let el = startEl;
2572
- const gcs = getGlobal().getComputedStyle;
2908
+ function getViewportMetrics() {
2909
+ const globalObject = getGlobal();
2910
+ const documentElement = globalObject.document?.documentElement;
2911
+ const viewport = globalObject.visualViewport;
2912
+ const metrics = resolveSelectViewportMetrics({
2913
+ layoutWidth: Math.max(
2914
+ documentElement?.clientWidth || 0,
2915
+ globalObject.innerWidth || 0,
2916
+ ),
2917
+ layoutHeight: Math.max(
2918
+ documentElement?.clientHeight || 0,
2919
+ globalObject.innerHeight || 0,
2920
+ ),
2921
+ visualWidth: viewport?.width || 0,
2922
+ visualHeight: viewport?.height || 0,
2923
+ offsetLeft: viewport?.offsetLeft || 0,
2924
+ offsetTop: viewport?.offsetTop || 0,
2925
+ padding: SELECT_VIEWPORT_PADDING,
2926
+ });
2573
2927
 
2574
- while (el) {
2575
- const style = gcs(el);
2576
- const overflowY = style.overflowY;
2928
+ return {
2929
+ width: metrics.width,
2930
+ height: metrics.height,
2931
+ left: metrics.left,
2932
+ top: metrics.top,
2933
+ padding: metrics.padding,
2934
+ };
2935
+ }
2577
2936
 
2578
- const isScrollable = ["auto", "scroll", "hidden"].includes(overflowY);
2937
+ function resolveSelectViewportMetrics({
2938
+ layoutWidth = 0,
2939
+ layoutHeight = 0,
2940
+ visualWidth = 0,
2941
+ visualHeight = 0,
2942
+ offsetLeft = 0,
2943
+ offsetTop = 0,
2944
+ padding = SELECT_VIEWPORT_PADDING,
2945
+ }) {
2946
+ return {
2947
+ width: Math.max(layoutWidth, visualWidth),
2948
+ height: Math.max(layoutHeight, visualHeight),
2949
+ left: offsetLeft,
2950
+ top: offsetTop,
2951
+ padding,
2952
+ };
2953
+ }
2579
2954
 
2580
- if (isScrollable && el.clientHeight > 0) {
2581
- return el.clientHeight;
2582
- }
2955
+ function resolveSelectVisibleRect({
2956
+ viewportMetrics,
2957
+ boundaryRect = null,
2958
+ }) {
2959
+ const viewportLeft = (viewportMetrics?.left || 0) + (viewportMetrics?.padding || 0);
2960
+ const viewportTop = (viewportMetrics?.top || 0) + (viewportMetrics?.padding || 0);
2961
+ const viewportRight =
2962
+ (viewportMetrics?.left || 0) +
2963
+ (viewportMetrics?.width || 0) -
2964
+ (viewportMetrics?.padding || 0);
2965
+ const viewportBottom =
2966
+ (viewportMetrics?.top || 0) +
2967
+ (viewportMetrics?.height || 0) -
2968
+ (viewportMetrics?.padding || 0);
2969
+
2970
+ const left = boundaryRect
2971
+ ? Math.max(viewportLeft, boundaryRect.left)
2972
+ : viewportLeft;
2973
+ const top = boundaryRect
2974
+ ? Math.max(viewportTop, boundaryRect.top)
2975
+ : viewportTop;
2976
+ const right = boundaryRect
2977
+ ? Math.min(viewportRight, boundaryRect.right)
2978
+ : viewportRight;
2979
+ const bottom = boundaryRect
2980
+ ? Math.min(viewportBottom, boundaryRect.bottom)
2981
+ : viewportBottom;
2583
2982
 
2584
- const root = el.getRootNode?.();
2585
- if (root instanceof ShadowRoot && root.host) {
2586
- el = root.host;
2587
- } else {
2588
- el = el.parentElement;
2589
- }
2983
+ return {
2984
+ left,
2985
+ top,
2986
+ right,
2987
+ bottom,
2988
+ width: Math.max(0, right - left),
2989
+ height: Math.max(0, bottom - top),
2990
+ };
2991
+ }
2992
+
2993
+ function getSelectPopperGeometry() {
2994
+ const viewport = getViewportMetrics();
2995
+ const boundaryElement = resolveClippingBoundaryElement(
2996
+ this[controlElementSymbol],
2997
+ this[popperElementSymbol],
2998
+ );
2999
+ const controlRect = this[controlElementSymbol].getBoundingClientRect();
3000
+ const boundaryRect =
3001
+ boundaryElement instanceof HTMLElement
3002
+ ? boundaryElement.getBoundingClientRect()
3003
+ : null;
3004
+ const visibleRect = resolveSelectVisibleRect({
3005
+ viewportMetrics: viewport,
3006
+ boundaryRect,
3007
+ });
3008
+ const spaceAbove = Math.max(
3009
+ 0,
3010
+ controlRect.top - visibleRect.top,
3011
+ );
3012
+ const spaceBelow = Math.max(
3013
+ 0,
3014
+ visibleRect.bottom - controlRect.bottom,
3015
+ );
3016
+ const availableHeight = Math.max(spaceAbove, spaceBelow, 0);
3017
+
3018
+ return {
3019
+ spaceAbove,
3020
+ spaceBelow,
3021
+ availableHeight,
3022
+ availableWidth: visibleRect.width,
3023
+ boundaryElement,
3024
+ };
3025
+ }
3026
+
3027
+ function getSelectPopperPositionOptions() {
3028
+ const popperOptions = Object.assign({}, this.getOption("popper", {}));
3029
+ const middleware = popperOptions.middleware;
3030
+ const usesLegacyDefaultMiddleware =
3031
+ isArray(middleware) &&
3032
+ middleware.length === 2 &&
3033
+ middleware[0] === "flip" &&
3034
+ middleware[1] === "offset:1";
3035
+
3036
+ if (
3037
+ !isArray(middleware) ||
3038
+ middleware.length === 0 ||
3039
+ usesLegacyDefaultMiddleware
3040
+ ) {
3041
+ popperOptions.middleware = getDefaultSelectPopperPositionProfile().middleware;
3042
+ }
3043
+
3044
+ if (
3045
+ !isString(popperOptions.placement) ||
3046
+ popperOptions.placement === "bottom"
3047
+ ) {
3048
+ popperOptions.placement =
3049
+ getDefaultSelectPopperPositionProfile().placement;
3050
+ }
3051
+
3052
+ if (
3053
+ resolveParentPopperContentBoundary(
3054
+ this[controlElementSymbol],
3055
+ this[popperElementSymbol],
3056
+ )
3057
+ ) {
3058
+ // Nested selects inside another popper must position against the viewport
3059
+ // so the parent content wrapper does not become their clipping boundary.
3060
+ popperOptions.strategy = "fixed";
3061
+ }
3062
+
3063
+ return popperOptions;
3064
+ }
3065
+
3066
+ function getDefaultSelectPopperPositionProfile() {
3067
+ return {
3068
+ placement: "bottom-start",
3069
+ middleware: ["flip", "offset:4", "shift:crossAxis", "size"],
3070
+ };
3071
+ }
3072
+
3073
+ function resetSelectPopperDimensionStyles() {
3074
+ if (!(this[popperElementSymbol] instanceof HTMLElement)) {
3075
+ return;
3076
+ }
3077
+
3078
+ delete this[popperElementSymbol].dataset.monsterPreferredWidth;
3079
+ delete this[popperElementSymbol].dataset.monsterPreferredMaxWidth;
3080
+ this[popperElementSymbol].style.removeProperty("width");
3081
+ this[popperElementSymbol].style.removeProperty("minWidth");
3082
+ this[popperElementSymbol].style.removeProperty("maxWidth");
3083
+ this[popperElementSymbol].style.removeProperty("maxHeight");
3084
+
3085
+ const content = this[popperElementSymbol].querySelector('[part="content"]');
3086
+ if (content instanceof HTMLElement) {
3087
+ content.style.removeProperty("maxHeight");
2590
3088
  }
2591
3089
 
2592
- return window.innerHeight;
3090
+ if (this[optionsElementSymbol] instanceof HTMLElement) {
3091
+ this[optionsElementSymbol].style.removeProperty("height");
3092
+ this[optionsElementSymbol].style.removeProperty("maxHeight");
3093
+ }
2593
3094
  }
2594
3095
 
2595
3096
  /**
@@ -2805,12 +3306,6 @@ function filterFromRemote() {
2805
3306
  return Promise.reject(new Error("Missing Filter Element."));
2806
3307
  }
2807
3308
 
2808
- const url = this.getOption("url");
2809
- if (!url) {
2810
- addErrorAttribute(this, "Missing URL for Remote Filter.");
2811
- return Promise.reject(new Error("Missing URL for Remote Filter."));
2812
- }
2813
-
2814
3309
  let filterValue;
2815
3310
  let showFlag = false;
2816
3311
 
@@ -2834,6 +3329,16 @@ function filterFromRemote() {
2834
3329
  page: this[currentPageSymbol] || 1,
2835
3330
  };
2836
3331
 
3332
+ if (shouldUseDefaultOptionsUrl.call(this, filterValue)) {
3333
+ return loadDefaultOptionsFromUrl.call(this, showFlag);
3334
+ }
3335
+
3336
+ const url = this.getOption("url");
3337
+ if (!url) {
3338
+ addErrorAttribute(this, "Missing URL for Remote Filter.");
3339
+ return Promise.reject(new Error("Missing URL for Remote Filter."));
3340
+ }
3341
+
2837
3342
  return filterFromRemoteByValue.call(this, url, params, showFlag);
2838
3343
  }
2839
3344
 
@@ -2843,7 +3348,8 @@ function filterFromRemote() {
2843
3348
  * @param {object} params
2844
3349
  * @returns {string}
2845
3350
  */
2846
- function formatURL(url, params = {}) {
3351
+ function formatURL(url, params = {}, formatOptions = {}) {
3352
+ const preserveEmptyFilter = formatOptions?.preserveEmptyFilter === true;
2847
3353
  const paramsDefaults = this.getOption("filter.paramsDefaults");
2848
3354
  const externalParams = this.getOption("filter.params");
2849
3355
  if (isObject(paramsDefaults) || isObject(externalParams)) {
@@ -2871,7 +3377,7 @@ function formatURL(url, params = {}) {
2871
3377
  if (
2872
3378
  params.filter === undefined ||
2873
3379
  params.filter === null ||
2874
- params.filter === ""
3380
+ (params.filter === "" && preserveEmptyFilter !== true)
2875
3381
  ) {
2876
3382
  const defaultValue = this.getOption("filter.defaultValue");
2877
3383
  if (defaultValue === undefined || defaultValue === null) {
@@ -2911,9 +3417,14 @@ function formatURL(url, params = {}) {
2911
3417
  * @param {boolean} [openPopper] Flag indicating whether to open the popper.
2912
3418
  * @return {string} The formatted URL with the applied filters and markers.
2913
3419
  */
2914
- function filterFromRemoteByValue(optionUrl, params, openPopper) {
3420
+ function filterFromRemoteByValue(
3421
+ optionUrl,
3422
+ params,
3423
+ openPopper,
3424
+ formatOptions = {},
3425
+ ) {
2915
3426
  return new Processing(() => {
2916
- let url = formatURL.call(this, optionUrl, params);
3427
+ let url = formatURL.call(this, optionUrl, params, formatOptions);
2917
3428
 
2918
3429
  if (url.indexOf(disabledRequestMarker.toString()) !== -1) {
2919
3430
  this.setOption("total", null);
@@ -3027,6 +3538,51 @@ function getCurrentFilterValue() {
3027
3538
  return "";
3028
3539
  }
3029
3540
 
3541
+ function shouldUseDefaultOptionsUrl(filterValue) {
3542
+ if (!isString(this.getOption("filter.defaultOptionsUrl"))) {
3543
+ return false;
3544
+ }
3545
+
3546
+ if (filterValue === undefined || filterValue === null) {
3547
+ return true;
3548
+ }
3549
+
3550
+ if (isString(filterValue)) {
3551
+ return filterValue.trim() === "";
3552
+ }
3553
+
3554
+ return false;
3555
+ }
3556
+
3557
+ function loadDefaultOptionsFromUrl(openPopper = false) {
3558
+ const url = this.getOption("filter.defaultOptionsUrl");
3559
+ if (!isString(url)) {
3560
+ return Promise.resolve(false);
3561
+ }
3562
+
3563
+ this[cleanupOptionsListSymbol] = true;
3564
+
3565
+ return filterFromRemoteByValue
3566
+ .call(
3567
+ this,
3568
+ url,
3569
+ {
3570
+ filter: "",
3571
+ page: this[currentPageSymbol] || 1,
3572
+ },
3573
+ openPopper,
3574
+ {
3575
+ preserveEmptyFilter: true,
3576
+ },
3577
+ )
3578
+ .then(() => {
3579
+ if (isPositionedPopperOpen(this[popperElementSymbol])) {
3580
+ setStatusOrRemoveBadges.call(this, "open");
3581
+ }
3582
+ return true;
3583
+ });
3584
+ }
3585
+
3030
3586
  /**
3031
3587
  * @private
3032
3588
  */
@@ -3146,10 +3702,7 @@ function gatherState() {
3146
3702
  );
3147
3703
 
3148
3704
  for (const e of elements) {
3149
- selection.push({
3150
- label: getSelectionLabel.call(this, e.value),
3151
- value: e.value,
3152
- });
3705
+ selection.push(buildSelectionItem.call(this, e.value));
3153
3706
  }
3154
3707
 
3155
3708
  filteredSelection = selection;
@@ -3166,10 +3719,7 @@ function gatherState() {
3166
3719
  (sel) => !currentInputValues.has(sel.value),
3167
3720
  );
3168
3721
  for (const input of checkedElements) {
3169
- filteredSelection.push({
3170
- label: getSelectionLabel.call(this, input.value),
3171
- value: input.value,
3172
- });
3722
+ filteredSelection.push(buildSelectionItem.call(this, input.value));
3173
3723
  }
3174
3724
  }
3175
3725
 
@@ -3448,16 +3998,10 @@ function convertValueToSelection(value) {
3448
3998
  }
3449
3999
 
3450
4000
  if (isString(value) || isInteger(value)) {
3451
- selection.push({
3452
- label: getSelectionLabel.call(this, value),
3453
- value: value,
3454
- });
4001
+ selection.push(buildSelectionItem.call(this, value));
3455
4002
  } else if (isArray(value)) {
3456
4003
  for (const v of value) {
3457
- selection.push({
3458
- label: getSelectionLabel.call(this, v),
3459
- value: v,
3460
- });
4004
+ selection.push(buildSelectionItem.call(this, v));
3461
4005
  }
3462
4006
 
3463
4007
  value = value.join(",");
@@ -3582,8 +4126,6 @@ function areSelectionValuesEqual(current, next) {
3582
4126
  * @returns {Promise<unknown | void>}
3583
4127
  */
3584
4128
  function setSelection(selection) {
3585
- const self = this;
3586
-
3587
4129
  if (isString(selection) || isInteger(selection)) {
3588
4130
  const result = convertValueToSelection.call(this, selection);
3589
4131
  selection = result?.selection;
@@ -3600,15 +4142,9 @@ function setSelection(selection) {
3600
4142
  continue;
3601
4143
  }
3602
4144
 
3603
- let l = getSelectionLabel.call(this, selection[i].value);
3604
- if (l === selection[i].value) {
3605
- l = selection[i].label;
3606
- }
3607
-
3608
- resultSelection.push({
3609
- label: l,
3610
- value: selection[i].value,
3611
- });
4145
+ resultSelection.push(
4146
+ buildSelectionItem.call(this, selection[i].value, selection[i].label),
4147
+ );
3612
4148
  }
3613
4149
 
3614
4150
  selection = resultSelection;
@@ -3643,17 +4179,6 @@ function setSelection(selection) {
3643
4179
  fireEvent(this, "change"); // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/291
3644
4180
  }
3645
4181
 
3646
- if (this[runLookupOnceSymbol] !== true && selection.length > 0) {
3647
- this[runLookupOnceSymbol] = true;
3648
-
3649
- const lazyLoadFlag =
3650
- this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true;
3651
- const remoteFilterFlag = getFilterMode.call(this) === FILTER_MODE_REMOTE;
3652
- if (lazyLoadFlag || remoteFilterFlag) {
3653
- lookupSelection.call(self);
3654
- }
3655
- }
3656
-
3657
4182
  return new Processing(() => {
3658
4183
  const CLASSNAME = "selected";
3659
4184
 
@@ -3751,6 +4276,7 @@ function fetchData(url) {
3751
4276
  * @private
3752
4277
  */
3753
4278
  function hide() {
4279
+ resetSelectPopperDimensionStyles.call(this);
3754
4280
  closePositionedPopper(this[popperElementSymbol]);
3755
4281
  setStatusOrRemoveBadges.call(this, "closed");
3756
4282
  removeAttributeToken(this[controlElementSymbol], "class", "open");
@@ -3804,12 +4330,6 @@ function show() {
3804
4330
  return;
3805
4331
  }
3806
4332
 
3807
- const hasDefaultOptionsUrl = isString(
3808
- this.getOption("filter.defaultOptionsUrl"),
3809
- );
3810
-
3811
- initDefaultOptionsFromUrl.call(this);
3812
-
3813
4333
  const hasPopperFilterFlag =
3814
4334
  this.getOption("filter.position") === FILTER_POSITION_POPPER &&
3815
4335
  getFilterMode.call(this) !== FILTER_MODE_DISABLED;
@@ -3819,11 +4339,12 @@ function show() {
3819
4339
  return;
3820
4340
  }
3821
4341
 
4342
+ resetSelectPopperDimensionStyles.call(this);
3822
4343
  this[popperElementSymbol].style.visibility = "hidden";
3823
4344
  openPositionedPopper(
3824
4345
  this[controlElementSymbol],
3825
4346
  this[popperElementSymbol],
3826
- this.getOption("popper", {}),
4347
+ getSelectPopperPositionOptions.call(this),
3827
4348
  );
3828
4349
  setStatusOrRemoveBadges.call(this, "open");
3829
4350
 
@@ -3831,17 +4352,23 @@ function show() {
3831
4352
  registerWithHost.call(this);
3832
4353
 
3833
4354
  new Processing(() => {
3834
- if (!self?.[remoteFilterFirstOpendSymbol]) {
3835
- self[remoteFilterFirstOpendSymbol] = true;
4355
+ const shouldLoadRemoteOptions =
4356
+ getFilterMode.call(self) === FILTER_MODE_REMOTE &&
4357
+ getOptionElements.call(self).length === 0;
3836
4358
 
3837
- if (!hasDefaultOptionsUrl) {
3838
- setTimeout(() => {
3839
- self[cleanupOptionsListSymbol] = true;
3840
- filterFromRemote.call(self).catch((e) => {
3841
- addErrorAttribute(self, e);
3842
- });
3843
- }, 0);
3844
- }
4359
+ if (shouldUseDefaultOptionsUrl.call(self, getCurrentFilterValue.call(self))) {
4360
+ setTimeout(() => {
4361
+ loadDefaultOptionsFromUrl.call(self).catch((e) => {
4362
+ addErrorAttribute(self, e);
4363
+ });
4364
+ }, 0);
4365
+ } else if (shouldLoadRemoteOptions) {
4366
+ setTimeout(() => {
4367
+ self[cleanupOptionsListSymbol] = true;
4368
+ filterFromRemote.call(self).catch((e) => {
4369
+ addErrorAttribute(self, e);
4370
+ });
4371
+ }, 0);
3845
4372
  }
3846
4373
  calcAndSetOptionsDimension.call(this);
3847
4374
  focusFilter.call(this);
@@ -3905,31 +4432,6 @@ function unregisterFromHost() {
3905
4432
  this[hostElementSymbol].unregisterDismissable?.(this);
3906
4433
  }
3907
4434
 
3908
- function initDefaultOptionsFromUrl() {
3909
- const url = this.getOption("filter.defaultOptionsUrl");
3910
- if (!url) {
3911
- return;
3912
- }
3913
-
3914
- this.setOption("filter.defaultOptionsUrl", null);
3915
-
3916
- fetchData
3917
- .call(this, url)
3918
- .then((data) => {
3919
- this[cleanupOptionsListSymbol] = false;
3920
- importOptionsIntern.call(this, data);
3921
- setStatusOrRemoveBadges.call(this, "open");
3922
- initTotal.call(this, data);
3923
- })
3924
- .catch((e) => {
3925
- addErrorAttribute(this, e);
3926
- setStatusOrRemoveBadges.call(this, "error");
3927
- });
3928
- }
3929
-
3930
- /**
3931
- * @private
3932
- */
3933
4435
  /**
3934
4436
  * @private
3935
4437
  */
@@ -4493,7 +4995,7 @@ function updatePopper() {
4493
4995
  this,
4494
4996
  this[controlElementSymbol],
4495
4997
  this[popperElementSymbol],
4496
- this.getOption("popper", {}),
4998
+ getSelectPopperPositionOptions.call(this),
4497
4999
  );
4498
5000
  requestAnimationFrame(() => {
4499
5001
  refreshSelectPaginationLayout.call(this);
@@ -4540,7 +5042,8 @@ function getTemplate() {
4540
5042
  part="badge"
4541
5043
  data-monster-attributes="
4542
5044
  data-monster-value path:selection | index:value,
4543
- class path:classes | index:badge,
5045
+ data-monster-unresolved path:selection | index:unresolved,
5046
+ class path:selection | index:class,
4544
5047
  part path:type | suffix:-option | prefix: form-" tabindex="-1">
4545
5048
  <div data-monster-replace="path:selection | index:label" part="badge-label"
4546
5049
  data-monster-role="badge-label"></div>