Skip to content
Snippets Groups Projects
Select Git revision
  • 4b03c86fb52aabff29ece83f5683e3bcdd6257a8
  • master default protected
  • 1.31
  • 4.24.3
  • 4.24.2
  • 4.24.1
  • 4.24.0
  • 4.23.6
  • 4.23.5
  • 4.23.4
  • 4.23.3
  • 4.23.2
  • 4.23.1
  • 4.23.0
  • 4.22.3
  • 4.22.2
  • 4.22.1
  • 4.22.0
  • 4.21.0
  • 4.20.1
  • 4.20.0
  • 4.19.0
  • 4.18.0
23 results

tabs.mjs

Blame
  • customelement.mjs 27.94 KiB
    /**
     * Copyright schukai GmbH and contributors 2023. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    
    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} from "../types/is.mjs";
    import {Observer} from "../types/observer.mjs";
    import {ProxyObserver} from "../types/proxyobserver.mjs";
    import {validateFunction, validateInstance, validateObject, validateString} from "../types/validate.mjs";
    import {clone} from "../util/clone.mjs";
    import {addAttributeToken, getLinkedObjects, hasObjectLink} from "./attributes.mjs";
    import {
        ATTRIBUTE_DISABLED,
        ATTRIBUTE_ERRORMESSAGE,
        ATTRIBUTE_OPTIONS,
        ATTRIBUTE_OPTIONS_SELECTOR,
        customElementUpdaterLinkSymbol,
    } from "./constants.mjs";
    import {findDocumentTemplate, Template} from "./template.mjs";
    import {addObjectWithUpdaterToElement} from "./updater.mjs";
    import {instanceSymbol} from "../constants.mjs";
    import {getDocumentTranslations, Translations} from "../i18n/translations.mjs";
    
    export {
        CustomElement,
        initMethodSymbol,
        assembleMethodSymbol,
        attributeObserverSymbol,
        registerCustomElement,
        getSlottedElements,
    };
    
    /**
     * @memberOf Monster.DOM
     * @type {symbol}
     */
    const initMethodSymbol = Symbol.for("@schukai/monster/dom/@@initMethodSymbol");
    
    /**
     * @memberOf Monster.DOM
     * @type {symbol}
     */
    const assembleMethodSymbol = Symbol.for("@schukai/monster/dom/@@assembleMethodSymbol");
    
    /**
     * this symbol holds the attribute observer callbacks. The key is the attribute name.
     * @memberOf Monster.DOM
     * @type {symbol}
     */
    const attributeObserverSymbol = Symbol.for("@schukai/monster/dom/@@attributeObserver");
    
    /**
     * HTMLElement
     * @external HTMLElement
     * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
     *
     * @startuml customelement-sequencediagram.png
     * skinparam monochrome true
     * skinparam shadowing false
     *
     * autonumber
     *
     * Script -> DOM: element = document.createElement('my-element')
     * DOM -> CustomElement: constructor()
     * CustomElement -> CustomElement: [initMethodSymbol]()
     *
     * CustomElement --> DOM: Element
     * DOM --> Script : element
     *
     *
     * Script -> DOM: document.querySelector('body').append(element)
     *
     * DOM -> CustomElement : connectedCallback()
     *
     * note right CustomElement: is only called at\nthe first connection
     * CustomElement -> CustomElement : [assembleMethodSymbol]()
     *
     * ... ...
     *
     * autonumber
     *
     * Script -> DOM: document.querySelector('monster-confirm-button').parentNode.removeChild(element)
     * DOM -> CustomElement: disconnectedCallback()
     *
     *
     * @enduml
     *
     * @startuml customelement-class.png
     * skinparam monochrome true
     * skinparam shadowing false
     * HTMLElement <|-- CustomElement
     * @enduml
     */
    
    /**
     * To define a new HTML element we need the power of CustomElement
     *
     * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called
     * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM.
     *
     * <img src="./images/customelement-class.png">
     *
     * You can create the object via the function `document.createElement()`.
     *
     *
     * ## Interaction
     *
     * <img src="./images/customelement-sequencediagram.png">
     *
     * ## Styling
     *
     * For optimal display of custom-elements the pseudo-class :defined can be used.
     *
     * To prevent the custom elements from being displayed and flickering until the control is registered, it is recommended to create a css directive.
     *
     * In the simplest case, you can simply hide the control.
     *
     * ```
     * <style>
     *
     * my-custom-element:not(:defined) {
     *     display: none;
     * }
     *
     * my-custom-element:defined {
     *     display: flex;
     * }
     *
     * </style>
     * ```
     *
     * Alternatively you can also display a loader
     *
     * ```
     * my-custom-element:not(:defined) {
     *            display: flex;
     *            box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15);
     *            border-radius: 4px;
     *            height: 200px;
     *            position: relative;
     *            overflow: hidden;
     *        }
     *
     * my-custom-element:not(:defined)::before {
     *            content: '';
     *            display: block;
     *            position: absolute;
     *            left: -150px;
     *            top: 0;
     *            height: 100%;
     *            width: 150px;
     *            background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%);
     *            animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
     *        }
     *
     * @keyframes load {
     *            from {
     *                left: -150px;
     *            }
     *            to   {
     *                left: 100%;
     *            }
     *        }
     *
     * my-custom-element:defined {
     *           display: flex;
     *       }
     * ```
     *
     * @externalExample ../../example/dom/theme.mjs
     * @see https://github.com/WICG/webcomponents
     * @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements
     * @license AGPLv3
     * @since 1.7.0
     * @copyright schukai GmbH
     * @memberOf Monster.DOM
     * @extends external:HTMLElement
     * @summary A base class for HTML5 customcontrols
     */
    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.
         *
         * @throws {Error} the options attribute does not contain a valid json definition.
         * @since 1.7.0
         */
        constructor() {
            super();
            this[internalSymbol] = new ProxyObserver({
                options: extend({}, this.defaults),
            });
            this[attributeObserverSymbol] = {};
            initOptionObserver.call(this);
            this[initMethodSymbol]();
        }
    
        /**
         * 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");
        }
    
        /**
         * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
         *
         * @return {string[]}
         * @since 1.15.0
         */
        static get observedAttributes() {
            return [ATTRIBUTE_OPTIONS, ATTRIBUTE_DISABLED];
        }
    
        /**
         * Derived classes can override and extend this method as follows.
         *
         * ```
         * get defaults() {
         *    return Object.assign({}, super.defaults, {
         *        myValue:true
         *    });
         * }
         * ```
         *
         * To set the options via the html tag the attribute data-monster-options must be set.
         * As value a JSON object with the desired values must be defined.
         *
         * Since 1.18.0 the JSON can be specified as a DataURI.
         *
         * ```
         * new Monster.Types.DataUrl(btoa(JSON.stringify({
         *        shadowMode: 'open',
         *        delegatesFocus: true,
         *        templates: {
         *            main: undefined
         *        }
         *    })),'application/json',true).toString()
         * ```
         *
         * The attribute data-monster-options-selector can be used to access a script tag that contains additional configuration.
         *
         * As value a selector must be specified, which belongs to a script tag and contains the configuration as json.
         *
         * ```
         * <script id="id-for-this-config" type="application/json">
         *    {
         *        "config-key": "config-value"
         *    }
         * </script>
         * ```
         *
         * The individual configuration values can be found in the table.
         *
         * @property {boolean} disabled=false Object The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form.
         * @property {string} shadowMode=open `open` Elements of the shadow root are accessible from JavaScript outside the root, for example using. `close` Denies access to the node(s) of a closed shadow root from JavaScript outside it
         * @property {Boolean} delegatesFocus=true A boolean that, when set to true, specifies behavior that 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 Templates
         * @property {string} templates.main=undefined Main template
         * @property {Object} templateMapping Template mapping
         *
         * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
         * @since 1.8.0
         */
        get defaults() {
            return {
                ATTRIBUTE_DISABLED: this.getAttribute(ATTRIBUTE_DISABLED),
                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;
            }
    
            let 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;
        }
    
        /**
         * There is no check on the name by this class. the developer is responsible for assigning an appropriate tag.
         * if the name is not valid, registerCustomElement() will issue an error
         *
         * @link https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
         * @return {string}
         * @throws {Error} the method getTag must be overwritten by the derived class.
         * @since 1.7.0
         */
        static getTag() {
            throw new Error("the method getTag must be overwritten by the derived class.");
        }
    
    
        /**
         * At this point a `CSSStyleSheet` object can be returned. If the environment does not
         * support a constructor, then an object can also be built using the following detour.
         *
         * If `undefined` is returned then the shadowRoot does not get a stylesheet.
         *
         * ```
         * const doc = document.implementation.createHTMLDocument('title');
         *
         * let style = doc.createElement("style");
         * style.innerHTML="p{color:red;}";
         *
         * // WebKit Hack
         * style.appendChild(document.createTextNode(""));
         * // Add the <style> element to the page
         * doc.head.appendChild(style);
         * return doc.styleSheets[0];
         * ;
         * ```
         *
         * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined}
         */
        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) {
            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);
            }
    
            const self = this;
            extend(self[internalSymbol].getSubject()["options"], self.defaults, options);
    
            return self;
        }
    
        /**
         * Is called once via the constructor
         *
         * @return {CustomElement}
         * @since 1.8.0
         */
        [initMethodSymbol]() {
            return this;
        }
    
        /**
         * Is called once when the object is included in the DOM for the first time.
         *
         * @return {CustomElement}
         * @since 1.8.0
         */
        [assembleMethodSymbol]() {
            const self = this;
            let elements;
            let nodeList;
    
            const AttributeOptions = getOptionsFromAttributes.call(self);
            if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) {
                self.setOptions(AttributeOptions);
            }
    
            const ScriptOptions = getOptionsFromScriptTag.call(self);
            if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
                self.setOptions(ScriptOptions);
            }
    
            if (self.getOption("shadowMode", false) !== false) {
                try {
                    initShadowRoot.call(self);
                    elements = self.shadowRoot.childNodes;
                } catch (e) {
                }
    
                try {
                    initCSSStylesheet.call(this);
                } catch (e) {
                    addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
                }
            }
    
            if (!(elements instanceof NodeList)) {
                if (!(elements instanceof NodeList)) {
                    initHtmlContent.call(this);
                    elements = this.childNodes;
                }
            }
    
            try {
                nodeList = new Set([...elements, ...getSlottedElements.call(self)]);
            } catch (e) {
                nodeList = elements;
            }
    
            addObjectWithUpdaterToElement.call(
                self,
                nodeList,
                customElementUpdaterLinkSymbol,
                clone(self[internalSymbol].getRealSubject()["options"]),
            );
            return self;
        }
    
        /**
         * Called every time the element is inserted into the DOM. Useful for running setup code, such as
         * fetching resources or rendering. Generally, you should try to delay work until this time.
         *
         * @return {void}
         * @since 1.7.0
         */
        connectedCallback() {
            let self = this;
            if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
                self[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) {
            const self = this;
    
            const callback = self[attributeObserverSymbol]?.[attrName];
    
            if (isFunction(callback)) {
                callback.call(self, newVal, oldVal);
            }
        }
    
        /**
         *
         * @param {Node} node
         * @return {boolean}
         * @throws {TypeError} value is not an instance of
         * @since 1.19.0
         */
        hasNode(node) {
            const self = this;
    
            if (containChildNode.call(self, validateInstance(node, Node))) {
                return true;
            }
    
            if (!(self.shadowRoot instanceof ShadowRoot)) {
                return false;
            }
    
            return containChildNode.call(self.shadowRoot, node);
        }
    }
    
    /**
     * @private
     * @param {String|undefined} query
     * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.)
     * @return {*}
     * @this CustomElement
     * @license AGPLv3
     * @since 1.23.0
     * @throws {Error} query must be a string
     */
    function getSlottedElements(query, name) {
        const self = this;
        const result = new Set();
    
        if (!(self.shadowRoot instanceof ShadowRoot)) {
            return result;
        }
    
        let selector = "slot";
        if (name !== undefined) {
            if (name === null) {
                selector += ":not([name])";
            } else {
                selector += `[name=${validateString(name)}]`;
            }
        }
    
        const slots = self.shadowRoot.querySelectorAll(selector);
    
        for (const [, slot] of Object.entries(slots)) {
            slot.assignedElements().forEach(function (node) {
                if (!(node instanceof HTMLElement)) return;
    
                if (isString(query)) {
                    node.querySelectorAll(query).forEach(function (n) {
                        result.add(n);
                    });
    
                    if (node.matches(query)) {
                        result.add(node);
                    }
                } else if (query !== undefined) {
                    throw new Error("query must be a string");
                } else {
                    result.add(node);
                }
            });
        }
    
        return result;
    }
    
    /**
     * @this CustomElement
     * @private
     * @param {Node} node
     * @return {boolean}
     */
    function containChildNode(node) {
        const self = this;
    
        if (self.contains(node)) {
            return true;
        }
    
        for (const [, e] of Object.entries(self.childNodes)) {
            if (e.contains(node)) {
                return true;
            }
    
            containChildNode.call(e, node);
        }
    
        return false;
    }
    
    /**
     * @license AGPLv3
     * @since 1.15.0
     * @private
     * @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) {
                        let 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);
            }
        };
    }
    
    /**
     * @private
     * @return {object}
     * @throws {TypeError} value is not a object
     */
    function getOptionsFromScriptTag() {
        const self = this;
    
        if (!self.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
            return {};
        }
    
        const node = document.querySelector(self.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR));
        if (!(node instanceof HTMLScriptElement)) {
            addAttributeToken(
                self,
                ATTRIBUTE_ERRORMESSAGE,
                `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${self.getAttribute(
                    ATTRIBUTE_OPTIONS_SELECTOR,
                )}) but not found.`,
            );
            return {};
        }
    
        let obj = {};
    
        try {
            obj = parseOptionsJSON.call(this, node.textContent.trim());
        } catch (e) {
            addAttributeToken(
                self,
                ATTRIBUTE_ERRORMESSAGE,
                `when analyzing the configuration from the script tag there was an error. ${e}`,
            );
        }
    
        return obj;
    }
    
    /**
     * @private
     * @return {object}
     */
    function getOptionsFromAttributes() {
        const self = this;
    
        if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
            try {
                return parseOptionsJSON.call(self, this.getAttribute(ATTRIBUTE_OPTIONS));
            } catch (e) {
                addAttributeToken(
                    self,
                    ATTRIBUTE_ERRORMESSAGE,
                    `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
                        ATTRIBUTE_OPTIONS,
                    )}).${e}`,
                );
            }
        }
    
        return {};
    }
    
    /**
     * @private
     * @param data
     * @return {Object}
     */
    function parseOptionsJSON(data) {
        let obj = {};
    
        if (!isString(data)) {
            return obj;
        }
    
        // the configuration can be specified as a data url.
        try {
            let dataUrl = parseDataURL(data);
            data = dataUrl.content;
        } catch (e) {
        }
    
        try {
            obj = JSON.parse(data);
        } catch (e) {
            throw e;
        }
    
        return validateObject(obj);
    }
    
    /**
     * @private
     * @return {initHtmlContent}
     */
    function initHtmlContent() {
        try {
            let 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;
    }
    
    /**
     * @private
     * @return {CustomElement}
     * @memberOf Monster.DOM
     * @this CustomElement
     * @license AGPLv3
     * @since 1.16.0
     * @throws {TypeError} value is not an instance of
     */
    function initCSSStylesheet() {
        const self = this;
    
        if (!(this.shadowRoot instanceof ShadowRoot)) {
            return self;
        }
    
        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 (let s of styleSheet) {
                if (isString(s)) {
                    let trimedStyleSheet = s.trim();
                    if (trimedStyleSheet !== "") {
                        const style = document.createElement("style");
                        style.innerHTML = trimedStyleSheet;
                        self.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)) {
            let trimedStyleSheet = styleSheet.trim();
            if (trimedStyleSheet !== "") {
                const style = document.createElement("style");
                style.innerHTML = styleSheet;
                self.shadowRoot.prepend(style);
            }
        }
    
        return self;
    }
    
    /**
     * @private
     * @return {CustomElement}
     * @throws {Error} html is not set.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
     * @memberOf Monster.DOM
     * @license AGPLv3
     * @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;
    }
    
    /**
     * This method registers a new element. The string returned by `CustomElement.getTag()` is used as the tag.
     *
     * @param {CustomElement} element
     * @return {void}
     * @license AGPLv3
     * @since 1.7.0
     * @copyright schukai GmbH
     * @memberOf Monster.DOM
     * @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.");
        }
    
        if (customElements.get(element.getTag()) !== undefined) {
            return;
        }
    
        customElements.define(element.getTag(), element);
    }