From 79c2c6e8cf13cdbd5658f1e5300ce71929c64f17 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Sat, 2 Mar 2024 17:11:41 +0100
Subject: [PATCH] feat: new updaterTransformerMethodsSymbol method #163

---
 source/dom/customelement.mjs | 1775 +++++++++++++++++-----------------
 1 file changed, 911 insertions(+), 864 deletions(-)

diff --git a/source/dom/customelement.mjs b/source/dom/customelement.mjs
index 209700b64..4465099f8 100644
--- a/source/dom/customelement.mjs
+++ b/source/dom/customelement.mjs
@@ -5,63 +5,64 @@
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
  */
 
-import { findElementWithIdUpwards } from "./util.mjs";
-import { internalSymbol } from "../constants.mjs";
-import { extend } from "../data/extend.mjs";
-import { Pathfinder } from "../data/pathfinder.mjs";
-import { Formatter } from "../text/formatter.mjs";
-
-import { parseDataURL } from "../types/dataurl.mjs";
-import { getGlobalObject } from "../types/global.mjs";
+import {findElementWithIdUpwards} from "./util.mjs";
+import {internalSymbol} from "../constants.mjs";
+import {extend} from "../data/extend.mjs";
+import {Pathfinder} from "../data/pathfinder.mjs";
+import {Formatter} from "../text/formatter.mjs";
+
+import {parseDataURL} from "../types/dataurl.mjs";
+import {getGlobalObject} from "../types/global.mjs";
 import {
-	isArray,
-	isFunction,
-	isIterable,
-	isObject,
-	isString,
+    isArray,
+    isFunction,
+    isIterable,
+    isObject,
+    isString,
 } from "../types/is.mjs";
-import { Observer } from "../types/observer.mjs";
-import { ProxyObserver } from "../types/proxyobserver.mjs";
+import {Observer} from "../types/observer.mjs";
+import {ProxyObserver} from "../types/proxyobserver.mjs";
 import {
-	validateFunction,
-	validateInstance,
-	validateObject,
-	validateString,
+    validateFunction,
+    validateInstance,
+    validateObject,
+    validateString,
 } from "../types/validate.mjs";
-import { clone } from "../util/clone.mjs";
+import {clone} from "../util/clone.mjs";
 import {
-	addAttributeToken,
-	getLinkedObjects,
-	hasObjectLink,
+    addAttributeToken,
+    getLinkedObjects,
+    hasObjectLink,
 } from "./attributes.mjs";
 import {
-	ATTRIBUTE_DISABLED,
-	ATTRIBUTE_ERRORMESSAGE,
-	ATTRIBUTE_OPTIONS,
-	ATTRIBUTE_INIT_CALLBACK,
-	ATTRIBUTE_OPTIONS_SELECTOR,
-	ATTRIBUTE_SCRIPT_HOST,
-	customElementUpdaterLinkSymbol,
-	initControlCallbackName,
+    ATTRIBUTE_DISABLED,
+    ATTRIBUTE_ERRORMESSAGE,
+    ATTRIBUTE_OPTIONS,
+    ATTRIBUTE_INIT_CALLBACK,
+    ATTRIBUTE_OPTIONS_SELECTOR,
+    ATTRIBUTE_SCRIPT_HOST,
+    customElementUpdaterLinkSymbol,
+    initControlCallbackName,
 } from "./constants.mjs";
-import { findDocumentTemplate, Template } from "./template.mjs";
-import { addObjectWithUpdaterToElement } from "./updater.mjs";
-import { instanceSymbol } from "../constants.mjs";
+import {findDocumentTemplate, Template} from "./template.mjs";
+import {addObjectWithUpdaterToElement} from "./updater.mjs";
+import {instanceSymbol} from "../constants.mjs";
 import {
-	getDocumentTranslations,
-	Translations,
+    getDocumentTranslations,
+    Translations,
 } from "../i18n/translations.mjs";
-import { getSlottedElements } from "./slotted.mjs";
-import { initOptionsFromAttributes } from "./util/init-options-from-attributes.mjs";
-import { setOptionFromAttribute } from "./util/set-option-from-attribute.mjs";
+import {getSlottedElements} from "./slotted.mjs";
+import {initOptionsFromAttributes} from "./util/init-options-from-attributes.mjs";
+import {setOptionFromAttribute} from "./util/set-option-from-attribute.mjs";
 
 export {
-	CustomElement,
-	initMethodSymbol,
-	assembleMethodSymbol,
-	attributeObserverSymbol,
-	registerCustomElement,
-	getSlottedElements,
+    CustomElement,
+    initMethodSymbol,
+    assembleMethodSymbol,
+    attributeObserverSymbol,
+    registerCustomElement,
+    getSlottedElements,
+    updaterTransformerMethodsSymbol
 };
 
 /**
@@ -75,7 +76,15 @@ const initMethodSymbol = Symbol.for("@schukai/monster/dom/@@initMethodSymbol");
  * @type {symbol}
  */
 const assembleMethodSymbol = Symbol.for(
-	"@schukai/monster/dom/@@assembleMethodSymbol",
+    "@schukai/monster/dom/@@assembleMethodSymbol",
+);
+
+/**
+ * @memberOf Monster.DOM
+ * @type {symbol}
+ */
+const updaterTransformerMethodsSymbol = Symbol.for(
+    "@schukai/monster/dom/@@updaterTransformerMethodsSymbol",
 );
 
 /**
@@ -84,7 +93,7 @@ const assembleMethodSymbol = Symbol.for(
  * @type {symbol}
  */
 const attributeObserverSymbol = Symbol.for(
-	"@schukai/monster/dom/@@attributeObserver",
+    "@schukai/monster/dom/@@attributeObserver",
 );
 
 /**
@@ -92,7 +101,7 @@ const attributeObserverSymbol = Symbol.for(
  * @type {symbol}
  */
 const attributeMutationObserverSymbol = Symbol(
-	"@schukai/monster/dom/@@mutationObserver",
+    "@schukai/monster/dom/@@mutationObserver",
 );
 
 /**
@@ -231,480 +240,517 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
  * @summary A base class for HTML5 custom controls.
  */
 class CustomElement extends HTMLElement {
-	/**
-	 * A new object is created. First the `initOptions` method is called. Here the
-	 * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
-	 *
-	 * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
-	 *
-	 * @throws {Error} the options attribute does not contain a valid json definition.
-	 * @since 1.7.0
-	 */
-	constructor() {
-		super();
-
-		this[attributeObserverSymbol] = {};
-		this[internalSymbol] = new ProxyObserver({
-			options: initOptionsFromAttributes(this, extend({}, this.defaults)),
-		});
-		this[initMethodSymbol]();
-		initOptionObserver.call(this);
-		this[scriptHostElementSymbol] = [];
-	}
-
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @returns {symbol}
-	 * @since 2.1.0
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/dom/custom-element@@instance");
-	}
-
-	/**
-	 * This method determines which attributes are to be
-	 * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
-	 * Therefore, the `observedAttributes` property cannot be changed during runtime.
-	 *
-	 * @return {string[]}
-	 * @since 1.15.0
-	 */
-	static get observedAttributes() {
-		return [];
-	}
-
-	/**
-	 *
-	 * @param attribute
-	 * @param callback
-	 * @returns {Monster.DOM.CustomElement}
-	 */
-	addAttributeObserver(attribute, callback) {
-		validateFunction(callback);
-		this[attributeObserverSymbol][attribute] = callback;
-		return this;
-	}
-
-	/**
-	 *
-	 * @param attribute
-	 * @returns {Monster.DOM.CustomElement}
-	 */
-	removeAttributeObserver(attribute) {
-		delete this[attributeObserverSymbol][attribute];
-		return this;
-	}
-
-	/**
-	 * The `defaults` property defines the default values for a control. If you want to override these,
-	 * you can use various methods, which are described in the documentation available at
-	 * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
-	 *
-	 * The individual configuration values are listed below:
-	 *
-	 * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
-	 * 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).
-	 *
-	 * 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).
-	 *
-	 * 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).
-	 *
-	 * @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.
-	 * @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.
-	 * @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.
-	 * @property {Object} templates Specifies the templates used by the control.
-	 * @property {string} templates.main=undefined Specifies the main template used by the control.
-	 * @property {Object} templateMapping Specifies the mapping of templates.
-	 * @since 1.8.0
-	 */
-	get defaults() {
-		return {
-			disabled: false,
-			shadowMode: "open",
-			delegatesFocus: true,
-			templates: {
-				main: undefined,
-			},
-			templateMapping: {},
-		};
-	}
-
-	/**
-	 * This method updates the labels of the element.
-	 * The labels are defined in the options object.
-	 * The key of the label is used to retrieve the translation from the document.
-	 * If the translation is different from the label, the label is updated.
-	 *
-	 * Before you can use this method, you must have loaded the translations.
-	 *
-	 * @returns {Monster.DOM.CustomElement}
-	 * @throws {Error}  Cannot find element with translations. Add a translations object to the document.
-	 */
-	updateI18n() {
-		const translations = getDocumentTranslations();
-		if (!translations) {
-			return this;
-		}
-
-		const labels = this.getOption("labels");
-		if (!(isObject(labels) || isIterable(labels))) {
-			return this;
-		}
-
-		for (const key in labels) {
-			const def = labels[key];
-
-			if (isString(def)) {
-				const text = translations.getText(key, def);
-				if (text !== def) {
-					this.setOption(`labels.${key}`, text);
-				}
-				continue;
-			} else if (isObject(def)) {
-				for (const k in def) {
-					const d = def[k];
-
-					const text = translations.getPluralRuleText(key, k, d);
-					if (!isString(text)) {
-						throw new Error("Invalid labels definition");
-					}
-					if (text !== d) {
-						this.setOption(`labels.${key}.${k}`, text);
-					}
-				}
-				continue;
-			}
-
-			throw new Error("Invalid labels definition");
-		}
-		return this;
-	}
-
-	/**
-	 * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
-	 * by the derived class.
-	 *
-	 * Note that there is no check on the name of the tag in this class. It is the responsibility of
-	 * the developer to assign an appropriate tag name. If the name is not valid, the
-	 * `registerCustomElement()` method will issue an error.
-	 *
-	 * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
-	 * @throws {Error} This method must be overridden by the derived class.
-	 * @return {string} The tag name associated with the custom element.
-	 * @since 1.7.0
-	 */
-	static getTag() {
-		throw new Error(
-			"The method `getTag()` must be overridden by the derived class.",
-		);
-	}
-
-	/**
-	 * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
-	 * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
-	 *
-	 * If `undefined` is returned, then the shadow root does not receive a stylesheet.
-	 *
-	 * Example usage:
-	 *
-	 * ```js
-	 * static getCSSStyleSheet() {
-	 *     const sheet = new CSSStyleSheet();
-	 *     sheet.replaceSync("p { color: red; }");
-	 *     return sheet;
-	 * }
-	 * ```
-	 *
-	 * If the environment does not support the `CSSStyleSheet` constructor,
-	 * you can use the following workaround to create the stylesheet:
-	 *
-	 * ```js
-	 * const doc = document.implementation.createHTMLDocument('title');
-	 * let style = doc.createElement("style");
-	 * style.innerHTML = "p { color: red; }";
-	 * style.appendChild(document.createTextNode(""));
-	 * doc.head.appendChild(style);
-	 * return doc.styleSheets[0];
-	 * ```
-	 *
-	 * @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.
-	 */
-	static getCSSStyleSheet() {
-		return undefined;
-	}
-
-	/**
-	 * attach a new observer
-	 *
-	 * @param {Observer} observer
-	 * @returns {CustomElement}
-	 */
-	attachObserver(observer) {
-		this[internalSymbol].attachObserver(observer);
-		return this;
-	}
-
-	/**
-	 * detach a observer
-	 *
-	 * @param {Observer} observer
-	 * @returns {CustomElement}
-	 */
-	detachObserver(observer) {
-		this[internalSymbol].detachObserver(observer);
-		return this;
-	}
-
-	/**
-	 * @param {Observer} observer
-	 * @returns {ProxyObserver}
-	 */
-	containsObserver(observer) {
-		return this[internalSymbol].containsObserver(observer);
-	}
-
-	/**
-	 * nested options can be specified by path `a.b.c`
-	 *
-	 * @param {string} path
-	 * @param {*} defaultValue
-	 * @return {*}
-	 * @since 1.10.0
-	 */
-	getOption(path, defaultValue = undefined) {
-		let value;
-
-		try {
-			value = new Pathfinder(
-				this[internalSymbol].getRealSubject()["options"],
-			).getVia(path);
-		} catch (e) {}
-
-		if (value === undefined) return defaultValue;
-		return value;
-	}
-
-	/**
-	 * Set option and inform elements
-	 *
-	 * @param {string} path
-	 * @param {*} value
-	 * @return {CustomElement}
-	 * @since 1.14.0
-	 */
-	setOption(path, value) {
-		new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
-			path,
-			value,
-		);
-		return this;
-	}
-
-	/**
-	 * @since 1.15.0
-	 * @param {string|object} options
-	 * @return {CustomElement}
-	 */
-	setOptions(options) {
-		if (isString(options)) {
-			options = parseOptionsJSON.call(this, options);
-		}
-		// 2024-01-21: remove this.defaults, otherwise it will overwrite
-		// the current settings that have already been made.
-		// https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
-		extend(this[internalSymbol].getSubject()["options"], options);
-
-		return this;
-	}
-
-	/**
-	 * Is called once via the constructor
-	 *
-	 * @return {CustomElement}
-	 * @since 1.8.0
-	 */
-	[initMethodSymbol]() {
-		return this;
-	}
-
-	/**
-	 * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
-	 * 1. Extracts the options from the attributes and the script tag of the element and sets them.
-	 * 2. Initializes the shadow root and its CSS stylesheet (if specified).
-	 * 3. Initializes the HTML content of the element.
-	 * 4. Initializes the custom elements inside the shadow root and the slotted elements.
-	 * 5. Attaches a mutation observer to observe changes to the attributes of the element.
-	 *
-	 * @return {CustomElement} - The updated custom element.
-	 * @since 1.8.0
-	 */
-	[assembleMethodSymbol]() {
-		let elements;
-		let nodeList;
-
-		// Extract options from attributes and set them
-		const AttributeOptions = getOptionsFromAttributes.call(this);
-		if (
-			isObject(AttributeOptions) &&
-			Object.keys(AttributeOptions).length > 0
-		) {
-			this.setOptions(AttributeOptions);
-		}
-
-		// Extract options from script tag and set them
-		const ScriptOptions = getOptionsFromScriptTag.call(this);
-		if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
-			this.setOptions(ScriptOptions);
-		}
-
-		// Initialize the shadow root and its CSS stylesheet
-		if (this.getOption("shadowMode", false) !== false) {
-			try {
-				initShadowRoot.call(this);
-				elements = this.shadowRoot.childNodes;
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-
-			try {
-				initCSSStylesheet.call(this);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-
-		// If the elements are not found inside the shadow root, initialize the HTML content of the element
-		if (!(elements instanceof NodeList)) {
-			initHtmlContent.call(this);
-			elements = this.childNodes;
-		}
-
-		// Initialize the custom elements inside the shadow root and the slotted elements
-		initFromCallbackHost.call(this);
-		try {
-			nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
-		} catch (e) {
-			nodeList = elements;
-		}
-
-		this[updateCloneDataSymbol] = clone(
-			this[internalSymbol].getRealSubject()["options"],
-		);
-
-		addObjectWithUpdaterToElement.call(
-			this,
-			nodeList,
-			customElementUpdaterLinkSymbol,
-			this[updateCloneDataSymbol],
-		);
-
-		// Attach a mutation observer to observe changes to the attributes of the element
-		attachAttributeChangeMutationObserver.call(this);
-
-		return this;
-	}
-
-	/**
-	 * You know what you are doing? This function is only for advanced users.
-	 * The result is a clone of the internal data.
-	 *
-	 * @returns {*}
-	 */
-	getInternalUpdateCloneData() {
-		return clone(this[updateCloneDataSymbol]);
-	}
-
-	/**
-	 * This method is called every time the element is inserted into the DOM. It checks if the custom element
-	 * has already been initialized and if not, calls the assembleMethod to initialize it.
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
-	 */
-	connectedCallback() {
-		// Check if the object has already been initialized
-		if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
-			// If not, call the assembleMethod to initialize the object
-			this[assembleMethodSymbol]();
-		}
-	}
-
-	/**
-	 * Called every time the element is removed from the DOM. Useful for running clean up code.
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 */
-	disconnectedCallback() {}
-
-	/**
-	 * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 */
-	adoptedCallback() {}
-
-	/**
-	 * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
-	 * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
-	 * property will receive this callback.
-	 *
-	 * @param {string} attrName
-	 * @param {string} oldVal
-	 * @param {string} newVal
-	 * @return {void}
-	 * @since 1.15.0
-	 */
-	attributeChangedCallback(attrName, oldVal, newVal) {
-		if (attrName.startsWith("data-monster-option-")) {
-			setOptionFromAttribute(
-				this,
-				attrName,
-				this[internalSymbol].getSubject()["options"],
-			);
-		}
-
-		const callback = this[attributeObserverSymbol]?.[attrName];
-		if (isFunction(callback)) {
-			try {
-				callback.call(this, newVal, oldVal);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-	}
-
-	/**
-	 *
-	 * @param {Node} node
-	 * @return {boolean}
-	 * @throws {TypeError} value is not an instance of
-	 * @since 1.19.0
-	 */
-	hasNode(node) {
-		if (containChildNode.call(this, validateInstance(node, Node))) {
-			return true;
-		}
-
-		if (!(this.shadowRoot instanceof ShadowRoot)) {
-			return false;
-		}
-
-		return containChildNode.call(this.shadowRoot, node);
-	}
-
-	/**
-	 * Calls a callback function if it exists.
-	 *
-	 * @param {string} name
-	 * @param {*} args
-	 * @returns {*}
-	 */
-	callCallback(name, args) {
-		return callControlCallback.call(this, name, ...args);
-	}
+    /**
+     * A new object is created. First the `initOptions` method is called. Here the
+     * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
+     *
+     * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
+     *
+     * @throws {Error} the options attribute does not contain a valid json definition.
+     * @since 1.7.0
+     */
+    constructor() {
+        super();
+
+        this[attributeObserverSymbol] = {};
+        this[internalSymbol] = new ProxyObserver({
+            options: initOptionsFromAttributes(this, extend({}, this.defaults)),
+        });
+        this[initMethodSymbol]();
+        initOptionObserver.call(this);
+        this[scriptHostElementSymbol] = [];
+    }
+
+    /**
+     * This method is called by the `instanceof` operator.
+     * @returns {symbol}
+     * @since 2.1.0
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for("@schukai/monster/dom/custom-element@@instance");
+    }
+
+    /**
+     * This method determines which attributes are to be
+     * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
+     * Therefore, the `observedAttributes` property cannot be changed during runtime.
+     *
+     * @return {string[]}
+     * @since 1.15.0
+     */
+    static get observedAttributes() {
+        return [];
+    }
+
+    /**
+     *
+     * @param attribute
+     * @param callback
+     * @returns {Monster.DOM.CustomElement}
+     */
+    addAttributeObserver(attribute, callback) {
+        validateFunction(callback);
+        this[attributeObserverSymbol][attribute] = callback;
+        return this;
+    }
+
+    /**
+     *
+     * @param attribute
+     * @returns {Monster.DOM.CustomElement}
+     */
+    removeAttributeObserver(attribute) {
+        delete this[attributeObserverSymbol][attribute];
+        return this;
+    }
+
+    /**
+     * The `defaults` property defines the default values for a control. If you want to override these,
+     * you can use various methods, which are described in the documentation available at
+     * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
+     *
+     * The individual configuration values are listed below:
+     *
+     * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
+     * 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).
+     *
+     * 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).
+     *
+     * 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).
+     *
+     * @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.
+     * @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.
+     * @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.
+     * @property {Object} templates Specifies the templates used by the control.
+     * @property {string} templates.main=undefined Specifies the main template used by the control.
+     * @property {Object} templateMapping Specifies the mapping of templates.
+     * @since 1.8.0
+     */
+    get defaults() {
+        return {
+            disabled: false,
+            shadowMode: "open",
+            delegatesFocus: true,
+            templates: {
+                main: undefined,
+            },
+            templateMapping: {},
+        };
+    }
+
+    /**
+     * This method updates the labels of the element.
+     * The labels are defined in the options object.
+     * The key of the label is used to retrieve the translation from the document.
+     * If the translation is different from the label, the label is updated.
+     *
+     * Before you can use this method, you must have loaded the translations.
+     *
+     * @returns {Monster.DOM.CustomElement}
+     * @throws {Error}  Cannot find element with translations. Add a translations object to the document.
+     */
+    updateI18n() {
+        const translations = getDocumentTranslations();
+        if (!translations) {
+            return this;
+        }
+
+        const labels = this.getOption("labels");
+        if (!(isObject(labels) || isIterable(labels))) {
+            return this;
+        }
+
+        for (const key in labels) {
+            const def = labels[key];
+
+            if (isString(def)) {
+                const text = translations.getText(key, def);
+                if (text !== def) {
+                    this.setOption(`labels.${key}`, text);
+                }
+                continue;
+            } else if (isObject(def)) {
+                for (const k in def) {
+                    const d = def[k];
+
+                    const text = translations.getPluralRuleText(key, k, d);
+                    if (!isString(text)) {
+                        throw new Error("Invalid labels definition");
+                    }
+                    if (text !== d) {
+                        this.setOption(`labels.${key}.${k}`, text);
+                    }
+                }
+                continue;
+            }
+
+            throw new Error("Invalid labels definition");
+        }
+        return this;
+    }
+
+    /**
+     * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
+     * by the derived class.
+     *
+     * Note that there is no check on the name of the tag in this class. It is the responsibility of
+     * the developer to assign an appropriate tag name. If the name is not valid, the
+     * `registerCustomElement()` method will issue an error.
+     *
+     * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
+     * @throws {Error} This method must be overridden by the derived class.
+     * @return {string} The tag name associated with the custom element.
+     * @since 1.7.0
+     */
+    static getTag() {
+        throw new Error(
+            "The method `getTag()` must be overridden by the derived class.",
+        );
+    }
+
+    /**
+     * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
+     * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
+     *
+     * If `undefined` is returned, then the shadow root does not receive a stylesheet.
+     *
+     * Example usage:
+     *
+     * ```js
+     * static getCSSStyleSheet() {
+     *     const sheet = new CSSStyleSheet();
+     *     sheet.replaceSync("p { color: red; }");
+     *     return sheet;
+     * }
+     * ```
+     *
+     * If the environment does not support the `CSSStyleSheet` constructor,
+     * you can use the following workaround to create the stylesheet:
+     *
+     * ```js
+     * const doc = document.implementation.createHTMLDocument('title');
+     * let style = doc.createElement("style");
+     * style.innerHTML = "p { color: red; }";
+     * style.appendChild(document.createTextNode(""));
+     * doc.head.appendChild(style);
+     * return doc.styleSheets[0];
+     * ```
+     *
+     * @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.
+     */
+    static getCSSStyleSheet() {
+        return undefined;
+    }
+
+    /**
+     * attach a new observer
+     *
+     * @param {Observer} observer
+     * @returns {CustomElement}
+     */
+    attachObserver(observer) {
+        this[internalSymbol].attachObserver(observer);
+        return this;
+    }
+
+    /**
+     * detach a observer
+     *
+     * @param {Observer} observer
+     * @returns {CustomElement}
+     */
+    detachObserver(observer) {
+        this[internalSymbol].detachObserver(observer);
+        return this;
+    }
+
+    /**
+     * @param {Observer} observer
+     * @returns {ProxyObserver}
+     */
+    containsObserver(observer) {
+        return this[internalSymbol].containsObserver(observer);
+    }
+
+    /**
+     * nested options can be specified by path `a.b.c`
+     *
+     * @param {string} path
+     * @param {*} defaultValue
+     * @return {*}
+     * @since 1.10.0
+     */
+    getOption(path, defaultValue = undefined) {
+        let value;
+
+        try {
+            value = new Pathfinder(
+                this[internalSymbol].getRealSubject()["options"],
+            ).getVia(path);
+        } catch (e) {
+        }
+
+        if (value === undefined) return defaultValue;
+        return value;
+    }
+
+    /**
+     * Set option and inform elements
+     *
+     * @param {string} path
+     * @param {*} value
+     * @return {CustomElement}
+     * @since 1.14.0
+     */
+    setOption(path, value) {
+        new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
+            path,
+            value,
+        );
+        return this;
+    }
+
+    /**
+     * @since 1.15.0
+     * @param {string|object} options
+     * @return {CustomElement}
+     */
+    setOptions(options) {
+        if (isString(options)) {
+            options = parseOptionsJSON.call(this, options);
+        }
+        // 2024-01-21: remove this.defaults, otherwise it will overwrite
+        // the current settings that have already been made.
+        // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
+        extend(this[internalSymbol].getSubject()["options"], options);
+
+        return this;
+    }
+
+    /**
+     * Is called once via the constructor
+     *
+     * @return {CustomElement}
+     * @since 1.8.0
+     */
+    [initMethodSymbol]() {
+        return this;
+    }
+
+    /**
+     * This method is called once when the object is equipped with update for the dynamic change of the dom. 
+     * The functions returned here can be used as pipe functions in the template. 
+     * 
+     * In the example, the function `my-transformer` is defined. In the template you can use it as follows:
+     * 
+     * ```html
+     * <my-element data-monster-option-transformer="path:my-value | call:my-transformer"></my-element>
+     * ```
+     * 
+     * @example
+     * [updaterTransformerMethodsSymbol]() {
+     *    return {
+     *       "my-transformer": (value) => {
+     *           switch (typeof Wert) {
+     *           case "string":
+     *               return value + "!";
+     *           case "Zahl":
+     *               return value + 1;
+     *           default:
+     *               return value;
+     *           }
+     *    }    
+     *    };
+     *  };
+     *
+     * @return {object}
+     * @since 2.43.0
+     */
+    [updaterTransformerMethodsSymbol]() {
+        return {};
+    }
+
+    /**
+     * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
+     * 1. Extracts the options from the attributes and the script tag of the element and sets them.
+     * 2. Initializes the shadow root and its CSS stylesheet (if specified).
+     * 3. Initializes the HTML content of the element.
+     * 4. Initializes the custom elements inside the shadow root and the slotted elements.
+     * 5. Attaches a mutation observer to observe changes to the attributes of the element.
+     *
+     * @return {CustomElement} - The updated custom element.
+     * @since 1.8.0
+     */
+    [assembleMethodSymbol]() {
+        let elements;
+        let nodeList;
+
+        // Extract options from attributes and set them
+        const AttributeOptions = getOptionsFromAttributes.call(this);
+        if (
+            isObject(AttributeOptions) &&
+            Object.keys(AttributeOptions).length > 0
+        ) {
+            this.setOptions(AttributeOptions);
+        }
+
+        // Extract options from script tag and set them
+        const ScriptOptions = getOptionsFromScriptTag.call(this);
+        if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
+            this.setOptions(ScriptOptions);
+        }
+
+        // Initialize the shadow root and its CSS stylesheet
+        if (this.getOption("shadowMode", false) !== false) {
+            try {
+                initShadowRoot.call(this);
+                elements = this.shadowRoot.childNodes;
+            } catch (e) {
+                addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
+            }
+
+            try {
+                initCSSStylesheet.call(this);
+            } catch (e) {
+                addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
+            }
+        }
+
+        // If the elements are not found inside the shadow root, initialize the HTML content of the element
+        if (!(elements instanceof NodeList)) {
+            initHtmlContent.call(this);
+            elements = this.childNodes;
+        }
+
+        // Initialize the custom elements inside the shadow root and the slotted elements
+        initFromCallbackHost.call(this);
+        try {
+            nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
+        } catch (e) {
+            nodeList = elements;
+        }
+
+        this[updateCloneDataSymbol] = clone(
+            this[internalSymbol].getRealSubject()["options"],
+        );
+
+        addObjectWithUpdaterToElement.call(
+            this,
+            nodeList,
+            customElementUpdaterLinkSymbol,
+            this[updateCloneDataSymbol],
+        );
+
+        // Attach a mutation observer to observe changes to the attributes of the element
+        attachAttributeChangeMutationObserver.call(this);
+
+        return this;
+    }
+
+
+    /**
+     * You know what you are doing? This function is only for advanced users.
+     * The result is a clone of the internal data.
+     *
+     * @returns {*}
+     */
+    getInternalUpdateCloneData() {
+        return clone(this[updateCloneDataSymbol]);
+    }
+
+    /**
+     * This method is called every time the element is inserted into the DOM. It checks if the custom element
+     * has already been initialized and if not, calls the assembleMethod to initialize it.
+     *
+     * @return {void}
+     * @since 1.7.0
+     * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
+     */
+    connectedCallback() {
+        // Check if the object has already been initialized
+        if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
+            // If not, call the assembleMethod to initialize the object
+            this[assembleMethodSymbol]();
+        }
+    }
+
+    /**
+     * Called every time the element is removed from the DOM. Useful for running clean up code.
+     *
+     * @return {void}
+     * @since 1.7.0
+     */
+    disconnectedCallback() {
+    }
+
+    /**
+     * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
+     *
+     * @return {void}
+     * @since 1.7.0
+     */
+    adoptedCallback() {
+    }
+
+    /**
+     * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
+     * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
+     * property will receive this callback.
+     *
+     * @param {string} attrName
+     * @param {string} oldVal
+     * @param {string} newVal
+     * @return {void}
+     * @since 1.15.0
+     */
+    attributeChangedCallback(attrName, oldVal, newVal) {
+        if (attrName.startsWith("data-monster-option-")) {
+            setOptionFromAttribute(
+                this,
+                attrName,
+                this[internalSymbol].getSubject()["options"],
+            );
+        }
+
+        const callback = this[attributeObserverSymbol]?.[attrName];
+        if (isFunction(callback)) {
+            try {
+                callback.call(this, newVal, oldVal);
+            } catch (e) {
+                addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
+            }
+        }
+    }
+
+    /**
+     *
+     * @param {Node} node
+     * @return {boolean}
+     * @throws {TypeError} value is not an instance of
+     * @since 1.19.0
+     */
+    hasNode(node) {
+        if (containChildNode.call(this, validateInstance(node, Node))) {
+            return true;
+        }
+
+        if (!(this.shadowRoot instanceof ShadowRoot)) {
+            return false;
+        }
+
+        return containChildNode.call(this.shadowRoot, node);
+    }
+
+    /**
+     * Calls a callback function if it exists.
+     *
+     * @param {string} name
+     * @param {*} args
+     * @returns {*}
+     */
+    callCallback(name, args) {
+        return callControlCallback.call(this, name, ...args);
+    }
 }
 
 /**
@@ -713,50 +759,50 @@ class CustomElement extends HTMLElement {
  * @return {any}
  */
 function callControlCallback(callBackFunctionName, ...args) {
-	if (!isString(callBackFunctionName) || callBackFunctionName === "") {
-		return;
-	}
-
-	if (callBackFunctionName in this) {
-		return this[callBackFunctionName](this, ...args);
-	}
-
-	if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
-		return;
-	}
-
-	if (this[scriptHostElementSymbol].length === 0) {
-		const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
-		if (!targetId) {
-			return;
-		}
-
-		const list = targetId.split(",");
-		for (const id of list) {
-			const host = findElementWithIdUpwards(this, targetId);
-			if (!(host instanceof HTMLElement)) {
-				continue;
-			}
-
-			this[scriptHostElementSymbol].push(host);
-		}
-	}
-
-	for (const host of this[scriptHostElementSymbol]) {
-		if (callBackFunctionName in host) {
-			try {
-				return host[callBackFunctionName](this, ...args);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-	}
-
-	addAttributeToken(
-		this,
-		ATTRIBUTE_ERRORMESSAGE,
-		`callback ${callBackFunctionName} not found`,
-	);
+    if (!isString(callBackFunctionName) || callBackFunctionName === "") {
+        return;
+    }
+
+    if (callBackFunctionName in this) {
+        return this[callBackFunctionName](this, ...args);
+    }
+
+    if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
+        return;
+    }
+
+    if (this[scriptHostElementSymbol].length === 0) {
+        const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
+        if (!targetId) {
+            return;
+        }
+
+        const list = targetId.split(",");
+        for (const id of list) {
+            const host = findElementWithIdUpwards(this, targetId);
+            if (!(host instanceof HTMLElement)) {
+                continue;
+            }
+
+            this[scriptHostElementSymbol].push(host);
+        }
+    }
+
+    for (const host of this[scriptHostElementSymbol]) {
+        if (callBackFunctionName in host) {
+            try {
+                return host[callBackFunctionName](this, ...args);
+            } catch (e) {
+                addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
+            }
+        }
+    }
+
+    addAttributeToken(
+        this,
+        ATTRIBUTE_ERRORMESSAGE,
+        `callback ${callBackFunctionName} not found`,
+    );
 }
 
 /**
@@ -773,16 +819,16 @@ function callControlCallback(callBackFunctionName, ...args) {
  * @since 1.8.0
  */
 function initFromCallbackHost() {
-	// Set the default callback function name
-	let callBackFunctionName = initControlCallbackName;
+    // Set the default callback function name
+    let callBackFunctionName = initControlCallbackName;
 
-	// If the `data-monster-option-callback` attribute is set, use its value as the callback function name
-	if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
-		callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
-	}
+    // If the `data-monster-option-callback` attribute is set, use its value as the callback function name
+    if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
+        callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
+    }
 
-	// Call the callback function with the element as a parameter if it exists
-	callControlCallback.call(this, callBackFunctionName);
+    // Call the callback function with the element as a parameter if it exists
+    callControlCallback.call(this, callBackFunctionName);
 }
 
 /**
@@ -792,35 +838,35 @@ function initFromCallbackHost() {
  * @this CustomElement
  */
 function attachAttributeChangeMutationObserver() {
-	const self = this;
-
-	if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
-		return;
-	}
-
-	self[attributeMutationObserverSymbol] = new MutationObserver(function (
-		mutations,
-		observer,
-	) {
-		for (const mutation of mutations) {
-			if (mutation.type === "attributes") {
-				self.attributeChangedCallback(
-					mutation.attributeName,
-					mutation.oldValue,
-					mutation.target.getAttribute(mutation.attributeName),
-				);
-			}
-		}
-	});
-
-	try {
-		self[attributeMutationObserverSymbol].observe(self, {
-			attributes: true,
-			attributeOldValue: true,
-		});
-	} catch (e) {
-		addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
-	}
+    const self = this;
+
+    if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
+        return;
+    }
+
+    self[attributeMutationObserverSymbol] = new MutationObserver(function (
+        mutations,
+        observer,
+    ) {
+        for (const mutation of mutations) {
+            if (mutation.type === "attributes") {
+                self.attributeChangedCallback(
+                    mutation.attributeName,
+                    mutation.oldValue,
+                    mutation.target.getAttribute(mutation.attributeName),
+                );
+            }
+        }
+    });
+
+    try {
+        self[attributeMutationObserverSymbol].observe(self, {
+            attributes: true,
+            attributeOldValue: true,
+        });
+    } catch (e) {
+        addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
+    }
 }
 
 /**
@@ -830,19 +876,19 @@ function attachAttributeChangeMutationObserver() {
  * @return {boolean}
  */
 function containChildNode(node) {
-	if (this.contains(node)) {
-		return true;
-	}
+    if (this.contains(node)) {
+        return true;
+    }
 
-	for (const [, e] of Object.entries(this.childNodes)) {
-		if (e.contains(node)) {
-			return true;
-		}
+    for (const [, e] of Object.entries(this.childNodes)) {
+        if (e.contains(node)) {
+            return true;
+        }
 
-		containChildNode.call(e, node);
-	}
+        containChildNode.call(e, node);
+    }
 
-	return false;
+    return false;
 }
 
 /**
@@ -852,89 +898,89 @@ function containChildNode(node) {
  * @this CustomElement
  */
 function initOptionObserver() {
-	const self = this;
-
-	let lastDisabledValue = undefined;
-	self.attachObserver(
-		new Observer(function () {
-			const flag = self.getOption("disabled");
-
-			if (flag === lastDisabledValue) {
-				return;
-			}
-
-			lastDisabledValue = flag;
-
-			if (!(self.shadowRoot instanceof ShadowRoot)) {
-				return;
-			}
-
-			const query =
-				"button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
-			const elements = self.shadowRoot.querySelectorAll(query);
-
-			let nodeList;
-			try {
-				nodeList = new Set([
-					...elements,
-					...getSlottedElements.call(self, query),
-				]);
-			} catch (e) {
-				nodeList = elements;
-			}
-
-			for (const element of [...nodeList]) {
-				if (flag === true) {
-					element.setAttribute(ATTRIBUTE_DISABLED, "");
-				} else {
-					element.removeAttribute(ATTRIBUTE_DISABLED);
-				}
-			}
-		}),
-	);
-
-	self.attachObserver(
-		new Observer(function () {
-			// not initialised
-			if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
-				return;
-			}
-			// inform every element
-			const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
-
-			for (const list of updaters) {
-				for (const updater of list) {
-					const d = clone(self[internalSymbol].getRealSubject()["options"]);
-					Object.assign(updater.getSubject(), d);
-				}
-			}
-		}),
-	);
-
-	// disabled
-	self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
-		if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
-			self.setOption(ATTRIBUTE_DISABLED, true);
-		} else {
-			self.setOption(ATTRIBUTE_DISABLED, undefined);
-		}
-	};
-
-	// data-monster-options
-	self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
-		const options = getOptionsFromAttributes.call(self);
-		if (isObject(options) && Object.keys(options).length > 0) {
-			self.setOptions(options);
-		}
-	};
-
-	// data-monster-options-selector
-	self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
-		const options = getOptionsFromScriptTag.call(self);
-		if (isObject(options) && Object.keys(options).length > 0) {
-			self.setOptions(options);
-		}
-	};
+    const self = this;
+
+    let lastDisabledValue = undefined;
+    self.attachObserver(
+        new Observer(function () {
+            const flag = self.getOption("disabled");
+
+            if (flag === lastDisabledValue) {
+                return;
+            }
+
+            lastDisabledValue = flag;
+
+            if (!(self.shadowRoot instanceof ShadowRoot)) {
+                return;
+            }
+
+            const query =
+                "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
+            const elements = self.shadowRoot.querySelectorAll(query);
+
+            let nodeList;
+            try {
+                nodeList = new Set([
+                    ...elements,
+                    ...getSlottedElements.call(self, query),
+                ]);
+            } catch (e) {
+                nodeList = elements;
+            }
+
+            for (const element of [...nodeList]) {
+                if (flag === true) {
+                    element.setAttribute(ATTRIBUTE_DISABLED, "");
+                } else {
+                    element.removeAttribute(ATTRIBUTE_DISABLED);
+                }
+            }
+        }),
+    );
+
+    self.attachObserver(
+        new Observer(function () {
+            // not initialised
+            if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
+                return;
+            }
+            // inform every element
+            const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
+
+            for (const list of updaters) {
+                for (const updater of list) {
+                    const d = clone(self[internalSymbol].getRealSubject()["options"]);
+                    Object.assign(updater.getSubject(), d);
+                }
+            }
+        }),
+    );
+
+    // disabled
+    self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
+        if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
+            self.setOption(ATTRIBUTE_DISABLED, true);
+        } else {
+            self.setOption(ATTRIBUTE_DISABLED, undefined);
+        }
+    };
+
+    // data-monster-options
+    self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
+        const options = getOptionsFromAttributes.call(self);
+        if (isObject(options) && Object.keys(options).length > 0) {
+            self.setOptions(options);
+        }
+    };
+
+    // data-monster-options-selector
+    self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
+        const options = getOptionsFromScriptTag.call(self);
+        if (isObject(options) && Object.keys(options).length > 0) {
+            self.setOptions(options);
+        }
+    };
 }
 
 /**
@@ -943,37 +989,37 @@ function initOptionObserver() {
  * @throws {TypeError} value is not a object
  */
 function getOptionsFromScriptTag() {
-	if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
-		return {};
-	}
-
-	const node = document.querySelector(
-		this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
-	);
-	if (!(node instanceof HTMLScriptElement)) {
-		addAttributeToken(
-			this,
-			ATTRIBUTE_ERRORMESSAGE,
-			`the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
-				ATTRIBUTE_OPTIONS_SELECTOR,
-			)}) but not found.`,
-		);
-		return {};
-	}
-
-	let obj = {};
-
-	try {
-		obj = parseOptionsJSON.call(this, node.textContent.trim());
-	} catch (e) {
-		addAttributeToken(
-			this,
-			ATTRIBUTE_ERRORMESSAGE,
-			`when analyzing the configuration from the script tag there was an error. ${e}`,
-		);
-	}
-
-	return obj;
+    if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
+        return {};
+    }
+
+    const node = document.querySelector(
+        this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
+    );
+    if (!(node instanceof HTMLScriptElement)) {
+        addAttributeToken(
+            this,
+            ATTRIBUTE_ERRORMESSAGE,
+            `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
+                ATTRIBUTE_OPTIONS_SELECTOR,
+            )}) but not found.`,
+        );
+        return {};
+    }
+
+    let obj = {};
+
+    try {
+        obj = parseOptionsJSON.call(this, node.textContent.trim());
+    } catch (e) {
+        addAttributeToken(
+            this,
+            ATTRIBUTE_ERRORMESSAGE,
+            `when analyzing the configuration from the script tag there was an error. ${e}`,
+        );
+    }
+
+    return obj;
 }
 
 /**
@@ -981,21 +1027,21 @@ function getOptionsFromScriptTag() {
  * @return {object}
  */
 function getOptionsFromAttributes() {
-	if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
-		try {
-			return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
-		} catch (e) {
-			addAttributeToken(
-				this,
-				ATTRIBUTE_ERRORMESSAGE,
-				`the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
-					ATTRIBUTE_OPTIONS,
-				)}).${e}`,
-			);
-		}
-	}
-
-	return {};
+    if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
+        try {
+            return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
+        } catch (e) {
+            addAttributeToken(
+                this,
+                ATTRIBUTE_ERRORMESSAGE,
+                `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
+                    ATTRIBUTE_OPTIONS,
+                )}).${e}`,
+            );
+        }
+    }
+
+    return {};
 }
 
 /**
@@ -1007,25 +1053,26 @@ function getOptionsFromAttributes() {
  * @throws {error} Throws an error if the JSON data is not valid.
  */
 function parseOptionsJSON(data) {
-	let obj = {};
-
-	if (!isString(data)) {
-		return obj;
-	}
-
-	// the configuration can be specified as a data url.
-	try {
-		const dataUrl = parseDataURL(data);
-		data = dataUrl.content;
-	} catch (e) {}
-
-	try {
-		obj = JSON.parse(data);
-	} catch (e) {
-		throw e;
-	}
-
-	return validateObject(obj);
+    let obj = {};
+
+    if (!isString(data)) {
+        return obj;
+    }
+
+    // the configuration can be specified as a data url.
+    try {
+        const dataUrl = parseDataURL(data);
+        data = dataUrl.content;
+    } catch (e) {
+    }
+
+    try {
+        obj = JSON.parse(data);
+    } catch (e) {
+        throw e;
+    }
+
+    return validateObject(obj);
 }
 
 /**
@@ -1033,21 +1080,21 @@ function parseOptionsJSON(data) {
  * @return {initHtmlContent}
  */
 function initHtmlContent() {
-	try {
-		const template = findDocumentTemplate(this.constructor.getTag());
-		this.appendChild(template.createDocumentFragment());
-	} catch (e) {
-		let html = this.getOption("templates.main", "");
-		if (isString(html) && html.length > 0) {
-			const mapping = this.getOption("templateMapping", {});
-			if (isObject(mapping)) {
-				html = new Formatter(mapping, {}).format(html);
-			}
-			this.innerHTML = html;
-		}
-	}
-
-	return this;
+    try {
+        const template = findDocumentTemplate(this.constructor.getTag());
+        this.appendChild(template.createDocumentFragment());
+    } catch (e) {
+        let html = this.getOption("templates.main", "");
+        if (isString(html) && html.length > 0) {
+            const mapping = this.getOption("templateMapping", {});
+            if (isObject(mapping)) {
+                html = new Formatter(mapping, {}).format(html);
+            }
+            this.innerHTML = html;
+        }
+    }
+
+    return this;
 }
 
 /**
@@ -1060,49 +1107,49 @@ function initHtmlContent() {
  * @throws {TypeError} value is not an instance of
  */
 function initCSSStylesheet() {
-	if (!(this.shadowRoot instanceof ShadowRoot)) {
-		return this;
-	}
-
-	const styleSheet = this.constructor.getCSSStyleSheet();
-
-	if (styleSheet instanceof CSSStyleSheet) {
-		if (styleSheet.cssRules.length > 0) {
-			this.shadowRoot.adoptedStyleSheets = [styleSheet];
-		}
-	} else if (isArray(styleSheet)) {
-		const assign = [];
-		for (const s of styleSheet) {
-			if (isString(s)) {
-				const trimedStyleSheet = s.trim();
-				if (trimedStyleSheet !== "") {
-					const style = document.createElement("style");
-					style.innerHTML = trimedStyleSheet;
-					this.shadowRoot.prepend(style);
-				}
-				continue;
-			}
-
-			validateInstance(s, CSSStyleSheet);
-
-			if (s.cssRules.length > 0) {
-				assign.push(s);
-			}
-		}
-
-		if (assign.length > 0) {
-			this.shadowRoot.adoptedStyleSheets = assign;
-		}
-	} else if (isString(styleSheet)) {
-		const trimedStyleSheet = styleSheet.trim();
-		if (trimedStyleSheet !== "") {
-			const style = document.createElement("style");
-			style.innerHTML = styleSheet;
-			this.shadowRoot.prepend(style);
-		}
-	}
-
-	return this;
+    if (!(this.shadowRoot instanceof ShadowRoot)) {
+        return this;
+    }
+
+    const styleSheet = this.constructor.getCSSStyleSheet();
+
+    if (styleSheet instanceof CSSStyleSheet) {
+        if (styleSheet.cssRules.length > 0) {
+            this.shadowRoot.adoptedStyleSheets = [styleSheet];
+        }
+    } else if (isArray(styleSheet)) {
+        const assign = [];
+        for (const s of styleSheet) {
+            if (isString(s)) {
+                const trimedStyleSheet = s.trim();
+                if (trimedStyleSheet !== "") {
+                    const style = document.createElement("style");
+                    style.innerHTML = trimedStyleSheet;
+                    this.shadowRoot.prepend(style);
+                }
+                continue;
+            }
+
+            validateInstance(s, CSSStyleSheet);
+
+            if (s.cssRules.length > 0) {
+                assign.push(s);
+            }
+        }
+
+        if (assign.length > 0) {
+            this.shadowRoot.adoptedStyleSheets = assign;
+        }
+    } else if (isString(styleSheet)) {
+        const trimedStyleSheet = styleSheet.trim();
+        if (trimedStyleSheet !== "") {
+            const style = document.createElement("style");
+            style.innerHTML = styleSheet;
+            this.shadowRoot.prepend(style);
+        }
+    }
+
+    return this;
 }
 
 /**
@@ -1115,35 +1162,35 @@ function initCSSStylesheet() {
  * @since 1.8.0
  */
 function initShadowRoot() {
-	let template;
-	let html;
-
-	try {
-		template = findDocumentTemplate(this.constructor.getTag());
-	} catch (e) {
-		html = this.getOption("templates.main", "");
-		if (!isString(html) || html === undefined || html === "") {
-			throw new Error("html is not set.");
-		}
-	}
-
-	this.attachShadow({
-		mode: this.getOption("shadowMode", "open"),
-		delegatesFocus: this.getOption("delegatesFocus", true),
-	});
-
-	if (template instanceof Template) {
-		this.shadowRoot.appendChild(template.createDocumentFragment());
-		return this;
-	}
-
-	const mapping = this.getOption("templateMapping", {});
-	if (isObject(mapping)) {
-		html = new Formatter(mapping).format(html);
-	}
-
-	this.shadowRoot.innerHTML = html;
-	return this;
+    let template;
+    let html;
+
+    try {
+        template = findDocumentTemplate(this.constructor.getTag());
+    } catch (e) {
+        html = this.getOption("templates.main", "");
+        if (!isString(html) || html === undefined || html === "") {
+            throw new Error("html is not set.");
+        }
+    }
+
+    this.attachShadow({
+        mode: this.getOption("shadowMode", "open"),
+        delegatesFocus: this.getOption("delegatesFocus", true),
+    });
+
+    if (template instanceof Template) {
+        this.shadowRoot.appendChild(template.createDocumentFragment());
+        return this;
+    }
+
+    const mapping = this.getOption("templateMapping", {});
+    if (isObject(mapping)) {
+        html = new Formatter(mapping).format(html);
+    }
+
+    this.shadowRoot.innerHTML = html;
+    return this;
 }
 
 /**
@@ -1158,20 +1205,20 @@ function initShadowRoot() {
  * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name
  */
 function registerCustomElement(element) {
-	validateFunction(element);
-	const customElements = getGlobalObject("customElements");
-	if (customElements === undefined) {
-		throw new Error("customElements is not supported.");
-	}
-
-	const tag = element?.getTag();
-	if (!isString(tag) || tag === "") {
-		throw new Error("tag is not set.");
-	}
-
-	if (customElements.get(tag) !== undefined) {
-		return;
-	}
-
-	customElements.define(tag, element);
+    validateFunction(element);
+    const customElements = getGlobalObject("customElements");
+    if (customElements === undefined) {
+        throw new Error("customElements is not supported.");
+    }
+
+    const tag = element?.getTag();
+    if (!isString(tag) || tag === "") {
+        throw new Error("tag is not set.");
+    }
+
+    if (customElements.get(tag) !== undefined) {
+        return;
+    }
+
+    customElements.define(tag, element);
 }
-- 
GitLab