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