@schukai/monster 3.78.0 → 3.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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="