@schukai/monster 4.48.1 → 4.48.3

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.48.3] - 2025-12-02
6
+
7
+ ### Bug Fixes
8
+
9
+ - **select:** prevent NaN labels
10
+
11
+
12
+
13
+ ## [4.48.2] - 2025-12-02
14
+
15
+ ### Bug Fixes
16
+
17
+ - **select:** own lookup cache
18
+
19
+
20
+
5
21
  ## [4.48.1] - 2025-11-28
6
22
 
7
23
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.48.1"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.48.3"}
@@ -272,6 +272,18 @@ const currentPageSymbol = Symbol("currentPage");
272
272
  */
273
273
  const remoteFilterFirstOpendSymbol = Symbol("remoteFilterFirstOpend");
274
274
 
275
+ /**
276
+ * @private
277
+ * @type {symbol}
278
+ */
279
+ const lookupCacheSymbol = Symbol("lookupCache");
280
+
281
+ /**
282
+ * @private
283
+ * @type {symbol}
284
+ */
285
+ const lookupInProgressSymbol = Symbol("lookupInProgress");
286
+
275
287
  /**
276
288
  * @private
277
289
  * @type {number}
@@ -346,6 +358,8 @@ class Select extends CustomControl {
346
358
  constructor() {
347
359
  super();
348
360
  this[currentPageSymbol] = 1;
361
+ this[lookupCacheSymbol] = new Map();
362
+ this[lookupInProgressSymbol] = new Map();
349
363
  initOptionObserver.call(this);
350
364
  }
351
365
 
@@ -613,6 +627,10 @@ class Select extends CustomControl {
613
627
  try {
614
628
  hide.call(this);
615
629
 
630
+ // Clear the lookup cache
631
+ this[lookupCacheSymbol].clear();
632
+ this[lookupInProgressSymbol].clear();
633
+
616
634
  setSelection
617
635
  .call(this, null)
618
636
  .then(() => {
@@ -1845,6 +1863,11 @@ function parseSlotsToOptions() {
1845
1863
  * @return {*}
1846
1864
  */
1847
1865
  function buildSelectionLabel(value) {
1866
+ // First, check the lookup cache.
1867
+ if (this[lookupCacheSymbol].has(value)) {
1868
+ return this[lookupCacheSymbol].get(value);
1869
+ }
1870
+
1848
1871
  const options = this.getOption("options");
1849
1872
 
1850
1873
  for (let i = 0; i < options.length; i++) {
@@ -1879,6 +1902,74 @@ function buildSelectionLabel(value) {
1879
1902
  return undefined;
1880
1903
  }
1881
1904
 
1905
+ /**
1906
+ * @private
1907
+ * @param {string} value The value to look up.
1908
+ * @returns {Promise<void>}
1909
+ */
1910
+ async function lookupValueAndCache(value) {
1911
+ const lookupUrl = this.getOption("lookup.url");
1912
+ // A more robust check for invalid values. 0 and "" are valid.
1913
+ if (!lookupUrl || value === null || value === undefined || Number.isNaN(value)) {
1914
+ return;
1915
+ }
1916
+
1917
+ // Avoid duplicate lookups for the same value.
1918
+ if (this[lookupInProgressSymbol].has(value)) {
1919
+ return;
1920
+ }
1921
+
1922
+ try {
1923
+ this[lookupInProgressSymbol].set(value, true);
1924
+
1925
+ const markerOpen = this.getOption("filter.marker.open", "{");
1926
+ const markerClose = this.getOption("filter.marker.close", "}");
1927
+ const url = lookupUrl.replace(`${markerOpen}filter${markerClose}`, value);
1928
+
1929
+ const data = await fetchData.call(this, url);
1930
+
1931
+ const mappingOptions = this.getOption("mapping", {});
1932
+ const map = buildMap(
1933
+ data,
1934
+ mappingOptions.selector,
1935
+ mappingOptions.labelTemplate,
1936
+ mappingOptions.valueTemplate,
1937
+ mappingOptions.filter,
1938
+ );
1939
+
1940
+ let found = false;
1941
+ for (const [itemValue, itemLabel] of map.entries()) {
1942
+ // If the label template results in NaN, log an error and skip caching.
1943
+ if (itemLabel === "NaN") {
1944
+ addErrorAttribute(
1945
+ this,
1946
+ new Error(
1947
+ `Lookup for value '${itemValue}' resulted in a 'NaN' label. Check 'mapping.labelTemplate' and API response.`,
1948
+ ),
1949
+ );
1950
+ continue;
1951
+ }
1952
+
1953
+ // The lookup might return more than the requested value, so we cache all of them.
1954
+ if (!this[lookupCacheSymbol].has(itemValue)) {
1955
+ this[lookupCacheSymbol].set(itemValue, itemLabel);
1956
+ if (`${itemValue}` === `${value}`) {
1957
+ found = true;
1958
+ }
1959
+ }
1960
+ }
1961
+
1962
+ // If the specific value was found, trigger a re-render of the selection.
1963
+ if (found) {
1964
+ await setSelection.call(this, this.getOption("selection"));
1965
+ }
1966
+ } catch (e) {
1967
+ addErrorAttribute(this, e);
1968
+ } finally {
1969
+ this[lookupInProgressSymbol].delete(value);
1970
+ }
1971
+ }
1972
+
1882
1973
  /**
1883
1974
  * @private
1884
1975
  * @param {*} value
@@ -1892,6 +1983,15 @@ function getSelectionLabel(value) {
1892
1983
  if (isString(label)) return label;
1893
1984
  }
1894
1985
 
1986
+ // If the label was not found, and we have a lookup URL, trigger a lookup for valid values.
1987
+ const isValueValidForLookup =
1988
+ value !== null && value !== undefined && !Number.isNaN(value);
1989
+ if (this.getOption("lookup.url") && isValueValidForLookup) {
1990
+ lookupValueAndCache.call(this, value).catch((e) => {
1991
+ addErrorAttribute(this, e);
1992
+ });
1993
+ }
1994
+
1895
1995
  if (isString(value) || isInteger(value)) {
1896
1996
  return `${value}`;
1897
1997
  }