@schukai/monster 4.39.0 → 4.40.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.
@@ -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) {
1437
- return;
1438
- }
1439
-
1440
- if (self[isLoadingSymbol] === true) {
1441
- return;
1442
- }
1443
-
1444
- if (self[lazyLoadDoneSymbol] === true) {
1524
+ if (
1525
+ selection.length === 0 ||
1526
+ self[isLoadingSymbol] ||
1527
+ self[lazyLoadDoneSymbol]
1528
+ ) {
1445
1529
  return;
1446
1530
  }
1447
1531
 
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);
@@ -2252,6 +2327,7 @@ function handleFilterKeyEvents() {
2252
2327
  if (getFilterMode.call(this) !== FILTER_MODE_REMOTE) {
2253
2328
  filterOptions.call(this);
2254
2329
  } else {
2330
+ this[currentPageSymbol] = 1;
2255
2331
  this[cleanupOptionsListSymbol] = true;
2256
2332
 
2257
2333
  filterFromRemote.call(this).catch((e) => {
@@ -2291,7 +2367,6 @@ function filterFromRemote() {
2291
2367
  filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
2292
2368
  }
2293
2369
  showFlag = true;
2294
-
2295
2370
  break;
2296
2371
  case FILTER_POSITION_POPPER:
2297
2372
  default:
@@ -2300,24 +2375,44 @@ function filterFromRemote() {
2300
2375
  }
2301
2376
  }
2302
2377
 
2303
- return filterFromRemoteByValue.call(this, url, filterValue, showFlag);
2378
+ const params = {
2379
+ filter: filterValue,
2380
+ page: this[currentPageSymbol] || 1,
2381
+ };
2382
+
2383
+ return filterFromRemoteByValue.call(this, url, params, showFlag);
2304
2384
  }
2305
2385
 
2306
2386
  /**
2307
2387
  * @private
2308
2388
  * @param url
2309
- * @param value
2389
+ * @param {object} params
2310
2390
  * @returns {string}
2311
2391
  */
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();
2392
+ function formatURL(url, params = {}) {
2393
+ // Die Logik für den Default-Filterwert bleibt erhalten
2394
+ if (
2395
+ params.filter === undefined ||
2396
+ params.filter === null ||
2397
+ params.filter === ""
2398
+ ) {
2399
+ const defaultValue = this.getOption("filter.defaultValue");
2400
+ if (defaultValue === undefined || defaultValue === null) {
2401
+ params.filter = disabledRequestMarker.toString();
2402
+ } else {
2403
+ params.filter = defaultValue;
2317
2404
  }
2318
2405
  }
2319
2406
 
2320
- const formatter = new Formatter({ filter: encodeURI(value) });
2407
+ const encodedParams = {};
2408
+ for (const key in params) {
2409
+ if (Object.hasOwn(params, key)) {
2410
+ const value = params[key];
2411
+ encodedParams[key] = encodeURI(String(value));
2412
+ }
2413
+ }
2414
+
2415
+ const formatter = new Formatter(encodedParams);
2321
2416
  const openMarker = this.getOption("filter.marker.open");
2322
2417
  let closeMarker = this.getOption("filter.marker.close");
2323
2418
  if (!closeMarker) {
@@ -2339,14 +2434,15 @@ function formatURL(url, value) {
2339
2434
  * @param {boolean} [openPopper] Flag indicating whether to open the popper.
2340
2435
  * @return {string} The formatted URL with the applied filters and markers.
2341
2436
  */
2342
- function filterFromRemoteByValue(optionUrl, value, openPopper) {
2437
+ function filterFromRemoteByValue(optionUrl, params, openPopper) {
2343
2438
  return new Processing(() => {
2344
- let url = formatURL.call(this, optionUrl, value);
2439
+ let url = formatURL.call(this, optionUrl, params);
2440
+
2345
2441
  if (url.indexOf(disabledRequestMarker.toString()) !== -1) {
2346
- return;
2442
+ return Promise.resolve();
2347
2443
  }
2348
2444
 
2349
- fetchIt
2445
+ return fetchIt
2350
2446
  .call(this, url, {
2351
2447
  disableHiding: true,
2352
2448
  })
@@ -2359,12 +2455,9 @@ function filterFromRemoteByValue(optionUrl, value, openPopper) {
2359
2455
  .catch((e) => {
2360
2456
  addErrorAttribute(this, e);
2361
2457
  setStatusOrRemoveBadges.call(this, "error");
2458
+ throw e;
2362
2459
  });
2363
- })
2364
- .run()
2365
- .catch((e) => {
2366
- throw e;
2367
- });
2460
+ }).run();
2368
2461
  }
2369
2462
 
2370
2463
  /**
@@ -2646,7 +2739,21 @@ function areOptionsAvailableAndInitInternal() {
2646
2739
 
2647
2740
  this[areOptionsAvailableAndInitSymbol]++;
2648
2741
 
2649
- const options = this.getOption("options");
2742
+ let options = this.getOption("options");
2743
+
2744
+ if (isArray(options) && Array.length === 1 && isString(options?.[0])) {
2745
+ try {
2746
+ const obj = JSON.parse(options[0]);
2747
+ if (isArray(obj)) {
2748
+ this.setOption("options", obj);
2749
+ options = obj;
2750
+ }
2751
+ } catch (e) {
2752
+ addErrorAttribute(this, e);
2753
+ setStatusOrRemoveBadges.call(this, "error");
2754
+ return false;
2755
+ }
2756
+ }
2650
2757
 
2651
2758
  if (
2652
2759
  options === undefined ||
@@ -2688,7 +2795,6 @@ function areOptionsAvailableAndInitInternal() {
2688
2795
  }
2689
2796
  }, 1000);
2690
2797
 
2691
- //this.setOption("messages.control", msg);
2692
2798
  this.setOption("messages.summary", "");
2693
2799
 
2694
2800
  if (this.getOption("features.emptyValueIfNoOptions") === true) {
@@ -2717,6 +2823,11 @@ function areOptionsAvailableAndInitInternal() {
2717
2823
  let updated = false;
2718
2824
  let valueCounter = 1;
2719
2825
  for (const option of options) {
2826
+ if (isObject(option) === false) {
2827
+ console.error("option is not an object", option);
2828
+ continue;
2829
+ }
2830
+
2720
2831
  if (option?.visibility === undefined) {
2721
2832
  option.visibility = "visible";
2722
2833
  updated = true;
@@ -3058,6 +3169,8 @@ function hide() {
3058
3169
  * @private
3059
3170
  */
3060
3171
  function show() {
3172
+ const self = this;
3173
+
3061
3174
  if (this.getOption("disabled", undefined) === true) {
3062
3175
  return;
3063
3176
  }
@@ -3117,6 +3230,16 @@ function show() {
3117
3230
  addAttributeToken(this[controlElementSymbol], "class", "open");
3118
3231
 
3119
3232
  new Processing(() => {
3233
+ if (!self?.[remoteFilterFirstOpendSymbol]) {
3234
+ self[remoteFilterFirstOpendSymbol] = true;
3235
+ setTimeout(
3236
+ () =>
3237
+ filterFromRemote.call(self).catch((e) => {
3238
+ addErrorAttribute(self, e);
3239
+ }),
3240
+ 0,
3241
+ );
3242
+ }
3120
3243
  calcAndSetOptionsDimension.call(this);
3121
3244
  focusFilter.call(this);
3122
3245
  this[popperElementSymbol].style.removeProperty("visibility");
@@ -3150,6 +3273,9 @@ function initDefaultOptionsFromUrl() {
3150
3273
  });
3151
3274
  }
3152
3275
 
3276
+ /**
3277
+ * @private
3278
+ */
3153
3279
  /**
3154
3280
  * @private
3155
3281
  */
@@ -3159,9 +3285,9 @@ function initTotal() {
3159
3285
  }
3160
3286
 
3161
3287
  const url = this.getOption("remoteInfo.url");
3162
- const mapping = this.getOption("mapping.total");
3288
+ const mappingTotal = this.getOption("mapping.total");
3163
3289
 
3164
- if (!isString(mapping) || !isString(url)) {
3290
+ if (!isString(mappingTotal) || !isString(url)) {
3165
3291
  return;
3166
3292
  }
3167
3293
 
@@ -3171,28 +3297,20 @@ function initTotal() {
3171
3297
  .fetch(url, fetchOptions)
3172
3298
  .then((response) => {
3173
3299
  if (!response.ok) {
3174
- // Improved status checking using `response.ok`
3175
3300
  addErrorAttribute(
3176
3301
  this,
3177
3302
  `HTTP error status: ${response.status} - ${response.statusText}`,
3178
3303
  );
3179
3304
  return;
3180
3305
  }
3181
-
3182
3306
  return response.text();
3183
3307
  })
3184
3308
  .then((text) => {
3309
+ if (!text) return;
3185
3310
  try {
3186
3311
  const data = JSON.parse(String(text));
3187
- const pathfinder = new Pathfinder(data);
3188
- const total = pathfinder.getVia(mapping);
3189
3312
 
3190
- if (!isInteger(total)) {
3191
- addErrorAttribute(this, "total is not an integer");
3192
- return;
3193
- }
3194
-
3195
- this.setOption("total", total);
3313
+ processAndApplyPaginationData.call(this, data);
3196
3314
  } catch (e) {
3197
3315
  addErrorAttribute(this, e);
3198
3316
  }
@@ -3202,6 +3320,16 @@ function initTotal() {
3202
3320
  });
3203
3321
  }
3204
3322
 
3323
+ function updatePagination(total, currentPage, objectsPerPage) {
3324
+ const totalPages = Math.ceil(total / objectsPerPage);
3325
+ this[paginationElementSymbol].style.display = "block";
3326
+ this[paginationElementSymbol].setOption("objectsPerPage", objectsPerPage);
3327
+ this[paginationElementSymbol].setPaginationState({
3328
+ totalPages: totalPages,
3329
+ currentPage: currentPage,
3330
+ });
3331
+ }
3332
+
3205
3333
  /**
3206
3334
  * @private
3207
3335
  */
@@ -3441,6 +3569,16 @@ function initEventHandler() {
3441
3569
  subtree: true,
3442
3570
  });
3443
3571
 
3572
+ if (this[paginationElementSymbol]) {
3573
+ this[paginationElementSymbol].style.display = "none";
3574
+ this[paginationElementSymbol].setOption("callbacks.click", (page) => {
3575
+ self[currentPageSymbol] = page;
3576
+ self[cleanupOptionsListSymbol] = true;
3577
+ filterFromRemote.call(self).catch((e) => {
3578
+ addErrorAttribute(self, e);
3579
+ });
3580
+ });
3581
+ }
3444
3582
  return self;
3445
3583
  }
3446
3584
 
@@ -3524,6 +3662,10 @@ function initControlReferences() {
3524
3662
  throw new Error("no shadow-root is defined");
3525
3663
  }
3526
3664
 
3665
+ this[paginationElementSymbol] = this.shadowRoot.querySelector(
3666
+ `[${ATTRIBUTE_ROLE}=pagination]`,
3667
+ );
3668
+
3527
3669
  this[controlElementSymbol] = this.shadowRoot.querySelector(
3528
3670
  `[${ATTRIBUTE_ROLE}=control]`,
3529
3671
  );
@@ -3648,10 +3790,12 @@ function getTemplate() {
3648
3790
  autocomplete="off"
3649
3791
  tabindex="0">
3650
3792
  </div>
3793
+
3651
3794
  <div part="content" class="flex" data-monster-replace="path:content">
3652
3795
  <div part="options" data-monster-role="options" data-monster-insert="options path:options"
3653
3796
  tabindex="-1"></div>
3654
3797
  </div>
3798
+ <monster-pagination data-monster-role="pagination"></monster-pagination>
3655
3799
  <div part="remote-info"
3656
3800
  data-monster-role="remote-info"
3657
3801
  data-monster-attributes="class path:classes.remoteInfo"