@schukai/monster 4.39.0 → 4.40.1

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.
@@ -66,6 +66,8 @@ import { positionPopper } from "./util/floating-ui.mjs";
66
66
  import { Pathfinder } from "../../data/pathfinder.mjs";
67
67
  import { TokenList } from "../../types/tokenlist.mjs";
68
68
 
69
+ import "../datatable/pagination.mjs";
70
+
69
71
  export {
70
72
  getSelectionTemplate,
71
73
  getSummaryTemplate,
@@ -152,6 +154,13 @@ const changeEventHandler = Symbol("changeEventHandler");
152
154
  */
153
155
  const controlElementSymbol = Symbol("controlElement");
154
156
 
157
+ /**
158
+ * local symbol
159
+ * @private
160
+ * @type {Symbol}
161
+ */
162
+ const paginationElementSymbol = Symbol("paginationElement");
163
+
155
164
  /**
156
165
  * local symbol
157
166
  * @private
@@ -251,6 +260,17 @@ const cleanupOptionsListSymbol = Symbol("cleanupOptionsList");
251
260
  const debounceOptionsMutationObserverSymbol = Symbol(
252
261
  "debounceOptionsMutationObserver",
253
262
  );
263
+ /**
264
+ * @private
265
+ * @type {symbol}
266
+ */
267
+ const currentPageSymbol = Symbol("currentPage");
268
+
269
+ /**
270
+ * @private
271
+ * @type {symbol}
272
+ */
273
+ const remoteFilterFirstOpendSymbol = Symbol("remoteFilterFirstOpend");
254
274
 
255
275
  /**
256
276
  * @private
@@ -307,6 +327,9 @@ const FILTER_POSITION_INLINE = "inline";
307
327
  * @example /examples/components/form/select-fetch Fetch options
308
328
  * @example /examples/components/form/select-lazy Lazy load
309
329
  * @example /examples/components/form/select-remote-filter Remote filter
330
+ * @example /examples/components/form/select-remote-filter Server-side filtering with a remote URL
331
+ * @example /examples/components/form/select-remote-pagination Server-side filtering with pagination
332
+ * @example /examples/components/form/select-summary-template Using a summary template for selections
310
333
  *
311
334
  * @copyright schukai GmbH
312
335
  * @summary A beautiful select control that can make your life easier and also looks good.
@@ -322,6 +345,7 @@ class Select extends CustomControl {
322
345
  */
323
346
  constructor() {
324
347
  super();
348
+ this[currentPageSymbol] = 1;
325
349
  initOptionObserver.call(this);
326
350
  }
327
351
 
@@ -379,74 +403,91 @@ class Select extends CustomControl {
379
403
  addErrorAttribute(this, e);
380
404
  });
381
405
  }
382
-
383
406
  /**
384
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
407
+ * Defines the default configuration options for the monster-control.
408
+ * These options can be overridden via the HTML attribute `data-monster-options`.
385
409
  * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
386
410
  *
387
- * The individual configuration values can be found in the table.
388
- *
389
- * @property {string[]} toggleEventType Array of DOM event names (e.g. ["click","touch"]) to toggle the dropdown.
390
- * @property {boolean} delegatesFocus Whether the element delegates focus to its internal control (e.g. the filter input).
391
- * @property {Array<Object>} options Array of option objects {label,value,visibility?,data?} for static option list.
392
- * @property {string|string[]} selection Initial selected value(s) as string, comma-separated string, or array of strings.
393
- * @property {number} showMaxOptions Maximum visible options before the dropdown scrolls.
394
- * @property {"radio"|"checkbox"} type Selection mode: "radio" for single, "checkbox" for multiple.
395
- * @property {string} name Name of the hidden form field for form submission.
396
- * @property {string|null} url URL to dynamically fetch options via HTTP when opening or filtering.
397
- * @property {Object} lookup Configuration for lookup requests.
398
- * @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"`.
399
- * @property {boolean} lookup.grouping Group lookup requests: true to fetch all selected values in one request, false to fetch each individually.
400
- * @property {string} fetch.redirect Fetch redirect mode (e.g. "error").
401
- * @property {string} fetch.method HTTP method for fetching options (e.g. "GET").
402
- * @property {string} fetch.mode Fetch mode (e.g. "same-origin").
403
- * @property {string} fetch.credentials Credentials policy for fetch (e.g. "same-origin").
404
- * @property {Object.<string,string>} fetch.headers HTTP headers for fetch requests.
405
- * @property {string} labels.cannot-be-loaded Message when options cannot be loaded.
406
- * @property {string} labels.no-options-available Message when no static options are available.
407
- * @property {string} labels.click-to-load-options Message prompting user to click to load options when `features.lazyLoad` is enabled.
408
- * @property {string} labels.select-an-option Placeholder text when no selection is made.
409
- * @property {string} labels.no-options Message when neither slots nor fetched options exist.
410
- * @property {string} labels.no-options-found Message when filter yields no matching options.
411
- * @property {string} labels.summary-text.zero Plural template for zero selected entries (e.g. "No entries were selected").
412
- * @property {string} labels.summary-text.one Plural template for one selected entry.
413
- * @property {string} labels.summary-text.other Plural template for multiple selected entries.
414
- * @property {boolean} features.clearAll Show a "clear all" badge to reset selection.
415
- * @property {boolean} features.clear Show remove icon on individual selection badges.
416
- * @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"`).
417
- * @property {boolean} features.closeOnSelect Automatically close dropdown after selection.
418
- * @property {boolean} features.emptyValueIfNoOptions Set value to empty when no options are available.
419
- * @property {boolean} features.storeFetchedData Persist raw fetched data for later retrieval via `getLastFetchedData()`.
420
- * @property {boolean} features.useStrictValueComparison Use strict (`===`) comparison when matching option values.
421
- * @property {boolean} features.showRemoteInfo When the filter mode is set to "remote," display a badge indicating the possibility of additional remote options.
422
- * @property {Object} remoteInfo Configuration for remote info badge.
423
- * @property {string} remoteInfo.url URL for total count of options when `filter.mode==="remote"` is set.
424
- * @property {Object} placeholder Placeholder text for the control.
425
- * @property {string} placeholder.filter Placeholder text for filter input.
426
- * @property {string|null} filter.defaultValue Default filter value for remote requests; if unset or empty, disabled marker prevents request.
427
- * @property {"options"|"remote"|"disabled"} filter.mode Client-side ("options"), server-side ("remote"; disables `features.lazyLoad`), or disabled filtering.
428
- * @property {"inline"|"popper"} filter.position Position of filter input: inline within control or inside popper dropdown.
429
- * @property {string} filter.marker.open Opening marker for embedding filter value in `filter.mode==="remote"` URLs.
430
- * @property {string} filter.marker.close Closing marker for embedding filter value in URLs.
431
- * @property {string|null} filter.defaultOptionsUrl URL for default options when `filter.mode==="remote"` is set and no filter value is provided.
432
- * @property {string} templates.main HTML template string for rendering options and selection badges.
433
- * @property {string} templateMapping.selected Template variant for selected items (e.g. badge vs summary view).
434
- * @property {string} popper.placement Popper.js placement strategy for dropdown (e.g. "bottom").
435
- * @property {Array<string|Object>} popper.middleware Popper.js middleware or offset configurations.
436
- * @property {string} mapping.selector Data path or selector to identify entries in imported data.
437
- * @property {string} mapping.labelTemplate Template for option labels using placeholders like `${name}`.
438
- * @property {string} mapping.valueTemplate Template for option values using placeholders like `${name}`.
439
- * @property {Function} mapping.filter Optional callback to filter imported map entries before building `options[]`.
440
- * @property {string} empty.defaultValueRadio Default radio-value when no selection exists.
441
- * @property {Array} empty.defaultValueCheckbox Default checkbox-values array when no selection exists.
442
- * @property {Array} empty.equivalents Values considered empty (e.g. `undefined`, `null`, `""`, `NaN`) and normalized to defaults.
443
- * @property {Function} formatter.selection Callback `(value)=>string` to format the display label of each selected value.
444
- * @property {Object} classes CSS classes for styling.
445
- * @property {string} classes.badge CSS class for the selection badge.
446
- * @property {string} classes.statusOrRemoveBadge CSS class for the status or remove badge.
447
- * @property {string} classes.remoteInfo CSS class for the remote info badge.
448
- * @property {string} classes.noOptions CSS class for the no options available message.
449
- * @property {number|null} total Total number of options available.
411
+ * @property {string[]} toggleEventType - Array of DOM event names (e.g., ["click", "touch"]) that toggle the dropdown.
412
+ * @property {boolean} delegatesFocus - If `true`, the element delegates focus to its internal control (e.g., the filter input).
413
+ * @property {Array<Object>} options - Array of option objects `{label, value, visibility?, data?}` for a static option list.
414
+ * @property {string|string[]} selection - Initial selected value(s), as a string, comma-separated string, or array of strings.
415
+ * @property {number} showMaxOptions - Maximum number of visible options before the list becomes scrollable.
416
+ * @property {"radio"|"checkbox"} type - Selection mode: "radio" for single selection, "checkbox" for multiple selections.
417
+ * @property {string} name - Name of the hidden form field for form submission.
418
+ * @property {string|null} url - URL to dynamically fetch options via HTTP when opening or filtering.
419
+ * @property {number|null} total - Total number of available options, useful for pagination with remote data.
420
+ * @property {Object} lookup - Configuration for fetching initially selected values.
421
+ * @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"`.
422
+ * @property {boolean} lookup.grouping - If `true`, all selected values are fetched in a single request; otherwise, a separate request is sent for each value.
423
+ * @property {Object} fetch - Configuration for HTTP requests via `fetch`.
424
+ * @property {string} fetch.redirect - Fetch redirect mode (e.g., "error", "follow").
425
+ * @property {string} fetch.method - HTTP method for fetching options (e.g., "GET", "POST").
426
+ * @property {string} fetch.mode - Fetch mode (e.g., "cors", "same-origin").
427
+ * @property {string} fetch.credentials - Credentials policy for fetch (e.g., "include", "same-origin").
428
+ * @property {Object.<string, string>} fetch.headers - HTTP headers to be sent with every request.
429
+ * @property {Object} labels - Text labels for various states and UI elements.
430
+ * @property {string} labels.cannot-be-loaded - Message displayed when options cannot be loaded.
431
+ * @property {string} labels.no-options-available - Message displayed when no static options are provided.
432
+ * @property {string} labels.click-to-load-options - Prompt to load options when `features.lazyLoad` is enabled.
433
+ * @property {string} labels.select-an-option - Placeholder text when no selection has been made.
434
+ * @property {string} labels.no-options - Message displayed when no options are available from slots or fetch.
435
+ * @property {string} labels.no-options-found - Message displayed when the filter yields no matching options.
436
+ * @property {string} labels.summary-text.zero - Pluralization template for zero selected entries (e.g., "No entries selected").
437
+ * @property {string} labels.summary-text.one - Pluralization template for one selected entry.
438
+ * @property {string} labels.summary-text.other - Pluralization template for multiple selected entries.
439
+ * @property {Object} features - Toggles to enable/disable functionalities.
440
+ * @property {boolean} features.clearAll - Shows a "Clear all" button to reset the selection.
441
+ * @property {boolean} features.clear - Shows a "remove" icon on individual selection badges.
442
+ * @property {boolean} features.lazyLoad - If `true`, options are fetched from the `url` only when the dropdown is first opened. Ignored if `filter.mode` is `"remote"`.
443
+ * @property {boolean} features.closeOnSelect - If `true`, the dropdown closes automatically after a selection is made.
444
+ * @property {boolean} features.emptyValueIfNoOptions - Sets the hidden field's value to empty if no options are available.
445
+ * @property {boolean} features.storeFetchedData - If `true`, the raw fetched data is stored and can be retrieved via `getLastFetchedData()`.
446
+ * @property {boolean} features.useStrictValueComparison - Uses strict comparison (`===`) when matching option values.
447
+ * @property {boolean} features.showRemoteInfo - When `filter.mode === "remote"`, displays an info badge indicating that more options may exist on the server.
448
+ * @property {Object} remoteInfo - Configuration for the remote info display.
449
+ * @property {string|null} remoteInfo.url - URL to fetch the total count of remote options (used when `filter.mode === "remote"`).
450
+ * @property {Object} placeholder - Placeholder texts for input fields.
451
+ * @property {string} placeholder.filter - Placeholder text for the filter input field.
452
+ * @property {Object} filter - Configuration for the filtering functionality.
453
+ * @property {string|null} filter.defaultValue - Default filter value for remote requests. An empty string will prevent the initial request.
454
+ * @property {"options"|"remote"|"disabled"} filter.mode - Filter mode: `"options"` (client-side), `"remote"` (server-side, `lazyLoad` is ignored), or `"disabled"`.
455
+ * @property {"inline"|"popper"} filter.position - Position of the filter input: `"inline"` (inside the control) or `"popper"` (inside the dropdown).
456
+ * @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.
457
+ * @property {Object} filter.marker - Markers for embedding the filter value into the `url` for server-side filtering.
458
+ * @property {string} filter.marker.open - Opening marker (e.g., `{`).
459
+ * @property {string} filter.marker.close - Closing marker (e.g., `}`).
460
+ * @property {Object} templates - HTML templates for the main components.
461
+ * @property {string} templates.main - HTML template string for the control's basic structure.
462
+ * @property {Object} templateMapping - Mapping of templates for specific use cases.
463
+ * @property {string} templateMapping.selected - Template variant for selected items (e.g., for rendering as a badge).
464
+ * @property {Object} popper - Configuration for Popper.js to position the dropdown.
465
+ * @property {string} popper.placement - Popper.js placement strategy (e.g., "bottom-start").
466
+ * @property {Array<string|Object>} popper.middleware - Array of middleware configurations for Popper.js (e.g., ["flip", "offset:1"]).
467
+ * @property {Object} mapping - Defines how fetched data is transformed into `options`.
468
+ * @property {string} mapping.selector - Path/selector to find the array of items within the fetched data (e.g., "results" for `data.results`).
469
+ * @property {string} mapping.labelTemplate - Template for the option label using placeholders (e.g., `Label: ${name}`).
470
+ * @property {string} mapping.valueTemplate - Template for the option value using placeholders (e.g., `ID: ${id}`).
471
+ * @property {Function|null} mapping.filter - Optional callback function `(item) => boolean` to filter fetched items before processing.
472
+ * @property {string|null} mapping.total - Path/selector to the total number of items for pagination (e.g., "pagination.total").
473
+ * @property {string|null} mapping.currentPage - Path/selector to the current page number.
474
+ * @property {string|null} mapping.objectsPerPage - Path/selector to the number of objects per page.
475
+ * @property {Object} empty - Handling of empty or undefined values.
476
+ * @property {string} empty.defaultValueRadio - Default value for `type="radio"` when no selection exists.
477
+ * @property {Array} empty.defaultValueCheckbox - Default value (empty array) for `type="checkbox"`.
478
+ * @property {Array} empty.equivalents - Values that are considered "empty" (e.g., `undefined`, `null`, `""`) and are normalized to the default value.
479
+ * @property {Object} formatter - Functions for formatting display values.
480
+ * @property {Function} formatter.selection - Callback `(value, option) => string` to format the display text of selected values.
481
+ * @property {Object} classes - CSS classes for various elements.
482
+ * @property {string} classes.badge - CSS class for selection badges.
483
+ * @property {string} classes.statusOrRemoveBadge - CSS class for the status or remove badge.
484
+ * @property {string} classes.remoteInfo - CSS class for the remote info badge.
485
+ * @property {string} classes.noOptions - CSS class for the "no options" message.
486
+ * @property {Object} messages - Internal messages for ARIA attributes and screen readers (should not normally be changed).
487
+ * @property {string|null} messages.control - Message for the main control element.
488
+ * @property {string|null} messages.selected - Message for the selected items area.
489
+ * @property {string|null} messages.emptyOptions - Message for an empty options list.
490
+ * @property {string|null} messages.total - Message that communicates the total number of options.
450
491
  */
451
492
  get defaults() {
452
493
  return Object.assign(
@@ -529,6 +570,8 @@ class Select extends CustomControl {
529
570
  valueTemplate: "",
530
571
  filter: null,
531
572
  total: null,
573
+ currentPage: null,
574
+ objectsPerPage: null,
532
575
  },
533
576
 
534
577
  empty: {
@@ -781,10 +824,7 @@ class Select extends CustomControl {
781
824
  addErrorAttribute(this, e);
782
825
  });
783
826
 
784
- if (
785
- this.parentElement &&
786
- this.parentElement.nodeName === "MONSTER-BUTTON-BAR"
787
- ) {
827
+ if (this.parentElement.nodeName === "MONSTER-BUTTON-BAR") {
788
828
  this.shadowRoot
789
829
  .querySelector("[data-monster-role=control]")
790
830
  .classList.add("in-button-bar");
@@ -847,6 +887,49 @@ class Select extends CustomControl {
847
887
  }
848
888
  }
849
889
 
890
+ /**
891
+ * @private
892
+ * @param {object} data Die rohen Daten aus der API-Antwort.
893
+ */
894
+ function processAndApplyPaginationData(data) {
895
+ if (!this[paginationElementSymbol]) {
896
+ return;
897
+ }
898
+
899
+ const mappingTotal = this.getOption("mapping.total");
900
+ const mappingCurrentPage = this.getOption("mapping.currentPage");
901
+ const mappingObjectsPerPage = this.getOption("mapping.objectsPerPage");
902
+
903
+ if (!mappingTotal || !mappingCurrentPage || !mappingObjectsPerPage) {
904
+ return;
905
+ }
906
+
907
+ try {
908
+ const pathfinder = new Pathfinder(data);
909
+ const total = pathfinder.getVia(mappingTotal);
910
+ const currentPage = pathfinder.getVia(mappingCurrentPage);
911
+ const objectsPerPage = pathfinder.getVia(mappingObjectsPerPage);
912
+
913
+ if (!isInteger(total)) {
914
+ addErrorAttribute(this, "total is not an integer");
915
+ return;
916
+ }
917
+
918
+ this.setOption("total", total);
919
+
920
+ if (
921
+ isInteger(currentPage) &&
922
+ currentPage > 0 &&
923
+ isInteger(objectsPerPage) &&
924
+ objectsPerPage > 0
925
+ ) {
926
+ updatePagination.call(this, total, currentPage, objectsPerPage);
927
+ }
928
+ } catch (e) {
929
+ addErrorAttribute(this, e);
930
+ }
931
+ }
932
+
850
933
  /**
851
934
  * @private
852
935
  * @param data
@@ -911,6 +994,8 @@ function importOptionsIntern(data) {
911
994
  options = this.getOption("options", []);
912
995
  }
913
996
 
997
+ this[cleanupOptionsListSymbol] = false;
998
+
914
999
  if (!isIterable(map)) {
915
1000
  throw new Error("map is not iterable");
916
1001
  }
@@ -1419,6 +1504,9 @@ function getTranslations() {
1419
1504
  }
1420
1505
  }
1421
1506
 
1507
+ /**
1508
+ * @private
1509
+ */
1422
1510
  /**
1423
1511
  * @private
1424
1512
  */
@@ -1429,37 +1517,25 @@ function lookupSelection() {
1429
1517
  (entries, obs) => {
1430
1518
  for (const entry of entries) {
1431
1519
  if (entry.isIntersecting) {
1432
- obs.disconnect(); // Only observe once
1520
+ obs.disconnect();
1433
1521
 
1434
1522
  setTimeout(() => {
1435
1523
  const selection = self.getOption("selection");
1436
- if (selection.length === 0) {
1524
+ if (
1525
+ selection.length === 0 ||
1526
+ self[isLoadingSymbol] ||
1527
+ self[lazyLoadDoneSymbol]
1528
+ ) {
1437
1529
  return;
1438
1530
  }
1439
1531
 
1440
- if (self[isLoadingSymbol] === true) {
1441
- return;
1442
- }
1443
-
1444
- if (self[lazyLoadDoneSymbol] === true) {
1445
- return;
1446
- }
1447
-
1448
- let url = self.getOption("url");
1449
- const lookupUrl = self.getOption("lookup.url");
1450
- if (lookupUrl !== null) {
1451
- url = lookupUrl;
1452
- }
1453
-
1532
+ let url = self.getOption("lookup.url") || self.getOption("url");
1454
1533
  self[cleanupOptionsListSymbol] = false;
1455
1534
 
1456
1535
  if (self.getOption("lookup.grouping") === true) {
1536
+ const values = selection.map((s) => s?.["value"]);
1457
1537
  filterFromRemoteByValue
1458
- .call(
1459
- self,
1460
- url,
1461
- selection.map((s) => s?.["value"]),
1462
- )
1538
+ .call(self, url, { filter: values.join(",") })
1463
1539
  .catch((e) => {
1464
1540
  addErrorAttribute(self, e);
1465
1541
  });
@@ -1469,7 +1545,7 @@ function lookupSelection() {
1469
1545
  for (const s of selection) {
1470
1546
  if (s?.["value"]) {
1471
1547
  filterFromRemoteByValue
1472
- .call(self, url, s["value"])
1548
+ .call(self, url, { filter: s["value"] })
1473
1549
  .catch((e) => {
1474
1550
  addErrorAttribute(self, e);
1475
1551
  });
@@ -1482,10 +1558,8 @@ function lookupSelection() {
1482
1558
  { threshold: 0.1 },
1483
1559
  );
1484
1560
 
1485
- // Beobachte das Element selbst (dieses Element muss im DOM sein)
1486
1561
  observer.observe(self);
1487
1562
  }
1488
-
1489
1563
  /**
1490
1564
  *
1491
1565
  * @param url
@@ -1523,6 +1597,7 @@ function fetchIt(url, controlOptions) {
1523
1597
  ) {
1524
1598
  try {
1525
1599
  importOptionsIntern.call(self, map);
1600
+ processAndApplyPaginationData.call(self, map);
1526
1601
  } catch (e) {
1527
1602
  setStatusOrRemoveBadges.call(this, "error");
1528
1603
  reject(e);
@@ -1863,21 +1938,19 @@ function setTotalText() {
1863
1938
 
1864
1939
  const count = this.getOption("options").length;
1865
1940
  const total = Number.parseInt(this.getOption("total"));
1866
-
1867
1941
  if (Number.isNaN(total)) {
1868
1942
  this.setOption("messages.total", "");
1869
1943
  return;
1870
1944
  }
1871
1945
 
1872
1946
  const translations = getDefaultTranslation.call(this);
1873
- const text = translations.getPluralRuleText("total", total, "");
1874
1947
 
1875
1948
  const diff = total - count;
1876
1949
  if (diff < 0) {
1877
1950
  this.setOption("messages.total", "");
1878
1951
  return;
1879
1952
  }
1880
-
1953
+ const text = translations.getPluralRuleText("total", diff, "");
1881
1954
  const selectedText = new Formatter({
1882
1955
  count: String(diff),
1883
1956
  }).format(text);
@@ -2252,6 +2325,7 @@ function handleFilterKeyEvents() {
2252
2325
  if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) {
2253
2326
  filterOptions.call(this);
2254
2327
  } else {
2328
+ this[currentPageSymbol] = 1;
2255
2329
  this[cleanupOptionsListSymbol] = true;
2256
2330
 
2257
2331
  filterFromRemote.call(this).catch((e) => {
@@ -2291,7 +2365,6 @@ function filterFromRemote() {
2291
2365
  filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
2292
2366
  }
2293
2367
  showFlag = true;
2294
-
2295
2368
  break;
2296
2369
  case FILTER_POSITION_POPPER:
2297
2370
  default:
@@ -2300,24 +2373,44 @@ function filterFromRemote() {
2300
2373
  }
2301
2374
  }
2302
2375
 
2303
- return filterFromRemoteByValue.call(this, url, filterValue, showFlag);
2376
+ const params = {
2377
+ filter: filterValue,
2378
+ page: this[currentPageSymbol] || 1,
2379
+ };
2380
+
2381
+ return filterFromRemoteByValue.call(this, url, params, showFlag);
2304
2382
  }
2305
2383
 
2306
2384
  /**
2307
2385
  * @private
2308
2386
  * @param url
2309
- * @param value
2387
+ * @param {object} params
2310
2388
  * @returns {string}
2311
2389
  */
2312
- function formatURL(url, value) {
2313
- if (value === undefined || value === null || value === "") {
2314
- value = this.getOption("filter.defaultValue");
2315
- if (value === undefined || value === null) {
2316
- value = disabledRequestMarker.toString();
2390
+ function formatURL(url, params = {}) {
2391
+ // Die Logik für den Default-Filterwert bleibt erhalten
2392
+ if (
2393
+ params.filter === undefined ||
2394
+ params.filter === null ||
2395
+ params.filter === ""
2396
+ ) {
2397
+ const defaultValue = this.getOption("filter.defaultValue");
2398
+ if (defaultValue === undefined || defaultValue === null) {
2399
+ params.filter = disabledRequestMarker.toString();
2400
+ } else {
2401
+ params.filter = defaultValue;
2402
+ }
2403
+ }
2404
+
2405
+ const encodedParams = {};
2406
+ for (const key in params) {
2407
+ if (Object.hasOwn(params, key)) {
2408
+ const value = params[key];
2409
+ encodedParams[key] = encodeURI(String(value));
2317
2410
  }
2318
2411
  }
2319
2412
 
2320
- const formatter = new Formatter({ filter: encodeURI(value) });
2413
+ const formatter = new Formatter(encodedParams);
2321
2414
  const openMarker = this.getOption("filter.marker.open");
2322
2415
  let closeMarker = this.getOption("filter.marker.close");
2323
2416
  if (!closeMarker) {
@@ -2339,14 +2432,15 @@ function formatURL(url, value) {
2339
2432
  * @param {boolean} [openPopper] Flag indicating whether to open the popper.
2340
2433
  * @return {string} The formatted URL with the applied filters and markers.
2341
2434
  */
2342
- function filterFromRemoteByValue(optionUrl, value, openPopper) {
2435
+ function filterFromRemoteByValue(optionUrl, params, openPopper) {
2343
2436
  return new Processing(() => {
2344
- let url = formatURL.call(this, optionUrl, value);
2437
+ let url = formatURL.call(this, optionUrl, params);
2438
+
2345
2439
  if (url.indexOf(disabledRequestMarker.toString()) !== -1) {
2346
- return;
2440
+ return Promise.resolve();
2347
2441
  }
2348
2442
 
2349
- fetchIt
2443
+ return fetchIt
2350
2444
  .call(this, url, {
2351
2445
  disableHiding: true,
2352
2446
  })
@@ -2359,12 +2453,9 @@ function filterFromRemoteByValue(optionUrl, value, openPopper) {
2359
2453
  .catch((e) => {
2360
2454
  addErrorAttribute(this, e);
2361
2455
  setStatusOrRemoveBadges.call(this, "error");
2456
+ throw e;
2362
2457
  });
2363
- })
2364
- .run()
2365
- .catch((e) => {
2366
- throw e;
2367
- });
2458
+ }).run();
2368
2459
  }
2369
2460
 
2370
2461
  /**
@@ -2646,7 +2737,21 @@ function areOptionsAvailableAndInitInternal() {
2646
2737
 
2647
2738
  this[areOptionsAvailableAndInitSymbol]++;
2648
2739
 
2649
- const options = this.getOption("options");
2740
+ let options = this.getOption("options");
2741
+
2742
+ if (isArray(options) && Array.length === 1 && isString(options?.[0])) {
2743
+ try {
2744
+ const obj = JSON.parse(options[0]);
2745
+ if (isArray(obj)) {
2746
+ this.setOption("options", obj);
2747
+ options = obj;
2748
+ }
2749
+ } catch (e) {
2750
+ addErrorAttribute(this, e);
2751
+ setStatusOrRemoveBadges.call(this, "error");
2752
+ return false;
2753
+ }
2754
+ }
2650
2755
 
2651
2756
  if (
2652
2757
  options === undefined ||
@@ -2688,7 +2793,6 @@ function areOptionsAvailableAndInitInternal() {
2688
2793
  }
2689
2794
  }, 1000);
2690
2795
 
2691
- //this.setOption("messages.control", msg);
2692
2796
  this.setOption("messages.summary", "");
2693
2797
 
2694
2798
  if (this.getOption("features.emptyValueIfNoOptions") === true) {
@@ -2717,6 +2821,11 @@ function areOptionsAvailableAndInitInternal() {
2717
2821
  let updated = false;
2718
2822
  let valueCounter = 1;
2719
2823
  for (const option of options) {
2824
+ if (isObject(option) === false) {
2825
+ console.error("option is not an object", option);
2826
+ continue;
2827
+ }
2828
+
2720
2829
  if (option?.visibility === undefined) {
2721
2830
  option.visibility = "visible";
2722
2831
  updated = true;
@@ -3058,6 +3167,8 @@ function hide() {
3058
3167
  * @private
3059
3168
  */
3060
3169
  function show() {
3170
+ const self = this;
3171
+
3061
3172
  if (this.getOption("disabled", undefined) === true) {
3062
3173
  return;
3063
3174
  }
@@ -3117,6 +3228,16 @@ function show() {
3117
3228
  addAttributeToken(this[controlElementSymbol], "class", "open");
3118
3229
 
3119
3230
  new Processing(() => {
3231
+ if (!self?.[remoteFilterFirstOpendSymbol]) {
3232
+ self[remoteFilterFirstOpendSymbol] = true;
3233
+ setTimeout(
3234
+ () =>
3235
+ filterFromRemote.call(self).catch((e) => {
3236
+ addErrorAttribute(self, e);
3237
+ }),
3238
+ 0,
3239
+ );
3240
+ }
3120
3241
  calcAndSetOptionsDimension.call(this);
3121
3242
  focusFilter.call(this);
3122
3243
  this[popperElementSymbol].style.removeProperty("visibility");
@@ -3150,6 +3271,9 @@ function initDefaultOptionsFromUrl() {
3150
3271
  });
3151
3272
  }
3152
3273
 
3274
+ /**
3275
+ * @private
3276
+ */
3153
3277
  /**
3154
3278
  * @private
3155
3279
  */
@@ -3159,9 +3283,9 @@ function initTotal() {
3159
3283
  }
3160
3284
 
3161
3285
  const url = this.getOption("remoteInfo.url");
3162
- const mapping = this.getOption("mapping.total");
3286
+ const mappingTotal = this.getOption("mapping.total");
3163
3287
 
3164
- if (!isString(mapping) || !isString(url)) {
3288
+ if (!isString(mappingTotal) || !isString(url)) {
3165
3289
  return;
3166
3290
  }
3167
3291
 
@@ -3171,28 +3295,20 @@ function initTotal() {
3171
3295
  .fetch(url, fetchOptions)
3172
3296
  .then((response) => {
3173
3297
  if (!response.ok) {
3174
- // Improved status checking using `response.ok`
3175
3298
  addErrorAttribute(
3176
3299
  this,
3177
3300
  `HTTP error status: ${response.status} - ${response.statusText}`,
3178
3301
  );
3179
3302
  return;
3180
3303
  }
3181
-
3182
3304
  return response.text();
3183
3305
  })
3184
3306
  .then((text) => {
3307
+ if (!text) return;
3185
3308
  try {
3186
3309
  const data = JSON.parse(String(text));
3187
- const pathfinder = new Pathfinder(data);
3188
- const total = pathfinder.getVia(mapping);
3189
3310
 
3190
- if (!isInteger(total)) {
3191
- addErrorAttribute(this, "total is not an integer");
3192
- return;
3193
- }
3194
-
3195
- this.setOption("total", total);
3311
+ processAndApplyPaginationData.call(this, data);
3196
3312
  } catch (e) {
3197
3313
  addErrorAttribute(this, e);
3198
3314
  }
@@ -3202,6 +3318,16 @@ function initTotal() {
3202
3318
  });
3203
3319
  }
3204
3320
 
3321
+ function updatePagination(total, currentPage, objectsPerPage) {
3322
+ const totalPages = Math.ceil(total / objectsPerPage);
3323
+ this[paginationElementSymbol].style.display = "block";
3324
+ this[paginationElementSymbol].setOption("objectsPerPage", objectsPerPage);
3325
+ this[paginationElementSymbol].setPaginationState({
3326
+ totalPages: totalPages,
3327
+ currentPage: currentPage,
3328
+ });
3329
+ }
3330
+
3205
3331
  /**
3206
3332
  * @private
3207
3333
  */
@@ -3441,6 +3567,16 @@ function initEventHandler() {
3441
3567
  subtree: true,
3442
3568
  });
3443
3569
 
3570
+ if (this[paginationElementSymbol]) {
3571
+ this[paginationElementSymbol].style.display = "none";
3572
+ this[paginationElementSymbol].setOption("callbacks.click", (page) => {
3573
+ self[currentPageSymbol] = page;
3574
+ self[cleanupOptionsListSymbol] = true;
3575
+ filterFromRemote.call(self).catch((e) => {
3576
+ addErrorAttribute(self, e);
3577
+ });
3578
+ });
3579
+ }
3444
3580
  return self;
3445
3581
  }
3446
3582
 
@@ -3524,6 +3660,10 @@ function initControlReferences() {
3524
3660
  throw new Error("no shadow-root is defined");
3525
3661
  }
3526
3662
 
3663
+ this[paginationElementSymbol] = this.shadowRoot.querySelector(
3664
+ `[${ATTRIBUTE_ROLE}=pagination]`,
3665
+ );
3666
+
3527
3667
  this[controlElementSymbol] = this.shadowRoot.querySelector(
3528
3668
  `[${ATTRIBUTE_ROLE}=control]`,
3529
3669
  );
@@ -3648,10 +3788,12 @@ function getTemplate() {
3648
3788
  autocomplete="off"
3649
3789
  tabindex="0">
3650
3790
  </div>
3791
+
3651
3792
  <div part="content" class="flex" data-monster-replace="path:content">
3652
3793
  <div part="options" data-monster-role="options" data-monster-insert="options path:options"
3653
3794
  tabindex="-1"></div>
3654
3795
  </div>
3796
+ <monster-pagination data-monster-role="pagination"></monster-pagination>
3655
3797
  <div part="remote-info"
3656
3798
  data-monster-role="remote-info"
3657
3799
  data-monster-attributes="class path:classes.remoteInfo"