@schukai/monster 3.56.0 → 3.57.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/example/components/form/toggle-switch.mjs +7 -0
  3. package/package.json +1 -1
  4. package/source/components/datatable/change-button.mjs +5 -1
  5. package/source/components/form/button.mjs +3 -3
  6. package/source/components/form/select.mjs +1773 -1745
  7. package/source/components/form/style/select.pcss +1 -1
  8. package/source/components/form/style/toggle-switch.pcss +74 -0
  9. package/source/components/form/stylesheet/select.mjs +1 -1
  10. package/source/components/form/stylesheet/toggle-switch.mjs +27 -0
  11. package/source/components/form/toggle-switch.mjs +427 -0
  12. package/source/data/transformer.mjs +33 -1
  13. package/source/dom/attributes.mjs +1 -1
  14. package/source/dom/customcontrol.mjs +6 -2
  15. package/source/dom/customelement.mjs +909 -864
  16. package/source/dom/updater.mjs +754 -732
  17. package/source/dom/util/set-option-from-attribute.mjs +2 -1
  18. package/source/monster.mjs +5 -0
  19. package/source/types/version.mjs +1 -1
  20. package/test/cases/components/form/select.mjs +1 -1
  21. package/test/cases/components/form/toggle-switch.mjs +310 -0
  22. package/test/cases/components/form/tree-select.mjs +1 -8
  23. package/test/cases/data/transformer.mjs +16 -0
  24. package/test/cases/dom/customcontrol.mjs +53 -8
  25. package/test/cases/dom/customelement-initfromscripthost.mjs +0 -4
  26. package/test/cases/dom/customelement.mjs +30 -26
  27. package/test/cases/dom/updater.mjs +14 -3
  28. package/test/cases/monster.mjs +1 -1
  29. package/test/util/jsdom.mjs +9 -10
  30. package/test/web/test.html +2 -2
  31. package/test/web/tests.js +3044 -1023
@@ -5,63 +5,62 @@
5
5
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
6
  */
7
7
 
8
- import { findElementWithIdUpwards } from "./util.mjs";
9
- import { internalSymbol } from "../constants.mjs";
10
- import { extend } from "../data/extend.mjs";
11
- import { Pathfinder } from "../data/pathfinder.mjs";
12
- import { Formatter } from "../text/formatter.mjs";
13
-
14
- import { parseDataURL } from "../types/dataurl.mjs";
15
- import { getGlobalObject } from "../types/global.mjs";
8
+ import {findElementWithIdUpwards} from "./util.mjs";
9
+ import {internalSymbol} from "../constants.mjs";
10
+ import {extend} from "../data/extend.mjs";
11
+ import {Pathfinder} from "../data/pathfinder.mjs";
12
+ import {Formatter} from "../text/formatter.mjs";
13
+
14
+ import {parseDataURL} from "../types/dataurl.mjs";
15
+ import {getGlobalObject} from "../types/global.mjs";
16
16
  import {
17
- isArray,
18
- isFunction,
19
- isIterable,
20
- isObject,
21
- isString,
17
+ isArray,
18
+ isFunction,
19
+ isIterable,
20
+ isObject,
21
+ isString,
22
22
  } from "../types/is.mjs";
23
- import { Observer } from "../types/observer.mjs";
24
- import { ProxyObserver } from "../types/proxyobserver.mjs";
23
+ import {Observer} from "../types/observer.mjs";
24
+ import {ProxyObserver} from "../types/proxyobserver.mjs";
25
25
  import {
26
- validateFunction,
27
- validateInstance,
28
- validateObject,
29
- validateString,
26
+ validateFunction,
27
+ validateInstance,
28
+ validateObject,
30
29
  } from "../types/validate.mjs";
31
- import { clone } from "../util/clone.mjs";
30
+ import {clone} from "../util/clone.mjs";
32
31
  import {
33
- addAttributeToken,
34
- getLinkedObjects,
35
- hasObjectLink,
32
+ addAttributeToken,
33
+ getLinkedObjects,
34
+ hasObjectLink,
36
35
  } from "./attributes.mjs";
37
36
  import {
38
- ATTRIBUTE_DISABLED,
39
- ATTRIBUTE_ERRORMESSAGE,
40
- ATTRIBUTE_OPTIONS,
41
- ATTRIBUTE_INIT_CALLBACK,
42
- ATTRIBUTE_OPTIONS_SELECTOR,
43
- ATTRIBUTE_SCRIPT_HOST,
44
- customElementUpdaterLinkSymbol,
45
- initControlCallbackName,
37
+ ATTRIBUTE_DISABLED,
38
+ ATTRIBUTE_ERRORMESSAGE,
39
+ ATTRIBUTE_OPTIONS,
40
+ ATTRIBUTE_INIT_CALLBACK,
41
+ ATTRIBUTE_OPTIONS_SELECTOR,
42
+ ATTRIBUTE_SCRIPT_HOST,
43
+ customElementUpdaterLinkSymbol,
44
+ initControlCallbackName,
46
45
  } from "./constants.mjs";
47
- import { findDocumentTemplate, Template } from "./template.mjs";
48
- import { addObjectWithUpdaterToElement } from "./updater.mjs";
49
- import { instanceSymbol } from "../constants.mjs";
46
+ import {findDocumentTemplate, Template} from "./template.mjs";
47
+ import {addObjectWithUpdaterToElement} from "./updater.mjs";
48
+ import {instanceSymbol} from "../constants.mjs";
50
49
  import {
51
- getDocumentTranslations,
52
- Translations,
50
+ getDocumentTranslations,
53
51
  } from "../i18n/translations.mjs";
54
- import { getSlottedElements } from "./slotted.mjs";
55
- import { initOptionsFromAttributes } from "./util/init-options-from-attributes.mjs";
56
- import { setOptionFromAttribute } from "./util/set-option-from-attribute.mjs";
52
+ import {getSlottedElements} from "./slotted.mjs";
53
+ import {initOptionsFromAttributes} from "./util/init-options-from-attributes.mjs";
54
+ import {setOptionFromAttribute} from "./util/set-option-from-attribute.mjs";
57
55
 
58
56
  export {
59
- CustomElement,
60
- initMethodSymbol,
61
- assembleMethodSymbol,
62
- attributeObserverSymbol,
63
- registerCustomElement,
64
- getSlottedElements,
57
+ CustomElement,
58
+ initMethodSymbol,
59
+ assembleMethodSymbol,
60
+ attributeObserverSymbol,
61
+ registerCustomElement,
62
+ getSlottedElements,
63
+ updaterTransformerMethodsSymbol
65
64
  };
66
65
 
67
66
  /**
@@ -75,7 +74,15 @@ const initMethodSymbol = Symbol.for("@schukai/monster/dom/@@initMethodSymbol");
75
74
  * @type {symbol}
76
75
  */
77
76
  const assembleMethodSymbol = Symbol.for(
78
- "@schukai/monster/dom/@@assembleMethodSymbol",
77
+ "@schukai/monster/dom/@@assembleMethodSymbol",
78
+ );
79
+
80
+ /**
81
+ * @memberOf Monster.DOM
82
+ * @type {symbol}
83
+ */
84
+ const updaterTransformerMethodsSymbol = Symbol.for(
85
+ "@schukai/monster/dom/@@updaterTransformerMethodsSymbol",
79
86
  );
80
87
 
81
88
  /**
@@ -84,7 +91,7 @@ const assembleMethodSymbol = Symbol.for(
84
91
  * @type {symbol}
85
92
  */
86
93
  const attributeObserverSymbol = Symbol.for(
87
- "@schukai/monster/dom/@@attributeObserver",
94
+ "@schukai/monster/dom/@@attributeObserver",
88
95
  );
89
96
 
90
97
  /**
@@ -92,7 +99,7 @@ const attributeObserverSymbol = Symbol.for(
92
99
  * @type {symbol}
93
100
  */
94
101
  const attributeMutationObserverSymbol = Symbol(
95
- "@schukai/monster/dom/@@mutationObserver",
102
+ "@schukai/monster/dom/@@mutationObserver",
96
103
  );
97
104
 
98
105
  /**
@@ -231,480 +238,517 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
231
238
  * @summary A base class for HTML5 custom controls.
232
239
  */
233
240
  class CustomElement extends HTMLElement {
234
- /**
235
- * A new object is created. First the `initOptions` method is called. Here the
236
- * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
237
- *
238
- * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
239
- *
240
- * @throws {Error} the options attribute does not contain a valid json definition.
241
- * @since 1.7.0
242
- */
243
- constructor() {
244
- super();
245
-
246
- this[attributeObserverSymbol] = {};
247
- this[internalSymbol] = new ProxyObserver({
248
- options: initOptionsFromAttributes(this, extend({}, this.defaults)),
249
- });
250
- this[initMethodSymbol]();
251
- initOptionObserver.call(this);
252
- this[scriptHostElementSymbol] = [];
253
- }
254
-
255
- /**
256
- * This method is called by the `instanceof` operator.
257
- * @returns {symbol}
258
- * @since 2.1.0
259
- */
260
- static get [instanceSymbol]() {
261
- return Symbol.for("@schukai/monster/dom/custom-element@@instance");
262
- }
263
-
264
- /**
265
- * This method determines which attributes are to be
266
- * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
267
- * Therefore, the `observedAttributes` property cannot be changed during runtime.
268
- *
269
- * @return {string[]}
270
- * @since 1.15.0
271
- */
272
- static get observedAttributes() {
273
- return [];
274
- }
275
-
276
- /**
277
- *
278
- * @param attribute
279
- * @param callback
280
- * @returns {Monster.DOM.CustomElement}
281
- */
282
- addAttributeObserver(attribute, callback) {
283
- validateFunction(callback);
284
- this[attributeObserverSymbol][attribute] = callback;
285
- return this;
286
- }
287
-
288
- /**
289
- *
290
- * @param attribute
291
- * @returns {Monster.DOM.CustomElement}
292
- */
293
- removeAttributeObserver(attribute) {
294
- delete this[attributeObserverSymbol][attribute];
295
- return this;
296
- }
297
-
298
- /**
299
- * The `defaults` property defines the default values for a control. If you want to override these,
300
- * you can use various methods, which are described in the documentation available at
301
- * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
302
- *
303
- * The individual configuration values are listed below:
304
- *
305
- * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
306
- * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
307
- *
308
- * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
309
- *
310
- * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).
311
- *
312
- * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form.
313
- * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it.
314
- * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
315
- * @property {Object} templates Specifies the templates used by the control.
316
- * @property {string} templates.main=undefined Specifies the main template used by the control.
317
- * @property {Object} templateMapping Specifies the mapping of templates.
318
- * @since 1.8.0
319
- */
320
- get defaults() {
321
- return {
322
- disabled: false,
323
- shadowMode: "open",
324
- delegatesFocus: true,
325
- templates: {
326
- main: undefined,
327
- },
328
- templateMapping: {},
329
- };
330
- }
331
-
332
- /**
333
- * This method updates the labels of the element.
334
- * The labels are defined in the options object.
335
- * The key of the label is used to retrieve the translation from the document.
336
- * If the translation is different from the label, the label is updated.
337
- *
338
- * Before you can use this method, you must have loaded the translations.
339
- *
340
- * @returns {Monster.DOM.CustomElement}
341
- * @throws {Error} Cannot find element with translations. Add a translations object to the document.
342
- */
343
- updateI18n() {
344
- const translations = getDocumentTranslations();
345
- if (!translations) {
346
- return this;
347
- }
348
-
349
- const labels = this.getOption("labels");
350
- if (!(isObject(labels) || isIterable(labels))) {
351
- return this;
352
- }
353
-
354
- for (const key in labels) {
355
- const def = labels[key];
356
-
357
- if (isString(def)) {
358
- const text = translations.getText(key, def);
359
- if (text !== def) {
360
- this.setOption(`labels.${key}`, text);
361
- }
362
- continue;
363
- } else if (isObject(def)) {
364
- for (const k in def) {
365
- const d = def[k];
366
-
367
- const text = translations.getPluralRuleText(key, k, d);
368
- if (!isString(text)) {
369
- throw new Error("Invalid labels definition");
370
- }
371
- if (text !== d) {
372
- this.setOption(`labels.${key}.${k}`, text);
373
- }
374
- }
375
- continue;
376
- }
377
-
378
- throw new Error("Invalid labels definition");
379
- }
380
- return this;
381
- }
382
-
383
- /**
384
- * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
385
- * by the derived class.
386
- *
387
- * Note that there is no check on the name of the tag in this class. It is the responsibility of
388
- * the developer to assign an appropriate tag name. If the name is not valid, the
389
- * `registerCustomElement()` method will issue an error.
390
- *
391
- * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
392
- * @throws {Error} This method must be overridden by the derived class.
393
- * @return {string} The tag name associated with the custom element.
394
- * @since 1.7.0
395
- */
396
- static getTag() {
397
- throw new Error(
398
- "The method `getTag()` must be overridden by the derived class.",
399
- );
400
- }
401
-
402
- /**
403
- * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
404
- * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
405
- *
406
- * If `undefined` is returned, then the shadow root does not receive a stylesheet.
407
- *
408
- * Example usage:
409
- *
410
- * ```js
411
- * static getCSSStyleSheet() {
412
- * const sheet = new CSSStyleSheet();
413
- * sheet.replaceSync("p { color: red; }");
414
- * return sheet;
415
- * }
416
- * ```
417
- *
418
- * If the environment does not support the `CSSStyleSheet` constructor,
419
- * you can use the following workaround to create the stylesheet:
420
- *
421
- * ```js
422
- * const doc = document.implementation.createHTMLDocument('title');
423
- * let style = doc.createElement("style");
424
- * style.innerHTML = "p { color: red; }";
425
- * style.appendChild(document.createTextNode(""));
426
- * doc.head.appendChild(style);
427
- * return doc.styleSheets[0];
428
- * ```
429
- *
430
- * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied.
431
- */
432
- static getCSSStyleSheet() {
433
- return undefined;
434
- }
435
-
436
- /**
437
- * attach a new observer
438
- *
439
- * @param {Observer} observer
440
- * @returns {CustomElement}
441
- */
442
- attachObserver(observer) {
443
- this[internalSymbol].attachObserver(observer);
444
- return this;
445
- }
446
-
447
- /**
448
- * detach a observer
449
- *
450
- * @param {Observer} observer
451
- * @returns {CustomElement}
452
- */
453
- detachObserver(observer) {
454
- this[internalSymbol].detachObserver(observer);
455
- return this;
456
- }
457
-
458
- /**
459
- * @param {Observer} observer
460
- * @returns {ProxyObserver}
461
- */
462
- containsObserver(observer) {
463
- return this[internalSymbol].containsObserver(observer);
464
- }
465
-
466
- /**
467
- * nested options can be specified by path `a.b.c`
468
- *
469
- * @param {string} path
470
- * @param {*} defaultValue
471
- * @return {*}
472
- * @since 1.10.0
473
- */
474
- getOption(path, defaultValue = undefined) {
475
- let value;
476
-
477
- try {
478
- value = new Pathfinder(
479
- this[internalSymbol].getRealSubject()["options"],
480
- ).getVia(path);
481
- } catch (e) {}
482
-
483
- if (value === undefined) return defaultValue;
484
- return value;
485
- }
486
-
487
- /**
488
- * Set option and inform elements
489
- *
490
- * @param {string} path
491
- * @param {*} value
492
- * @return {CustomElement}
493
- * @since 1.14.0
494
- */
495
- setOption(path, value) {
496
- new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
497
- path,
498
- value,
499
- );
500
- return this;
501
- }
502
-
503
- /**
504
- * @since 1.15.0
505
- * @param {string|object} options
506
- * @return {CustomElement}
507
- */
508
- setOptions(options) {
509
- if (isString(options)) {
510
- options = parseOptionsJSON.call(this, options);
511
- }
512
- // 2024-01-21: remove this.defaults, otherwise it will overwrite
513
- // the current settings that have already been made.
514
- // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
515
- extend(this[internalSymbol].getSubject()["options"], options);
516
-
517
- return this;
518
- }
519
-
520
- /**
521
- * Is called once via the constructor
522
- *
523
- * @return {CustomElement}
524
- * @since 1.8.0
525
- */
526
- [initMethodSymbol]() {
527
- return this;
528
- }
529
-
530
- /**
531
- * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
532
- * 1. Extracts the options from the attributes and the script tag of the element and sets them.
533
- * 2. Initializes the shadow root and its CSS stylesheet (if specified).
534
- * 3. Initializes the HTML content of the element.
535
- * 4. Initializes the custom elements inside the shadow root and the slotted elements.
536
- * 5. Attaches a mutation observer to observe changes to the attributes of the element.
537
- *
538
- * @return {CustomElement} - The updated custom element.
539
- * @since 1.8.0
540
- */
541
- [assembleMethodSymbol]() {
542
- let elements;
543
- let nodeList;
544
-
545
- // Extract options from attributes and set them
546
- const AttributeOptions = getOptionsFromAttributes.call(this);
547
- if (
548
- isObject(AttributeOptions) &&
549
- Object.keys(AttributeOptions).length > 0
550
- ) {
551
- this.setOptions(AttributeOptions);
552
- }
553
-
554
- // Extract options from script tag and set them
555
- const ScriptOptions = getOptionsFromScriptTag.call(this);
556
- if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
557
- this.setOptions(ScriptOptions);
558
- }
559
-
560
- // Initialize the shadow root and its CSS stylesheet
561
- if (this.getOption("shadowMode", false) !== false) {
562
- try {
563
- initShadowRoot.call(this);
564
- elements = this.shadowRoot.childNodes;
565
- } catch (e) {
566
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
567
- }
568
-
569
- try {
570
- initCSSStylesheet.call(this);
571
- } catch (e) {
572
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
573
- }
574
- }
575
-
576
- // If the elements are not found inside the shadow root, initialize the HTML content of the element
577
- if (!(elements instanceof NodeList)) {
578
- initHtmlContent.call(this);
579
- elements = this.childNodes;
580
- }
581
-
582
- // Initialize the custom elements inside the shadow root and the slotted elements
583
- initFromCallbackHost.call(this);
584
- try {
585
- nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
586
- } catch (e) {
587
- nodeList = elements;
588
- }
589
-
590
- this[updateCloneDataSymbol] = clone(
591
- this[internalSymbol].getRealSubject()["options"],
592
- );
593
-
594
- addObjectWithUpdaterToElement.call(
595
- this,
596
- nodeList,
597
- customElementUpdaterLinkSymbol,
598
- this[updateCloneDataSymbol],
599
- );
600
-
601
- // Attach a mutation observer to observe changes to the attributes of the element
602
- attachAttributeChangeMutationObserver.call(this);
603
-
604
- return this;
605
- }
606
-
607
- /**
608
- * You know what you are doing? This function is only for advanced users.
609
- * The result is a clone of the internal data.
610
- *
611
- * @returns {*}
612
- */
613
- getInternalUpdateCloneData() {
614
- return clone(this[updateCloneDataSymbol]);
615
- }
616
-
617
- /**
618
- * This method is called every time the element is inserted into the DOM. It checks if the custom element
619
- * has already been initialized and if not, calls the assembleMethod to initialize it.
620
- *
621
- * @return {void}
622
- * @since 1.7.0
623
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
624
- */
625
- connectedCallback() {
626
- // Check if the object has already been initialized
627
- if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
628
- // If not, call the assembleMethod to initialize the object
629
- this[assembleMethodSymbol]();
630
- }
631
- }
632
-
633
- /**
634
- * Called every time the element is removed from the DOM. Useful for running clean up code.
635
- *
636
- * @return {void}
637
- * @since 1.7.0
638
- */
639
- disconnectedCallback() {}
640
-
641
- /**
642
- * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
643
- *
644
- * @return {void}
645
- * @since 1.7.0
646
- */
647
- adoptedCallback() {}
648
-
649
- /**
650
- * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
651
- * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
652
- * property will receive this callback.
653
- *
654
- * @param {string} attrName
655
- * @param {string} oldVal
656
- * @param {string} newVal
657
- * @return {void}
658
- * @since 1.15.0
659
- */
660
- attributeChangedCallback(attrName, oldVal, newVal) {
661
- if (attrName.startsWith("data-monster-option-")) {
662
- setOptionFromAttribute(
663
- this,
664
- attrName,
665
- this[internalSymbol].getSubject()["options"],
666
- );
667
- }
668
-
669
- const callback = this[attributeObserverSymbol]?.[attrName];
670
- if (isFunction(callback)) {
671
- try {
672
- callback.call(this, newVal, oldVal);
673
- } catch (e) {
674
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
675
- }
676
- }
677
- }
678
-
679
- /**
680
- *
681
- * @param {Node} node
682
- * @return {boolean}
683
- * @throws {TypeError} value is not an instance of
684
- * @since 1.19.0
685
- */
686
- hasNode(node) {
687
- if (containChildNode.call(this, validateInstance(node, Node))) {
688
- return true;
689
- }
690
-
691
- if (!(this.shadowRoot instanceof ShadowRoot)) {
692
- return false;
693
- }
694
-
695
- return containChildNode.call(this.shadowRoot, node);
696
- }
697
-
698
- /**
699
- * Calls a callback function if it exists.
700
- *
701
- * @param {string} name
702
- * @param {*} args
703
- * @returns {*}
704
- */
705
- callCallback(name, args) {
706
- return callControlCallback.call(this, name, ...args);
707
- }
241
+ /**
242
+ * A new object is created. First the `initOptions` method is called. Here the
243
+ * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
244
+ *
245
+ * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
246
+ *
247
+ * @throws {Error} the options attribute does not contain a valid json definition.
248
+ * @since 1.7.0
249
+ */
250
+ constructor() {
251
+ super();
252
+
253
+ this[attributeObserverSymbol] = {};
254
+ this[internalSymbol] = new ProxyObserver({
255
+ options: initOptionsFromAttributes(this, extend({}, this.defaults)),
256
+ });
257
+ this[initMethodSymbol]();
258
+ initOptionObserver.call(this);
259
+ this[scriptHostElementSymbol] = [];
260
+ }
261
+
262
+ /**
263
+ * This method is called by the `instanceof` operator.
264
+ * @returns {symbol}
265
+ * @since 2.1.0
266
+ */
267
+ static get [instanceSymbol]() {
268
+ return Symbol.for("@schukai/monster/dom/custom-element@@instance");
269
+ }
270
+
271
+ /**
272
+ * This method determines which attributes are to be
273
+ * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
274
+ * Therefore, the `observedAttributes` property cannot be changed during runtime.
275
+ *
276
+ * @return {string[]}
277
+ * @since 1.15.0
278
+ */
279
+ static get observedAttributes() {
280
+ return [];
281
+ }
282
+
283
+ /**
284
+ *
285
+ * @param attribute
286
+ * @param callback
287
+ * @returns {Monster.DOM.CustomElement}
288
+ */
289
+ addAttributeObserver(attribute, callback) {
290
+ validateFunction(callback);
291
+ this[attributeObserverSymbol][attribute] = callback;
292
+ return this;
293
+ }
294
+
295
+ /**
296
+ *
297
+ * @param attribute
298
+ * @returns {Monster.DOM.CustomElement}
299
+ */
300
+ removeAttributeObserver(attribute) {
301
+ delete this[attributeObserverSymbol][attribute];
302
+ return this;
303
+ }
304
+
305
+ /**
306
+ * The `defaults` property defines the default values for a control. If you want to override these,
307
+ * you can use various methods, which are described in the documentation available at
308
+ * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
309
+ *
310
+ * The individual configuration values are listed below:
311
+ *
312
+ * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
313
+ * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
314
+ *
315
+ * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
316
+ *
317
+ * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).
318
+ *
319
+ * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form.
320
+ * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it.
321
+ * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
322
+ * @property {Object} templates Specifies the templates used by the control.
323
+ * @property {string} templates.main=undefined Specifies the main template used by the control.
324
+ * @property {Object} templateMapping Specifies the mapping of templates.
325
+ * @since 1.8.0
326
+ */
327
+ get defaults() {
328
+ return {
329
+ disabled: false,
330
+ shadowMode: "open",
331
+ delegatesFocus: true,
332
+ templates: {
333
+ main: undefined,
334
+ },
335
+ templateMapping: {},
336
+ };
337
+ }
338
+
339
+ /**
340
+ * This method updates the labels of the element.
341
+ * The labels are defined in the options object.
342
+ * The key of the label is used to retrieve the translation from the document.
343
+ * If the translation is different from the label, the label is updated.
344
+ *
345
+ * Before you can use this method, you must have loaded the translations.
346
+ *
347
+ * @returns {Monster.DOM.CustomElement}
348
+ * @throws {Error} Cannot find element with translations. Add a translations object to the document.
349
+ */
350
+ updateI18n() {
351
+ const translations = getDocumentTranslations();
352
+ if (!translations) {
353
+ return this;
354
+ }
355
+
356
+ const labels = this.getOption("labels");
357
+ if (!(isObject(labels) || isIterable(labels))) {
358
+ return this;
359
+ }
360
+
361
+ for (const key in labels) {
362
+ const def = labels[key];
363
+
364
+ if (isString(def)) {
365
+ const text = translations.getText(key, def);
366
+ if (text !== def) {
367
+ this.setOption(`labels.${key}`, text);
368
+ }
369
+ continue;
370
+ } else if (isObject(def)) {
371
+ for (const k in def) {
372
+ const d = def[k];
373
+
374
+ const text = translations.getPluralRuleText(key, k, d);
375
+ if (!isString(text)) {
376
+ throw new Error("Invalid labels definition");
377
+ }
378
+ if (text !== d) {
379
+ this.setOption(`labels.${key}.${k}`, text);
380
+ }
381
+ }
382
+ continue;
383
+ }
384
+
385
+ throw new Error("Invalid labels definition");
386
+ }
387
+ return this;
388
+ }
389
+
390
+ /**
391
+ * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
392
+ * by the derived class.
393
+ *
394
+ * Note that there is no check on the name of the tag in this class. It is the responsibility of
395
+ * the developer to assign an appropriate tag name. If the name is not valid, the
396
+ * `registerCustomElement()` method will issue an error.
397
+ *
398
+ * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
399
+ * @throws {Error} This method must be overridden by the derived class.
400
+ * @return {string} The tag name associated with the custom element.
401
+ * @since 1.7.0
402
+ */
403
+ static getTag() {
404
+ throw new Error(
405
+ "The method `getTag()` must be overridden by the derived class.",
406
+ );
407
+ }
408
+
409
+ /**
410
+ * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
411
+ * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
412
+ *
413
+ * If `undefined` is returned, then the shadow root does not receive a stylesheet.
414
+ *
415
+ * Example usage:
416
+ *
417
+ * ```js
418
+ * static getCSSStyleSheet() {
419
+ * const sheet = new CSSStyleSheet();
420
+ * sheet.replaceSync("p { color: red; }");
421
+ * return sheet;
422
+ * }
423
+ * ```
424
+ *
425
+ * If the environment does not support the `CSSStyleSheet` constructor,
426
+ * you can use the following workaround to create the stylesheet:
427
+ *
428
+ * ```js
429
+ * const doc = document.implementation.createHTMLDocument('title');
430
+ * let style = doc.createElement("style");
431
+ * style.innerHTML = "p { color: red; }";
432
+ * style.appendChild(document.createTextNode(""));
433
+ * doc.head.appendChild(style);
434
+ * return doc.styleSheets[0];
435
+ * ```
436
+ *
437
+ * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied.
438
+ */
439
+ static getCSSStyleSheet() {
440
+ return undefined;
441
+ }
442
+
443
+ /**
444
+ * attach a new observer
445
+ *
446
+ * @param {Observer} observer
447
+ * @returns {CustomElement}
448
+ */
449
+ attachObserver(observer) {
450
+ this[internalSymbol].attachObserver(observer);
451
+ return this;
452
+ }
453
+
454
+ /**
455
+ * detach a observer
456
+ *
457
+ * @param {Observer} observer
458
+ * @returns {CustomElement}
459
+ */
460
+ detachObserver(observer) {
461
+ this[internalSymbol].detachObserver(observer);
462
+ return this;
463
+ }
464
+
465
+ /**
466
+ * @param {Observer} observer
467
+ * @returns {ProxyObserver}
468
+ */
469
+ containsObserver(observer) {
470
+ return this[internalSymbol].containsObserver(observer);
471
+ }
472
+
473
+ /**
474
+ * nested options can be specified by path `a.b.c`
475
+ *
476
+ * @param {string} path
477
+ * @param {*} defaultValue
478
+ * @return {*}
479
+ * @since 1.10.0
480
+ */
481
+ getOption(path, defaultValue = undefined) {
482
+ let value;
483
+
484
+ try {
485
+ value = new Pathfinder(
486
+ this[internalSymbol].getRealSubject()["options"],
487
+ ).getVia(path);
488
+ } catch (e) {
489
+ }
490
+
491
+ if (value === undefined) return defaultValue;
492
+ return value;
493
+ }
494
+
495
+ /**
496
+ * Set option and inform elements
497
+ *
498
+ * @param {string} path
499
+ * @param {*} value
500
+ * @return {CustomElement}
501
+ * @since 1.14.0
502
+ */
503
+ setOption(path, value) {
504
+ new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
505
+ path,
506
+ value,
507
+ );
508
+ return this;
509
+ }
510
+
511
+ /**
512
+ * @since 1.15.0
513
+ * @param {string|object} options
514
+ * @return {CustomElement}
515
+ */
516
+ setOptions(options) {
517
+ if (isString(options)) {
518
+ options = parseOptionsJSON.call(this, options);
519
+ }
520
+ // 2024-01-21: remove this.defaults, otherwise it will overwrite
521
+ // the current settings that have already been made.
522
+ // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
523
+ extend(this[internalSymbol].getSubject()["options"], options);
524
+
525
+ return this;
526
+ }
527
+
528
+ /**
529
+ * Is called once via the constructor
530
+ *
531
+ * @return {CustomElement}
532
+ * @since 1.8.0
533
+ */
534
+ [initMethodSymbol]() {
535
+ return this;
536
+ }
537
+
538
+ /**
539
+ * This method is called once when the object is equipped with update for the dynamic change of the dom.
540
+ * The functions returned here can be used as pipe functions in the template.
541
+ *
542
+ * In the example, the function `my-transformer` is defined. In the template you can use it as follows:
543
+ *
544
+ * ```html
545
+ * <my-element data-monster-option-transformer="path:my-value | call:my-transformer"></my-element>
546
+ * ```
547
+ *
548
+ * @example
549
+ * [updaterTransformerMethodsSymbol]() {
550
+ * return {
551
+ * "my-transformer": (value) => {
552
+ * switch (typeof Wert) {
553
+ * case "string":
554
+ * return value + "!";
555
+ * case "Zahl":
556
+ * return value + 1;
557
+ * default:
558
+ * return value;
559
+ * }
560
+ * }
561
+ * };
562
+ * };
563
+ *
564
+ * @return {object}
565
+ * @since 2.43.0
566
+ */
567
+ [updaterTransformerMethodsSymbol]() {
568
+ return {};
569
+ }
570
+
571
+ /**
572
+ * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
573
+ * 1. Extracts the options from the attributes and the script tag of the element and sets them.
574
+ * 2. Initializes the shadow root and its CSS stylesheet (if specified).
575
+ * 3. Initializes the HTML content of the element.
576
+ * 4. Initializes the custom elements inside the shadow root and the slotted elements.
577
+ * 5. Attaches a mutation observer to observe changes to the attributes of the element.
578
+ *
579
+ * @return {CustomElement} - The updated custom element.
580
+ * @since 1.8.0
581
+ */
582
+ [assembleMethodSymbol]() {
583
+ let elements;
584
+ let nodeList;
585
+
586
+ // Extract options from attributes and set them
587
+ const AttributeOptions = getOptionsFromAttributes.call(this);
588
+ if (
589
+ isObject(AttributeOptions) &&
590
+ Object.keys(AttributeOptions).length > 0
591
+ ) {
592
+ this.setOptions(AttributeOptions);
593
+ }
594
+
595
+ // Extract options from script tag and set them
596
+ const ScriptOptions = getOptionsFromScriptTag.call(this);
597
+ if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
598
+ this.setOptions(ScriptOptions);
599
+ }
600
+
601
+ // Initialize the shadow root and its CSS stylesheet
602
+ if (this.getOption("shadowMode", false) !== false) {
603
+ try {
604
+ initShadowRoot.call(this);
605
+ elements = this.shadowRoot.childNodes;
606
+ } catch (e) {
607
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
608
+ }
609
+
610
+ try {
611
+ initCSSStylesheet.call(this);
612
+ } catch (e) {
613
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
614
+ }
615
+ }
616
+
617
+ // If the elements are not found inside the shadow root, initialize the HTML content of the element
618
+ if (!(elements instanceof NodeList)) {
619
+ initHtmlContent.call(this);
620
+ elements = this.childNodes;
621
+ }
622
+
623
+ // Initialize the custom elements inside the shadow root and the slotted elements
624
+ initFromCallbackHost.call(this);
625
+ try {
626
+ nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
627
+ } catch (e) {
628
+ nodeList = elements;
629
+ }
630
+
631
+ this[updateCloneDataSymbol] = clone(
632
+ this[internalSymbol].getRealSubject()["options"],
633
+ );
634
+
635
+ addObjectWithUpdaterToElement.call(
636
+ this,
637
+ nodeList,
638
+ customElementUpdaterLinkSymbol,
639
+ this[updateCloneDataSymbol],
640
+ );
641
+
642
+ // Attach a mutation observer to observe changes to the attributes of the element
643
+ attachAttributeChangeMutationObserver.call(this);
644
+
645
+ return this;
646
+ }
647
+
648
+
649
+ /**
650
+ * You know what you are doing? This function is only for advanced users.
651
+ * The result is a clone of the internal data.
652
+ *
653
+ * @returns {*}
654
+ */
655
+ getInternalUpdateCloneData() {
656
+ return clone(this[updateCloneDataSymbol]);
657
+ }
658
+
659
+ /**
660
+ * This method is called every time the element is inserted into the DOM. It checks if the custom element
661
+ * has already been initialized and if not, calls the assembleMethod to initialize it.
662
+ *
663
+ * @return {void}
664
+ * @since 1.7.0
665
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
666
+ */
667
+ connectedCallback() {
668
+ // Check if the object has already been initialized
669
+ if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
670
+ // If not, call the assembleMethod to initialize the object
671
+ this[assembleMethodSymbol]();
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Called every time the element is removed from the DOM. Useful for running clean up code.
677
+ *
678
+ * @return {void}
679
+ * @since 1.7.0
680
+ */
681
+ disconnectedCallback() {
682
+ }
683
+
684
+ /**
685
+ * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
686
+ *
687
+ * @return {void}
688
+ * @since 1.7.0
689
+ */
690
+ adoptedCallback() {
691
+ }
692
+
693
+ /**
694
+ * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
695
+ * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
696
+ * property will receive this callback.
697
+ *
698
+ * @param {string} attrName
699
+ * @param {string} oldVal
700
+ * @param {string} newVal
701
+ * @return {void}
702
+ * @since 1.15.0
703
+ */
704
+ attributeChangedCallback(attrName, oldVal, newVal) {
705
+ if (attrName.startsWith("data-monster-option-")) {
706
+ setOptionFromAttribute(
707
+ this,
708
+ attrName,
709
+ this[internalSymbol].getSubject()["options"],
710
+ );
711
+ }
712
+
713
+ const callback = this[attributeObserverSymbol]?.[attrName];
714
+ if (isFunction(callback)) {
715
+ try {
716
+ callback.call(this, newVal, oldVal);
717
+ } catch (e) {
718
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
719
+ }
720
+ }
721
+ }
722
+
723
+ /**
724
+ *
725
+ * @param {Node} node
726
+ * @return {boolean}
727
+ * @throws {TypeError} value is not an instance of
728
+ * @since 1.19.0
729
+ */
730
+ hasNode(node) {
731
+ if (containChildNode.call(this, validateInstance(node, Node))) {
732
+ return true;
733
+ }
734
+
735
+ if (!(this.shadowRoot instanceof ShadowRoot)) {
736
+ return false;
737
+ }
738
+
739
+ return containChildNode.call(this.shadowRoot, node);
740
+ }
741
+
742
+ /**
743
+ * Calls a callback function if it exists.
744
+ *
745
+ * @param {string} name
746
+ * @param {*} args
747
+ * @returns {*}
748
+ */
749
+ callCallback(name, args) {
750
+ return callControlCallback.call(this, name, ...args);
751
+ }
708
752
  }
709
753
 
710
754
  /**
@@ -713,50 +757,50 @@ class CustomElement extends HTMLElement {
713
757
  * @return {any}
714
758
  */
715
759
  function callControlCallback(callBackFunctionName, ...args) {
716
- if (!isString(callBackFunctionName) || callBackFunctionName === "") {
717
- return;
718
- }
719
-
720
- if (callBackFunctionName in this) {
721
- return this[callBackFunctionName](this, ...args);
722
- }
723
-
724
- if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
725
- return;
726
- }
727
-
728
- if (this[scriptHostElementSymbol].length === 0) {
729
- const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
730
- if (!targetId) {
731
- return;
732
- }
733
-
734
- const list = targetId.split(",");
735
- for (const id of list) {
736
- const host = findElementWithIdUpwards(this, targetId);
737
- if (!(host instanceof HTMLElement)) {
738
- continue;
739
- }
740
-
741
- this[scriptHostElementSymbol].push(host);
742
- }
743
- }
744
-
745
- for (const host of this[scriptHostElementSymbol]) {
746
- if (callBackFunctionName in host) {
747
- try {
748
- return host[callBackFunctionName](this, ...args);
749
- } catch (e) {
750
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
751
- }
752
- }
753
- }
754
-
755
- addAttributeToken(
756
- this,
757
- ATTRIBUTE_ERRORMESSAGE,
758
- `callback ${callBackFunctionName} not found`,
759
- );
760
+ if (!isString(callBackFunctionName) || callBackFunctionName === "") {
761
+ return;
762
+ }
763
+
764
+ if (callBackFunctionName in this) {
765
+ return this[callBackFunctionName](this, ...args);
766
+ }
767
+
768
+ if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
769
+ return;
770
+ }
771
+
772
+ if (this[scriptHostElementSymbol].length === 0) {
773
+ const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
774
+ if (!targetId) {
775
+ return;
776
+ }
777
+
778
+ const list = targetId.split(",");
779
+ for (const id of list) {
780
+ const host = findElementWithIdUpwards(this, targetId);
781
+ if (!(host instanceof HTMLElement)) {
782
+ continue;
783
+ }
784
+
785
+ this[scriptHostElementSymbol].push(host);
786
+ }
787
+ }
788
+
789
+ for (const host of this[scriptHostElementSymbol]) {
790
+ if (callBackFunctionName in host) {
791
+ try {
792
+ return host[callBackFunctionName](this, ...args);
793
+ } catch (e) {
794
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
795
+ }
796
+ }
797
+ }
798
+
799
+ addAttributeToken(
800
+ this,
801
+ ATTRIBUTE_ERRORMESSAGE,
802
+ `callback ${callBackFunctionName} not found`,
803
+ );
760
804
  }
761
805
 
762
806
  /**
@@ -773,16 +817,16 @@ function callControlCallback(callBackFunctionName, ...args) {
773
817
  * @since 1.8.0
774
818
  */
775
819
  function initFromCallbackHost() {
776
- // Set the default callback function name
777
- let callBackFunctionName = initControlCallbackName;
820
+ // Set the default callback function name
821
+ let callBackFunctionName = initControlCallbackName;
778
822
 
779
- // If the `data-monster-option-callback` attribute is set, use its value as the callback function name
780
- if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
781
- callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
782
- }
823
+ // If the `data-monster-option-callback` attribute is set, use its value as the callback function name
824
+ if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
825
+ callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
826
+ }
783
827
 
784
- // Call the callback function with the element as a parameter if it exists
785
- callControlCallback.call(this, callBackFunctionName);
828
+ // Call the callback function with the element as a parameter if it exists
829
+ callControlCallback.call(this, callBackFunctionName);
786
830
  }
787
831
 
788
832
  /**
@@ -792,35 +836,35 @@ function initFromCallbackHost() {
792
836
  * @this CustomElement
793
837
  */
794
838
  function attachAttributeChangeMutationObserver() {
795
- const self = this;
796
-
797
- if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
798
- return;
799
- }
800
-
801
- self[attributeMutationObserverSymbol] = new MutationObserver(function (
802
- mutations,
803
- observer,
804
- ) {
805
- for (const mutation of mutations) {
806
- if (mutation.type === "attributes") {
807
- self.attributeChangedCallback(
808
- mutation.attributeName,
809
- mutation.oldValue,
810
- mutation.target.getAttribute(mutation.attributeName),
811
- );
812
- }
813
- }
814
- });
815
-
816
- try {
817
- self[attributeMutationObserverSymbol].observe(self, {
818
- attributes: true,
819
- attributeOldValue: true,
820
- });
821
- } catch (e) {
822
- addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
823
- }
839
+ const self = this;
840
+
841
+ if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
842
+ return;
843
+ }
844
+
845
+ self[attributeMutationObserverSymbol] = new MutationObserver(function (
846
+ mutations,
847
+ observer,
848
+ ) {
849
+ for (const mutation of mutations) {
850
+ if (mutation.type === "attributes") {
851
+ self.attributeChangedCallback(
852
+ mutation.attributeName,
853
+ mutation.oldValue,
854
+ mutation.target.getAttribute(mutation.attributeName),
855
+ );
856
+ }
857
+ }
858
+ });
859
+
860
+ try {
861
+ self[attributeMutationObserverSymbol].observe(self, {
862
+ attributes: true,
863
+ attributeOldValue: true,
864
+ });
865
+ } catch (e) {
866
+ addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
867
+ }
824
868
  }
825
869
 
826
870
  /**
@@ -830,19 +874,19 @@ function attachAttributeChangeMutationObserver() {
830
874
  * @return {boolean}
831
875
  */
832
876
  function containChildNode(node) {
833
- if (this.contains(node)) {
834
- return true;
835
- }
877
+ if (this.contains(node)) {
878
+ return true;
879
+ }
836
880
 
837
- for (const [, e] of Object.entries(this.childNodes)) {
838
- if (e.contains(node)) {
839
- return true;
840
- }
881
+ for (const [, e] of Object.entries(this.childNodes)) {
882
+ if (e.contains(node)) {
883
+ return true;
884
+ }
841
885
 
842
- containChildNode.call(e, node);
843
- }
886
+ containChildNode.call(e, node);
887
+ }
844
888
 
845
- return false;
889
+ return false;
846
890
  }
847
891
 
848
892
  /**
@@ -852,89 +896,89 @@ function containChildNode(node) {
852
896
  * @this CustomElement
853
897
  */
854
898
  function initOptionObserver() {
855
- const self = this;
856
-
857
- let lastDisabledValue = undefined;
858
- self.attachObserver(
859
- new Observer(function () {
860
- const flag = self.getOption("disabled");
861
-
862
- if (flag === lastDisabledValue) {
863
- return;
864
- }
865
-
866
- lastDisabledValue = flag;
867
-
868
- if (!(self.shadowRoot instanceof ShadowRoot)) {
869
- return;
870
- }
871
-
872
- const query =
873
- "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
874
- const elements = self.shadowRoot.querySelectorAll(query);
875
-
876
- let nodeList;
877
- try {
878
- nodeList = new Set([
879
- ...elements,
880
- ...getSlottedElements.call(self, query),
881
- ]);
882
- } catch (e) {
883
- nodeList = elements;
884
- }
885
-
886
- for (const element of [...nodeList]) {
887
- if (flag === true) {
888
- element.setAttribute(ATTRIBUTE_DISABLED, "");
889
- } else {
890
- element.removeAttribute(ATTRIBUTE_DISABLED);
891
- }
892
- }
893
- }),
894
- );
895
-
896
- self.attachObserver(
897
- new Observer(function () {
898
- // not initialised
899
- if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
900
- return;
901
- }
902
- // inform every element
903
- const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
904
-
905
- for (const list of updaters) {
906
- for (const updater of list) {
907
- const d = clone(self[internalSymbol].getRealSubject()["options"]);
908
- Object.assign(updater.getSubject(), d);
909
- }
910
- }
911
- }),
912
- );
913
-
914
- // disabled
915
- self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
916
- if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
917
- self.setOption(ATTRIBUTE_DISABLED, true);
918
- } else {
919
- self.setOption(ATTRIBUTE_DISABLED, undefined);
920
- }
921
- };
922
-
923
- // data-monster-options
924
- self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
925
- const options = getOptionsFromAttributes.call(self);
926
- if (isObject(options) && Object.keys(options).length > 0) {
927
- self.setOptions(options);
928
- }
929
- };
930
-
931
- // data-monster-options-selector
932
- self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
933
- const options = getOptionsFromScriptTag.call(self);
934
- if (isObject(options) && Object.keys(options).length > 0) {
935
- self.setOptions(options);
936
- }
937
- };
899
+ const self = this;
900
+
901
+ let lastDisabledValue = undefined;
902
+ self.attachObserver(
903
+ new Observer(function () {
904
+ const flag = self.getOption("disabled");
905
+
906
+ if (flag === lastDisabledValue) {
907
+ return;
908
+ }
909
+
910
+ lastDisabledValue = flag;
911
+
912
+ if (!(self.shadowRoot instanceof ShadowRoot)) {
913
+ return;
914
+ }
915
+
916
+ const query =
917
+ "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
918
+ const elements = self.shadowRoot.querySelectorAll(query);
919
+
920
+ let nodeList;
921
+ try {
922
+ nodeList = new Set([
923
+ ...elements,
924
+ ...getSlottedElements.call(self, query),
925
+ ]);
926
+ } catch (e) {
927
+ nodeList = elements;
928
+ }
929
+
930
+ for (const element of [...nodeList]) {
931
+ if (flag === true) {
932
+ element.setAttribute(ATTRIBUTE_DISABLED, "");
933
+ } else {
934
+ element.removeAttribute(ATTRIBUTE_DISABLED);
935
+ }
936
+ }
937
+ }),
938
+ );
939
+
940
+ self.attachObserver(
941
+ new Observer(function () {
942
+ // not initialised
943
+ if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
944
+ return;
945
+ }
946
+ // inform every element
947
+ const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
948
+
949
+ for (const list of updaters) {
950
+ for (const updater of list) {
951
+ const d = clone(self[internalSymbol].getRealSubject()["options"]);
952
+ Object.assign(updater.getSubject(), d);
953
+ }
954
+ }
955
+ }),
956
+ );
957
+
958
+ // disabled
959
+ self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
960
+ if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
961
+ self.setOption(ATTRIBUTE_DISABLED, true);
962
+ } else {
963
+ self.setOption(ATTRIBUTE_DISABLED, undefined);
964
+ }
965
+ };
966
+
967
+ // data-monster-options
968
+ self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
969
+ const options = getOptionsFromAttributes.call(self);
970
+ if (isObject(options) && Object.keys(options).length > 0) {
971
+ self.setOptions(options);
972
+ }
973
+ };
974
+
975
+ // data-monster-options-selector
976
+ self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
977
+ const options = getOptionsFromScriptTag.call(self);
978
+ if (isObject(options) && Object.keys(options).length > 0) {
979
+ self.setOptions(options);
980
+ }
981
+ };
938
982
  }
939
983
 
940
984
  /**
@@ -943,37 +987,37 @@ function initOptionObserver() {
943
987
  * @throws {TypeError} value is not a object
944
988
  */
945
989
  function getOptionsFromScriptTag() {
946
- if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
947
- return {};
948
- }
949
-
950
- const node = document.querySelector(
951
- this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
952
- );
953
- if (!(node instanceof HTMLScriptElement)) {
954
- addAttributeToken(
955
- this,
956
- ATTRIBUTE_ERRORMESSAGE,
957
- `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
958
- ATTRIBUTE_OPTIONS_SELECTOR,
959
- )}) but not found.`,
960
- );
961
- return {};
962
- }
963
-
964
- let obj = {};
965
-
966
- try {
967
- obj = parseOptionsJSON.call(this, node.textContent.trim());
968
- } catch (e) {
969
- addAttributeToken(
970
- this,
971
- ATTRIBUTE_ERRORMESSAGE,
972
- `when analyzing the configuration from the script tag there was an error. ${e}`,
973
- );
974
- }
975
-
976
- return obj;
990
+ if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
991
+ return {};
992
+ }
993
+
994
+ const node = document.querySelector(
995
+ this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
996
+ );
997
+ if (!(node instanceof HTMLScriptElement)) {
998
+ addAttributeToken(
999
+ this,
1000
+ ATTRIBUTE_ERRORMESSAGE,
1001
+ `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
1002
+ ATTRIBUTE_OPTIONS_SELECTOR,
1003
+ )}) but not found.`,
1004
+ );
1005
+ return {};
1006
+ }
1007
+
1008
+ let obj = {};
1009
+
1010
+ try {
1011
+ obj = parseOptionsJSON.call(this, node.textContent.trim());
1012
+ } catch (e) {
1013
+ addAttributeToken(
1014
+ this,
1015
+ ATTRIBUTE_ERRORMESSAGE,
1016
+ `when analyzing the configuration from the script tag there was an error. ${e}`,
1017
+ );
1018
+ }
1019
+
1020
+ return obj;
977
1021
  }
978
1022
 
979
1023
  /**
@@ -981,21 +1025,21 @@ function getOptionsFromScriptTag() {
981
1025
  * @return {object}
982
1026
  */
983
1027
  function getOptionsFromAttributes() {
984
- if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
985
- try {
986
- return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
987
- } catch (e) {
988
- addAttributeToken(
989
- this,
990
- ATTRIBUTE_ERRORMESSAGE,
991
- `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
992
- ATTRIBUTE_OPTIONS,
993
- )}).${e}`,
994
- );
995
- }
996
- }
997
-
998
- return {};
1028
+ if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
1029
+ try {
1030
+ return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
1031
+ } catch (e) {
1032
+ addAttributeToken(
1033
+ this,
1034
+ ATTRIBUTE_ERRORMESSAGE,
1035
+ `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
1036
+ ATTRIBUTE_OPTIONS,
1037
+ )}).${e}`,
1038
+ );
1039
+ }
1040
+ }
1041
+
1042
+ return {};
999
1043
  }
1000
1044
 
1001
1045
  /**
@@ -1007,25 +1051,26 @@ function getOptionsFromAttributes() {
1007
1051
  * @throws {error} Throws an error if the JSON data is not valid.
1008
1052
  */
1009
1053
  function parseOptionsJSON(data) {
1010
- let obj = {};
1011
-
1012
- if (!isString(data)) {
1013
- return obj;
1014
- }
1015
-
1016
- // the configuration can be specified as a data url.
1017
- try {
1018
- const dataUrl = parseDataURL(data);
1019
- data = dataUrl.content;
1020
- } catch (e) {}
1021
-
1022
- try {
1023
- obj = JSON.parse(data);
1024
- } catch (e) {
1025
- throw e;
1026
- }
1027
-
1028
- return validateObject(obj);
1054
+ let obj = {};
1055
+
1056
+ if (!isString(data)) {
1057
+ return obj;
1058
+ }
1059
+
1060
+ // the configuration can be specified as a data url.
1061
+ try {
1062
+ const dataUrl = parseDataURL(data);
1063
+ data = dataUrl.content;
1064
+ } catch (e) {
1065
+ }
1066
+
1067
+ try {
1068
+ obj = JSON.parse(data);
1069
+ } catch (e) {
1070
+ throw e;
1071
+ }
1072
+
1073
+ return validateObject(obj);
1029
1074
  }
1030
1075
 
1031
1076
  /**
@@ -1033,21 +1078,21 @@ function parseOptionsJSON(data) {
1033
1078
  * @return {initHtmlContent}
1034
1079
  */
1035
1080
  function initHtmlContent() {
1036
- try {
1037
- const template = findDocumentTemplate(this.constructor.getTag());
1038
- this.appendChild(template.createDocumentFragment());
1039
- } catch (e) {
1040
- let html = this.getOption("templates.main", "");
1041
- if (isString(html) && html.length > 0) {
1042
- const mapping = this.getOption("templateMapping", {});
1043
- if (isObject(mapping)) {
1044
- html = new Formatter(mapping, {}).format(html);
1045
- }
1046
- this.innerHTML = html;
1047
- }
1048
- }
1049
-
1050
- return this;
1081
+ try {
1082
+ const template = findDocumentTemplate(this.constructor.getTag());
1083
+ this.appendChild(template.createDocumentFragment());
1084
+ } catch (e) {
1085
+ let html = this.getOption("templates.main", "");
1086
+ if (isString(html) && html.length > 0) {
1087
+ const mapping = this.getOption("templateMapping", {});
1088
+ if (isObject(mapping)) {
1089
+ html = new Formatter(mapping, {}).format(html);
1090
+ }
1091
+ this.innerHTML = html;
1092
+ }
1093
+ }
1094
+
1095
+ return this;
1051
1096
  }
1052
1097
 
1053
1098
  /**
@@ -1060,49 +1105,49 @@ function initHtmlContent() {
1060
1105
  * @throws {TypeError} value is not an instance of
1061
1106
  */
1062
1107
  function initCSSStylesheet() {
1063
- if (!(this.shadowRoot instanceof ShadowRoot)) {
1064
- return this;
1065
- }
1066
-
1067
- const styleSheet = this.constructor.getCSSStyleSheet();
1068
-
1069
- if (styleSheet instanceof CSSStyleSheet) {
1070
- if (styleSheet.cssRules.length > 0) {
1071
- this.shadowRoot.adoptedStyleSheets = [styleSheet];
1072
- }
1073
- } else if (isArray(styleSheet)) {
1074
- const assign = [];
1075
- for (const s of styleSheet) {
1076
- if (isString(s)) {
1077
- const trimedStyleSheet = s.trim();
1078
- if (trimedStyleSheet !== "") {
1079
- const style = document.createElement("style");
1080
- style.innerHTML = trimedStyleSheet;
1081
- this.shadowRoot.prepend(style);
1082
- }
1083
- continue;
1084
- }
1085
-
1086
- validateInstance(s, CSSStyleSheet);
1087
-
1088
- if (s.cssRules.length > 0) {
1089
- assign.push(s);
1090
- }
1091
- }
1092
-
1093
- if (assign.length > 0) {
1094
- this.shadowRoot.adoptedStyleSheets = assign;
1095
- }
1096
- } else if (isString(styleSheet)) {
1097
- const trimedStyleSheet = styleSheet.trim();
1098
- if (trimedStyleSheet !== "") {
1099
- const style = document.createElement("style");
1100
- style.innerHTML = styleSheet;
1101
- this.shadowRoot.prepend(style);
1102
- }
1103
- }
1104
-
1105
- return this;
1108
+ if (!(this.shadowRoot instanceof ShadowRoot)) {
1109
+ return this;
1110
+ }
1111
+
1112
+ const styleSheet = this.constructor.getCSSStyleSheet();
1113
+
1114
+ if (styleSheet instanceof CSSStyleSheet) {
1115
+ if (styleSheet.cssRules.length > 0) {
1116
+ this.shadowRoot.adoptedStyleSheets = [styleSheet];
1117
+ }
1118
+ } else if (isArray(styleSheet)) {
1119
+ const assign = [];
1120
+ for (const s of styleSheet) {
1121
+ if (isString(s)) {
1122
+ const trimedStyleSheet = s.trim();
1123
+ if (trimedStyleSheet !== "") {
1124
+ const style = document.createElement("style");
1125
+ style.innerHTML = trimedStyleSheet;
1126
+ this.shadowRoot.prepend(style);
1127
+ }
1128
+ continue;
1129
+ }
1130
+
1131
+ validateInstance(s, CSSStyleSheet);
1132
+
1133
+ if (s.cssRules.length > 0) {
1134
+ assign.push(s);
1135
+ }
1136
+ }
1137
+
1138
+ if (assign.length > 0) {
1139
+ this.shadowRoot.adoptedStyleSheets = assign;
1140
+ }
1141
+ } else if (isString(styleSheet)) {
1142
+ const trimedStyleSheet = styleSheet.trim();
1143
+ if (trimedStyleSheet !== "") {
1144
+ const style = document.createElement("style");
1145
+ style.innerHTML = styleSheet;
1146
+ this.shadowRoot.prepend(style);
1147
+ }
1148
+ }
1149
+
1150
+ return this;
1106
1151
  }
1107
1152
 
1108
1153
  /**
@@ -1115,35 +1160,35 @@ function initCSSStylesheet() {
1115
1160
  * @since 1.8.0
1116
1161
  */
1117
1162
  function initShadowRoot() {
1118
- let template;
1119
- let html;
1120
-
1121
- try {
1122
- template = findDocumentTemplate(this.constructor.getTag());
1123
- } catch (e) {
1124
- html = this.getOption("templates.main", "");
1125
- if (!isString(html) || html === undefined || html === "") {
1126
- throw new Error("html is not set.");
1127
- }
1128
- }
1129
-
1130
- this.attachShadow({
1131
- mode: this.getOption("shadowMode", "open"),
1132
- delegatesFocus: this.getOption("delegatesFocus", true),
1133
- });
1134
-
1135
- if (template instanceof Template) {
1136
- this.shadowRoot.appendChild(template.createDocumentFragment());
1137
- return this;
1138
- }
1139
-
1140
- const mapping = this.getOption("templateMapping", {});
1141
- if (isObject(mapping)) {
1142
- html = new Formatter(mapping).format(html);
1143
- }
1144
-
1145
- this.shadowRoot.innerHTML = html;
1146
- return this;
1163
+ let template;
1164
+ let html;
1165
+
1166
+ try {
1167
+ template = findDocumentTemplate(this.constructor.getTag());
1168
+ } catch (e) {
1169
+ html = this.getOption("templates.main", "");
1170
+ if (!isString(html) || html === undefined || html === "") {
1171
+ throw new Error("html is not set.");
1172
+ }
1173
+ }
1174
+
1175
+ this.attachShadow({
1176
+ mode: this.getOption("shadowMode", "open"),
1177
+ delegatesFocus: this.getOption("delegatesFocus", true),
1178
+ });
1179
+
1180
+ if (template instanceof Template) {
1181
+ this.shadowRoot.appendChild(template.createDocumentFragment());
1182
+ return this;
1183
+ }
1184
+
1185
+ const mapping = this.getOption("templateMapping", {});
1186
+ if (isObject(mapping)) {
1187
+ html = new Formatter(mapping).format(html);
1188
+ }
1189
+
1190
+ this.shadowRoot.innerHTML = html;
1191
+ return this;
1147
1192
  }
1148
1193
 
1149
1194
  /**
@@ -1158,20 +1203,20 @@ function initShadowRoot() {
1158
1203
  * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name
1159
1204
  */
1160
1205
  function registerCustomElement(element) {
1161
- validateFunction(element);
1162
- const customElements = getGlobalObject("customElements");
1163
- if (customElements === undefined) {
1164
- throw new Error("customElements is not supported.");
1165
- }
1166
-
1167
- const tag = element?.getTag();
1168
- if (!isString(tag) || tag === "") {
1169
- throw new Error("tag is not set.");
1170
- }
1171
-
1172
- if (customElements.get(tag) !== undefined) {
1173
- return;
1174
- }
1175
-
1176
- customElements.define(tag, element);
1206
+ validateFunction(element);
1207
+ const customElements = getGlobalObject("customElements");
1208
+ if (customElements === undefined) {
1209
+ throw new Error("customElements is not supported.");
1210
+ }
1211
+
1212
+ const tag = element?.getTag();
1213
+ if (!isString(tag) || tag === "") {
1214
+ throw new Error("tag is not set.");
1215
+ }
1216
+
1217
+ if (customElements.get(tag) !== undefined) {
1218
+ return;
1219
+ }
1220
+
1221
+ customElements.define(tag, element);
1177
1222
  }