@schukai/monster 3.77.0 → 3.79.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,61 +12,61 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol } from "../../constants.mjs";
16
- import { internalSymbol } from "../../constants.mjs";
17
- import { buildMap } from "../../data/buildmap.mjs";
18
- import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
19
- import { positionPopper } from "./util/floating-ui.mjs";
15
+ import {instanceSymbol} from "../../constants.mjs";
16
+ import {internalSymbol} from "../../constants.mjs";
17
+ import {buildMap} from "../../data/buildmap.mjs";
18
+ import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
19
+ import {positionPopper} from "./util/floating-ui.mjs";
20
20
  import {
21
- addAttributeToken,
22
- findClosestByAttribute,
23
- removeAttributeToken,
21
+ addAttributeToken,
22
+ findClosestByAttribute,
23
+ removeAttributeToken,
24
24
  } from "../../dom/attributes.mjs";
25
25
  import {
26
- ATTRIBUTE_ERRORMESSAGE,
27
- ATTRIBUTE_PREFIX,
28
- ATTRIBUTE_ROLE,
26
+ ATTRIBUTE_ERRORMESSAGE,
27
+ ATTRIBUTE_PREFIX,
28
+ ATTRIBUTE_ROLE,
29
29
  } from "../../dom/constants.mjs";
30
- import { CustomControl } from "../../dom/customcontrol.mjs";
30
+ import {CustomControl} from "../../dom/customcontrol.mjs";
31
31
  import {
32
- assembleMethodSymbol,
33
- getSlottedElements,
34
- registerCustomElement,
32
+ assembleMethodSymbol,
33
+ getSlottedElements,
34
+ registerCustomElement,
35
35
  } from "../../dom/customelement.mjs";
36
36
  import {
37
- findTargetElementFromEvent,
38
- fireCustomEvent,
39
- fireEvent,
37
+ findTargetElementFromEvent,
38
+ fireCustomEvent,
39
+ fireEvent,
40
40
  } from "../../dom/events.mjs";
41
- import { getDocument } from "../../dom/util.mjs";
42
- import { Formatter } from "../../text/formatter.mjs";
43
- import { getGlobal } from "../../types/global.mjs";
44
- import { ID } from "../../types/id.mjs";
41
+ import {getDocument} from "../../dom/util.mjs";
42
+ import {Formatter} from "../../text/formatter.mjs";
43
+ import {getGlobal} from "../../types/global.mjs";
44
+ import {ID} from "../../types/id.mjs";
45
45
  import {
46
- isArray,
47
- isFunction,
48
- isInteger,
49
- isIterable,
50
- isObject,
51
- isPrimitive,
52
- isString,
46
+ isArray,
47
+ isFunction,
48
+ isInteger,
49
+ isIterable,
50
+ isObject,
51
+ isPrimitive,
52
+ isString,
53
53
  } from "../../types/is.mjs";
54
- import { Observer } from "../../types/observer.mjs";
55
- import { ProxyObserver } from "../../types/proxyobserver.mjs";
56
- import { validateArray, validateString } from "../../types/validate.mjs";
57
- import { Processing } from "../../util/processing.mjs";
58
- import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
59
- import { SelectStyleSheet } from "./stylesheet/select.mjs";
54
+ import {Observer} from "../../types/observer.mjs";
55
+ import {ProxyObserver} from "../../types/proxyobserver.mjs";
56
+ import {validateArray, validateString} from "../../types/validate.mjs";
57
+ import {Processing} from "../../util/processing.mjs";
58
+ import {STYLE_DISPLAY_MODE_BLOCK} from "./constants.mjs";
59
+ import {SelectStyleSheet} from "./stylesheet/select.mjs";
60
60
  import {
61
- getDocumentTranslations,
62
- Translations,
61
+ getDocumentTranslations,
62
+ Translations,
63
63
  } from "../../i18n/translations.mjs";
64
64
 
65
65
  export {
66
- Select,
67
- popperElementSymbol,
68
- getSummaryTemplate,
69
- getSelectionTemplate,
66
+ Select,
67
+ popperElementSymbol,
68
+ getSummaryTemplate,
69
+ getSelectionTemplate,
70
70
  };
71
71
 
72
72
  /**
@@ -199,7 +199,7 @@ const popperFilterElementSymbol = Symbol("popperFilterElement");
199
199
  * @type {Symbol}
200
200
  */
201
201
  const popperFilterContainerElementSymbol = Symbol(
202
- "popperFilterContainerElement",
202
+ "popperFilterContainerElement",
203
203
  );
204
204
 
205
205
  /**
@@ -285,522 +285,585 @@ const FILTER_POSITION_INLINE = "inline";
285
285
  * @fires monster-changed
286
286
  */
287
287
  class Select extends CustomControl {
288
- /**
289
- * @extends CustomControl
290
- */
291
- constructor() {
292
- super();
293
- initOptionObserver.call(this);
294
- }
295
-
296
- /**
297
- * This method is called by the `instanceof` operator.
298
- * @returns {Symbol}
299
- */
300
- static get [instanceSymbol]() {
301
- return Symbol.for("@schukai/monster/components/form/select@@instance");
302
- }
303
-
304
- /**
305
- * The current selection of the Select
306
- *
307
- * ```
308
- * e = document.querySelector('monster-select');
309
- * console.log(e.value)
310
- * // ↦ 1
311
- * // ↦ ['1','2']
312
- * ```
313
- *
314
- * @property {string|array}
315
- */
316
- get value() {
317
- return convertSelectionToValue.call(this, this.getOption("selection"));
318
- }
319
-
320
- /**
321
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
322
- * @return {boolean}
323
- */
324
- static get formAssociated() {
325
- return true;
326
- }
327
-
328
- /**
329
- * Set selection
330
- *
331
- * ```
332
- * e = document.querySelector('monster-select');
333
- * e.value=1
334
- * ```
335
- *
336
- * @property {string|array} value
337
- * @throws {Error} unsupported type
338
- */
339
- set value(value) {
340
- const result = convertValueToSelection.call(this, value);
341
- setSelection
342
- .call(this, result.selection)
343
- .then(() => {})
344
- .catch((e) => {
345
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
346
- });
347
- }
348
-
349
- /**
350
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
351
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
352
- *
353
- * The individual configuration values can be found in the table.
354
- *
355
- * @property {Object} toggleEventType=click,touch List of event types to be observed for opening the dropdown
356
- * @property {boolean} delegatesFocus=false lorem [see mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus)
357
- * @property {Object[]} options Selection of key identifier pairs available for selection and displayed in the dropdown.
358
- * @property {string} options[].label
359
- * @property {string} options[].value
360
- * @property {string} options[].visibility hidden or visible
361
- * @property {Array} selection Selected options
362
- * @property {Integer} showMaxOptions=10 Maximum number of visible options before a scroll bar should be displayed.
363
- * @property {string} type=radio Multiple (checkbox) or single selection (radio)
364
- * @property {string} name=(random id) Name of the form field
365
- * @property {string} url Load options from server per url
366
- * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
367
- * @property {String} fetch.redirect=error
368
- * @property {String} fetch.method=GET
369
- * @property {String} fetch.mode=same-origin
370
- * @property {String} fetch.credentials=same-origin
371
- * @property {Object} fetch.headers={"accept":"application/json"}}
372
- * @property {Object} labels
373
- * @property {string} labels.cannot-be-loaded cannot be loaded
374
- * @property {string} labels.no-options-available no options available
375
- * @property {string} labels.select-an-option select an option
376
- * @property {string} labels.no-option no option in the list, maybe you have to change the filter
377
- * @property {Object} features List with features
378
- * @property {Boolean} features.clearAll=true Display of a delete button to delete the entire selection
379
- * @property {Boolean} features.clear=true Display of a delete key for deleting the specific selection
380
- * @property {Boolean} features.lazyLoad=false Load options when first opening the dropdown
381
- * @property {Boolean} features.closeOnSelect=false Close the dropdown when an option is selected (since 3.54.0)
382
- * @property {Boolean} features.emptyValueIfNoOptions=false If no options are available, the selection is set to an empty array
383
- * @property {Boolean} features.storeFetchedData=false Store fetched data in the object
384
- * @property {Boolean} features.useStrictValueComparison=true Use strict value comparison for the selection
385
- * @property {Boolean} filter.defaultValue=* Default filter value, if the filter is empty
386
- * @property {Boolean} filter.mode=options Filter mode, values: options, remote, disabled
387
- * @property {Object} templates Template definitions
388
- * @property {string} templates.main Main template
389
- * @property {string} templateMapping Mapping of the template placeholders
390
- * @property {string} templateMapping.selected Selected Template
391
- * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
392
- * @property {string} popper.placement=bottom PopperJS placement
393
- * @property {Object[]} modifiers={name:offset} PopperJS placement
394
- * @property {Object} mapping
395
- * @property {String} mapping.selector=* Path to select the appropriate entries
396
- * @property {String} mapping.labelTemplate="" template with the label placeholders in the form ${name}, where name is the key (**)
397
- * @property {String} mapping.valueTemplate="" template with the value placeholders in the form ${name}, where name is the key
398
- * @property {Monster.Components.Form~exampleFilterCallback|undefined} mapping.filter Filtering of values via a function
399
- * @property {Object} formatter
400
- * @property {Monster.Components.Form~formatterSelectionCallback|undefined} formatter.selection format selection label
401
- */
402
- get defaults() {
403
- return Object.assign(
404
- {},
405
- super.defaults,
406
- {
407
- toggleEventType: ["click", "touch"],
408
- delegatesFocus: false,
409
- options: [],
410
- selection: [],
411
- showMaxOptions: 10,
412
- type: "radio",
413
- name: new ID("s").toString(),
414
- features: {
415
- clearAll: true,
416
- clear: true,
417
- lazyLoad: false,
418
- closeOnSelect: false,
419
- emptyValueIfNoOptions: false,
420
- storeFetchedData: false,
421
- useStrictValueComparison: false,
422
- },
423
- url: null,
424
- labels: {
425
- "cannot-be-loaded": "Cannot be loaded",
426
- "no-options-available": noOptionsAvailableMessage,
427
- "click-to-load-options": clickToLoadOptionsMessage,
428
- "select-an-option": "Select an option",
429
- "summary-text": {
430
- zero: "No entries were selected",
431
- one: '<span class="monster-badge-primary-pill">1</span> entry was selected',
432
- other:
433
- '<span class="monster-badge-primary-pill">${count}</span> entries were selected',
434
- },
435
- "no-options":
436
- "Unfortunately, there are no options available in the list.",
437
- "no-options-found":
438
- "No options are available in the list. Please consider modifying the filter.",
439
- },
440
- messages: {
441
- control: null,
442
- selected: null,
443
- emptyOptions: null,
444
- },
445
- fetch: {
446
- redirect: "error",
447
- method: "GET",
448
- mode: "same-origin",
449
- credentials: "same-origin",
450
- headers: {
451
- accept: "application/json",
452
- },
453
- },
454
- filter: {
455
- defaultValue: "*",
456
- mode: FILTER_MODE_DISABLED,
457
- position: FILTER_POSITION_INLINE,
458
- marker: {
459
- open: "{",
460
- close: "}",
461
- },
462
- },
463
- classes: {
464
- badge: "monster-badge-primary",
465
- statusOrRemoveBadge: "empty",
466
- },
467
- mapping: {
468
- selector: "*",
469
- labelTemplate: "",
470
- valueTemplate: "",
471
- filter: null,
472
- },
473
- formatter: {
474
- selection: buildSelectionLabel,
475
- },
476
- templates: {
477
- main: getTemplate(),
478
- },
479
- templateMapping: {
480
- /** with the attribute `data-monster-selected-template` the template for the selected options can be defined. */
481
- selected: getSelectionTemplate(),
482
- },
483
-
484
- popper: {
485
- placement: "bottom",
486
- middleware: ["flip", "offset:1"],
487
- },
488
- },
489
- initOptionsFromArguments.call(this),
490
- );
491
- }
492
-
493
- /**
494
- * @return {Select}
495
- */
496
- [assembleMethodSymbol]() {
497
- const self = this;
498
- super[assembleMethodSymbol]();
499
-
500
- initControlReferences.call(self);
501
- initEventHandler.call(self);
502
-
503
- const lazyLoadFlag = self.getOption("features.lazyLoad");
504
-
505
- if (self.hasAttribute("value")) {
506
- new Processing(10, () => {
507
- this.value = this.getAttribute("value");
508
- })
509
- .run()
510
- .catch((e) => {
511
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
512
- });
513
- }
514
-
515
- if (self.getOption("url") !== null && !lazyLoadFlag) {
516
- self.fetch();
517
- }
518
-
519
- let lastValue = self.value;
520
- self[internalSymbol].attachObserver(
521
- new Observer(function () {
522
- if (isObject(this) && this instanceof ProxyObserver) {
523
- const n = this.getSubject()?.options?.value;
524
-
525
- if (lastValue !== n) {
526
- lastValue = n;
527
- setSelection
528
- .call(self, n)
529
- .then(() => {})
530
- .catch((e) => {
531
- addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, `${e}`);
532
- });
533
- }
534
- }
535
- }),
536
- );
537
-
538
- areOptionsAvailableAndInit.call(self);
539
-
540
- return this;
541
- }
542
-
543
- /**
544
- *
545
- * @returns {*}
546
- * @throws {Error} storeFetchedData is not enabled
547
- * @since 3.66.0
548
- */
549
- getLastFetchedData() {
550
- if (this.getOption("features.storeFetchedData") === false) {
551
- throw new Error("storeFetchedData is not enabled");
552
- }
553
-
554
- return this?.[lastFetchedDataSymbol];
555
- }
556
-
557
- /**
558
- * The Button.click() method simulates a click on the internal button element.
559
- *
560
- * @since 3.27.0
561
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
562
- */
563
- click() {
564
- if (this.getOption("disabled") === true) {
565
- return;
566
- }
567
-
568
- toggle.call(this);
569
- }
570
-
571
- /**
572
- * The Button.focus() method sets focus on the internal button element.
573
- *
574
- * @since 3.27.0
575
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
576
- */
577
- focus(options) {
578
- if (this.getOption("disabled") === true) {
579
- return;
580
- }
581
-
582
- new Processing(() => {
583
- gatherState.call(this);
584
- focusFilter.call(this, options);
585
- })
586
- .run()
587
- .catch((e) => {
588
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
589
- });
590
- }
591
-
592
- /**
593
- * The Button.blur() method removes focus from the internal button element.
594
- * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur
595
- */
596
- blur() {
597
- new Processing(() => {
598
- gatherState.call(this);
599
- blurFilter.call(this);
600
- })
601
- .run()
602
- .catch((e) => {
603
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
604
- });
605
- }
606
-
607
- /**
608
- * If no url is specified, the options are taken from the Component itself.
609
- *
610
- * @param {string|URL} url URL to fetch the options
611
- * @return {Promise}
612
- */
613
- fetch(url) {
614
- if (url instanceof URL) {
615
- url = url.toString();
616
- }
617
-
618
- if (url !== undefined && url !== null) {
619
- url = validateString(url);
620
- } else {
621
- url = this.getOption("url");
622
- if (url === null) {
623
- return Promise.reject(new Error("No url defined"));
624
- }
625
- }
626
-
627
- hide.call(this);
628
-
629
- return new Promise((resolve, reject) => {
630
- setStatusOrRemoveBadges.call(this, "loading");
631
-
632
- new Processing(10, () => {
633
- this.setOption("options", []);
634
-
635
- fetchData
636
- .call(this, url)
637
- .then((map) => {
638
- this[isLoadingSymbol] = false;
639
- if (
640
- isObject(map) ||
641
- isArray(map) ||
642
- map instanceof Set ||
643
- map instanceof Map
644
- ) {
645
- this.importOptions(map);
646
- this[lastFetchedDataSymbol] = map;
647
-
648
- let result;
649
- const selection = this.getOption("selection");
650
- let newValue = [];
651
- if (selection) {
652
- newValue = selection;
653
- } else if (this.hasAttribute("value")) {
654
- newValue = this.getAttribute("value");
655
- }
656
-
657
- result = setSelection.call(this, newValue);
658
- setTimeout(() => {
659
- checkOptionState.call(this);
660
- setStatusOrRemoveBadges.call(this, "closed");
661
- resolve(result);
662
- }, 10);
663
-
664
- return;
665
- }
666
-
667
- setStatusOrRemoveBadges.call(this, "error");
668
- reject(new Error("invalid response"));
669
- })
670
- .catch((e) => {
671
- this[isLoadingSymbol] = false;
672
- setStatusOrRemoveBadges.call(this, "error");
673
- reject(e);
674
- });
675
- })
676
- .run()
677
- .catch((e) => {
678
- setStatusOrRemoveBadges.call(this, "error");
679
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
680
- });
681
- });
682
- }
683
-
684
- /**
685
- * @return {void}
686
- */
687
- connectedCallback() {
688
- super.connectedCallback();
689
- const document = getDocument();
690
-
691
- for (const [, type] of Object.entries(["click", "touch"])) {
692
- // close on outside ui-events
693
- document.addEventListener(type, this[closeEventHandler]);
694
- }
695
-
696
- parseSlotsToOptions.call(this);
697
- attachResizeObserver.call(this);
698
- updatePopper.call(this);
699
-
700
- new Processing(() => {
701
- gatherState.call(this);
702
- focusFilter.call(this);
703
- }).run();
704
- }
705
-
706
- /**
707
- * @return {void}
708
- */
709
- disconnectedCallback() {
710
- super.disconnectedCallback();
711
- const document = getDocument();
712
-
713
- // close on outside ui-events
714
- for (const [, type] of Object.entries(["click", "touch"])) {
715
- document.removeEventListener(type, this[closeEventHandler]);
716
- }
717
-
718
- disconnectResizeObserver.call(this);
719
- }
720
-
721
- /**
722
- * Import Select Options from dataset
723
- * Not to be confused with the control defaults/options
724
- *
725
- * @param {array|object|Map|Set} data
726
- * @return {Select}
727
- * @throws {Error} map is not iterable
728
- * @throws {Error} missing label configuration
729
- */
730
- importOptions(data) {
731
- const mappingOptions = this.getOption("mapping", {});
732
- const selector = mappingOptions?.["selector"];
733
- const labelTemplate = mappingOptions?.["labelTemplate"];
734
- const valueTemplate = mappingOptions?.["valueTemplate"];
735
- const filter = mappingOptions?.["filter"];
736
-
737
- let flag = false;
738
- if (labelTemplate === "") {
739
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty label template");
740
- flag = true;
741
- }
742
-
743
- if (valueTemplate === "") {
744
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty value template");
745
- flag = true;
746
- }
747
-
748
- if (flag === true) {
749
- throw new Error("missing label configuration");
750
- }
751
-
752
- const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
753
-
754
- const options = [];
755
- if (!isIterable(map)) {
756
- throw new Error("map is not iterable");
757
- }
758
-
759
- const visibility = "visible";
760
-
761
- map.forEach((label, value) => {
762
- options.push({
763
- value,
764
- label,
765
- visibility,
766
- data: map.get(value),
767
- });
768
- });
769
-
770
- runAsOptionLengthChanged.call(this, map.size);
771
- this.setOption("options", options);
772
-
773
- fireCustomEvent(this, "monster-options-set", {
774
- options,
775
- });
776
-
777
- return this;
778
- }
779
-
780
- /**
781
- * @private
782
- * @return {Select}
783
- */
784
- calcAndSetOptionsDimension() {
785
- calcAndSetOptionsDimension.call(this);
786
- return this;
787
- }
788
-
789
- /**
790
- *
791
- * @return {string}
792
- */
793
- static getTag() {
794
- return "monster-select";
795
- }
796
-
797
- /**
798
- *
799
- * @return {CSSStyleSheet[]}
800
- */
801
- static getCSSStyleSheet() {
802
- return [SelectStyleSheet];
803
- }
288
+
289
+ /**
290
+ *
291
+ */
292
+ constructor() {
293
+ super();
294
+ initOptionObserver.call(this);
295
+ }
296
+
297
+ /**
298
+ * This method is called by the `instanceof` operator.
299
+ * @returns {Symbol}
300
+ */
301
+ static get [instanceSymbol]() {
302
+ return Symbol.for("@schukai/monster/components/form/select@@instance");
303
+ }
304
+
305
+ /**
306
+ * The current selection of the Select
307
+ *
308
+ * ```
309
+ * e = document.querySelector('monster-select');
310
+ * console.log(e.value)
311
+ * // ↦ 1
312
+ * // ↦ ['1','2']
313
+ * ```
314
+ *
315
+ * @returns {string}
316
+ */
317
+ get value() {
318
+ return convertSelectionToValue.call(this, this.getOption("selection"));
319
+ }
320
+
321
+ /**
322
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
323
+ * @return {boolean}
324
+ */
325
+ static get formAssociated() {
326
+ return true;
327
+ }
328
+
329
+ /**
330
+ * Set selection
331
+ *
332
+ * ```
333
+ * e = document.querySelector('monster-select');
334
+ * e.value=1
335
+ * ```
336
+ *
337
+ * @property {string|array} value
338
+ * @throws {Error} unsupported type
339
+ */
340
+ set value(value) {
341
+ const result = convertValueToSelection.call(this, value);
342
+ setSelection
343
+ .call(this, result.selection)
344
+ .then(() => {
345
+ })
346
+ .catch((e) => {
347
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
348
+ });
349
+ }
350
+
351
+ /**
352
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
353
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
354
+ *
355
+ * The individual configuration values can be found in the table.
356
+ *
357
+ * @property {Object} toggleEventType=click,touch List of event types to be observed for opening the dropdown
358
+ * @property {boolean} delegatesFocus=false lorem [see mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus)
359
+ * @property {Object[]} options Selection of key identifier pairs available for selection and displayed in the dropdown.
360
+ * @property {string} options[].label
361
+ * @property {string} options[].value
362
+ * @property {string} options[].visibility hidden or visible
363
+ * @property {Array} selection Selected options
364
+ * @property {Integer} showMaxOptions=10 Maximum number of visible options before a scroll bar should be displayed.
365
+ * @property {string} type=radio Multiple (checkbox) or single selection (radio)
366
+ * @property {string} name=(random id) Name of the form field
367
+ * @property {string} url Load options from server per url
368
+ * @property {string} lookup Load options from server per url
369
+ * @property {boolean} lookup.url=null Load options from server per url
370
+ * @property {boolean} lookup.individually=false Load options individually
371
+ * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
372
+ * @property {String} fetch.redirect=error
373
+ * @property {String} fetch.method=GET
374
+ * @property {String} fetch.mode=same-origin
375
+ * @property {String} fetch.credentials=same-origin
376
+ * @property {Object} fetch.headers={"accept":"application/json"}}
377
+ * @property {Object} labels
378
+ * @property {string} labels.cannot-be-loaded cannot be loaded
379
+ * @property {string} labels.no-options-available no options available
380
+ * @property {string} labels.select-an-option select an option
381
+ * @property {string} labels.no-option no option in the list, maybe you have to change the filter
382
+ * @property {Object} features List with features
383
+ * @property {Boolean} features.clearAll=true Display of a delete button to delete the entire selection
384
+ * @property {Boolean} features.clear=true Display of a delete key for deleting the specific selection
385
+ * @property {Boolean} features.lazyLoad=false Load options when first opening the dropdown
386
+ * @property {Boolean} features.closeOnSelect=false Close the dropdown when an option is selected (since 3.54.0)
387
+ * @property {Boolean} features.emptyValueIfNoOptions=false If no options are available, the selection is set to an empty array
388
+ * @property {Boolean} features.storeFetchedData=false Store fetched data in the object
389
+ * @property {Boolean} features.useStrictValueComparison=true Use strict value comparison for the selection
390
+ * @property {Boolean} filter.defaultValue=* Default filter value, if the filter is empty
391
+ * @property {Boolean} filter.mode=options Filter mode, values: options, remote, disabled
392
+ * @property {Object} templates Template definitions
393
+ * @property {string} templates.main Main template
394
+ * @property {string} templateMapping Mapping of the template placeholders
395
+ * @property {string} templateMapping.selected Selected Template
396
+ * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
397
+ * @property {string} popper.placement=bottom PopperJS placement
398
+ * @property {Object[]} modifiers={name:offset} PopperJS placement
399
+ * @property {Object} mapping
400
+ * @property {String} mapping.selector=* Path to select the appropriate entries
401
+ * @property {String} mapping.labelTemplate="" template with the label placeholders in the form ${name}, where name is the key (**)
402
+ * @property {String} mapping.valueTemplate="" template with the value placeholders in the form ${name}, where name is the key
403
+ * @property {Monster.Components.Form~exampleFilterCallback|undefined} mapping.filter Filtering of values via a function
404
+ * @property {Object} formatter
405
+ * @property {Monster.Components.Form~formatterSelectionCallback|undefined} formatter.selection format selection label
406
+ */
407
+ get defaults() {
408
+ return Object.assign(
409
+ {},
410
+ super.defaults,
411
+ {
412
+ toggleEventType: ["click", "touch"],
413
+ delegatesFocus: false,
414
+ options: [],
415
+ selection: [],
416
+ showMaxOptions: 10,
417
+ type: "radio",
418
+ name: new ID("s").toString(),
419
+ features: {
420
+ clearAll: true,
421
+ clear: true,
422
+ lazyLoad: false,
423
+ closeOnSelect: false,
424
+ emptyValueIfNoOptions: false,
425
+ storeFetchedData: false,
426
+ useStrictValueComparison: false,
427
+ },
428
+ url: null,
429
+ lookup: {
430
+ url: null,
431
+ grouping: false,
432
+ },
433
+ labels: {
434
+ "cannot-be-loaded": "Cannot be loaded",
435
+ "no-options-available": noOptionsAvailableMessage,
436
+ "click-to-load-options": clickToLoadOptionsMessage,
437
+ "select-an-option": "Select an option",
438
+ "summary-text": {
439
+ zero: "No entries were selected",
440
+ one: '<span class="monster-badge-primary-pill">1</span> entry was selected',
441
+ other:
442
+ '<span class="monster-badge-primary-pill">${count}</span> entries were selected',
443
+ },
444
+ "no-options":
445
+ "Unfortunately, there are no options available in the list.",
446
+ "no-options-found":
447
+ "No options are available in the list. Please consider modifying the filter.",
448
+ },
449
+ messages: {
450
+ control: null,
451
+ selected: null,
452
+ emptyOptions: null,
453
+ },
454
+ fetch: {
455
+ redirect: "error",
456
+ method: "GET",
457
+ mode: "same-origin",
458
+ credentials: "same-origin",
459
+ headers: {
460
+ accept: "application/json",
461
+ },
462
+ },
463
+ filter: {
464
+ defaultValue: "*",
465
+ mode: FILTER_MODE_DISABLED,
466
+ position: FILTER_POSITION_INLINE,
467
+ marker: {
468
+ open: "{",
469
+ close: "}",
470
+ },
471
+ },
472
+ classes: {
473
+ badge: "monster-badge-primary",
474
+ statusOrRemoveBadge: "empty",
475
+ },
476
+ mapping: {
477
+ selector: "*",
478
+ labelTemplate: "",
479
+ valueTemplate: "",
480
+ filter: null,
481
+ },
482
+ formatter: {
483
+ selection: buildSelectionLabel,
484
+ },
485
+ templates: {
486
+ main: getTemplate(),
487
+ },
488
+ templateMapping: {
489
+ /** with the attribute `data-monster-selected-template` the template for the selected options can be defined. */
490
+ selected: getSelectionTemplate(),
491
+ },
492
+
493
+ popper: {
494
+ placement: "bottom",
495
+ middleware: ["flip", "offset:1"],
496
+ },
497
+ },
498
+ initOptionsFromArguments.call(this),
499
+ );
500
+ }
501
+
502
+ /**
503
+ * @return {Select}
504
+ */
505
+ [assembleMethodSymbol]() {
506
+ const self = this;
507
+ super[assembleMethodSymbol]();
508
+
509
+ initControlReferences.call(self);
510
+ initEventHandler.call(self);
511
+
512
+ const lazyLoadFlag = self.getOption("features.lazyLoad");
513
+
514
+ if (self.hasAttribute("value")) {
515
+ new Processing(10, () => {
516
+ this.value = this.getAttribute("value");
517
+ })
518
+ .run()
519
+ .catch((e) => {
520
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
521
+ });
522
+ }
523
+
524
+ if (self.getOption("url") !== null) {
525
+
526
+ if (lazyLoadFlag) {
527
+ lookupSelection.call(self);
528
+ } else {
529
+ self.fetch().catch((e) => {
530
+ addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, `${e}`);
531
+ });
532
+ }
533
+ }
534
+
535
+ let lastValue = self.value;
536
+ self[internalSymbol].attachObserver(
537
+ new Observer(function () {
538
+ if (isObject(this) && this instanceof ProxyObserver) {
539
+ const n = this.getSubject()?.options?.value;
540
+
541
+ if (lastValue !== n) {
542
+ lastValue = n;
543
+ setSelection
544
+ .call(self, n)
545
+ .then(() => {
546
+ })
547
+ .catch((e) => {
548
+ addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, `${e}`);
549
+ });
550
+ }
551
+ }
552
+ }),
553
+ );
554
+
555
+ areOptionsAvailableAndInit.call(self);
556
+
557
+ return this;
558
+ }
559
+
560
+ /**
561
+ *
562
+ * @returns {*}
563
+ * @throws {Error} storeFetchedData is not enabled
564
+ * @since 3.66.0
565
+ */
566
+ getLastFetchedData() {
567
+ if (this.getOption("features.storeFetchedData") === false) {
568
+ throw new Error("storeFetchedData is not enabled");
569
+ }
570
+
571
+ return this?.[lastFetchedDataSymbol];
572
+ }
573
+
574
+ /**
575
+ * The Button.click() method simulates a click on the internal button element.
576
+ *
577
+ * @since 3.27.0
578
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
579
+ */
580
+ click() {
581
+ if (this.getOption("disabled") === true) {
582
+ return;
583
+ }
584
+
585
+ toggle.call(this);
586
+ }
587
+
588
+ /**
589
+ * The Button.focus() method sets focus on the internal button element.
590
+ *
591
+ * @since 3.27.0
592
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
593
+ */
594
+ focus(options) {
595
+ if (this.getOption("disabled") === true) {
596
+ return;
597
+ }
598
+
599
+ new Processing(() => {
600
+ gatherState.call(this);
601
+ focusFilter.call(this, options);
602
+ })
603
+ .run()
604
+ .catch((e) => {
605
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
606
+ });
607
+ }
608
+
609
+ /**
610
+ * The Button.blur() method removes focus from the internal button element.
611
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur
612
+ */
613
+ blur() {
614
+ new Processing(() => {
615
+ gatherState.call(this);
616
+ blurFilter.call(this);
617
+ })
618
+ .run()
619
+ .catch((e) => {
620
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
621
+ });
622
+ }
623
+
624
+ /**
625
+ * If no url is specified, the options are taken from the Component itself.
626
+ *
627
+ * @param {string|URL} url URL to fetch the options
628
+ * @return {Promise}
629
+ */
630
+ fetch(url) {
631
+ if (url instanceof URL) {
632
+ url = url.toString();
633
+ }
634
+
635
+ if (url !== undefined && url !== null) {
636
+ url = validateString(url);
637
+ } else {
638
+ url = this.getOption("url");
639
+ if (url === null) {
640
+ return Promise.reject(new Error("No url defined"));
641
+ }
642
+ }
643
+
644
+ hide.call(this);
645
+
646
+ return new Promise((resolve, reject) => {
647
+ setStatusOrRemoveBadges.call(this, "loading");
648
+
649
+ new Processing(10, () => {
650
+ this.setOption("options", []);
651
+
652
+ fetchData
653
+ .call(this, url)
654
+ .then((map) => {
655
+ if (
656
+ isObject(map) ||
657
+ isArray(map) ||
658
+ map instanceof Set ||
659
+ map instanceof Map
660
+ ) {
661
+
662
+ try {
663
+ this.importOptions(map);
664
+ } catch (e) {
665
+ setStatusOrRemoveBadges.call(this, "error");
666
+ reject(e);
667
+ return;
668
+ }
669
+
670
+ this[lastFetchedDataSymbol] = map;
671
+
672
+ let result;
673
+ const selection = this.getOption("selection");
674
+ let newValue = [];
675
+ if (selection) {
676
+ newValue = selection;
677
+ } else if (this.hasAttribute("value")) {
678
+ newValue = this.getAttribute("value");
679
+ }
680
+
681
+ result = setSelection.call(this, newValue);
682
+ requestAnimationFrame(() => {
683
+ checkOptionState.call(this);
684
+ setStatusOrRemoveBadges.call(this, "closed");
685
+ resolve(result);
686
+ });
687
+
688
+ return;
689
+ }
690
+
691
+ setStatusOrRemoveBadges.call(this, "error");
692
+ reject(new Error("invalid response"));
693
+ })
694
+ .catch((e) => {
695
+ setStatusOrRemoveBadges.call(this, "error");
696
+ reject(e);
697
+ });
698
+ })
699
+ .run()
700
+ .catch((e) => {
701
+ setStatusOrRemoveBadges.call(this, "error");
702
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
703
+ reject(e);
704
+ });
705
+ });
706
+ }
707
+
708
+ /**
709
+ * @return {void}
710
+ */
711
+ connectedCallback() {
712
+ super.connectedCallback();
713
+ const document = getDocument();
714
+
715
+ for (const [, type] of Object.entries(["click", "touch"])) {
716
+ // close on outside ui-events
717
+ document.addEventListener(type, this[closeEventHandler]);
718
+ }
719
+
720
+ parseSlotsToOptions.call(this);
721
+ attachResizeObserver.call(this);
722
+ updatePopper.call(this);
723
+
724
+ new Processing(() => {
725
+ gatherState.call(this);
726
+ focusFilter.call(this);
727
+ }).run();
728
+ }
729
+
730
+ /**
731
+ * @return {void}
732
+ */
733
+ disconnectedCallback() {
734
+ super.disconnectedCallback();
735
+ const document = getDocument();
736
+
737
+ // close on outside ui-events
738
+ for (const [, type] of Object.entries(["click", "touch"])) {
739
+ document.removeEventListener(type, this[closeEventHandler]);
740
+ }
741
+
742
+ disconnectResizeObserver.call(this);
743
+ }
744
+
745
+ /**
746
+ * Import Select Options from dataset
747
+ * Not to be confused with the control defaults/options
748
+ *
749
+ * @param {array|object|Map|Set} data
750
+ * @return {Select}
751
+ * @throws {Error} map is not iterable
752
+ * @throws {Error} missing label configuration
753
+ */
754
+ importOptions(data) {
755
+ const mappingOptions = this.getOption("mapping", {});
756
+ const selector = mappingOptions?.["selector"];
757
+ const labelTemplate = mappingOptions?.["labelTemplate"];
758
+ const valueTemplate = mappingOptions?.["valueTemplate"];
759
+ const filter = mappingOptions?.["filter"];
760
+
761
+ let flag = false;
762
+ if (labelTemplate === "") {
763
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty label template");
764
+ flag = true;
765
+ }
766
+
767
+ if (valueTemplate === "") {
768
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty value template");
769
+ flag = true;
770
+ }
771
+
772
+ if (flag === true) {
773
+ throw new Error("missing label configuration");
774
+ }
775
+
776
+ const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
777
+
778
+ const options = [];
779
+ if (!isIterable(map)) {
780
+ throw new Error("map is not iterable");
781
+ }
782
+
783
+ const visibility = "visible";
784
+
785
+ map.forEach((label, value) => {
786
+ options.push({
787
+ value,
788
+ label,
789
+ visibility,
790
+ data: map.get(value),
791
+ });
792
+ });
793
+
794
+ runAsOptionLengthChanged.call(this, map.size);
795
+ this.setOption("options", options);
796
+
797
+ fireCustomEvent(this, "monster-options-set", {
798
+ options,
799
+ });
800
+
801
+ return this;
802
+ }
803
+
804
+ /**
805
+ * @private
806
+ * @return {Select}
807
+ */
808
+ calcAndSetOptionsDimension() {
809
+ calcAndSetOptionsDimension.call(this);
810
+ return this;
811
+ }
812
+
813
+ /**
814
+ *
815
+ * @return {string}
816
+ */
817
+ static getTag() {
818
+ return "monster-select";
819
+ }
820
+
821
+ /**
822
+ *
823
+ * @return {CSSStyleSheet[]}
824
+ */
825
+ static getCSSStyleSheet() {
826
+ return [SelectStyleSheet];
827
+ }
828
+ }
829
+
830
+ function lookupSelection() {
831
+ const self = this;
832
+
833
+ setTimeout(() => {
834
+
835
+ const selection = self.getOption("selection");
836
+ if (selection.length === 0) {
837
+ return;
838
+ }
839
+
840
+ if (self[isLoadingSymbol] === true) {
841
+ return;
842
+ }
843
+
844
+ if (self[lazyLoadDoneSymbol] === true) {
845
+ return;
846
+ }
847
+
848
+ let url = self.getOption("url");
849
+ let lookupUrl = self.getOption("lookup.url");
850
+ if (lookupUrl !== null) {
851
+ url = lookupUrl;
852
+ }
853
+
854
+ if(this.getOption("lookup.grouping") === true) {
855
+ filterFromRemoteByValue.call(self, url, selection.map((s) => s?.["value"]));
856
+ return;
857
+ }
858
+
859
+ for (const s of selection) {
860
+ if (s?.["value"]) {
861
+ filterFromRemoteByValue.call(self, url, s?.["value"]);
862
+ }
863
+ }
864
+
865
+ }, 100);
866
+
804
867
  }
805
868
 
806
869
  /**
@@ -815,64 +878,64 @@ class Select extends CustomControl {
815
878
  * @return {object}
816
879
  */
817
880
  function initOptionsFromArguments() {
818
- const options = {};
819
-
820
- const template = this.getAttribute("data-monster-selected-template");
821
- if (isString(template)) {
822
- if (!options["templateMapping"]) options["templateMapping"] = {};
823
-
824
- switch (template) {
825
- case "summary":
826
- case "default":
827
- options["templateMapping"]["selected"] = getSummaryTemplate();
828
- break;
829
- case "selected":
830
- options["templateMapping"]["selected"] = getSelectionTemplate();
831
- break;
832
- default:
833
- addAttributeToken(
834
- this,
835
- ATTRIBUTE_ERRORMESSAGE,
836
- "invalid template, use summary or selected",
837
- );
838
- }
839
- }
840
-
841
- return options;
881
+ const options = {};
882
+
883
+ const template = this.getAttribute("data-monster-selected-template");
884
+ if (isString(template)) {
885
+ if (!options["templateMapping"]) options["templateMapping"] = {};
886
+
887
+ switch (template) {
888
+ case "summary":
889
+ case "default":
890
+ options["templateMapping"]["selected"] = getSummaryTemplate();
891
+ break;
892
+ case "selected":
893
+ options["templateMapping"]["selected"] = getSelectionTemplate();
894
+ break;
895
+ default:
896
+ addAttributeToken(
897
+ this,
898
+ ATTRIBUTE_ERRORMESSAGE,
899
+ "invalid template, use summary or selected",
900
+ );
901
+ }
902
+ }
903
+
904
+ return options;
842
905
  }
843
906
 
844
907
  /**
845
908
  * @private
846
909
  */
847
910
  function attachResizeObserver() {
848
- // against flickering
849
- this[resizeObserverSymbol] = new ResizeObserver((entries) => {
850
- if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
851
- try {
852
- this[timerCallbackSymbol].touch();
853
- return;
854
- } catch (e) {
855
- delete this[timerCallbackSymbol];
856
- }
857
- }
858
-
859
- this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
860
- updatePopper.call(this);
861
- delete this[timerCallbackSymbol];
862
- });
863
- });
864
-
865
- this[resizeObserverSymbol].observe(this.parentElement);
911
+ // against flickering
912
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
913
+ if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
914
+ try {
915
+ this[timerCallbackSymbol].touch();
916
+ return;
917
+ } catch (e) {
918
+ delete this[timerCallbackSymbol];
919
+ }
920
+ }
921
+
922
+ this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
923
+ updatePopper.call(this);
924
+ delete this[timerCallbackSymbol];
925
+ });
926
+ });
927
+
928
+ this[resizeObserverSymbol].observe(this.parentElement);
866
929
  }
867
930
 
868
931
  function disconnectResizeObserver() {
869
- if (this[resizeObserverSymbol] instanceof ResizeObserver) {
870
- this[resizeObserverSymbol].disconnect();
871
- }
932
+ if (this[resizeObserverSymbol] instanceof ResizeObserver) {
933
+ this[resizeObserverSymbol].disconnect();
934
+ }
872
935
  }
873
936
 
874
937
  function getSelectionTemplate() {
875
- return `<div data-monster-role="selection"
938
+ return `<div data-monster-role="selection"
876
939
  data-monster-insert="selection path:selection" role="search"
877
940
  ><input type="text" role="searchbox"
878
941
  part="inline-filter" name="inline-filter"
@@ -884,7 +947,7 @@ function getSelectionTemplate() {
884
947
  }
885
948
 
886
949
  function getSummaryTemplate() {
887
- return `<div data-monster-role="selection" role="search">
950
+ return `<div data-monster-role="selection" role="search">
888
951
  <input type="text" role="searchbox"
889
952
  part="inline-filter" name="inline-filter"
890
953
  data-monster-role="filter"
@@ -900,35 +963,35 @@ function getSummaryTemplate() {
900
963
  * @private
901
964
  */
902
965
  function parseSlotsToOptions() {
903
- let options = this.getOption("options");
904
- if (!isIterable(options)) {
905
- options = [];
906
- }
907
-
908
- let counter = 1;
909
- getSlottedElements.call(this, "div").forEach((node) => {
910
- let value = (counter++).toString();
911
- let visibility = "visible";
912
-
913
- if (node.hasAttribute("data-monster-value")) {
914
- value = node.getAttribute("data-monster-value");
915
- }
916
-
917
- if (node.style.display === "none") {
918
- visibility = "hidden";
919
- }
920
-
921
- const label = node.outerHTML;
922
-
923
- options.push({
924
- value,
925
- label,
926
- visibility,
927
- });
928
- });
929
-
930
- runAsOptionLengthChanged.call(this, options.length);
931
- this.setOption("options", options);
966
+ let options = this.getOption("options");
967
+ if (!isIterable(options)) {
968
+ options = [];
969
+ }
970
+
971
+ let counter = 1;
972
+ getSlottedElements.call(this, "div").forEach((node) => {
973
+ let value = (counter++).toString();
974
+ let visibility = "visible";
975
+
976
+ if (node.hasAttribute("data-monster-value")) {
977
+ value = node.getAttribute("data-monster-value");
978
+ }
979
+
980
+ if (node.style.display === "none") {
981
+ visibility = "hidden";
982
+ }
983
+
984
+ const label = node.outerHTML;
985
+
986
+ options.push({
987
+ value,
988
+ label,
989
+ visibility,
990
+ });
991
+ });
992
+
993
+ runAsOptionLengthChanged.call(this, options.length);
994
+ this.setOption("options", options);
932
995
  }
933
996
 
934
997
  /**
@@ -938,39 +1001,39 @@ function parseSlotsToOptions() {
938
1001
  * @param {int} targetLength
939
1002
  */
940
1003
  function runAsOptionLengthChanged(targetLength) {
941
- const self = this;
942
-
943
- if (!self[optionsElementSymbol]) {
944
- return;
945
- }
946
-
947
- const callback = function (mutationsList, observer) {
948
- const run = false;
949
- for (const mutation of mutationsList) {
950
- if (mutation.type === "childList") {
951
- const run = true;
952
- break;
953
- }
954
- }
955
-
956
- if (run === true) {
957
- const nodes = self[optionsElementSymbol].querySelectorAll(
958
- `div[${ATTRIBUTE_ROLE}=option]`,
959
- );
960
-
961
- if (nodes.length === targetLength) {
962
- checkOptionState.call(self);
963
- observer.disconnect();
964
- }
965
- }
966
- };
967
-
968
- const observer = new MutationObserver(callback);
969
- observer.observe(self[optionsElementSymbol], {
970
- attributes: false,
971
- childList: true,
972
- subtree: true,
973
- });
1004
+ const self = this;
1005
+
1006
+ if (!self[optionsElementSymbol]) {
1007
+ return;
1008
+ }
1009
+
1010
+ const callback = function (mutationsList, observer) {
1011
+ const run = false;
1012
+ for (const mutation of mutationsList) {
1013
+ if (mutation.type === "childList") {
1014
+ const run = true;
1015
+ break;
1016
+ }
1017
+ }
1018
+
1019
+ if (run === true) {
1020
+ const nodes = self[optionsElementSymbol].querySelectorAll(
1021
+ `div[${ATTRIBUTE_ROLE}=option]`,
1022
+ );
1023
+
1024
+ if (nodes.length === targetLength) {
1025
+ checkOptionState.call(self);
1026
+ observer.disconnect();
1027
+ }
1028
+ }
1029
+ };
1030
+
1031
+ const observer = new MutationObserver(callback);
1032
+ observer.observe(self[optionsElementSymbol], {
1033
+ attributes: false,
1034
+ childList: true,
1035
+ subtree: true,
1036
+ });
974
1037
  }
975
1038
 
976
1039
  /**
@@ -979,38 +1042,38 @@ function runAsOptionLengthChanged(targetLength) {
979
1042
  * @return {*}
980
1043
  */
981
1044
  function buildSelectionLabel(value) {
982
- const options = this.getOption("options");
983
-
984
- for (let i = 0; i < options.length; i++) {
985
- let o = options?.[i];
986
- let l, v, v2;
987
-
988
- if (this.getOption("features.useStrictValueComparison") === true) {
989
- v = value;
990
- } else {
991
- v = `${value}`;
992
- }
993
-
994
- if (isPrimitive(o) && o === value) {
995
- return o;
996
- } else if (!isObject(o)) {
997
- continue;
998
- }
999
-
1000
- if (this.getOption("features.useStrictValueComparison") === true) {
1001
- l = o?.["label"];
1002
- v2 = o?.["value"];
1003
- } else {
1004
- l = `${o?.["label"]}`;
1005
- v2 = `${o?.["value"]}`;
1006
- }
1007
-
1008
- if (v2 === v) {
1009
- return l;
1010
- }
1011
- }
1012
-
1013
- return undefined;
1045
+ const options = this.getOption("options");
1046
+
1047
+ for (let i = 0; i < options.length; i++) {
1048
+ let o = options?.[i];
1049
+ let l, v, v2;
1050
+
1051
+ if (this.getOption("features.useStrictValueComparison") === true) {
1052
+ v = value;
1053
+ } else {
1054
+ v = `${value}`;
1055
+ }
1056
+
1057
+ if (isPrimitive(o) && o === value) {
1058
+ return o;
1059
+ } else if (!isObject(o)) {
1060
+ continue;
1061
+ }
1062
+
1063
+ if (this.getOption("features.useStrictValueComparison") === true) {
1064
+ l = o?.["label"];
1065
+ v2 = o?.["value"];
1066
+ } else {
1067
+ l = `${o?.["label"]}`;
1068
+ v2 = `${o?.["value"]}`;
1069
+ }
1070
+
1071
+ if (v2 === v) {
1072
+ return l;
1073
+ }
1074
+ }
1075
+
1076
+ return undefined;
1014
1077
  }
1015
1078
 
1016
1079
  /**
@@ -1020,17 +1083,17 @@ function buildSelectionLabel(value) {
1020
1083
  * @throws {Error} no value found
1021
1084
  */
1022
1085
  function getSelectionLabel(value) {
1023
- const callback = this.getOption("formatter.selection");
1024
- if (isFunction(callback)) {
1025
- const label = callback.call(this, value);
1026
- if (isString(label)) return label;
1027
- }
1086
+ const callback = this.getOption("formatter.selection");
1087
+ if (isFunction(callback)) {
1088
+ const label = callback.call(this, value);
1089
+ if (isString(label)) return label;
1090
+ }
1028
1091
 
1029
- if (isString(value) || isInteger(value)) {
1030
- return `${value}`;
1031
- }
1092
+ if (isString(value) || isInteger(value)) {
1093
+ return `${value}`;
1094
+ }
1032
1095
 
1033
- return this.getOption("labels.cannot-be-loaded", value);
1096
+ return this.getOption("labels.cannot-be-loaded", value);
1034
1097
  }
1035
1098
 
1036
1099
  /**
@@ -1038,25 +1101,25 @@ function getSelectionLabel(value) {
1038
1101
  * @param {Event} event
1039
1102
  */
1040
1103
  function handleToggleKeyboardEvents(event) {
1041
- switch (event?.["code"]) {
1042
- case "Escape":
1043
- toggle.call(this);
1044
- event.preventDefault();
1045
- break;
1046
- case "Space":
1047
- toggle.call(this);
1048
- event.preventDefault();
1049
- break;
1050
- case "ArrowDown":
1051
- show.call(this);
1052
- activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1053
- event.preventDefault();
1054
- break;
1055
- case "ArrowUp":
1056
- hide.call(this);
1057
- event.preventDefault();
1058
- break;
1059
- }
1104
+ switch (event?.["code"]) {
1105
+ case "Escape":
1106
+ toggle.call(this);
1107
+ event.preventDefault();
1108
+ break;
1109
+ case "Space":
1110
+ toggle.call(this);
1111
+ event.preventDefault();
1112
+ break;
1113
+ case "ArrowDown":
1114
+ show.call(this);
1115
+ activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1116
+ event.preventDefault();
1117
+ break;
1118
+ case "ArrowUp":
1119
+ hide.call(this);
1120
+ event.preventDefault();
1121
+ break;
1122
+ }
1060
1123
  }
1061
1124
 
1062
1125
  /**
@@ -1066,35 +1129,46 @@ function handleToggleKeyboardEvents(event) {
1066
1129
  * @this CustomElement
1067
1130
  */
1068
1131
  function initOptionObserver() {
1069
- const self = this;
1070
-
1071
- self.attachObserver(
1072
- new Observer(function () {
1073
- new Processing(() => {
1074
- try {
1075
- self.updateI18n();
1076
- } catch (e) {}
1077
- try {
1078
- areOptionsAvailableAndInit.call(self);
1079
- } catch (e) {}
1080
-
1081
- setSummaryAndControlText.call(self);
1082
- }).run();
1083
- }),
1084
- );
1132
+ const self = this;
1133
+
1134
+ self.attachObserver(
1135
+ new Observer(function () {
1136
+ new Processing(() => {
1137
+ try {
1138
+ self.updateI18n();
1139
+ } catch (e) {
1140
+ console.error(e);
1141
+ requestAnimationFrame(() => {
1142
+ setStatusOrRemoveBadges.call(self, "error");
1143
+ });
1144
+ }
1145
+ try {
1146
+ areOptionsAvailableAndInit.call(self);
1147
+ } catch (e) {
1148
+ console.error(e);
1149
+ requestAnimationFrame(() => {
1150
+ setStatusOrRemoveBadges.call(self, "error");
1151
+ });
1152
+ }
1153
+
1154
+ setSummaryAndControlText.call(self);
1155
+ }).run();
1156
+ }),
1157
+ );
1085
1158
  }
1086
1159
 
1087
1160
  function getDefaultTranslation() {
1088
- const translation = new Translations("en").assignTranslations(
1089
- this.getOption("labels", {}),
1090
- );
1161
+ const translation = new Translations("en").assignTranslations(
1162
+ this.getOption("labels", {}),
1163
+ );
1091
1164
 
1092
- try {
1093
- const doc = getDocumentTranslations();
1094
- translation.locale = doc.locale;
1095
- } catch (e) {}
1165
+ try {
1166
+ const doc = getDocumentTranslations();
1167
+ translation.locale = doc.locale;
1168
+ } catch (e) {
1169
+ }
1096
1170
 
1097
- return translation;
1171
+ return translation;
1098
1172
  }
1099
1173
 
1100
1174
  /**
@@ -1102,36 +1176,36 @@ function getDefaultTranslation() {
1102
1176
  * @returns {string|*}
1103
1177
  */
1104
1178
  function setSummaryAndControlText() {
1105
- const translations = getDefaultTranslation.call(this);
1106
- const selections = this.getOption("selection");
1107
-
1108
- const text = translations.getPluralRuleText(
1109
- "summary-text",
1110
- selections.length,
1111
- "",
1112
- );
1113
-
1114
- const selectedText = new Formatter({
1115
- count: String(selections.length),
1116
- }).format(text);
1117
-
1118
- this.setOption("messages.selected", selectedText);
1119
-
1120
- const current = this.getOption("messages.control");
1121
- const msg = this.getOption("labels.select-an-option");
1122
-
1123
- if (
1124
- current === "" ||
1125
- current === undefined ||
1126
- current === msg ||
1127
- current === null
1128
- ) {
1129
- if (selections === undefined || selections.length === 0) {
1130
- this.setOption("messages.control", msg);
1131
- } else {
1132
- this.setOption("messages.control", "");
1133
- }
1134
- }
1179
+ const translations = getDefaultTranslation.call(this);
1180
+ const selections = this.getOption("selection");
1181
+
1182
+ const text = translations.getPluralRuleText(
1183
+ "summary-text",
1184
+ selections.length,
1185
+ "",
1186
+ );
1187
+
1188
+ const selectedText = new Formatter({
1189
+ count: String(selections.length),
1190
+ }).format(text);
1191
+
1192
+ this.setOption("messages.selected", selectedText);
1193
+
1194
+ const current = this.getOption("messages.control");
1195
+ const msg = this.getOption("labels.select-an-option");
1196
+
1197
+ if (
1198
+ current === "" ||
1199
+ current === undefined ||
1200
+ current === msg ||
1201
+ current === null
1202
+ ) {
1203
+ if (selections === undefined || selections.length === 0) {
1204
+ this.setOption("messages.control", msg);
1205
+ } else {
1206
+ this.setOption("messages.control", "");
1207
+ }
1208
+ }
1135
1209
  }
1136
1210
 
1137
1211
  /**
@@ -1139,9 +1213,9 @@ function setSummaryAndControlText() {
1139
1213
  * @return {NodeList}
1140
1214
  */
1141
1215
  function getOptionElements() {
1142
- return this[optionsElementSymbol].querySelectorAll(
1143
- `[${ATTRIBUTE_ROLE}=option]`,
1144
- );
1216
+ return this[optionsElementSymbol].querySelectorAll(
1217
+ `[${ATTRIBUTE_ROLE}=option]`,
1218
+ );
1145
1219
  }
1146
1220
 
1147
1221
  /**
@@ -1167,82 +1241,82 @@ function getOptionElements() {
1167
1241
  * @private
1168
1242
  */
1169
1243
  function calcAndSetOptionsDimension() {
1170
- const options = getOptionElements.call(this);
1171
- const container = this[optionsElementSymbol];
1172
- if (!(container instanceof HTMLElement && options instanceof NodeList)) {
1173
- return;
1174
- }
1175
-
1176
- let visible = 0;
1177
- let optionHeight = 0;
1178
- const max = this.getOption("showMaxOptions", 10);
1179
-
1180
- let scrollFlag = false;
1181
- for (const [, option] of Object.entries(options)) {
1182
- const computedStyle = getGlobal().getComputedStyle(option);
1183
- if (computedStyle.display === "none") continue;
1184
-
1185
- let h = option.getBoundingClientRect().height;
1186
- h += parseInt(computedStyle.getPropertyValue("margin-top"), 10);
1187
- h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10);
1188
- optionHeight += h;
1189
-
1190
- visible++;
1191
-
1192
- if (visible > max) {
1193
- break;
1194
- }
1195
- }
1196
-
1197
- if (visible > max) {
1198
- visible = max;
1199
- scrollFlag = true;
1200
- }
1201
-
1202
- if (visible === 0) {
1203
- if (this.getOption("options").length === 0) {
1204
- this.setOption(
1205
- "messages.emptyOptions",
1206
- this.getOption("labels.no-options-available"),
1207
- );
1208
- } else {
1209
- if (this.getOption("filter.mode") === FILTER_MODE_DISABLED) {
1210
- this.setOption(
1211
- "messages.emptyOptions",
1212
- this.getOption("labels.no-options-available"),
1213
- );
1214
- } else {
1215
- this.setOption(
1216
- "messages.emptyOptions",
1217
- this.getOption("labels.no-options-found"),
1218
- );
1219
- }
1220
- }
1221
- this[noOptionsAvailableElementSymbol].classList.remove("d-none");
1222
- } else {
1223
- this[noOptionsAvailableElementSymbol].classList.add("d-none");
1224
- }
1225
-
1226
- const styles = getGlobal().getComputedStyle(this[optionsElementSymbol]);
1227
- let padding = parseInt(styles.getPropertyValue("padding-top"), 10);
1228
- padding += parseInt(styles.getPropertyValue("padding-bottom"), 10);
1229
-
1230
- let margin = parseInt(styles.getPropertyValue("margin-top"), 10);
1231
- margin += parseInt(styles.getPropertyValue("margin-bottom"), 10);
1232
-
1233
- const containerHeight = optionHeight + padding + margin;
1234
- container.style.height = `${containerHeight}px`;
1235
-
1236
- if (scrollFlag === true) {
1237
- container.style.overflowY = "scroll";
1238
- } else {
1239
- container.style.overflowY = "auto";
1240
- }
1241
-
1242
- const domRect = this[controlElementSymbol].getBoundingClientRect();
1243
-
1244
- this[popperElementSymbol].style.width = `${domRect.width}px`;
1245
- container.style.overflowX = "auto";
1244
+ const options = getOptionElements.call(this);
1245
+ const container = this[optionsElementSymbol];
1246
+ if (!(container instanceof HTMLElement && options instanceof NodeList)) {
1247
+ return;
1248
+ }
1249
+
1250
+ let visible = 0;
1251
+ let optionHeight = 0;
1252
+ const max = this.getOption("showMaxOptions", 10);
1253
+
1254
+ let scrollFlag = false;
1255
+ for (const [, option] of Object.entries(options)) {
1256
+ const computedStyle = getGlobal().getComputedStyle(option);
1257
+ if (computedStyle.display === "none") continue;
1258
+
1259
+ let h = option.getBoundingClientRect().height;
1260
+ h += parseInt(computedStyle.getPropertyValue("margin-top"), 10);
1261
+ h += parseInt(computedStyle.getPropertyValue("margin-bottom"), 10);
1262
+ optionHeight += h;
1263
+
1264
+ visible++;
1265
+
1266
+ if (visible > max) {
1267
+ break;
1268
+ }
1269
+ }
1270
+
1271
+ if (visible > max) {
1272
+ visible = max;
1273
+ scrollFlag = true;
1274
+ }
1275
+
1276
+ if (visible === 0) {
1277
+ if (this.getOption("options").length === 0) {
1278
+ this.setOption(
1279
+ "messages.emptyOptions",
1280
+ this.getOption("labels.no-options-available"),
1281
+ );
1282
+ } else {
1283
+ if (this.getOption("filter.mode") === FILTER_MODE_DISABLED) {
1284
+ this.setOption(
1285
+ "messages.emptyOptions",
1286
+ this.getOption("labels.no-options-available"),
1287
+ );
1288
+ } else {
1289
+ this.setOption(
1290
+ "messages.emptyOptions",
1291
+ this.getOption("labels.no-options-found"),
1292
+ );
1293
+ }
1294
+ }
1295
+ this[noOptionsAvailableElementSymbol].classList.remove("d-none");
1296
+ } else {
1297
+ this[noOptionsAvailableElementSymbol].classList.add("d-none");
1298
+ }
1299
+
1300
+ const styles = getGlobal().getComputedStyle(this[optionsElementSymbol]);
1301
+ let padding = parseInt(styles.getPropertyValue("padding-top"), 10);
1302
+ padding += parseInt(styles.getPropertyValue("padding-bottom"), 10);
1303
+
1304
+ let margin = parseInt(styles.getPropertyValue("margin-top"), 10);
1305
+ margin += parseInt(styles.getPropertyValue("margin-bottom"), 10);
1306
+
1307
+ const containerHeight = optionHeight + padding + margin;
1308
+ container.style.height = `${containerHeight}px`;
1309
+
1310
+ if (scrollFlag === true) {
1311
+ container.style.overflowY = "scroll";
1312
+ } else {
1313
+ container.style.overflowY = "auto";
1314
+ }
1315
+
1316
+ const domRect = this[controlElementSymbol].getBoundingClientRect();
1317
+
1318
+ this[popperElementSymbol].style.width = `${domRect.width}px`;
1319
+ container.style.overflowX = "auto";
1246
1320
  }
1247
1321
 
1248
1322
  /**
@@ -1251,126 +1325,126 @@ function calcAndSetOptionsDimension() {
1251
1325
  * @throws {Error} no shadow-root is defined
1252
1326
  */
1253
1327
  function activateCurrentOption(direction) {
1254
- if (!this.shadowRoot) {
1255
- throw new Error("no shadow-root is defined");
1256
- }
1257
-
1258
- let focused = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`);
1259
-
1260
- if (
1261
- !(focused instanceof HTMLElement) ||
1262
- focused.matches("[data-monster-visibility=hidden]")
1263
- ) {
1264
- for (const [, e] of Object.entries(
1265
- this.shadowRoot.querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`),
1266
- )) {
1267
- if (e.matches("[data-monster-visibility=visible]")) {
1268
- focused = e;
1269
- break;
1270
- }
1271
- }
1272
- } else {
1273
- if (direction === FOCUS_DIRECTION_DOWN) {
1274
- while (focused.nextSibling) {
1275
- focused = focused.nextSibling;
1276
-
1277
- if (
1278
- focused instanceof HTMLElement &&
1279
- focused.hasAttribute(ATTRIBUTE_ROLE) &&
1280
- focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
1281
- focused.matches("[data-monster-visibility=visible]") &&
1282
- focused.matches(":not([data-monster-filtered=true])")
1283
- ) {
1284
- break;
1285
- }
1286
- }
1287
- } else {
1288
- let found = false;
1289
- while (focused.previousSibling) {
1290
- focused = focused.previousSibling;
1291
- if (
1292
- focused instanceof HTMLElement &&
1293
- focused.hasAttribute(ATTRIBUTE_ROLE) &&
1294
- focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
1295
- focused.matches("[data-monster-visibility=visible]") &&
1296
- focused.matches(":not([data-monster-filtered=true])")
1297
- ) {
1298
- found = true;
1299
- break;
1300
- }
1301
- }
1302
- if (found === false) {
1303
- focusFilter.call(this);
1304
- }
1305
- }
1306
- }
1307
-
1308
- new Processing(() => {
1309
- if (focused instanceof HTMLElement) {
1310
- this.shadowRoot
1311
- .querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`)
1312
- .forEach((e) => {
1313
- e.removeAttribute(`${ATTRIBUTE_PREFIX}focused`);
1314
- });
1315
-
1316
- focused.focus();
1317
- focused.setAttribute(`${ATTRIBUTE_PREFIX}focused`, true);
1318
- }
1319
- })
1320
- .run()
1321
- .catch((e) => {
1322
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1323
- });
1328
+ if (!this.shadowRoot) {
1329
+ throw new Error("no shadow-root is defined");
1330
+ }
1331
+
1332
+ let focused = this.shadowRoot.querySelector(`[${ATTRIBUTE_PREFIX}focused]`);
1333
+
1334
+ if (
1335
+ !(focused instanceof HTMLElement) ||
1336
+ focused.matches("[data-monster-visibility=hidden]")
1337
+ ) {
1338
+ for (const [, e] of Object.entries(
1339
+ this.shadowRoot.querySelectorAll(`[${ATTRIBUTE_ROLE}=option]`),
1340
+ )) {
1341
+ if (e.matches("[data-monster-visibility=visible]")) {
1342
+ focused = e;
1343
+ break;
1344
+ }
1345
+ }
1346
+ } else {
1347
+ if (direction === FOCUS_DIRECTION_DOWN) {
1348
+ while (focused.nextSibling) {
1349
+ focused = focused.nextSibling;
1350
+
1351
+ if (
1352
+ focused instanceof HTMLElement &&
1353
+ focused.hasAttribute(ATTRIBUTE_ROLE) &&
1354
+ focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
1355
+ focused.matches("[data-monster-visibility=visible]") &&
1356
+ focused.matches(":not([data-monster-filtered=true])")
1357
+ ) {
1358
+ break;
1359
+ }
1360
+ }
1361
+ } else {
1362
+ let found = false;
1363
+ while (focused.previousSibling) {
1364
+ focused = focused.previousSibling;
1365
+ if (
1366
+ focused instanceof HTMLElement &&
1367
+ focused.hasAttribute(ATTRIBUTE_ROLE) &&
1368
+ focused.getAttribute(ATTRIBUTE_ROLE) === "option" &&
1369
+ focused.matches("[data-monster-visibility=visible]") &&
1370
+ focused.matches(":not([data-monster-filtered=true])")
1371
+ ) {
1372
+ found = true;
1373
+ break;
1374
+ }
1375
+ }
1376
+ if (found === false) {
1377
+ focusFilter.call(this);
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ new Processing(() => {
1383
+ if (focused instanceof HTMLElement) {
1384
+ this.shadowRoot
1385
+ .querySelectorAll(`[${ATTRIBUTE_PREFIX}focused]`)
1386
+ .forEach((e) => {
1387
+ e.removeAttribute(`${ATTRIBUTE_PREFIX}focused`);
1388
+ });
1389
+
1390
+ focused.focus();
1391
+ focused.setAttribute(`${ATTRIBUTE_PREFIX}focused`, true);
1392
+ }
1393
+ })
1394
+ .run()
1395
+ .catch((e) => {
1396
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1397
+ });
1324
1398
  }
1325
1399
 
1326
1400
  /**
1327
1401
  * @private
1328
1402
  */
1329
1403
  function filterOptions() {
1330
- new Processing(() => {
1331
- let filterValue;
1332
-
1333
- switch (this.getOption("filter.position")) {
1334
- case FILTER_POSITION_INLINE:
1335
- if (this[inlineFilterElementSymbol] instanceof HTMLElement) {
1336
- filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
1337
- } else {
1338
- return;
1339
- }
1340
-
1341
- break;
1342
- case FILTER_POSITION_POPPER:
1343
- default:
1344
- if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
1345
- filterValue = this[popperFilterElementSymbol].value.toLowerCase();
1346
- } else {
1347
- return;
1348
- }
1349
- }
1350
-
1351
- const options = this.getOption("options");
1352
- for (const [i, option] of Object.entries(options)) {
1353
- if (option.label.toLowerCase().indexOf(filterValue) === -1) {
1354
- this.setOption(`options.${i}.filtered`, "true");
1355
- } else {
1356
- this.setOption(`options.${i}.filtered`, undefined);
1357
- }
1358
- }
1359
- })
1360
- .run()
1361
- .then(() => {
1362
- new Processing(100, () => {
1363
- calcAndSetOptionsDimension.call(this);
1364
- focusFilter.call(this);
1365
- })
1366
- .run()
1367
- .catch((e) => {
1368
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1369
- });
1370
- })
1371
- .catch((e) => {
1372
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1373
- });
1404
+ new Processing(() => {
1405
+ let filterValue;
1406
+
1407
+ switch (this.getOption("filter.position")) {
1408
+ case FILTER_POSITION_INLINE:
1409
+ if (this[inlineFilterElementSymbol] instanceof HTMLElement) {
1410
+ filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
1411
+ } else {
1412
+ return;
1413
+ }
1414
+
1415
+ break;
1416
+ case FILTER_POSITION_POPPER:
1417
+ default:
1418
+ if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
1419
+ filterValue = this[popperFilterElementSymbol].value.toLowerCase();
1420
+ } else {
1421
+ return;
1422
+ }
1423
+ }
1424
+
1425
+ const options = this.getOption("options");
1426
+ for (const [i, option] of Object.entries(options)) {
1427
+ if (option.label.toLowerCase().indexOf(filterValue) === -1) {
1428
+ this.setOption(`options.${i}.filtered`, "true");
1429
+ } else {
1430
+ this.setOption(`options.${i}.filtered`, undefined);
1431
+ }
1432
+ }
1433
+ })
1434
+ .run()
1435
+ .then(() => {
1436
+ new Processing(100, () => {
1437
+ calcAndSetOptionsDimension.call(this);
1438
+ focusFilter.call(this);
1439
+ })
1440
+ .run()
1441
+ .catch((e) => {
1442
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1443
+ });
1444
+ })
1445
+ .catch((e) => {
1446
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1447
+ });
1374
1448
  }
1375
1449
 
1376
1450
  /**
@@ -1378,37 +1452,37 @@ function filterOptions() {
1378
1452
  * @param {Event} event
1379
1453
  */
1380
1454
  function handleFilterKeyboardEvents(event) {
1381
- const shiftKey = event?.["shiftKey"];
1382
-
1383
- switch (event?.["code"]) {
1384
- case "Tab":
1385
- activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1386
- event.preventDefault();
1387
- break;
1388
- case "Escape":
1389
- toggle.call(this);
1390
- event.preventDefault();
1391
- break;
1392
- case "Tab" && shiftKey === true:
1393
- case "ArrowUp":
1394
- activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
1395
- event.preventDefault();
1396
- break;
1397
- case "Tab" && !shiftKey:
1398
- case "ArrowDown":
1399
- activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1400
- event.preventDefault();
1401
- break;
1402
- default:
1403
- if (
1404
- this.getOption("features.lazyLoad") === true &&
1405
- this[lazyLoadDoneSymbol] !== true
1406
- ) {
1407
- this.click();
1408
- }
1409
-
1410
- handleFilterKeyEvents.call(this);
1411
- }
1455
+ const shiftKey = event?.["shiftKey"];
1456
+
1457
+ switch (event?.["code"]) {
1458
+ case "Tab":
1459
+ activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1460
+ event.preventDefault();
1461
+ break;
1462
+ case "Escape":
1463
+ toggle.call(this);
1464
+ event.preventDefault();
1465
+ break;
1466
+ case "Tab" && shiftKey === true:
1467
+ case "ArrowUp":
1468
+ activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
1469
+ event.preventDefault();
1470
+ break;
1471
+ case "Tab" && !shiftKey:
1472
+ case "ArrowDown":
1473
+ activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1474
+ event.preventDefault();
1475
+ break;
1476
+ default:
1477
+ if (
1478
+ this.getOption("features.lazyLoad") === true &&
1479
+ this[lazyLoadDoneSymbol] !== true
1480
+ ) {
1481
+ this.click();
1482
+ }
1483
+
1484
+ handleFilterKeyEvents.call(this);
1485
+ }
1412
1486
  }
1413
1487
 
1414
1488
  /**
@@ -1427,74 +1501,85 @@ function handleFilterKeyboardEvents(event) {
1427
1501
  * @returns {void} This method does not return anything.
1428
1502
  */
1429
1503
  function handleFilterKeyEvents() {
1430
- if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) {
1431
- try {
1432
- this[keyFilterEventSymbol].touch();
1433
- return;
1434
- } catch (e) {
1435
- delete this[keyFilterEventSymbol];
1436
- }
1437
- }
1438
-
1439
- this[keyFilterEventSymbol] = new DeadMansSwitch(200, () => {
1440
- if (this.getOption("filter.mode") !== FILTER_MODE_REMOTE) {
1441
- filterOptions.call(this);
1442
- } else {
1443
- filterFromRemote.call(this).catch((e) => {
1444
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1445
- });
1446
- }
1447
-
1448
- delete this[keyFilterEventSymbol];
1449
- });
1504
+ if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) {
1505
+ try {
1506
+ this[keyFilterEventSymbol].touch();
1507
+ return;
1508
+ } catch (e) {
1509
+ delete this[keyFilterEventSymbol];
1510
+ }
1511
+ }
1512
+
1513
+ this[keyFilterEventSymbol] = new DeadMansSwitch(200, () => {
1514
+ if (this.getOption("filter.mode") !== FILTER_MODE_REMOTE) {
1515
+ filterOptions.call(this);
1516
+ } else {
1517
+ filterFromRemote.call(this).catch((e) => {
1518
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1519
+ });
1520
+ }
1521
+
1522
+ delete this[keyFilterEventSymbol];
1523
+ });
1450
1524
  }
1451
1525
 
1452
1526
  /**
1453
1527
  * @private
1454
1528
  */
1455
1529
  function filterFromRemote() {
1456
- if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
1457
- return;
1458
- }
1459
-
1460
- const optionUrl = this.getOption("url");
1461
- if (!optionUrl) {
1462
- addAttributeToken(
1463
- this,
1464
- ATTRIBUTE_ERRORMESSAGE,
1465
- "Missing URL for Remote Filter.",
1466
- );
1467
- return;
1468
- }
1469
-
1470
- return new Processing(() => {
1471
- const filterValue = encodeURI(
1472
- this[inlineFilterElementSymbol].value.toLowerCase(),
1473
- );
1474
- let url = optionUrl;
1475
- if (filterValue.length > 0) {
1476
- const formatter = new Formatter({ filter: filterValue });
1477
- const openMarker = this.getOption("formatter.marker.open");
1478
- let closeMarker = this.getOption("formatter.marker.close");
1479
- if (!closeMarker) {
1480
- closeMarker = openMarker;
1481
- }
1482
-
1483
- if (openMarker && closeMarker) {
1484
- formatter.setMarker(openMarker, closeMarker);
1485
- }
1486
-
1487
- url = formatter.format(optionUrl);
1488
- }
1489
-
1490
- this.fetch(url)
1491
- .then(() => {
1492
- checkOptionState.call(this);
1493
- })
1494
- .catch((e) => {
1495
- throw e;
1496
- });
1497
- }).run();
1530
+ if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
1531
+ return;
1532
+ }
1533
+
1534
+ const optionUrl = this.getOption("url");
1535
+ if (!optionUrl) {
1536
+ addAttributeToken(
1537
+ this,
1538
+ ATTRIBUTE_ERRORMESSAGE,
1539
+ "Missing URL for Remote Filter.",
1540
+ );
1541
+ return;
1542
+ }
1543
+
1544
+
1545
+ return filterFromRemoteByValue.call(this, optionUrl, this[inlineFilterElementSymbol].value);
1546
+
1547
+ }
1548
+
1549
+ /**
1550
+ * @private
1551
+ */
1552
+ function filterFromRemoteByValue(optionUrl, value) {
1553
+
1554
+ return new Processing(() => {
1555
+ const filterValue = encodeURI(
1556
+ value
1557
+ );
1558
+ let url = optionUrl;
1559
+ if (filterValue.length > 0) {
1560
+
1561
+ const formatter = new Formatter({filter: filterValue});
1562
+ const openMarker = this.getOption("filter.marker.open");
1563
+ let closeMarker = this.getOption("filter.marker.close");
1564
+ if (!closeMarker) {
1565
+ closeMarker = openMarker;
1566
+ }
1567
+
1568
+ if (openMarker && closeMarker) {
1569
+ formatter.setMarker(openMarker, closeMarker);
1570
+ }
1571
+
1572
+ url = formatter.format(optionUrl);
1573
+ }
1574
+
1575
+ this.fetch(url)
1576
+ .then(() => {
1577
+ checkOptionState.call(this);
1578
+ })
1579
+ .catch((e) => {
1580
+ throw e;
1581
+ });
1582
+ }).run();
1498
1583
  }
1499
1584
 
1500
1585
  /**
@@ -1503,50 +1588,50 @@ function filterFromRemote() {
1503
1588
  * @private
1504
1589
  */
1505
1590
  function handleOptionKeyboardEvents(event) {
1506
- const shiftKey = event?.["shiftKey"];
1507
-
1508
- switch (event?.["code"]) {
1509
- case "Escape":
1510
- toggle.call(this);
1511
- event.preventDefault();
1512
- break;
1513
- case "Enter":
1514
- case "Space":
1515
- const path = event.composedPath();
1516
- const element = path?.[0];
1517
- if (element instanceof HTMLElement) {
1518
- const input = element.getElementsByTagName("input");
1519
- if (!input) {
1520
- return;
1521
- }
1522
- fireEvent(input, "click");
1523
- }
1524
- event.preventDefault();
1525
- break;
1526
-
1527
- case "Tab" && shiftKey === true:
1528
- case "ArrowUp":
1529
- activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
1530
- event.preventDefault();
1531
- break;
1532
-
1533
- case "Tab" && !shiftKey:
1534
- case "ArrowLeft":
1535
- case "ArrowRight":
1536
- // handled by tree select
1537
- break;
1538
- case "ArrowDown":
1539
- activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1540
- event.preventDefault();
1541
- break;
1542
- default:
1543
- const p = event.composedPath();
1544
- if (p?.[0] instanceof HTMLInputElement) {
1545
- return;
1546
- }
1547
- focusFilter.call(this);
1548
- break;
1549
- }
1591
+ const shiftKey = event?.["shiftKey"];
1592
+
1593
+ switch (event?.["code"]) {
1594
+ case "Escape":
1595
+ toggle.call(this);
1596
+ event.preventDefault();
1597
+ break;
1598
+ case "Enter":
1599
+ case "Space":
1600
+ const path = event.composedPath();
1601
+ const element = path?.[0];
1602
+ if (element instanceof HTMLElement) {
1603
+ const input = element.getElementsByTagName("input");
1604
+ if (!input) {
1605
+ return;
1606
+ }
1607
+ fireEvent(input, "click");
1608
+ }
1609
+ event.preventDefault();
1610
+ break;
1611
+
1612
+ case "Tab" && shiftKey === true:
1613
+ case "ArrowUp":
1614
+ activateCurrentOption.call(this, FOCUS_DIRECTION_UP);
1615
+ event.preventDefault();
1616
+ break;
1617
+
1618
+ case "Tab" && !shiftKey:
1619
+ case "ArrowLeft":
1620
+ case "ArrowRight":
1621
+ // handled by tree select
1622
+ break;
1623
+ case "ArrowDown":
1624
+ activateCurrentOption.call(this, FOCUS_DIRECTION_DOWN);
1625
+ event.preventDefault();
1626
+ break;
1627
+ default:
1628
+ const p = event.composedPath();
1629
+ if (p?.[0] instanceof HTMLInputElement) {
1630
+ return;
1631
+ }
1632
+ focusFilter.call(this);
1633
+ break;
1634
+ }
1550
1635
  }
1551
1636
 
1552
1637
  /**
@@ -1554,33 +1639,33 @@ function handleOptionKeyboardEvents(event) {
1554
1639
  * @returns {string}
1555
1640
  */
1556
1641
  function getFilterMode() {
1557
- switch (this.getOption("filter.mode")) {
1558
- case FILTER_MODE_OPTIONS:
1559
- return FILTER_MODE_OPTIONS;
1560
- case FILTER_MODE_REMOTE:
1561
- return FILTER_MODE_REMOTE;
1562
- default:
1563
- return FILTER_MODE_DISABLED;
1564
- }
1642
+ switch (this.getOption("filter.mode")) {
1643
+ case FILTER_MODE_OPTIONS:
1644
+ return FILTER_MODE_OPTIONS;
1645
+ case FILTER_MODE_REMOTE:
1646
+ return FILTER_MODE_REMOTE;
1647
+ default:
1648
+ return FILTER_MODE_DISABLED;
1649
+ }
1565
1650
  }
1566
1651
 
1567
1652
  /**
1568
1653
  * @private
1569
1654
  */
1570
1655
  function blurFilter() {
1571
- if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
1572
- return;
1573
- }
1656
+ if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
1657
+ return;
1658
+ }
1574
1659
 
1575
- if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
1576
- return;
1577
- }
1660
+ if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
1661
+ return;
1662
+ }
1578
1663
 
1579
- this[popperFilterContainerElementSymbol].classList.remove("active");
1580
- this[popperFilterContainerElementSymbol].blur();
1664
+ this[popperFilterContainerElementSymbol].classList.remove("active");
1665
+ this[popperFilterContainerElementSymbol].blur();
1581
1666
 
1582
- this[inlineFilterElementSymbol].classList.remove("active");
1583
- this[inlineFilterElementSymbol].blur();
1667
+ this[inlineFilterElementSymbol].classList.remove("active");
1668
+ this[inlineFilterElementSymbol].blur();
1584
1669
  }
1585
1670
 
1586
1671
  /**
@@ -1588,29 +1673,29 @@ function blurFilter() {
1588
1673
  * @param focusOptions
1589
1674
  */
1590
1675
  function focusPopperFilter(focusOptions) {
1591
- this[popperFilterContainerElementSymbol].classList.remove("d-none");
1592
- this[popperFilterElementSymbol].classList.add("active");
1593
- this[inlineFilterElementSymbol].classList.remove("active");
1594
- this[inlineFilterElementSymbol].classList.add("d-none");
1595
-
1596
- if (!(this[popperFilterElementSymbol] instanceof HTMLElement)) {
1597
- addAttributeToken(
1598
- this,
1599
- ATTRIBUTE_ERRORMESSAGE,
1600
- "Missing Popper Filter Element.",
1601
- );
1602
- return;
1603
- }
1604
-
1605
- // visibility is set to visible, because focus() does not work on invisible elements
1606
- // and the class definition is assigned later in the processing
1607
- setTimeout(() => {
1608
- if (focusOptions === undefined || focusOptions === null) {
1609
- this[popperFilterElementSymbol].focus();
1610
- } else {
1611
- this[popperFilterElementSymbol].focus(focusOptions);
1612
- }
1613
- }, 100);
1676
+ this[popperFilterContainerElementSymbol].classList.remove("d-none");
1677
+ this[popperFilterElementSymbol].classList.add("active");
1678
+ this[inlineFilterElementSymbol].classList.remove("active");
1679
+ this[inlineFilterElementSymbol].classList.add("d-none");
1680
+
1681
+ if (!(this[popperFilterElementSymbol] instanceof HTMLElement)) {
1682
+ addAttributeToken(
1683
+ this,
1684
+ ATTRIBUTE_ERRORMESSAGE,
1685
+ "Missing Popper Filter Element.",
1686
+ );
1687
+ return;
1688
+ }
1689
+
1690
+ // visibility is set to visible, because focus() does not work on invisible elements
1691
+ // and the class definition is assigned later in the processing
1692
+ setTimeout(() => {
1693
+ if (focusOptions === undefined || focusOptions === null) {
1694
+ this[popperFilterElementSymbol].focus();
1695
+ } else {
1696
+ this[popperFilterElementSymbol].focus(focusOptions);
1697
+ }
1698
+ }, 100);
1614
1699
  }
1615
1700
 
1616
1701
  /**
@@ -1618,44 +1703,44 @@ function focusPopperFilter(focusOptions) {
1618
1703
  * @param focusOptions
1619
1704
  */
1620
1705
  function focusInlineFilter(focusOptions) {
1621
- const options = this.getOption("options");
1622
- if (
1623
- (!isArray(options) || options.length === 0) &&
1624
- this.getOption("filter.mode") !== FILTER_MODE_REMOTE
1625
- ) {
1626
- return;
1627
- }
1628
-
1629
- this[popperFilterContainerElementSymbol].classList.add("d-none");
1630
- this[inlineFilterElementSymbol].classList.add("active");
1631
- this[inlineFilterElementSymbol].classList.remove("d-none");
1632
-
1633
- // visibility is set to visible, because focus() does not work on invisible elements
1634
- // and the class definition is assigned later in the processing
1635
- setTimeout(() => {
1636
- if (focusOptions === undefined || focusOptions === null) {
1637
- this[inlineFilterElementSymbol].focus();
1638
- } else {
1639
- this[inlineFilterElementSymbol].focus(focusOptions);
1640
- }
1641
- }, 100);
1706
+ const options = this.getOption("options");
1707
+ if (
1708
+ (!isArray(options) || options.length === 0) &&
1709
+ this.getOption("filter.mode") !== FILTER_MODE_REMOTE
1710
+ ) {
1711
+ return;
1712
+ }
1713
+
1714
+ this[popperFilterContainerElementSymbol].classList.add("d-none");
1715
+ this[inlineFilterElementSymbol].classList.add("active");
1716
+ this[inlineFilterElementSymbol].classList.remove("d-none");
1717
+
1718
+ // visibility is set to visible, because focus() does not work on invisible elements
1719
+ // and the class definition is assigned later in the processing
1720
+ setTimeout(() => {
1721
+ if (focusOptions === undefined || focusOptions === null) {
1722
+ this[inlineFilterElementSymbol].focus();
1723
+ } else {
1724
+ this[inlineFilterElementSymbol].focus(focusOptions);
1725
+ }
1726
+ }, 100);
1642
1727
  }
1643
1728
 
1644
1729
  /**
1645
1730
  * @private
1646
1731
  */
1647
1732
  function focusFilter(focusOptions) {
1648
- if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
1649
- this[popperFilterContainerElementSymbol].classList.add("d-none");
1650
- this[inlineFilterElementSymbol].classList.add("d-none");
1651
- return;
1652
- }
1733
+ if (getFilterMode.call(this) === FILTER_MODE_DISABLED) {
1734
+ this[popperFilterContainerElementSymbol].classList.add("d-none");
1735
+ this[inlineFilterElementSymbol].classList.add("d-none");
1736
+ return;
1737
+ }
1653
1738
 
1654
- if (this.getOption("filter.position") === FILTER_POSITION_INLINE) {
1655
- return focusInlineFilter.call(this, focusOptions);
1656
- }
1739
+ if (this.getOption("filter.position") === FILTER_POSITION_INLINE) {
1740
+ return focusInlineFilter.call(this, focusOptions);
1741
+ }
1657
1742
 
1658
- return focusPopperFilter.call(this, focusOptions);
1743
+ return focusPopperFilter.call(this, focusOptions);
1659
1744
  }
1660
1745
 
1661
1746
  /**
@@ -1665,39 +1750,40 @@ function focusFilter(focusOptions) {
1665
1750
  * @throws {Error} unsupported type
1666
1751
  */
1667
1752
  function gatherState() {
1668
- const type = this.getOption("type");
1669
- if (["radio", "checkbox"].indexOf(type) === -1) {
1670
- throw new Error("unsupported type");
1671
- }
1672
-
1673
- if (!this.shadowRoot) {
1674
- throw new Error("no shadow-root is defined");
1675
- }
1676
-
1677
- const selection = [];
1678
- const elements = this.shadowRoot.querySelectorAll(
1679
- `input[type=${type}]:checked`,
1680
- );
1681
-
1682
- for (const e of elements) {
1683
- selection.push({
1684
- label: getSelectionLabel.call(this, e.value),
1685
- value: e.value,
1686
- });
1687
- }
1688
-
1689
- setSelection
1690
- .call(this, selection)
1691
- .then(() => {})
1692
- .catch((e) => {
1693
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
1694
- });
1695
-
1696
- if (this.getOption("features.closeOnSelect") === true) {
1697
- toggle.call(this);
1698
- }
1699
-
1700
- return this;
1753
+ const type = this.getOption("type");
1754
+ if (["radio", "checkbox"].indexOf(type) === -1) {
1755
+ throw new Error("unsupported type");
1756
+ }
1757
+
1758
+ if (!this.shadowRoot) {
1759
+ throw new Error("no shadow-root is defined");
1760
+ }
1761
+
1762
+ const selection = [];
1763
+ const elements = this.shadowRoot.querySelectorAll(
1764
+ `input[type=${type}]:checked`,
1765
+ );
1766
+
1767
+ for (const e of elements) {
1768
+ selection.push({
1769
+ label: getSelectionLabel.call(this, e.value),
1770
+ value: e.value,
1771
+ });
1772
+ }
1773
+
1774
+ setSelection
1775
+ .call(this, selection)
1776
+ .then(() => {
1777
+ })
1778
+ .catch((e) => {
1779
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
1780
+ });
1781
+
1782
+ if (this.getOption("features.closeOnSelect") === true) {
1783
+ toggle.call(this);
1784
+ }
1785
+
1786
+ return this;
1701
1787
  }
1702
1788
 
1703
1789
  /**
@@ -1706,120 +1792,121 @@ function gatherState() {
1706
1792
  * @throws {Error} unsupported type
1707
1793
  */
1708
1794
  function clearSelection() {
1709
- const type = this.getOption("type");
1710
- if (["radio", "checkbox"].indexOf(type) === -1) {
1711
- throw new Error("unsupported type");
1712
- }
1713
-
1714
- if (!this.shadowRoot) {
1715
- throw new Error("no shadow-root is defined");
1716
- }
1717
-
1718
- setSelection
1719
- .call(this, [])
1720
- .then(() => {})
1721
- .catch((e) => {
1722
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
1723
- });
1795
+ const type = this.getOption("type");
1796
+ if (["radio", "checkbox"].indexOf(type) === -1) {
1797
+ throw new Error("unsupported type");
1798
+ }
1799
+
1800
+ if (!this.shadowRoot) {
1801
+ throw new Error("no shadow-root is defined");
1802
+ }
1803
+
1804
+ setSelection
1805
+ .call(this, [])
1806
+ .then(() => {
1807
+ })
1808
+ .catch((e) => {
1809
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
1810
+ });
1724
1811
  }
1725
1812
 
1726
1813
  /**
1727
1814
  * @private
1728
1815
  */
1729
1816
  function areOptionsAvailableAndInit() {
1730
- // prevent multiple calls
1731
- if (this[areOptionsAvailableAndInitSymbol] === undefined) {
1732
- this[areOptionsAvailableAndInitSymbol] = 0;
1733
- }
1734
-
1735
- if (this[areOptionsAvailableAndInitSymbol] > 0) {
1736
- this[areOptionsAvailableAndInitSymbol]--;
1737
- return true;
1738
- }
1739
-
1740
- this[areOptionsAvailableAndInitSymbol]++;
1741
-
1742
- const options = this.getOption("options");
1743
-
1744
- if (
1745
- options === undefined ||
1746
- options === null ||
1747
- (isArray(options) && options.length === 0)
1748
- ) {
1749
- setStatusOrRemoveBadges.call(this, "empty");
1750
-
1751
- hide.call(this);
1752
-
1753
- let msg = this.getOption("labels.no-options-available");
1754
-
1755
- if (
1756
- this.getOption("url") !== null &&
1757
- this.getOption("features.lazyLoad") === true &&
1758
- this[lazyLoadDoneSymbol] !== true
1759
- ) {
1760
- msg = this.getOption("labels.click-to-load-options");
1761
- }
1762
-
1763
- this.setOption("messages.control", msg);
1764
- this.setOption("messages.summary", "");
1765
-
1766
- if (this.getOption("features.emptyValueIfNoOptions") === true) {
1767
- this.value = "";
1768
- }
1769
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, noOptionsAvailableMessage);
1770
- return false;
1771
- }
1772
-
1773
- const selections = this.getOption("selection");
1774
- if (
1775
- selections === undefined ||
1776
- selections === null ||
1777
- selections.length === 0
1778
- ) {
1779
- this.setOption(
1780
- "messages.control",
1781
- this.getOption("labels.select-an-option"),
1782
- );
1783
- } else {
1784
- this.setOption("messages.control", "");
1785
- }
1786
-
1787
- this.setOption("messages.summary", setSummaryAndControlText.call(this));
1788
-
1789
- let updated = false;
1790
- let valueCounter = 1;
1791
- for (const option of options) {
1792
- if (option?.visibility === undefined) {
1793
- option.visibility = "visible";
1794
- updated = true;
1795
- }
1796
-
1797
- if (option?.value === undefined && option?.label === undefined) {
1798
- option.value = `${valueCounter++}`;
1799
- option.label = option.value;
1800
- updated = true;
1801
- continue;
1802
- }
1803
-
1804
- if (option?.value === undefined) {
1805
- option.value = option.label;
1806
- updated = true;
1807
- }
1808
-
1809
- if (option?.label === undefined) {
1810
- option.label = option.value;
1811
- updated = true;
1812
- }
1813
- }
1814
-
1815
- if (updated) {
1816
- this.setOption("options", options);
1817
- }
1818
-
1819
- setStatusOrRemoveBadges.call(this);
1820
-
1821
- removeAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, noOptionsAvailableMessage);
1822
- return true;
1817
+ // prevent multiple calls
1818
+ if (this[areOptionsAvailableAndInitSymbol] === undefined) {
1819
+ this[areOptionsAvailableAndInitSymbol] = 0;
1820
+ }
1821
+
1822
+ if (this[areOptionsAvailableAndInitSymbol] > 0) {
1823
+ this[areOptionsAvailableAndInitSymbol]--;
1824
+ return true;
1825
+ }
1826
+
1827
+ this[areOptionsAvailableAndInitSymbol]++;
1828
+
1829
+ const options = this.getOption("options");
1830
+
1831
+ if (
1832
+ options === undefined ||
1833
+ options === null ||
1834
+ (isArray(options) && options.length === 0)
1835
+ ) {
1836
+ setStatusOrRemoveBadges.call(this, "empty");
1837
+
1838
+ hide.call(this);
1839
+
1840
+ let msg = this.getOption("labels.no-options-available");
1841
+
1842
+ if (
1843
+ this.getOption("url") !== null &&
1844
+ this.getOption("features.lazyLoad") === true &&
1845
+ this[lazyLoadDoneSymbol] !== true
1846
+ ) {
1847
+ msg = this.getOption("labels.click-to-load-options");
1848
+ }
1849
+
1850
+ this.setOption("messages.control", msg);
1851
+ this.setOption("messages.summary", "");
1852
+
1853
+ if (this.getOption("features.emptyValueIfNoOptions") === true) {
1854
+ this.value = "";
1855
+ }
1856
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, noOptionsAvailableMessage);
1857
+ return false;
1858
+ }
1859
+
1860
+ const selections = this.getOption("selection");
1861
+ if (
1862
+ selections === undefined ||
1863
+ selections === null ||
1864
+ selections.length === 0
1865
+ ) {
1866
+ this.setOption(
1867
+ "messages.control",
1868
+ this.getOption("labels.select-an-option"),
1869
+ );
1870
+ } else {
1871
+ this.setOption("messages.control", "");
1872
+ }
1873
+
1874
+ this.setOption("messages.summary", setSummaryAndControlText.call(this));
1875
+
1876
+ let updated = false;
1877
+ let valueCounter = 1;
1878
+ for (const option of options) {
1879
+ if (option?.visibility === undefined) {
1880
+ option.visibility = "visible";
1881
+ updated = true;
1882
+ }
1883
+
1884
+ if (option?.value === undefined && option?.label === undefined) {
1885
+ option.value = `${valueCounter++}`;
1886
+ option.label = option.value;
1887
+ updated = true;
1888
+ continue;
1889
+ }
1890
+
1891
+ if (option?.value === undefined) {
1892
+ option.value = option.label;
1893
+ updated = true;
1894
+ }
1895
+
1896
+ if (option?.label === undefined) {
1897
+ option.label = option.value;
1898
+ updated = true;
1899
+ }
1900
+ }
1901
+
1902
+ if (updated) {
1903
+ this.setOption("options", options);
1904
+ }
1905
+
1906
+ setStatusOrRemoveBadges.call(this);
1907
+
1908
+ removeAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, noOptionsAvailableMessage);
1909
+ return true;
1823
1910
  }
1824
1911
 
1825
1912
  /**
@@ -1827,30 +1914,30 @@ function areOptionsAvailableAndInit() {
1827
1914
  * @throws {Error} no shadow-root is defined
1828
1915
  */
1829
1916
  function checkOptionState() {
1830
- if (!this.shadowRoot) {
1831
- throw new Error("no shadow-root is defined");
1832
- }
1833
-
1834
- const elements = this.shadowRoot.querySelectorAll(
1835
- `[${ATTRIBUTE_ROLE}=option] input`,
1836
- );
1837
-
1838
- let selection = this.getOption("selection");
1839
- if (!isArray(selection)) {
1840
- selection = [];
1841
- }
1842
-
1843
- const checkedValues = selection.map((a) => {
1844
- return a.value;
1845
- });
1846
-
1847
- for (const e of elements) {
1848
- if (checkedValues.indexOf(e.value) !== -1) {
1849
- if (e.checked !== true) e.checked = true;
1850
- } else {
1851
- if (e.checked !== false) e.checked = false;
1852
- }
1853
- }
1917
+ if (!this.shadowRoot) {
1918
+ throw new Error("no shadow-root is defined");
1919
+ }
1920
+
1921
+ const elements = this.shadowRoot.querySelectorAll(
1922
+ `[${ATTRIBUTE_ROLE}=option] input`,
1923
+ );
1924
+
1925
+ let selection = this.getOption("selection");
1926
+ if (!isArray(selection)) {
1927
+ selection = [];
1928
+ }
1929
+
1930
+ const checkedValues = selection.map((a) => {
1931
+ return a.value;
1932
+ });
1933
+
1934
+ for (const e of elements) {
1935
+ if (checkedValues.indexOf(e.value) !== -1) {
1936
+ if (e.checked !== true) e.checked = true;
1937
+ } else {
1938
+ if (e.checked !== false) e.checked = false;
1939
+ }
1940
+ }
1854
1941
  }
1855
1942
 
1856
1943
  /**
@@ -1859,41 +1946,41 @@ function checkOptionState() {
1859
1946
  * @return {Object}
1860
1947
  */
1861
1948
  function convertValueToSelection(value) {
1862
- const selection = [];
1863
-
1864
- if (isString(value)) {
1865
- value = value
1866
- .split(",")
1867
- .map((a) => {
1868
- return a.trim();
1869
- })
1870
- .filter((a) => {
1871
- return a !== "";
1872
- });
1873
- }
1874
-
1875
- if (isString(value) || isInteger(value)) {
1876
- selection.push({
1877
- label: getSelectionLabel.call(this, value),
1878
- value: value,
1879
- });
1880
- } else if (isArray(value)) {
1881
- for (const v of value) {
1882
- selection.push({
1883
- label: getSelectionLabel.call(this, v),
1884
- value: v,
1885
- });
1886
- }
1887
-
1888
- value = value.join(",");
1889
- } else {
1890
- throw new Error("unsupported type");
1891
- }
1892
-
1893
- return {
1894
- selection: selection,
1895
- value: value,
1896
- };
1949
+ const selection = [];
1950
+
1951
+ if (isString(value)) {
1952
+ value = value
1953
+ .split(",")
1954
+ .map((a) => {
1955
+ return a.trim();
1956
+ })
1957
+ .filter((a) => {
1958
+ return a !== "";
1959
+ });
1960
+ }
1961
+
1962
+ if (isString(value) || isInteger(value)) {
1963
+ selection.push({
1964
+ label: getSelectionLabel.call(this, value),
1965
+ value: value,
1966
+ });
1967
+ } else if (isArray(value)) {
1968
+ for (const v of value) {
1969
+ selection.push({
1970
+ label: getSelectionLabel.call(this, v),
1971
+ value: v,
1972
+ });
1973
+ }
1974
+
1975
+ value = value.join(",");
1976
+ } else {
1977
+ throw new Error("unsupported type");
1978
+ }
1979
+
1980
+ return {
1981
+ selection: selection,
1982
+ value: value,
1983
+ };
1897
1984
  }
1898
1985
 
1899
1986
  /**
@@ -1902,22 +1989,22 @@ function convertValueToSelection(value) {
1902
1989
  * @return {string}
1903
1990
  */
1904
1991
  function convertSelectionToValue(selection) {
1905
- const value = [];
1906
-
1907
- if (isArray(selection)) {
1908
- for (const obj of selection) {
1909
- const v = obj?.["value"];
1910
- if (v !== undefined) value.push(v);
1911
- }
1912
- }
1913
-
1914
- if (value.length === 0) {
1915
- return "";
1916
- } else if (value.length === 1) {
1917
- return value.pop();
1918
- }
1919
-
1920
- return value.join(",");
1992
+ const value = [];
1993
+
1994
+ if (isArray(selection)) {
1995
+ for (const obj of selection) {
1996
+ const v = obj?.["value"];
1997
+ if (v !== undefined) value.push(v);
1998
+ }
1999
+ }
2000
+
2001
+ if (value.length === 0) {
2002
+ return "";
2003
+ } else if (value.length === 1) {
2004
+ return value.pop();
2005
+ }
2006
+
2007
+ return value.join(",");
1921
2008
  }
1922
2009
 
1923
2010
  /**
@@ -1927,67 +2014,67 @@ function convertSelectionToValue(selection) {
1927
2014
  * @throws {Error} no shadow-root is defined
1928
2015
  */
1929
2016
  function setSelection(selection) {
1930
- if (isString(selection)) {
1931
- const result = convertValueToSelection.call(this, selection);
1932
- selection = result?.selection;
1933
- } else if (selection === undefined) {
1934
- selection = [];
1935
- }
1936
-
1937
- validateArray(selection);
1938
-
1939
- for (let i = 0; i < selection.length; i++) {
1940
- selection[i] = {
1941
- label: getSelectionLabel.call(this, selection[i].value),
1942
- value: selection[i].value,
1943
- };
1944
- }
1945
-
1946
- this.setOption("selection", selection);
1947
- checkOptionState.call(this);
1948
-
1949
- try {
1950
- this?.setFormValue(this.value);
1951
- } catch (e) {
1952
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1953
- }
1954
-
1955
- fireCustomEvent(this, "monster-selected", {
1956
- selection,
1957
- });
1958
-
1959
- return new Processing(() => {
1960
- const CLASSNAME = "selected";
1961
-
1962
- if (!this.shadowRoot) {
1963
- throw new Error("no shadow-root is defined");
1964
- }
1965
-
1966
- const notSelected = this.shadowRoot.querySelectorAll(":not(:checked)");
1967
-
1968
- if (notSelected) {
1969
- notSelected.forEach((node) => {
1970
- const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
1971
- if (parent) {
1972
- parent.classList.remove(CLASSNAME);
1973
- }
1974
- });
1975
- }
1976
-
1977
- const selected = this.shadowRoot.querySelectorAll(":checked");
1978
- if (selected) {
1979
- selected.forEach((node) => {
1980
- const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
1981
- if (parent) {
1982
- parent.classList.add(CLASSNAME);
1983
- }
1984
- });
1985
- }
1986
- })
1987
- .run()
1988
- .catch((e) => {
1989
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
1990
- });
2017
+ if (isString(selection)) {
2018
+ const result = convertValueToSelection.call(this, selection);
2019
+ selection = result?.selection;
2020
+ } else if (selection === undefined) {
2021
+ selection = [];
2022
+ }
2023
+
2024
+ validateArray(selection);
2025
+
2026
+ for (let i = 0; i < selection.length; i++) {
2027
+ selection[i] = {
2028
+ label: getSelectionLabel.call(this, selection[i].value),
2029
+ value: selection[i].value,
2030
+ };
2031
+ }
2032
+
2033
+ this.setOption("selection", selection);
2034
+ checkOptionState.call(this);
2035
+
2036
+ try {
2037
+ this?.setFormValue(this.value);
2038
+ } catch (e) {
2039
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2040
+ }
2041
+
2042
+ fireCustomEvent(this, "monster-selected", {
2043
+ selection,
2044
+ });
2045
+
2046
+ return new Processing(() => {
2047
+ const CLASSNAME = "selected";
2048
+
2049
+ if (!this.shadowRoot) {
2050
+ throw new Error("no shadow-root is defined");
2051
+ }
2052
+
2053
+ const notSelected = this.shadowRoot.querySelectorAll(":not(:checked)");
2054
+
2055
+ if (notSelected) {
2056
+ notSelected.forEach((node) => {
2057
+ const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
2058
+ if (parent) {
2059
+ parent.classList.remove(CLASSNAME);
2060
+ }
2061
+ });
2062
+ }
2063
+
2064
+ const selected = this.shadowRoot.querySelectorAll(":checked");
2065
+ if (selected) {
2066
+ selected.forEach((node) => {
2067
+ const parent = node.closest(`[${ATTRIBUTE_ROLE}=option]`);
2068
+ if (parent) {
2069
+ parent.classList.add(CLASSNAME);
2070
+ }
2071
+ });
2072
+ }
2073
+ })
2074
+ .run()
2075
+ .catch((e) => {
2076
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2077
+ });
1991
2078
  }
1992
2079
 
1993
2080
  /**
@@ -1998,319 +2085,328 @@ function setSelection(selection) {
1998
2085
  * @throws {TypeError} unsupported response
1999
2086
  */
2000
2087
  function fetchData(url) {
2001
- if (!url) url = this.getOption("url");
2002
- if (!url) return Promise.resolve();
2003
-
2004
- const fetchOptions = this.getOption("fetch", {});
2005
-
2006
- let delayWatch = false;
2007
-
2008
- // if fetch short time, do not show loading badge, because of flickering
2009
- setTimeout(() => {
2010
- if (delayWatch === true) return;
2011
- setStatusOrRemoveBadges.call(this, "loading");
2012
- delayWatch = true;
2013
- }, 500);
2014
-
2015
- url = new Formatter({ filter: this.getOption("filter.defaultValue") }).format(
2016
- url,
2017
- );
2018
-
2019
- const global = getGlobal();
2020
- return global
2021
- .fetch(url, fetchOptions)
2022
- .then((response) => {
2023
- delayWatch = true;
2024
- const contentType = response.headers.get("content-type");
2025
- if (contentType && contentType.indexOf("application/json") !== -1) {
2026
- return response.text();
2027
- }
2028
-
2029
- throw new TypeError(`unsupported response ${contentType}`);
2030
- })
2031
- .then((text) => {
2032
- try {
2033
- return Promise.resolve(JSON.parse(String(text)));
2034
- } catch (e) {
2035
- throw new TypeError("the result cannot be parsed");
2036
- }
2037
- })
2038
- .catch((e) => {
2039
- delayWatch = true;
2040
- throw e;
2041
- });
2088
+ const self = this;
2089
+ if (!url) url = this.getOption("url");
2090
+ if (!url) return Promise.resolve();
2091
+
2092
+ const fetchOptions = this.getOption("fetch", {});
2093
+
2094
+ let delayWatch = false;
2095
+
2096
+ // if fetch short time, do not show loading badge, because of flickering
2097
+ requestAnimationFrame(() => {
2098
+ if (delayWatch === true) return;
2099
+ setStatusOrRemoveBadges.call(this, "loading");
2100
+ delayWatch = true;
2101
+ });
2102
+
2103
+ url = new Formatter({filter: this.getOption("filter.defaultValue")}).format(
2104
+ url,
2105
+ );
2106
+
2107
+ self[isLoadingSymbol] = true;
2108
+ const global = getGlobal();
2109
+ return global
2110
+ .fetch(url, fetchOptions)
2111
+ .then((response) => {
2112
+ self[isLoadingSymbol] = false;
2113
+ delayWatch = true;
2114
+ const contentType = response.headers.get("content-type");
2115
+ if (contentType && contentType.indexOf("application/json") !== -1) {
2116
+ return response.text();
2117
+ }
2118
+
2119
+ throw new TypeError(`unsupported response ${contentType}`);
2120
+ })
2121
+ .then((text) => {
2122
+ try {
2123
+ return Promise.resolve(JSON.parse(String(text)));
2124
+ } catch (e) {
2125
+ throw new TypeError("the result cannot be parsed, check the URL");
2126
+ }
2127
+ })
2128
+ .catch((e) => {
2129
+ self[isLoadingSymbol] = false;
2130
+ delayWatch = true;
2131
+ throw e;
2132
+ });
2042
2133
  }
2043
2134
 
2044
2135
  /**
2045
2136
  * @private
2046
2137
  */
2047
2138
  function hide() {
2048
- this[popperElementSymbol].style.display = "none";
2049
- setStatusOrRemoveBadges.call(this, "closed");
2050
- removeAttributeToken(this[controlElementSymbol], "class", "open");
2139
+ this[popperElementSymbol].style.display = "none";
2140
+ setStatusOrRemoveBadges.call(this, "closed");
2141
+ removeAttributeToken(this[controlElementSymbol], "class", "open");
2051
2142
  }
2052
2143
 
2053
2144
  /**
2054
2145
  * @private
2055
2146
  */
2056
2147
  function show() {
2057
- if (this.getOption("disabled", undefined) === true) {
2058
- return;
2059
- }
2060
-
2061
- if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
2062
- return;
2063
- }
2064
-
2065
- focusFilter.call(this);
2066
-
2067
- const lazyLoadFlag =
2068
- this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true;
2069
-
2070
- if (lazyLoadFlag === true) {
2071
- this[lazyLoadDoneSymbol] = true;
2072
- setStatusOrRemoveBadges.call(this, "loading");
2073
-
2074
- new Processing(200, () => {
2075
- this.fetch()
2076
- .then(() => {
2077
- checkOptionState.call(this);
2078
- })
2079
- .catch((e) => {
2080
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2081
- setStatusOrRemoveBadges.call(this, "error");
2082
- });
2083
- })
2084
- .run()
2085
- .catch((e) => {
2086
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2087
- });
2088
-
2089
- return;
2090
- }
2091
-
2092
- const options = getOptionElements.call(this);
2093
- if (options.length === 0) {
2094
- return;
2095
- }
2096
-
2097
- this[popperElementSymbol].style.visibility = "hidden";
2098
- this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
2099
- setStatusOrRemoveBadges.call(this, "open");
2100
-
2101
- addAttributeToken(this[controlElementSymbol], "class", "open");
2102
-
2103
- new Processing(() => {
2104
- calcAndSetOptionsDimension.call(this);
2105
- focusFilter.call(this);
2106
- this[popperElementSymbol].style.removeProperty("visibility");
2107
- updatePopper.call(this);
2108
- })
2109
- .run()
2110
- .catch((e) => {
2111
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2112
- });
2148
+ if (this.getOption("disabled", undefined) === true) {
2149
+ return;
2150
+ }
2151
+
2152
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
2153
+ return;
2154
+ }
2155
+
2156
+ focusFilter.call(this);
2157
+
2158
+ const lazyLoadFlag =
2159
+ this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true;
2160
+
2161
+ if (lazyLoadFlag === true) {
2162
+ this[lazyLoadDoneSymbol] = true;
2163
+ setStatusOrRemoveBadges.call(this, "loading");
2164
+
2165
+ new Processing(200, () => {
2166
+ this.fetch()
2167
+ .then(() => {
2168
+ checkOptionState.call(this);
2169
+ requestAnimationFrame(() => {
2170
+ show.call(this);
2171
+ })
2172
+ })
2173
+ .catch((e) => {
2174
+
2175
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2176
+ setStatusOrRemoveBadges.call(this, "error");
2177
+ });
2178
+ })
2179
+ .run()
2180
+ .catch((e) => {
2181
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2182
+ setStatusOrRemoveBadges.call(this, "error");
2183
+ });
2184
+
2185
+ return;
2186
+ }
2187
+
2188
+ const options = getOptionElements.call(this);
2189
+ if (options.length === 0) {
2190
+ return;
2191
+ }
2192
+
2193
+ this[popperElementSymbol].style.visibility = "hidden";
2194
+ this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
2195
+ setStatusOrRemoveBadges.call(this, "open");
2196
+
2197
+ addAttributeToken(this[controlElementSymbol], "class", "open");
2198
+
2199
+ new Processing(() => {
2200
+ calcAndSetOptionsDimension.call(this);
2201
+ focusFilter.call(this);
2202
+ this[popperElementSymbol].style.removeProperty("visibility");
2203
+ updatePopper.call(this);
2204
+ })
2205
+ .run()
2206
+ .catch((e) => {
2207
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2208
+ });
2113
2209
  }
2114
2210
 
2115
2211
  /**
2116
2212
  * @private
2117
2213
  */
2118
2214
  function toggle() {
2119
- if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
2120
- hide.call(this);
2121
- } else {
2122
- show.call(this);
2123
- }
2215
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
2216
+ hide.call(this);
2217
+ } else {
2218
+ show.call(this);
2219
+ }
2124
2220
  }
2125
2221
 
2126
2222
  /**
2127
2223
  * @private
2128
- * @fires Monster.Components.Form.event:monster-selection-removed
2129
- * @fires Monster.Components.Form.event:monster-selection-cleared
2224
+ * @fires monster-selection-removed
2225
+ * @fires monster-selection-cleared
2130
2226
  */
2131
2227
  function initEventHandler() {
2132
- const self = this;
2133
-
2134
- /**
2135
- * @param {Event} event
2136
- */
2137
- self[clearOptionEventHandler] = (event) => {
2138
- const element = findTargetElementFromEvent(
2139
- event,
2140
- ATTRIBUTE_ROLE,
2141
- "remove-badge",
2142
- );
2143
-
2144
- if (element instanceof HTMLElement) {
2145
- const badge = findClosestByAttribute(element, ATTRIBUTE_ROLE, "badge");
2146
- if (badge instanceof HTMLElement) {
2147
- const value = badge.getAttribute(`${ATTRIBUTE_PREFIX}value`);
2148
-
2149
- let selection = self.getOption("selection");
2150
- selection = selection.filter((b) => {
2151
- return value !== b.value;
2152
- });
2153
-
2154
- setSelection
2155
- .call(self, selection)
2156
- .then(() => {
2157
- fireCustomEvent(self, "monster-selection-removed", {
2158
- value,
2159
- });
2160
- })
2161
- .catch((e) => {
2162
- addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.message);
2163
- });
2164
- }
2165
- }
2166
- };
2167
-
2168
- /**
2169
- * @param {Event} event
2170
- */
2171
- self[closeEventHandler] = (event) => {
2172
- const path = event.composedPath();
2173
-
2174
- for (const [, element] of Object.entries(path)) {
2175
- if (element === self) {
2176
- return;
2177
- }
2178
- }
2179
- hide.call(self);
2180
- };
2181
-
2182
- /**
2183
- * @param {Event} event
2184
- */
2185
- self[inputEventHandler] = (event) => {
2186
- const path = event.composedPath();
2187
- const element = path?.[0];
2188
-
2189
- if (element instanceof HTMLElement) {
2190
- if (
2191
- element.hasAttribute(ATTRIBUTE_ROLE) &&
2192
- element.getAttribute(ATTRIBUTE_ROLE) === "option-control"
2193
- ) {
2194
- fireCustomEvent(self, "monster-change", {
2195
- type: event.type,
2196
- value: element.value,
2197
- checked: element.checked,
2198
- });
2199
- } else if (
2200
- element.hasAttribute(ATTRIBUTE_ROLE) &&
2201
- element.getAttribute(ATTRIBUTE_ROLE) === "filter"
2202
- ) {
2203
- }
2204
- }
2205
- };
2206
-
2207
- /**
2208
- * @param {Event} event
2209
- */
2210
- self[changeEventHandler] = (event) => {
2211
- gatherState.call(self);
2212
- fireCustomEvent(self, "monster-changed", event?.detail);
2213
- };
2214
-
2215
- self[keyEventHandler] = (event) => {
2216
- const path = event.composedPath();
2217
- const element = path.shift();
2218
-
2219
- let role;
2220
-
2221
- if (element instanceof HTMLElement) {
2222
- if (element.hasAttribute(ATTRIBUTE_ROLE)) {
2223
- role = element.getAttribute(ATTRIBUTE_ROLE);
2224
- } else if (element === this) {
2225
- show.call(this);
2226
- // focusFilter.call(self);
2227
- } else {
2228
- const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
2229
- if (e instanceof HTMLElement && e.hasAttribute(ATTRIBUTE_ROLE)) {
2230
- role = e.getAttribute(ATTRIBUTE_ROLE);
2231
- }
2232
- }
2233
- } else {
2234
- return;
2235
- }
2236
-
2237
- switch (role) {
2238
- case "filter":
2239
- handleFilterKeyboardEvents.call(self, event);
2240
- break;
2241
- case "option-label":
2242
- case "option-control":
2243
- case "option":
2244
- handleOptionKeyboardEvents.call(self, event);
2245
- break;
2246
- case "control":
2247
- case "toggle":
2248
- handleToggleKeyboardEvents.call(self, event);
2249
- break;
2250
- }
2251
- };
2252
-
2253
- const types = self.getOption("toggleEventType", ["click"]);
2254
-
2255
- for (const [, type] of Object.entries(types)) {
2256
- self[controlElementSymbol]
2257
- .querySelector(`[${ATTRIBUTE_ROLE}="container"]`)
2258
- .addEventListener(type, function (event) {
2259
- const element = findTargetElementFromEvent(
2260
- event,
2261
- ATTRIBUTE_ROLE,
2262
- "remove-badge",
2263
- );
2264
- if (element instanceof HTMLElement) {
2265
- return;
2266
- }
2267
-
2268
- toggle.call(self);
2269
- });
2270
-
2271
- self[controlElementSymbol]
2272
- .querySelector(`[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`)
2273
- .addEventListener(type, function (event) {
2274
- if (self.getOption("disabled", undefined) === true) {
2275
- return;
2276
- }
2277
-
2278
- const path = event.composedPath();
2279
- const element = path?.[0];
2280
- if (element instanceof HTMLElement) {
2281
- const control = element.closest(
2282
- `[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`,
2283
- );
2284
- if (control instanceof HTMLElement) {
2285
- if (control.classList.contains("clear")) {
2286
- clearSelection.call(self);
2287
-
2288
- fireCustomEvent(self, "monster-selection-cleared", {});
2289
- } else {
2290
- const element = findTargetElementFromEvent(
2291
- event,
2292
- ATTRIBUTE_ROLE,
2293
- "remove-badge",
2294
- );
2295
- if (element instanceof HTMLElement) {
2296
- return;
2297
- }
2298
-
2299
- toggle.call(self);
2300
- }
2301
- }
2302
- }
2303
- });
2304
-
2305
- // badge, selection
2306
- self.addEventListener(type, self[clearOptionEventHandler]);
2307
- }
2308
-
2309
- self.addEventListener("monster-change", self[changeEventHandler]);
2310
- self.addEventListener("input", self[inputEventHandler]);
2311
- self.addEventListener("keydown", self[keyEventHandler]);
2312
-
2313
- return self;
2228
+ const self = this;
2229
+
2230
+ /**
2231
+ * @param {Event} event
2232
+ */
2233
+ self[clearOptionEventHandler] = (event) => {
2234
+ const element = findTargetElementFromEvent(
2235
+ event,
2236
+ ATTRIBUTE_ROLE,
2237
+ "remove-badge",
2238
+ );
2239
+
2240
+ if (element instanceof HTMLElement) {
2241
+ const badge = findClosestByAttribute(element, ATTRIBUTE_ROLE, "badge");
2242
+ if (badge instanceof HTMLElement) {
2243
+ const value = badge.getAttribute(`${ATTRIBUTE_PREFIX}value`);
2244
+
2245
+ let selection = self.getOption("selection");
2246
+ selection = selection.filter((b) => {
2247
+ return value !== b.value;
2248
+ });
2249
+
2250
+ setSelection
2251
+ .call(self, selection)
2252
+ .then(() => {
2253
+ fireCustomEvent(self, "monster-selection-removed", {
2254
+ value,
2255
+ });
2256
+ })
2257
+ .catch((e) => {
2258
+ addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.message);
2259
+ });
2260
+ }
2261
+ }
2262
+ };
2263
+
2264
+ /**
2265
+ * @param {Event} event
2266
+ */
2267
+ self[closeEventHandler] = (event) => {
2268
+ const path = event.composedPath();
2269
+
2270
+ for (const [, element] of Object.entries(path)) {
2271
+ if (element === self) {
2272
+ return;
2273
+ }
2274
+ }
2275
+ hide.call(self);
2276
+ };
2277
+
2278
+ /**
2279
+ * @param {Event} event
2280
+ */
2281
+ self[inputEventHandler] = (event) => {
2282
+ const path = event.composedPath();
2283
+ const element = path?.[0];
2284
+
2285
+ if (element instanceof HTMLElement) {
2286
+ if (
2287
+ element.hasAttribute(ATTRIBUTE_ROLE) &&
2288
+ element.getAttribute(ATTRIBUTE_ROLE) === "option-control"
2289
+ ) {
2290
+ fireCustomEvent(self, "monster-change", {
2291
+ type: event.type,
2292
+ value: element.value,
2293
+ checked: element.checked,
2294
+ });
2295
+ } else if (
2296
+ element.hasAttribute(ATTRIBUTE_ROLE) &&
2297
+ element.getAttribute(ATTRIBUTE_ROLE) === "filter"
2298
+ ) {
2299
+ }
2300
+ }
2301
+ };
2302
+
2303
+ /**
2304
+ * @param {Event} event
2305
+ */
2306
+ self[changeEventHandler] = (event) => {
2307
+ gatherState.call(self);
2308
+ fireCustomEvent(self, "monster-changed", event?.detail);
2309
+ };
2310
+
2311
+ self[keyEventHandler] = (event) => {
2312
+ const path = event.composedPath();
2313
+ const element = path.shift();
2314
+
2315
+ let role;
2316
+
2317
+ if (element instanceof HTMLElement) {
2318
+ if (element.hasAttribute(ATTRIBUTE_ROLE)) {
2319
+ role = element.getAttribute(ATTRIBUTE_ROLE);
2320
+ } else if (element === this) {
2321
+ show.call(this);
2322
+ // focusFilter.call(self);
2323
+ } else {
2324
+ const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
2325
+ if (e instanceof HTMLElement && e.hasAttribute(ATTRIBUTE_ROLE)) {
2326
+ role = e.getAttribute(ATTRIBUTE_ROLE);
2327
+ }
2328
+ }
2329
+ } else {
2330
+ return;
2331
+ }
2332
+
2333
+ switch (role) {
2334
+ case "filter":
2335
+ handleFilterKeyboardEvents.call(self, event);
2336
+ break;
2337
+ case "option-label":
2338
+ case "option-control":
2339
+ case "option":
2340
+ handleOptionKeyboardEvents.call(self, event);
2341
+ break;
2342
+ case "control":
2343
+ case "toggle":
2344
+ handleToggleKeyboardEvents.call(self, event);
2345
+ break;
2346
+ }
2347
+ };
2348
+
2349
+ const types = self.getOption("toggleEventType", ["click"]);
2350
+
2351
+ for (const [, type] of Object.entries(types)) {
2352
+ self[controlElementSymbol]
2353
+ .querySelector(`[${ATTRIBUTE_ROLE}="container"]`)
2354
+ .addEventListener(type, function (event) {
2355
+ const element = findTargetElementFromEvent(
2356
+ event,
2357
+ ATTRIBUTE_ROLE,
2358
+ "remove-badge",
2359
+ );
2360
+ if (element instanceof HTMLElement) {
2361
+ return;
2362
+ }
2363
+
2364
+ toggle.call(self);
2365
+ });
2366
+
2367
+ self[controlElementSymbol]
2368
+ .querySelector(`[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`)
2369
+ .addEventListener(type, function (event) {
2370
+ if (self.getOption("disabled", undefined) === true) {
2371
+ return;
2372
+ }
2373
+
2374
+ const path = event.composedPath();
2375
+ const element = path?.[0];
2376
+ if (element instanceof HTMLElement) {
2377
+ const control = element.closest(
2378
+ `[${ATTRIBUTE_ROLE}="status-or-remove-badges"]`,
2379
+ );
2380
+ if (control instanceof HTMLElement) {
2381
+ if (control.classList.contains("clear")) {
2382
+ clearSelection.call(self);
2383
+
2384
+ fireCustomEvent(self, "monster-selection-cleared", {});
2385
+ } else {
2386
+ const element = findTargetElementFromEvent(
2387
+ event,
2388
+ ATTRIBUTE_ROLE,
2389
+ "remove-badge",
2390
+ );
2391
+ if (element instanceof HTMLElement) {
2392
+ return;
2393
+ }
2394
+
2395
+ toggle.call(self);
2396
+ }
2397
+ }
2398
+ }
2399
+ });
2400
+
2401
+ // badge, selection
2402
+ self.addEventListener(type, self[clearOptionEventHandler]);
2403
+ }
2404
+
2405
+ self.addEventListener("monster-change", self[changeEventHandler]);
2406
+ self.addEventListener("input", self[inputEventHandler]);
2407
+ self.addEventListener("keydown", self[keyEventHandler]);
2408
+
2409
+ return self;
2314
2410
  }
2315
2411
 
2316
2412
  /**
@@ -2318,64 +2414,72 @@ function initEventHandler() {
2318
2414
  * @return {Select}
2319
2415
  */
2320
2416
  function setStatusOrRemoveBadges(suggestion) {
2321
- setTimeout(() => {
2322
- const selection = this.getOption("selection");
2323
-
2324
- const clearAllFlag =
2325
- isArray(selection) &&
2326
- selection.length > 0 &&
2327
- this.getOption("features.clearAll") === true;
2328
-
2329
- const current = this.getOption("classes.statusOrRemoveBadge");
2330
-
2331
- if (this[isLoadingSymbol] === true) {
2332
- if (current !== "loading") {
2333
- this.setOption("classes.statusOrRemoveBadge", "loading");
2334
- }
2335
- return;
2336
- }
2337
-
2338
- if (suggestion === "loading") {
2339
- this[isLoadingSymbol] = true;
2340
- if (current !== "loading") {
2341
- this.setOption("classes.statusOrRemoveBadge", "loading");
2342
- }
2343
- return;
2344
- }
2345
-
2346
- if (clearAllFlag) {
2347
- if (current !== "clear") {
2348
- this.setOption("classes.statusOrRemoveBadge", "clear");
2349
- }
2350
- return;
2351
- }
2352
-
2353
- if (this[controlElementSymbol].classList.contains("open")) {
2354
- if (current !== "open") {
2355
- this.setOption("classes.statusOrRemoveBadge", "open");
2356
- }
2357
- return;
2358
- }
2359
-
2360
- const options = this.getOption("options");
2361
- if (
2362
- options === undefined ||
2363
- options === null ||
2364
- (isArray(options) && options.length === 0)
2365
- ) {
2366
- if (current !== "empty") {
2367
- this.setOption("classes.statusOrRemoveBadge", "empty");
2368
- }
2369
- return;
2370
- }
2371
-
2372
- if (suggestion) {
2373
- if (current !== suggestion) {
2374
- this.setOption("classes.statusOrRemoveBadge", suggestion);
2375
- }
2376
- return;
2377
- }
2378
- }, 2);
2417
+
2418
+ requestAnimationFrame(() => {
2419
+ const selection = this.getOption("selection");
2420
+
2421
+ const clearAllFlag =
2422
+ isArray(selection) &&
2423
+ selection.length > 0 &&
2424
+ this.getOption("features.clearAll") === true;
2425
+
2426
+ const current = this.getOption("classes.statusOrRemoveBadge");
2427
+
2428
+ if (suggestion === "error") {
2429
+ if (current !== "error") {
2430
+ this.setOption("classes.statusOrRemoveBadge", "error");
2431
+ }
2432
+ return;
2433
+ }
2434
+
2435
+ if (this[isLoadingSymbol] === true) {
2436
+ if (current !== "loading") {
2437
+ this.setOption("classes.statusOrRemoveBadge", "loading");
2438
+ }
2439
+ return;
2440
+ }
2441
+
2442
+ if (suggestion === "loading") {
2443
+ if (current !== "loading") {
2444
+ this.setOption("classes.statusOrRemoveBadge", "loading");
2445
+ }
2446
+ return;
2447
+ }
2448
+
2449
+ if (clearAllFlag) {
2450
+ if (current !== "clear") {
2451
+ this.setOption("classes.statusOrRemoveBadge", "clear");
2452
+ }
2453
+ return;
2454
+ }
2455
+
2456
+ if (this[controlElementSymbol].classList.contains("open")) {
2457
+ if (current !== "open") {
2458
+ this.setOption("classes.statusOrRemoveBadge", "open");
2459
+ }
2460
+ return;
2461
+ }
2462
+
2463
+ const options = this.getOption("options");
2464
+ if (
2465
+ options === undefined ||
2466
+ options === null ||
2467
+ (isArray(options) && options.length === 0)
2468
+ ) {
2469
+ if (current !== "empty") {
2470
+ this.setOption("classes.statusOrRemoveBadge", "empty");
2471
+ }
2472
+ return;
2473
+ }
2474
+
2475
+ if (suggestion) {
2476
+ if (current !== suggestion) {
2477
+ this.setOption("classes.statusOrRemoveBadge", suggestion);
2478
+ }
2479
+ return;
2480
+ }
2481
+ })
2482
+
2379
2483
  }
2380
2484
 
2381
2485
  /**
@@ -2384,68 +2488,68 @@ function setStatusOrRemoveBadges(suggestion) {
2384
2488
  * @throws {Error} no shadow-root is defined
2385
2489
  */
2386
2490
  function initControlReferences() {
2387
- if (!this.shadowRoot) {
2388
- throw new Error("no shadow-root is defined");
2389
- }
2390
-
2391
- this[controlElementSymbol] = this.shadowRoot.querySelector(
2392
- `[${ATTRIBUTE_ROLE}=control]`,
2393
- );
2394
- this[selectionElementSymbol] = this.shadowRoot.querySelector(
2395
- `[${ATTRIBUTE_ROLE}=selection]`,
2396
- );
2397
- this[containerElementSymbol] = this.shadowRoot.querySelector(
2398
- `[${ATTRIBUTE_ROLE}=container]`,
2399
- );
2400
- this[popperElementSymbol] = this.shadowRoot.querySelector(
2401
- `[${ATTRIBUTE_ROLE}=popper]`,
2402
- );
2403
- this[inlineFilterElementSymbol] = this.shadowRoot.querySelector(
2404
- `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`,
2405
- );
2406
- this[popperFilterElementSymbol] = this.shadowRoot.querySelector(
2407
- `[${ATTRIBUTE_ROLE}=filter][name="popper-filter"]`,
2408
- );
2409
- this[popperFilterContainerElementSymbol] =
2410
- this[popperFilterElementSymbol].parentElement;
2411
- this[optionsElementSymbol] = this.shadowRoot.querySelector(
2412
- `[${ATTRIBUTE_ROLE}=options]`,
2413
- );
2414
- this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector(
2415
- `[${ATTRIBUTE_ROLE}="no-options"]`,
2416
- );
2417
- this[statusOrRemoveBadgesElementSymbol] = this.shadowRoot.querySelector(
2418
- `[${ATTRIBUTE_ROLE}=status-or-remove-badges]`,
2419
- );
2491
+ if (!this.shadowRoot) {
2492
+ throw new Error("no shadow-root is defined");
2493
+ }
2494
+
2495
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
2496
+ `[${ATTRIBUTE_ROLE}=control]`,
2497
+ );
2498
+ this[selectionElementSymbol] = this.shadowRoot.querySelector(
2499
+ `[${ATTRIBUTE_ROLE}=selection]`,
2500
+ );
2501
+ this[containerElementSymbol] = this.shadowRoot.querySelector(
2502
+ `[${ATTRIBUTE_ROLE}=container]`,
2503
+ );
2504
+ this[popperElementSymbol] = this.shadowRoot.querySelector(
2505
+ `[${ATTRIBUTE_ROLE}=popper]`,
2506
+ );
2507
+ this[inlineFilterElementSymbol] = this.shadowRoot.querySelector(
2508
+ `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`,
2509
+ );
2510
+ this[popperFilterElementSymbol] = this.shadowRoot.querySelector(
2511
+ `[${ATTRIBUTE_ROLE}=filter][name="popper-filter"]`,
2512
+ );
2513
+ this[popperFilterContainerElementSymbol] =
2514
+ this[popperFilterElementSymbol].parentElement;
2515
+ this[optionsElementSymbol] = this.shadowRoot.querySelector(
2516
+ `[${ATTRIBUTE_ROLE}=options]`,
2517
+ );
2518
+ this[noOptionsAvailableElementSymbol] = this.shadowRoot.querySelector(
2519
+ `[${ATTRIBUTE_ROLE}="no-options"]`,
2520
+ );
2521
+ this[statusOrRemoveBadgesElementSymbol] = this.shadowRoot.querySelector(
2522
+ `[${ATTRIBUTE_ROLE}=status-or-remove-badges]`,
2523
+ );
2420
2524
  }
2421
2525
 
2422
2526
  /**
2423
2527
  * @private
2424
2528
  */
2425
2529
  function updatePopper() {
2426
- if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
2427
- return;
2428
- }
2429
-
2430
- if (this.getOption("disabled", false) === true) {
2431
- return;
2432
- }
2433
-
2434
- new Processing(() => {
2435
- calcAndSetOptionsDimension.call(this);
2436
- positionPopper.call(
2437
- this,
2438
- this[controlElementSymbol],
2439
- this[popperElementSymbol],
2440
- this.getOption("popper", {}),
2441
- );
2442
- })
2443
- .run()
2444
- .catch((e) => {
2445
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2446
- });
2447
-
2448
- return this;
2530
+ if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
2531
+ return;
2532
+ }
2533
+
2534
+ if (this.getOption("disabled", false) === true) {
2535
+ return;
2536
+ }
2537
+
2538
+ new Processing(() => {
2539
+ calcAndSetOptionsDimension.call(this);
2540
+ positionPopper.call(
2541
+ this,
2542
+ this[controlElementSymbol],
2543
+ this[popperElementSymbol],
2544
+ this.getOption("popper", {}),
2545
+ );
2546
+ })
2547
+ .run()
2548
+ .catch((e) => {
2549
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
2550
+ });
2551
+
2552
+ return this;
2449
2553
  }
2450
2554
 
2451
2555
  /**
@@ -2453,8 +2557,8 @@ function updatePopper() {
2453
2557
  * @return {string}
2454
2558
  */
2455
2559
  function getTemplate() {
2456
- // language=HTML
2457
- return `
2560
+ // language=HTML
2561
+ return `
2458
2562
  <template id="options">
2459
2563
  <div data-monster-role="option" tabindex="-1"
2460
2564
  data-monster-attributes="