diff --git a/development/issues/closed/217.html b/development/issues/closed/217.html
index 8b88b8bdb22e407aa8b4668533d1ccdac4f429ad..a663753d2def72cfb6630a8254e46508f7b59130 100644
--- a/development/issues/closed/217.html
+++ b/development/issues/closed/217.html
@@ -1,71 +1,83 @@
-        <!DOCTYPE html>
-        <html lang="en">
-        <head>
-            <meta charset="UTF-8">
-            <meta name="viewport" content="width=device-width, initial-scale=1.0">
-            <title>only try to transfer the part that has been changed #217</title>
-            <script src="./217.mjs" type="module"></script>
-        </head>
-        <body>
-            <h1>only try to transfer the part that has been changed #217</h1>
-            <p></p>
-            <ul>
-                <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/217">Issue #217</a></li>
-                <li><a href="/">Back to overview</a></li>
-            </ul>
-            <main>
-
-                <monster-datasource-rest id="ds210"
-                                         data-monster-option-read-url="/issue-217.json"
-                                         data-monster-option-write-url="/issue-217"
-                                         data-monster-option-write-acceptedstatus="400::200"
-                                         data-monster-option-features-autoinit="true">
-                </monster-datasource-rest>
-
-                <monster-dataset data-monster-option-index="0"
-                                 data-monster-option-datasource-selector="#ds210"
-                                 data-monster-option-mapping-data="">
-                    ID:
-                    <div data-monster-replace="path:data.id"></div>
-                    <div data-monster-replace="path:data.name"></div>
-                </monster-dataset>
-
-
-                <monster-form data-monster-option-datasource-selector="#ds210"
-                              data-monster-option-mapping-data=""
-                              data-monster-option-features-mutationobserver="true"
-                >
-
-                    <monster-field-set data-monster-option-labels-title="my title">
-
-                        <label for="id">field id</label><input id="id" type="number"
-                                                               data-monster-attributes="value path:data.id"
-                                                               data-monster-bind="path:data.id">
-
-                        <label for="field1">field id</label><input id="field1"
-                                                               data-monster-attributes="value path:data.field1"
-                                                               data-monster-bind="path:data.field1">
-
-                        <label for="field2">field id</label><input id="field2"
-                                                               data-monster-attributes="value path:data.field2"
-                                                               data-monster-bind="path:data.field2">
-
-                        <label for="field3">field id</label><input id="field3"
-                                                               data-monster-attributes="value path:data.field3"
-                                                               data-monster-bind="path:data.field3">
-
-                        
-                    </monster-field-set>
-                    <monster-field-set data-monster-option-labels-title="">
-
-                        <monster-datasource-save-button data-monster-option-datasource-selector="#ds210">ok
-                        </monster-datasource-save-button>
-
-                    </monster-field-set>
-
-
-                </monster-form>
-
-            </main>
-        </body>
-        </html>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>only try to transfer the part that has been changed #217</title>
+    <script src="./217.mjs" type="module"></script>
+</head>
+<body>
+<h1>only try to transfer the part that has been changed #217</h1>
+<p></p>
+<ul>
+    <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/217">Issue #217</a></li>
+    <li><a href="/">Back to overview</a></li>
+</ul>
+<main>
+
+    <monster-datasource-rest id="ds210"
+                             data-monster-option-read-url="/issue-217.json"
+                             data-monster-option-write-url="/issue-217"
+                             data-monster-option-write-acceptedstatus="400::200"
+                             data-monster-option-features-autoinit="true">
+    </monster-datasource-rest>
+
+    <monster-dataset data-monster-option-index="0"
+                     data-monster-option-datasource-selector="#ds210"
+                     data-monster-option-mapping-data="">
+        ID:
+        <div data-monster-replace="path:data.id"></div>
+        <div data-monster-replace="path:data.name"></div>
+        <div data-monster-replace="path:data.field4"></div>
+    </monster-dataset>
+
+
+    <monster-form data-monster-option-datasource-selector="#ds210"
+                  data-monster-option-mapping-data=""
+                  data-monster-option-features-mutationobserver="true"
+    >
+
+        <monster-field-set data-monster-option-labels-title="my title">
+
+            <label for="id">field id</label><input id="id" type="number"
+                                                   data-monster-attributes="value path:data.id"
+                                                   data-monster-bind="path:data.id">
+
+            <label for="field1">field id</label><input id="field1"
+                                                       data-monster-attributes="value path:data.field1"
+                                                       data-monster-bind="path:data.field1">
+
+            <label for="field2">field id</label><input id="field2"
+                                                       data-monster-attributes="value path:data.field2"
+                                                       data-monster-bind="path:data.field2">
+
+            <label for="field3">field id</label><input id="field3"
+                                                       data-monster-attributes="value path:data.field3"
+                                                       data-monster-bind="path:data.field3">
+
+            <label for="field3">field id</label>
+            <monster-select id="field4"
+                            data-monster-option-type="checkbox"
+                            data-monster-attributes="value path:data.field4"
+                            data-monster-bind="path:data.field4">
+                <div data-monster-value="a">a</div>
+                <div data-monster-value="b">b</div>
+                <div data-monster-value="c">c</div>
+                <div data-monster-value="d">d</div>
+            </monster-select>
+
+
+        </monster-field-set>
+        <monster-field-set data-monster-option-labels-title="">
+
+            <monster-datasource-save-button data-monster-option-datasource-selector="#ds210">ok
+            </monster-datasource-save-button>
+
+        </monster-field-set>
+
+
+    </monster-form>
+
+</main>
+</body>
+</html>
diff --git a/development/issues/closed/217.mjs b/development/issues/closed/217.mjs
index c9737abad038f65c70bbe0accad1b6356f40cf12..eeb11897b9f134d0ab779683a32442139b5529b1 100644
--- a/development/issues/closed/217.mjs
+++ b/development/issues/closed/217.mjs
@@ -22,6 +22,12 @@ import {domReady} from "../../../source/dom/ready.mjs";
 
 domReady.then(() => {
 
+    const element = document.getElementById("field4");
+    console.log(element);
+    setTimeout(() => {
+        element.value = "x";
+    },100);
+    element.value = "x";
 
 
 });
diff --git a/development/issues/open/289.html b/development/issues/open/289.html
new file mode 100644
index 0000000000000000000000000000000000000000..ee1b3888dc46b51bfc634b73b634150b98d4247b
--- /dev/null
+++ b/development/issues/open/289.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>function for tags in datatable #289</title>
+    <script src="./289.mjs" type="module"></script>
+</head>
+<body>
+    <h1>function for tags in datatable #289</h1>
+    <p></p>
+    <ul>
+        <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/289">Issue #289</a></li>
+        <li><a href="/">Back to overview</a></li>
+    </ul>
+    <main>
+
+        <main>
+
+            <monster-host><monster-config-manager></monster-config-manager></monster-host>
+            <monster-notify data-monster-option-orientation="bottom right"></monster-notify>
+            <monster-monitor-attribute-errors
+                    data-monster-option-features-notifyUser="false"
+            ></monster-monitor-attribute-errors>
+
+
+            <h2>Control with DOM datasource</h2>
+
+            <monster-datasource-rest id="ds277"
+                                     data-monster-option-features-autoInit="true"
+                                     data-monster-option-write-url="/issue-274.json"
+                                     data-monster-option-read-url="/issue-274.json?limit=5&page=${page}&q=${query}&order=${order}"
+                                     data-monster-option-filter-id="ds277-filter"
+                                     data-monster-option-features-filter="true"
+            ></monster-datasource-rest>
+
+
+            <monster-datatable data-monster-datasource-selector="#ds277" id="dt277"
+                               data-monster-option-datasource-orderdelimiter="::">
+
+                <monster-collapse id="filter-collapse" data-monster-role="filter-collapse">
+                    <div class="flex">
+
+                        <monster-tabs style="width: 100%"
+                                      data-monster-option-features-opendelay="500"
+                                      data-monster-option-classes-navigation="monster-theme-background-inherit"
+                                      data-monster-option-classes-button="monster-theme-background-inherit" id="filtertabs">
+                            <div data-monster-button-label="Filter" data-monster-state="active" class="active">
+
+                                <monster-datatable-filter id="ds277-filter" slot="filter"
+                                                          data-monster-option-storedconfig-selector="#filtertabs">
+
+                                    <label data-monster-label="ID" id="id"
+                                           data-monster-template="${value | call:range:id}">
+                                        ID
+                                        <monster-filter-range></monster-filter-range>
+                                    </label>
+
+                                    <label data-monster-label="Fullname" id="fullname"
+                                           data-monster-template="${value}">
+                                        ID
+                                        <monster-filter-input></monster-filter-input>
+                                    </label>
+
+                                </monster-datatable-filter>
+                            </div>
+                        </monster-tabs>
+                    </div>
+                </monster-collapse>
+
+                <div slot="bar" class="monster-button-group">
+
+
+                    <monster-embedded-pagination data-monster-datasource-selector="#ds277"></monster-embedded-pagination>
+                    <monster-datasource-status
+                            data-monster-option-datasource-selector="#ds277"></monster-datasource-status>
+
+                    <monster-datatable-filter-button data-monster-reference="#filter-collapse"
+                                                     data-monster-role="filter-button">Filter
+                    </monster-datatable-filter-button>
+                </div>
+
+                <template id="dt277-row">
+                    <div data-monster-order-template="${field}=${direction}" data-monster-sortable="id" data-monster-grid-template="3rem" data-monster-mode="fixed" data-monster-head="id" data-monster-replace="path:dt277-row.id"></div>
+                    <div data-monster-grid-template="2rem" data-monster-mode="fixed" data-monster-features="select"></div>
+                    <div data-monster-head="username" data-monster-replace="path:dt277-row.username"></div>
+                    <div data-monster-head="email" data-monster-replace="path:dt277-row.email"></div>
+                    <div data-monster-sortable="full_name" data-monster-head="full_name" data-monster-replace="path:dt277-row.full_name"></div>
+                    <div data-monster-head="age" data-monster-replace="path:dt277-row.age"></div>
+                    <div data-monster-head="country" data-monster-replace="path:dt277-row.country"></div>
+                    <div data-monster-head="registered_date" data-monster-replace="path:dt277-row.registered_date"></div>
+                    <div data-monster-head="status" data-monster-replace="path:dt277-row.status | call:badge-column"></div>
+                </template>
+
+
+            </monster-datatable>
+        </main>
+
+    </main>
+</body>
+</html>
diff --git a/development/issues/open/289.mjs b/development/issues/open/289.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..b9c9a142c481979ed7dd9bbb361b1934c66c82f9
--- /dev/null
+++ b/development/issues/open/289.mjs
@@ -0,0 +1,54 @@
+/**
+* @file development/issues/open/289.mjs
+* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/289
+* @description function for tags in datatable
+* @issue 289
+*/
+
+import "../../../source/components/style/property.pcss";
+import "../../../source/components/style/link.pcss";
+import "../../../source/components/style/color.pcss";
+import "../../../source/components/style/theme.pcss";
+import "../../../source/components/style/normalize.pcss";
+import "../../../source/components/style/typography.pcss";
+import "../../../source/components/datatable/datasource/rest.mjs";
+import "../../../source/components/datatable/datasource/dom.mjs";
+import "../../../source/components/datatable/dataset.mjs";
+import "../../../source/components/datatable/status.mjs";
+import "../../../source/components/datatable/change-button.mjs";
+import "../../../source/components/datatable/save-button.mjs";
+import "../../../source/components/datatable/pagination.mjs";
+import "../../../source/components/datatable/embedded-pagination.mjs";
+import "../../../source/components/datatable/filter.mjs";
+import "../../../source/components/form/button.mjs";
+import "../../../source/components/form/form.mjs";
+import "../../../source/components/form/field-set.mjs";
+import "../../../source/components/form/button-bar.mjs";
+import "../../../source/components/form/toggle-switch.mjs";
+import "../../../source/components/form/context-help.mjs";
+import "../../../source/components/form/context-error.mjs";
+import "../../../source/components/form/input-group.mjs";
+import "../../../source/components/datatable/filter/input.mjs";
+import "../../../source/components/datatable/filter/date-range.mjs";
+import "../../../source/components/datatable/filter/range.mjs";
+import "../../../source/components/layout/tabs.mjs";
+import "../../../source/components/datatable/datatable.mjs";
+import "../../../source/components/notify/monitor-attribute-errors.mjs";
+
+//const actionButton = document.getElementById("action-button-1")
+const datatable = document.getElementById("dt277");
+
+const eventHandler = function () {
+    const selected = datatable.getSelectedRows();
+
+    // if (selected.length === 0) {
+    //     actionButton.style.visibility = "hidden";
+    // } else {
+    //     actionButton.style.visibility = "visible";
+    // }
+
+    console.log("issue 227 selected: "+selected.join(", "));
+}
+
+datatable.addEventListener("monster-datatable-selection-changed", eventHandler);
+
diff --git a/development/mock/issue-217.js b/development/mock/issue-217.js
index d3af360ed74fe4c3da01186f59eeb8ce37996272..db4c8ea0b18dea4b3a6a5432f8fb665fd9d9f466 100644
--- a/development/mock/issue-217.js
+++ b/development/mock/issue-217.js
@@ -4,13 +4,15 @@ const json =
                     "id": 1000,
                     "field1": "dataset 1, value field 1",
                     "field2": "dataset 1, value field 2",
-                    "field3": "dataset 1, value field 3"
+                    "field3": "dataset 1, value field 3",
+                    "field4": "a,c,d"
                 },
                "1": {
                     "id": 1001,
                     "field1": "dataset 2, value field 1",
                     "field2": "dataset 2, value field 2",
-                    "field3": "dataset 2, value field 3"
+                    "field3": "dataset 2, value field 3",
+                    "field4": "a,b"
                 }
                
             }`;
diff --git a/development/templates/vite.config.mjs b/development/templates/vite.config.mjs
index 67f59bf675f621f50107c701bb39f0e6f9f536c2..6ffc14a9a58d088e8a46f8874a7582d40711daec 100644
--- a/development/templates/vite.config.mjs
+++ b/development/templates/vite.config.mjs
@@ -88,7 +88,7 @@ export default defineConfig({
     },
 
     server: {
-        port: 8443,
+        port: 8440,
         host: "localhost.alvine.dev",
         https: {
             key: "${LOCALHOST_CERTS_DIR}/localhost.alvine.dev.key",
diff --git a/source/components/accessibility/locale-picker.mjs b/source/components/accessibility/locale-picker.mjs
index 1ee50490aed4481d80c730632f7db6d1ae582b14..38e67fe223741f8d4d898808a253e05a7dbc86c9 100644
--- a/source/components/accessibility/locale-picker.mjs
+++ b/source/components/accessibility/locale-picker.mjs
@@ -75,7 +75,7 @@ const detectedLanguagesSymbol = Symbol("detectedLanguages");
  * @example /examples/components/accessibility/locale-picker-simple Simple example
  * @example /examples/components/accessibility/locale-picker-reset Reset Selection
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/276.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/276.html
  *
  * @since 3.97.0
  * @copyright schukai GmbH
diff --git a/source/components/host/style/viewer.pcss b/source/components/content/style/viewer.pcss
similarity index 100%
rename from source/components/host/style/viewer.pcss
rename to source/components/content/style/viewer.pcss
diff --git a/source/components/host/stylesheet/viewer.mjs b/source/components/content/stylesheet/viewer.mjs
similarity index 100%
rename from source/components/host/stylesheet/viewer.mjs
rename to source/components/content/stylesheet/viewer.mjs
diff --git a/source/components/content/viewer.mjs b/source/components/content/viewer.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..917d558ab6b65f603a9a828ae53bcce77ba53749
--- /dev/null
+++ b/source/components/content/viewer.mjs
@@ -0,0 +1,289 @@
+/**
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
+ * Node module: @schukai/monster
+ *
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
+ *
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
+ *
+ * SPDX-License-Identifier: AGPL-3.0
+ */
+
+import {
+	assembleMethodSymbol,
+	CustomElement,
+	registerCustomElement,
+} from "../../dom/customelement.mjs";
+import "../notify/notify.mjs";
+import { ViewerStyleSheet } from "./stylesheet/viewer.mjs";
+import { instanceSymbol } from "../../constants.mjs";
+import { isString } from "../../types/is.mjs";
+import { getGlobal } from "../../types/global.mjs";
+
+export { Viewer };
+
+/**
+ * @private
+ * @type {symbol}
+ */
+const viewerElementSymbol = Symbol("viewerElement");
+
+/**
+ * The Viewer component is used to show a PDF, HTML or Image.
+ *
+ * @fragments /fragments/components/content/viewer
+ *
+ * @example /examples/components/content/pdf-viewer with a PDF
+ * @example /examples/components/content/image-viewer with an image
+ * @example /examples/components/content/html-viewer with HTML content
+ *
+ * @copyright schukai GmbH
+ * @summary A simple viewer component for PDF, HTML and images.
+ */
+class Viewer extends CustomElement {
+	/**
+	 * This method is called by the `instanceof` operator.
+	 * @return {symbol}
+	 */
+	static get [instanceSymbol]() {
+		return Symbol.for("@schukai/monster/components/content/viewer@@instance");
+	}
+
+	/**
+	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
+	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
+	 *
+	 * The individual configuration values can be found in the table.
+	 *
+	 * @property {Object} templates Template definitions
+	 * @property {string} templates.main Main template
+	 * @property {Object} classes Css classes
+	 * @property {Object} features Feature definitions
+	 */
+	get defaults() {
+		return Object.assign({}, super.defaults, {
+			templates: {
+				main: getTemplate(),
+			},
+			content: "<slot></slot>",
+			classes: {
+				viewer: "",
+			},
+			features: {},
+		});
+	}
+
+	/**
+	 *
+	 * @param {string} content
+	 * @returns {Viewer}
+	 */
+	setContent(content) {
+		this.setOption("content", content);
+		return this;
+	}
+
+	/**
+	 *
+	 * @param {Blob|URL|string} data
+	 * @param {boolean} navigation
+	 * @param {boolean} toolbar
+	 * @param {boolean} scrollbar
+	 */
+	setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
+		const hashes =
+			"#toolbar=" +
+			(toolbar ? "1" : "0") +
+			"&navpanes=" +
+			(navigation ? "1" : "0") +
+			"&scrollbar=" +
+			(scrollbar ? "1" : "0");
+
+		let pdfContent = "";
+		if (isBlob(data)) {
+			pdfContent = URL.createObjectURL(data);
+			pdfContent += hashes;
+		} else if (isURL(data)) {
+			pdfContent = data;
+			// check if the url already contains the hashes
+			if (pdfContent.indexOf("#") === -1) {
+				pdfContent += hashes;
+			}
+		} else if (isString(data)) {
+			//URL.createObjectURL(data);
+			const blobObj = new Blob([atob(data)], { type: "application/pdf" });
+			const url = window.URL.createObjectURL(blobObj);
+
+			pdfContent = data;
+		} else {
+			throw new Error("Blob or URL expected");
+		}
+
+		const html =
+			'<object data="' +
+			pdfContent +
+			'" width="100%" height="100%" type="application/pdf"></object>';
+		this.setContent(html);
+	}
+
+	/**
+	 *
+	 * @param {Blob|URL|string} data
+	 */
+	setImage(data) {
+		if (isBlob(data)) {
+			data = URL.createObjectURL(data);
+		} else if (isURL(data)) {
+			// nothing to do
+		} else if (isString(data)) {
+			// nothing to do
+		} else {
+			throw new Error("Blob or URL expected");
+		}
+
+		const html = '<img src="' + data + '" alt="image" />';
+		this.setContent(html);
+	}
+
+	/**
+	 *
+	 * if the data is a string, it is interpreted as HTML.
+	 * if the data is an url, the HTML is loaded from the url and set as content.
+	 * if the data is an HTMLElement, the outerHTML is used as content.
+	 *
+	 * @param {HTMLElement|URL|string|Blob} data
+	 */
+	setHTML(data) {
+		if (data instanceof Blob) {
+			blobToText(data)
+				.then((html) => {
+					this.setHTML(html);
+				})
+				.catch((error) => {
+					throw new Error(error);
+				});
+
+			return;
+		} else if (data instanceof HTMLElement) {
+			data = data.outerHTML;
+		} else if (isString(data)) {
+			// nothing to do
+		} else if (isURL(data)) {
+			// fetch element
+			getGlobal()
+				.fetch(data)
+				.then((response) => {
+					return response.text();
+				})
+				.then((html) => {
+					this.setHTML(html);
+				})
+				.catch((error) => {
+					throw new Error(error);
+				});
+		} else {
+			throw new Error("HTMLElement or string expected");
+		}
+
+		this.setContent(data);
+	}
+
+	/**
+	 *
+	 * @return {Viewer}
+	 */
+	[assembleMethodSymbol]() {
+		super[assembleMethodSymbol]();
+
+		initControlReferences.call(this);
+		initEventHandler.call(this);
+	}
+
+	/**
+	 *
+	 * @return {string}
+	 */
+	static getTag() {
+		return "monster-viewer";
+	}
+
+	/**
+	 * @return {CSSStyleSheet[]}
+	 */
+	static getCSSStyleSheet() {
+		return [ViewerStyleSheet];
+	}
+}
+
+/**
+ * @private
+ * @param variable
+ * @return {boolean}
+ */
+function isURL(variable) {
+	try {
+		new URL(variable);
+		return true;
+	} catch (error) {
+		return false;
+	}
+}
+
+/**
+ * @private
+ * @param variable
+ * @return {boolean}
+ */
+function isBlob(variable) {
+	return variable instanceof Blob;
+}
+
+/**
+ * @private
+ * @param blob
+ * @return {Promise<unknown>}
+ */
+function blobToText(blob) {
+	return new Promise((resolve, reject) => {
+		const reader = new FileReader();
+		reader.onloadend = () => resolve(reader.result);
+		reader.onerror = reject;
+		reader.readAsText(blob);
+	});
+}
+
+/**
+ * @private
+ * @return {Select}
+ * @throws {Error} no shadow-root is defined
+ */
+function initControlReferences() {
+	if (!this.shadowRoot) {
+		throw new Error("no shadow-root is defined");
+	}
+
+	this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
+}
+
+/**
+ * @private
+ */
+function initEventHandler() {
+	return this;
+}
+
+/**
+ * @private
+ * @return {string}
+ */
+function getTemplate() {
+	// language=HTML
+	return `
+        <div id="viewer" data-monster-role="viewer" part="viewer" data-monster-replace="path:content" data-monster-attributes="class path:classes.viewer">
+        </div>`;
+}
+
+registerCustomElement(Viewer);
diff --git a/source/components/datatable/change-button.mjs b/source/components/datatable/change-button.mjs
index 4fc97a164fadd67a69ae347e939694a07b8facce..246692002da5fa3e15810fbc30bebb0da42510b0 100644
--- a/source/components/datatable/change-button.mjs
+++ b/source/components/datatable/change-button.mjs
@@ -53,7 +53,7 @@ const overlayLinkedElementSymbol = Symbol("overlayLinkedElement");
  *
  * @example /examples/components/datatable/change-button-simple Simple change button
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html
  *
  * @copyright schukai GmbH
  * @summary The Status component is used to show the current status of a datasource.
diff --git a/source/components/datatable/dataset.mjs b/source/components/datatable/dataset.mjs
index e534c59b08c86d4a835d35f1acfd1d15000c901c..c0409b689b0e91c9ae574e512746dbe7b287961b 100644
--- a/source/components/datatable/dataset.mjs
+++ b/source/components/datatable/dataset.mjs
@@ -50,7 +50,7 @@ export { DataSet };
  * @example /examples/components/datatable/dataset-dom Dom dataset
  * @example /examples/components/datatable/dataset-rest Rest dataset
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html
  *
  * @copyright schukai GmbH
  * @summary A dataset component that can be used to show the data of a data source
diff --git a/source/components/datatable/datasource.mjs b/source/components/datatable/datasource.mjs
index 32990568459893708d2683b90e2c0535c537191d..0688de399b84658d7be3bc9fc31e42cf5e7a24e0 100644
--- a/source/components/datatable/datasource.mjs
+++ b/source/components/datatable/datasource.mjs
@@ -36,7 +36,7 @@ const dataSourceSymbol = Symbol.for(
  *
  * @example /examples/components/datatable/datasource
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html
  *
  * @copyright schukai GmbH
  * @summary A generic datasource
diff --git a/source/components/datatable/datasource/rest.mjs b/source/components/datatable/datasource/rest.mjs
index 6d9b124cd0cddce3ec62f77d005593b2287431c4..8e1644ecde73b870209e0f3e2e92e8a8fd584375 100644
--- a/source/components/datatable/datasource/rest.mjs
+++ b/source/components/datatable/datasource/rest.mjs
@@ -70,7 +70,7 @@ const filterObserverSymbol = Symbol("filterObserver");
  * @example /examples/components/datatable/datasource-rest-auto-init Auto init
  * @example /examples/components/datatable/datasource-rest-do-fetch Rest datasource with fetch
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html
  *
  * @copyright schukai GmbH
  * @summary A rest api datasource for the datatable or other components
diff --git a/source/components/datatable/datatable.mjs b/source/components/datatable/datatable.mjs
index 8b3f2eaa77866e6b5bc7228b160e9bc5434bddac..745a907a2984232d35cbc83701efe97b81c653fb 100644
--- a/source/components/datatable/datatable.mjs
+++ b/source/components/datatable/datatable.mjs
@@ -12,79 +12,78 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import { Datasource } from "./datasource.mjs";
+import {Datasource} from "./datasource.mjs";
 import {
-	assembleMethodSymbol,
-	CustomElement,
-	registerCustomElement,
-	getSlottedElements,
+    assembleMethodSymbol,
+    CustomElement,
+    registerCustomElement,
+    getSlottedElements, updaterTransformerMethodsSymbol,
 } from "../../dom/customelement.mjs";
 import {
-	findTargetElementFromEvent,
-	fireCustomEvent,
+    findTargetElementFromEvent,
+    fireCustomEvent,
 } from "../../dom/events.mjs";
-import { clone } from "../../util/clone.mjs";
+import {clone} from "../../util/clone.mjs";
 import {
-	isString,
-	isFunction,
-	isInstance,
-	isObject,
-	isArray,
+    isString,
+    isFunction,
+    isInstance,
+    isObject,
+    isArray,
 } from "../../types/is.mjs";
 import {
-	validateArray,
-	validateInteger,
-	validateObject,
+    validateArray,
+    validateInteger,
+    validateObject,
 } from "../../types/validate.mjs";
-import { Observer } from "../../types/observer.mjs";
+import {Observer} from "../../types/observer.mjs";
 import {
-	ATTRIBUTE_DATATABLE_HEAD,
-	ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
-	ATTRIBUTE_DATASOURCE_SELECTOR,
-	ATTRIBUTE_DATATABLE_ALIGN,
-	ATTRIBUTE_DATATABLE_SORTABLE,
-	ATTRIBUTE_DATATABLE_MODE,
-	ATTRIBUTE_DATATABLE_INDEX,
-	ATTRIBUTE_DATATABLE_MODE_HIDDEN,
-	ATTRIBUTE_DATATABLE_FEATURES,
-	ATTRIBUTE_DATATABLE_MODE_VISIBLE,
-	ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
-	ATTRIBUTE_DATATABLE_MODE_FIXED,
+    ATTRIBUTE_DATATABLE_HEAD,
+    ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
+    ATTRIBUTE_DATASOURCE_SELECTOR,
+    ATTRIBUTE_DATATABLE_ALIGN,
+    ATTRIBUTE_DATATABLE_SORTABLE,
+    ATTRIBUTE_DATATABLE_MODE,
+    ATTRIBUTE_DATATABLE_INDEX,
+    ATTRIBUTE_DATATABLE_MODE_HIDDEN,
+    ATTRIBUTE_DATATABLE_FEATURES,
+    ATTRIBUTE_DATATABLE_MODE_VISIBLE,
+    ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
+    ATTRIBUTE_DATATABLE_MODE_FIXED,
 } from "./constants.mjs";
-import { instanceSymbol } from "../../constants.mjs";
+import {instanceSymbol} from "../../constants.mjs";
 import {
-	Header,
-	createOrderStatement,
-	DIRECTION_ASC,
-	DIRECTION_DESC,
-	DIRECTION_NONE,
+    Header,
+    createOrderStatement,
+    DIRECTION_ASC,
+    DIRECTION_DESC,
+    DIRECTION_NONE,
 } from "./datatable/header.mjs";
-import { DatatableStyleSheet } from "./stylesheet/datatable.mjs";
+import {DatatableStyleSheet} from "./stylesheet/datatable.mjs";
 import {
-	handleDataSourceChanges,
-	datasourceLinkedElementSymbol,
+    handleDataSourceChanges,
+    datasourceLinkedElementSymbol,
 } from "./util.mjs";
 import "./columnbar.mjs";
 import "./filter-button.mjs";
 import {
-	findElementWithSelectorUpwards,
-	getDocument,
-	getWindow,
+    findElementWithSelectorUpwards,
+    getWindow,
 } from "../../dom/util.mjs";
 
-import { getDocumentTranslations } from "../../i18n/translations.mjs";
+import {getDocumentTranslations} from "../../i18n/translations.mjs";
 import "../state/state.mjs";
 import "../host/collapse.mjs";
-import { generateUniqueConfigKey } from "../host/util.mjs";
+import {generateUniqueConfigKey} from "../host/util.mjs";
 
 import "./datasource/dom.mjs";
 import "./datasource/rest.mjs";
 
 import "../form/context-help.mjs";
-import { getLocaleOfDocument } from "../../dom/locale.mjs";
-import { addErrorAttribute } from "../../dom/error.mjs";
+import {getLocaleOfDocument} from "../../dom/locale.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
-export { DataTable };
+export {DataTable};
 
 /**
  * @private
@@ -139,7 +138,8 @@ const resizeObserverSymbol = Symbol("resizeObserver");
  * @example /examples/components/datatable/order-by Sort data
  * @example /examples/components/datatable/select-rows Select rows
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/277.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/277.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/289.html
  *
  * @copyright schukai GmbH
  * @summary A beautiful and highly customizable data table. It can be used to display data from a data source.
@@ -153,434 +153,434 @@ const resizeObserverSymbol = Symbol("resizeObserver");
  * @fires monster-datatable-selection-changed
  **/
 class DataTable extends CustomElement {
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @return {symbol}
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/components/datatable@@instance");
-	}
-
-	/**
-	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
-	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
-	 *
-	 * The individual configuration values can be found in the table.
-	 *
-	 * @property {Object} templates Template definitions
-	 * @property {string} templates.main Main template
-	 * @property {Object} datasource Datasource configuration
-	 * @property {string} datasource.selector Selector for the datasource
-	 * @property {string} datasource.orderDelimiter Order delimiter
-	 * @property {Object} mapping Mapping configuration
-	 * @property {string} mapping.data Data mapping
-	 * @property {Array} data Data
-	 * @property {Array} headers Headers
-	 * @property {Object} responsive Responsive configuration
-	 * @property {number} responsive.breakpoint Breakpoint for responsive mode
-	 * @property {Object} labels Labels
-	 * @property {string} labels.theListContainsNoEntries Label for empty state
-	 * @property {Object} classes Classes
-	 * @property {string} classes.container Container class
-	 * @property {Object} features Features
-	 * @property {boolean} features.settings Settings feature
-	 * @property {boolean} features.footer Footer feature
-	 * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
-	 * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
-	 * @property {boolean} features.copyAll Copy all feature
-	 * @property {boolean} features.help Help feature
-	 * @property {Object} templateMapping Template mapping
-	 * @property {string} templateMapping.row-key Row key
-	 * @property {string} templateMapping.filter-id Filter id
-	 **/
-	get defaults() {
-		return Object.assign(
-			{},
-			super.defaults,
-			{
-				templates: {
-					main: getTemplate(),
-					emptyState: getEmptyTemplate(),
-				},
-
-				datasource: {
-					selector: null,
-					orderDelimiter: ",", // look at  initOptionsFromArguments()
-				},
-
-				mapping: {
-					data: "dataset",
-				},
-
-				data: [],
-				headers: [],
-
-				responsive: {
-					breakpoint: 900,
-				},
-
-				labels: getTranslations(),
-
-				classes: {
-					control: "monster-theme-control-container-1",
-					container: "",
-					row: "monster-theme-control-row-1",
-				},
-
-				features: {
-					settings: true,
-					footer: true,
-					autoInit: true,
-					doubleClickCopyToClipboard: true,
-					copyAll: true,
-					help: true,
-				},
-
-				copy: {
-					delimiter: ";",
-					quoteOpen: '"',
-					quoteClose: '"',
-					rowBreak: "\n",
-				},
-
-				templateMapping: {
-					"row-key": null,
-					"filter-id": null,
-				},
-			},
-			initOptionsFromArguments.call(this),
-		);
-	}
-
-	/**
-	 *
-	 * @param {string} selector
-	 * @return {NodeList}
-	 */
-	getGridElements(selector) {
-		return this[gridElementSymbol].querySelectorAll(selector);
-	}
-
-	/**
-	 *
-	 * @return {string}
-	 */
-	static getTag() {
-		return "monster-datatable";
-	}
-
-	/**
-	 * @return {void}
-	 */
-	disconnectedCallback() {
-		super.disconnectedCallback();
-		if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
-			this[resizeObserverSymbol].disconnect();
-		}
-	}
-
-	/**
-	 * @return {void}
-	 */
-	connectedCallback() {
-		const self = this;
-		super.connectedCallback();
-
-		this[resizeObserverSymbol] = new ResizeObserver((entries) => {
-			updateGrid.call(self);
-		});
-
-		requestAnimationFrame(() => {
-			let parent = this.parentNode;
-			while (!(parent instanceof HTMLElement) && parent !== null) {
-				parent = parent.parentNode;
-			}
-
-			if (parent instanceof HTMLElement) {
-				this[resizeObserverSymbol].observe(parent);
-			}
-		});
-	}
-
-	/**
-	 * Get the row number of the selected rows as an array
-	 *
-	 * @returns {number[]}
-	 */
-	getSelectedRows() {
-		const rows = this.getGridElements(`[data-monster-role="select-row"]`);
-		const selectedRows = [];
-		rows.forEach((row) => {
-			if (row.checked) {
-				const key = row.parentNode.getAttribute(
-					"data-monster-insert-reference",
-				);
-				const index = key.split("-").pop();
-				selectedRows.push(parseInt(index, 10));
-			}
-		});
-
-		return selectedRows;
-	}
-
-	/**
-	 * @return void
-	 */
-	[assembleMethodSymbol]() {
-		const rawKey = this.getOption("templateMapping.row-key");
-
-		if (rawKey === null) {
-			if (this.id !== null && this.id !== "") {
-				const rawKey = this.getOption("templateMapping.row-key");
-				if (rawKey === null) {
-					this.setOption("templateMapping.row-key", this.id + "-row");
-				}
-			} else {
-				this.setOption("templateMapping.row-key", "row");
-			}
-		}
-
-		if (this.id !== null && this.id !== "") {
-			this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
-		} else {
-			this.setOption("templateMapping.filter-id", "filter");
-		}
-
-		super[assembleMethodSymbol]();
-
-		initControlReferences.call(this);
-		initEventHandler.call(this);
-
-		getSlottedElements
-			.call(this, "[data-monster-role=row-action-button]", "bar")
-			.forEach((i, e) => {
-				if (e instanceof HTMLElement) {
-					e.style.visibility = "hidden";
-					e.style.width = "max-content";
-
-					const pN = e.parentNode;
-					if (pN instanceof HTMLElement) {
-						pN.style.flexGrow = "10";
-						pN.style.display = "flex";
-						pN.style.justifyContent = "flex-start";
-					}
-				}
-			});
-
-		getHostConfig
-			.call(this, getColumnVisibilityConfigKey)
-			.then((config) => {
-				const headerOrderMap = new Map();
-
-				getHostConfig
-					.call(this, getStoredOrderConfigKey)
-					.then((orderConfig) => {
-						if (isArray(orderConfig) || orderConfig.length > 0) {
-							for (let i = 0; i < orderConfig.length; i++) {
-								const item = orderConfig[i];
-								const parts = item.split(" ");
-								const field = parts[0];
-								const direction = parts[1] || DIRECTION_ASC;
-								headerOrderMap.set(field, direction);
-							}
-						}
-					})
-					.then(() => {
-						try {
-							initGridAndStructs.call(this, config, headerOrderMap);
-						} catch (error) {
-							addErrorAttribute(this, error);
-						}
-
-						updateColumnBar.call(this);
-					})
-					.catch((error) => {
-						addErrorAttribute(this, error);
-					});
-			})
-			.catch((error) => {
-				addErrorAttribute(this, error);
-			})
-			.finally(() => {
-				const selector = this.getOption("datasource.selector");
-
-				if (isString(selector)) {
-					const element = findElementWithSelectorUpwards(this, selector);
-					if (element === null) {
-						throw new Error("the selector must match exactly one element");
-					}
-
-					if (!isInstance(element, Datasource)) {
-						throw new TypeError("the element must be a datasource");
-					}
-
-					this[datasourceLinkedElementSymbol] = element;
-
-					getWindow().requestAnimationFrame(() => {
-						handleDataSourceChanges.call(this);
-						if (element && "datasource" in element) {
-							element.datasource.attachObserver(
-								new Observer(handleDataSourceChanges.bind(this)),
-							);
-						}
-					});
-				}
-			});
-	}
-
-	/**
-	 * @return {CSSStyleSheet[]}
-	 */
-	static getCSSStyleSheet() {
-		return [DatatableStyleSheet];
-	}
-
-	/**
-	 * Copy a row from the datatable
-	 *
-	 * @param {number|string} fromIndex
-	 * @param {number|string} toIndex
-	 * @return {DataTable}
-	 * @fires monster-datatable-row-copied
-	 */
-	copyRow(fromIndex, toIndex) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-		const mapping = this.getOption("mapping.data");
-
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		if (toIndex === undefined) {
-			toIndex = rows.length;
-		}
-
-		if (isString(fromIndex)) {
-			fromIndex = parseInt(fromIndex);
-		}
-		if (isString(toIndex)) {
-			toIndex = parseInt(toIndex);
-		}
-
-		if (toIndex < 0 || toIndex > rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-
-		validateArray(rows);
-		validateInteger(fromIndex);
-		validateInteger(toIndex);
-
-		if (fromIndex < 0 || fromIndex >= rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-
-		rows.splice(toIndex, 0, clone(rows[fromIndex]));
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-copied", {
-			index: toIndex,
-		});
-
-		return this;
-	}
-
-	/**
-	 * Remove a row from the datatable
-	 *
-	 * @param {number|string} index
-	 * @return {DataTable}
-	 * @fires monster-datatable-row-removed
-	 */
-	removeRow(index) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-		const mapping = this.getOption("mapping.data");
-
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		if (isString(index)) {
-			index = parseInt(index);
-		}
-
-		validateArray(rows);
-		validateInteger(index);
-
-		if (index < 0 || index >= rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		rows.splice(index, 1);
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-removed", {
-			index: index,
-		});
-
-		return this;
-	}
-
-	/**
-	 * Add a row to the datatable
-	 *
-	 * @param {Object} data
-	 * @return {DataTable}
-	 *
-	 * @fires monster-datatable-row-added
-	 **/
-	addRow(data) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-
-		const mapping = this.getOption("mapping.data");
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		validateArray(rows);
-		validateObject(data);
-
-		rows.push(data);
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-added", {
-			index: rows.length - 1,
-		});
+    /**
+     * This method is called by the `instanceof` operator.
+     * @return {symbol}
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for("@schukai/monster/components/datatable@@instance");
+    }
+
+    /**
+     * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
+     * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
+     *
+     * The individual configuration values can be found in the table.
+     *
+     * @property {Object} templates Template definitions
+     * @property {string} templates.main Main template
+     * @property {Object} datasource Datasource configuration
+     * @property {string} datasource.selector Selector for the datasource
+     * @property {string} datasource.orderDelimiter Order delimiter
+     * @property {Object} mapping Mapping configuration
+     * @property {string} mapping.data Data mapping
+     * @property {Array} data Data
+     * @property {Array} headers Headers
+     * @property {Object} responsive Responsive configuration
+     * @property {number} responsive.breakpoint Breakpoint for responsive mode
+     * @property {Object} labels Labels
+     * @property {string} labels.theListContainsNoEntries Label for empty state
+     * @property {Object} classes Classes
+     * @property {string} classes.container Container class
+     * @property {Object} features Features
+     * @property {boolean} features.settings Settings feature
+     * @property {boolean} features.footer Footer feature
+     * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
+     * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
+     * @property {boolean} features.copyAll Copy all feature
+     * @property {boolean} features.help Help feature
+     * @property {Object} templateMapping Template mapping
+     * @property {string} templateMapping.row-key Row key
+     * @property {string} templateMapping.filter-id Filter id
+     **/
+    get defaults() {
+        return Object.assign(
+            {},
+            super.defaults,
+            {
+                templates: {
+                    main: getTemplate(),
+                    emptyState: getEmptyTemplate(),
+                },
+
+                datasource: {
+                    selector: null,
+                    orderDelimiter: ",", // look at  initOptionsFromArguments()
+                },
+
+                mapping: {
+                    data: "dataset",
+                },
+
+                data: [],
+                headers: [],
+
+                responsive: {
+                    breakpoint: 900,
+                },
+
+                labels: getTranslations(),
+
+                classes: {
+                    control: "monster-theme-control-container-1",
+                    container: "",
+                    row: "monster-theme-control-row-1",
+                },
+
+                features: {
+                    settings: true,
+                    footer: true,
+                    autoInit: true,
+                    doubleClickCopyToClipboard: true,
+                    copyAll: true,
+                    help: true,
+                },
+
+                copy: {
+                    delimiter: ";",
+                    quoteOpen: '"',
+                    quoteClose: '"',
+                    rowBreak: "\n",
+                },
+
+                templateMapping: {
+                    "row-key": null,
+                    "filter-id": null,
+                },
+            },
+            initOptionsFromArguments.call(this),
+        );
+    }
+
+    /**
+     *
+     * @param {string} selector
+     * @return {NodeList}
+     */
+    getGridElements(selector) {
+        return this[gridElementSymbol].querySelectorAll(selector);
+    }
+
+    /**
+     *
+     * @return {string}
+     */
+    static getTag() {
+        return "monster-datatable";
+    }
+
+    /**
+     * @return {void}
+     */
+    disconnectedCallback() {
+        super.disconnectedCallback();
+        if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
+            this[resizeObserverSymbol].disconnect();
+        }
+    }
+
+    /**
+     * @return {void}
+     */
+    connectedCallback() {
+        const self = this;
+        super.connectedCallback();
+
+        this[resizeObserverSymbol] = new ResizeObserver((entries) => {
+            updateGrid.call(self);
+        });
+
+        requestAnimationFrame(() => {
+            let parent = this.parentNode;
+            while (!(parent instanceof HTMLElement) && parent !== null) {
+                parent = parent.parentNode;
+            }
+
+            if (parent instanceof HTMLElement) {
+                this[resizeObserverSymbol].observe(parent);
+            }
+        });
+    }
+
+    /**
+     * Get the row number of the selected rows as an array
+     *
+     * @returns {number[]}
+     */
+    getSelectedRows() {
+        const rows = this.getGridElements(`[data-monster-role="select-row"]`);
+        const selectedRows = [];
+        rows.forEach((row) => {
+            if (row.checked) {
+                const key = row.parentNode.getAttribute(
+                    "data-monster-insert-reference",
+                );
+                const index = key.split("-").pop();
+                selectedRows.push(parseInt(index, 10));
+            }
+        });
+
+        return selectedRows;
+    }
+
+    /**
+     * @return void
+     */
+    [assembleMethodSymbol]() {
+        const rawKey = this.getOption("templateMapping.row-key");
+
+        if (rawKey === null) {
+            if (this.id !== null && this.id !== "") {
+                const rawKey = this.getOption("templateMapping.row-key");
+                if (rawKey === null) {
+                    this.setOption("templateMapping.row-key", this.id + "-row");
+                }
+            } else {
+                this.setOption("templateMapping.row-key", "row");
+            }
+        }
+
+        if (this.id !== null && this.id !== "") {
+            this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
+        } else {
+            this.setOption("templateMapping.filter-id", "filter");
+        }
+
+        super[assembleMethodSymbol]();
+
+        initControlReferences.call(this);
+        initEventHandler.call(this);
+
+        getSlottedElements
+            .call(this, "[data-monster-role=row-action-button]", "bar")
+            .forEach((i, e) => {
+                if (e instanceof HTMLElement) {
+                    e.style.visibility = "hidden";
+                    e.style.width = "max-content";
+
+                    const pN = e.parentNode;
+                    if (pN instanceof HTMLElement) {
+                        pN.style.flexGrow = "10";
+                        pN.style.display = "flex";
+                        pN.style.justifyContent = "flex-start";
+                    }
+                }
+            });
+
+        getHostConfig
+            .call(this, getColumnVisibilityConfigKey)
+            .then((config) => {
+                const headerOrderMap = new Map();
+
+                getHostConfig
+                    .call(this, getStoredOrderConfigKey)
+                    .then((orderConfig) => {
+                        if (isArray(orderConfig) || orderConfig.length > 0) {
+                            for (let i = 0; i < orderConfig.length; i++) {
+                                const item = orderConfig[i];
+                                const parts = item.split(" ");
+                                const field = parts[0];
+                                const direction = parts[1] || DIRECTION_ASC;
+                                headerOrderMap.set(field, direction);
+                            }
+                        }
+                    })
+                    .then(() => {
+                        try {
+                            initGridAndStructs.call(this, config, headerOrderMap);
+                        } catch (error) {
+                            addErrorAttribute(this, error);
+                        }
+
+                        updateColumnBar.call(this);
+                    })
+                    .catch((error) => {
+                        addErrorAttribute(this, error);
+                    });
+            })
+            .catch((error) => {
+                addErrorAttribute(this, error);
+            })
+            .finally(() => {
+                const selector = this.getOption("datasource.selector");
+
+                if (isString(selector)) {
+                    const element = findElementWithSelectorUpwards(this, selector);
+                    if (element === null) {
+                        throw new Error("the selector must match exactly one element");
+                    }
+
+                    if (!isInstance(element, Datasource)) {
+                        throw new TypeError("the element must be a datasource");
+                    }
+
+                    this[datasourceLinkedElementSymbol] = element;
+
+                    getWindow().requestAnimationFrame(() => {
+                        handleDataSourceChanges.call(this);
+                        if (element && "datasource" in element) {
+                            element.datasource.attachObserver(
+                                new Observer(handleDataSourceChanges.bind(this)),
+                            );
+                        }
+                    });
+                }
+            });
+    }
+
+    /**
+     * @return {CSSStyleSheet[]}
+     */
+    static getCSSStyleSheet() {
+        return [DatatableStyleSheet];
+    }
+
+    /**
+     * Copy a row from the datatable
+     *
+     * @param {number|string} fromIndex
+     * @param {number|string} toIndex
+     * @return {DataTable}
+     * @fires monster-datatable-row-copied
+     */
+    copyRow(fromIndex, toIndex) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+        const mapping = this.getOption("mapping.data");
+
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        if (toIndex === undefined) {
+            toIndex = rows.length;
+        }
+
+        if (isString(fromIndex)) {
+            fromIndex = parseInt(fromIndex);
+        }
+        if (isString(toIndex)) {
+            toIndex = parseInt(toIndex);
+        }
+
+        if (toIndex < 0 || toIndex > rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+
+        validateArray(rows);
+        validateInteger(fromIndex);
+        validateInteger(toIndex);
+
+        if (fromIndex < 0 || fromIndex >= rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+
+        rows.splice(toIndex, 0, clone(rows[fromIndex]));
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-copied", {
+            index: toIndex,
+        });
+
+        return this;
+    }
+
+    /**
+     * Remove a row from the datatable
+     *
+     * @param {number|string} index
+     * @return {DataTable}
+     * @fires monster-datatable-row-removed
+     */
+    removeRow(index) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+        const mapping = this.getOption("mapping.data");
+
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        if (isString(index)) {
+            index = parseInt(index);
+        }
+
+        validateArray(rows);
+        validateInteger(index);
+
+        if (index < 0 || index >= rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        rows.splice(index, 1);
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-removed", {
+            index: index,
+        });
+
+        return this;
+    }
+
+    /**
+     * Add a row to the datatable
+     *
+     * @param {Object} data
+     * @return {DataTable}
+     *
+     * @fires monster-datatable-row-added
+     **/
+    addRow(data) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+
+        const mapping = this.getOption("mapping.data");
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        validateArray(rows);
+        validateObject(data);
+
+        rows.push(data);
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-added", {
+            index: rows.length - 1,
+        });
 
 		return this;
 	}
@@ -591,7 +591,7 @@ class DataTable extends CustomElement {
  * @return {string}
  */
 function getColumnVisibilityConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
+    return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
 }
 
 /**
@@ -599,7 +599,7 @@ function getColumnVisibilityConfigKey() {
  * @return {string}
  */
 function getFilterConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "filter");
+    return generateUniqueConfigKey("datatable", this?.id, "filter");
 }
 
 /**
@@ -607,554 +607,558 @@ function getFilterConfigKey() {
  * @return {Promise}
  */
 function getHostConfig(callback) {
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-
-	if (!host) {
-		addErrorAttribute(this, "no host found");
-		return Promise.resolve({});
-	}
-
-	if (!this.id) {
-		addErrorAttribute(this, "no id found; id is required for config");
-		return Promise.resolve({});
-	}
-
-	if (!host || !isFunction(host?.getConfig)) {
-		throw new TypeError("the host must be a monster-host");
-	}
-
-	const configKey = callback.call(this);
-	return host.hasConfig(configKey).then((hasConfig) => {
-		if (hasConfig) {
-			return host.getConfig(configKey);
-		} else {
-			return {};
-		}
-	});
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+
+    if (!host) {
+        addErrorAttribute(this, "no host found");
+        return Promise.resolve({});
+    }
+
+    if (!this.id) {
+        addErrorAttribute(this, "no id found; id is required for config");
+        return Promise.resolve({});
+    }
+
+    if (!host || !isFunction(host?.getConfig)) {
+        throw new TypeError("the host must be a monster-host");
+    }
+
+    const configKey = callback.call(this);
+    return host.hasConfig(configKey).then((hasConfig) => {
+        if (hasConfig) {
+            return host.getConfig(configKey);
+        } else {
+            return {};
+        }
+    });
 }
 
 /**
  * @private
  */
 function updateColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
-
-	const columns = [];
-	for (const header of this.getOption("headers")) {
-		const mode = header.getInternal("mode");
-
-		if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
-			continue;
-		}
-
-		columns.push({
-			visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
-			name: header.label,
-			index: header.index,
-		});
-	}
-
-	this[columnBarElementSymbol].setOption("columns", columns);
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
+
+    const columns = [];
+    for (const header of this.getOption("headers")) {
+        const mode = header.getInternal("mode");
+
+        if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
+            continue;
+        }
+
+        columns.push({
+            visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
+            name: header.label,
+            index: header.index,
+        });
+    }
+
+    this[columnBarElementSymbol].setOption("columns", columns);
 }
 
 /**
  * @private
  */
 function updateHeaderFromColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
 
-	const options = this[columnBarElementSymbol].getOption("columns");
-	if (!isArray(options)) return;
+    const options = this[columnBarElementSymbol].getOption("columns");
+    if (!isArray(options)) return;
 
-	const invisibleMap = {};
+    const invisibleMap = {};
 
-	for (let i = 0; i < options.length; i++) {
-		const option = options[i];
-		invisibleMap[option.index] = option.visible;
-	}
+    for (let i = 0; i < options.length; i++) {
+        const option = options[i];
+        invisibleMap[option.index] = option.visible;
+    }
 
-	for (const header of this.getOption("headers")) {
-		const mode = header.getInternal("mode");
+    for (const header of this.getOption("headers")) {
+        const mode = header.getInternal("mode");
 
-		if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
-			continue;
-		}
+        if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
+            continue;
+        }
 
-		if (invisibleMap[header.index] === false) {
-			header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
-		} else {
-			header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
-		}
-	}
+        if (invisibleMap[header.index] === false) {
+            header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
+        } else {
+            header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
+        }
+    }
 }
 
 /**
  * @private
  */
 function updateConfigColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
-
-	const options = this[columnBarElementSymbol].getOption("columns");
-	if (!isArray(options)) return;
-
-	const map = {};
-	for (let i = 0; i < options.length; i++) {
-		const option = options[i];
-		map[option.name] = option.visible;
-	}
-
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-	if (!(host && this.id)) {
-		return;
-	}
-	const configKey = getColumnVisibilityConfigKey.call(this);
-
-	try {
-		host.setConfig(configKey, map);
-	} catch (error) {
-		addErrorAttribute(this, error);
-	}
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
+
+    const options = this[columnBarElementSymbol].getOption("columns");
+    if (!isArray(options)) return;
+
+    const map = {};
+    for (let i = 0; i < options.length; i++) {
+        const option = options[i];
+        map[option.name] = option.visible;
+    }
+
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+    if (!(host && this.id)) {
+        return;
+    }
+    const configKey = getColumnVisibilityConfigKey.call(this);
+
+    try {
+        host.setConfig(configKey, map);
+    } catch (error) {
+        addErrorAttribute(this, error);
+    }
 }
 
 /**
  * @private
  */
 function initEventHandler() {
-	const self = this;
-
-	const quoteOpenChar = this.getOption("copy.quoteOpen");
-	const quoteCloseChar = this.getOption("copy.quoteClose");
-	const delimiterChar = this.getOption("copy.delimiter");
-	const rowBreak = this.getOption("copy.rowBreak");
-
-	self[columnBarElementSymbol].attachObserver(
-		new Observer((e) => {
-			updateHeaderFromColumnBar.call(self);
-			updateGrid.call(self);
-			updateConfigColumnBar.call(self);
-		}),
-	);
-
-	self[gridHeadersElementSymbol].addEventListener("click", function (event) {
-		let element = null;
-		const datasource = self[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return;
-		}
-
-		element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
-		if (element instanceof HTMLElement) {
-			const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
-			const headers = self.getOption("headers");
-
-			event.preventDefault();
-
-			headers[index].changeDirection();
-
-			queueMicrotask(function () {
-				/** hotfix, normally this should be done via the updater, no idea why this is not possible. */
-				element.setAttribute(
-					ATTRIBUTE_DATATABLE_SORTABLE,
-					`${headers[index].field} ${headers[index].direction}`,
-				);
-
-				storeOrderStatement.call(self, true);
-			});
-		}
-	});
-
-	const eventHandlerDoubleClickCopyToClipboard = (event) => {
-		const element = findTargetElementFromEvent(event, "data-monster-head");
-		if (element) {
-			let text = "";
-
-			if (event.shiftKey) {
-				const index = element.getAttribute("data-monster-insert-reference");
-				if (index) {
-					const cols = self.getGridElements(
-						`[data-monster-insert-reference="${index}"]`,
-					);
-
-					const colTexts = [];
-					for (let i = 0; i < cols.length; i++) {
-						const col = cols[i];
-
-						if (
-							col.querySelector("monster-button-bar") ||
-							col.querySelector("monster-button")
-						) {
-							continue;
-						}
-
-						if (col.textContent) {
-							colTexts.push(
-								quoteOpenChar + col.textContent.trim() + quoteCloseChar,
-							);
-						}
-					}
-
-					text = colTexts.join(delimiterChar);
-				}
-			} else {
-				if (
-					element.querySelector("monster-button-bar") ||
-					element.querySelector("monster-button")
-				) {
-					return;
-				}
-
-				text = element.textContent.trim();
-			}
-
-			if (getWindow().navigator.clipboard && text) {
-				getWindow()
-					.navigator.clipboard.writeText(text)
-					.then(
-						() => {},
-						(err) => {},
-					);
-			}
-		}
-	};
-
-	if (self.getOption("features.doubleClickCopyToClipboard")) {
-		self[gridElementSymbol].addEventListener(
-			"dblclick",
-			eventHandlerDoubleClickCopyToClipboard,
-		);
-	}
-
-	if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
-		this[copyAllElementSymbol].addEventListener("click", (event) => {
-			event.preventDefault();
-
-			const table = [];
-			let currentRow = [];
-			let currentIndex = null;
-
-			const cols = self.getGridElements(`[data-monster-insert-reference]`);
-			const rowIndexes = new Map();
-			cols.forEach((col) => {
-				const index = col.getAttribute("data-monster-insert-reference");
-				rowIndexes.set(index, true);
-			});
-
-			rowIndexes.forEach((value, key) => {
-				const cols = self.getGridElements(
-					`[data-monster-insert-reference="${key}"]`,
-				);
-
-				for (let i = 0; i < cols.length; i++) {
-					const col = cols[i];
-
-					if (
-						col.querySelector("monster-button-bar") ||
-						col.querySelector("monster-button")
-					) {
-						continue;
-					}
-
-					if (col.textContent) {
-						currentRow.push(
-							quoteOpenChar + col.textContent.trim() + quoteCloseChar,
-						);
-					}
-				}
-
-				if (currentRow.length > 0) {
-					table.push(currentRow);
-				}
-				currentRow = [];
-			});
-
-			if (table.length > 0) {
-				const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
-				if (getWindow().navigator.clipboard && text) {
-					getWindow()
-						.navigator.clipboard.writeText(text)
-						.then(
-							() => {},
-							(err) => {},
-						);
-				}
-			}
-		});
-	}
-
-	const selectRowCallback = (event) => {
-		const element = findTargetElementFromEvent(
-			event,
-			"data-monster-role",
-			"select-row",
-		);
-
-		if (!(element instanceof HTMLInputElement)) {
-			return;
-		}
-
-		const parentNode = element.parentNode;
-		if (!(parentNode instanceof HTMLDivElement)) {
-			return;
-		}
-
-		const key = element.parentNode.getAttribute(
-			"data-monster-insert-reference",
-		);
-
-		const row = self.getGridElements(
-			`[data-monster-insert-reference="${key}"]`,
-		);
-
-		const index = key.split("-").pop();
-
-		if (element.checked) {
-			row.forEach((col) => {
-				col.classList.add("selected");
-			});
-
-			fireCustomEvent(self, "monster-datatable-row-selected", {
-				index: index,
-			});
-		} else {
-			row.forEach((col) => {
-				col.classList.remove("selected");
-			});
-
-			fireCustomEvent(self, "monster-datatable-row-deselected", {
-				index: index,
-			});
-		}
-
-		fireCustomEvent(this, "monster-datatable-selection-changed", {});
-
-		const rows = self.getGridElements(`[data-monster-role="select-row"]`);
-		const allSelected = Array.from(rows).every((row) => row.checked);
-
-		const selectAll = this[gridHeadersElementSymbol].querySelector(
-			`[data-monster-role="select-all"]`,
-		);
-
-		getSlottedElements
-			.call(this, "[data-monster-role=row-action-button]", "bar")
-			.forEach((i, e) => {
-				const selected = self.getSelectedRows();
-				const mode = selected.length === 0 ? "hidden" : "visible";
-				if (e instanceof HTMLElement) {
-					e.style.visibility = mode;
-				}
-			});
-
-		if (selectAll) {
-			selectAll.checked = allSelected;
-		}
-	};
-
-	this[gridElementSymbol].addEventListener("click", selectRowCallback);
-	this[gridElementSymbol].addEventListener("touch", selectRowCallback);
-
-	const selectAllCallback = (event) => {
-		const element = findTargetElementFromEvent(
-			event,
-			"data-monster-role",
-			"select-all",
-		);
-		if (element) {
-			const mode = element.checked;
-
-			const rows = this.getGridElements(`[data-monster-role="select-row"]`);
-			rows.forEach((row) => {
-				row.checked = mode;
-			});
-
-			if (mode) {
-				fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
-			} else {
-				fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
-			}
-
-			getSlottedElements
-				.call(this, "[data-monster-role=row-action-button]", "bar")
-				.forEach((i, e) => {
-					if (e instanceof HTMLElement) {
-						e.style.visibility = mode ? "visible" : "hidden";
-					}
-				});
-
-			fireCustomEvent(this, "monster-datatable-selection-changed", {});
-		}
-	};
-
-	this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
-	this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
+    const self = this;
+
+    const quoteOpenChar = this.getOption("copy.quoteOpen");
+    const quoteCloseChar = this.getOption("copy.quoteClose");
+    const delimiterChar = this.getOption("copy.delimiter");
+    const rowBreak = this.getOption("copy.rowBreak");
+
+    self[columnBarElementSymbol].attachObserver(
+        new Observer((e) => {
+            updateHeaderFromColumnBar.call(self);
+            updateGrid.call(self);
+            updateConfigColumnBar.call(self);
+        }),
+    );
+
+    self[gridHeadersElementSymbol].addEventListener("click", function (event) {
+        let element = null;
+        const datasource = self[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return;
+        }
+
+        element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
+        if (element instanceof HTMLElement) {
+            const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
+            const headers = self.getOption("headers");
+
+            event.preventDefault();
+
+            headers[index].changeDirection();
+
+            queueMicrotask(function () {
+                /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
+                element.setAttribute(
+                    ATTRIBUTE_DATATABLE_SORTABLE,
+                    `${headers[index].field} ${headers[index].direction}`,
+                );
+
+                storeOrderStatement.call(self, true);
+            });
+        }
+    });
+
+    const eventHandlerDoubleClickCopyToClipboard = (event) => {
+        const element = findTargetElementFromEvent(event, "data-monster-head");
+        if (element) {
+            let text = "";
+
+            if (event.shiftKey) {
+                const index = element.getAttribute("data-monster-insert-reference");
+                if (index) {
+                    const cols = self.getGridElements(
+                        `[data-monster-insert-reference="${index}"]`,
+                    );
+
+                    const colTexts = [];
+                    for (let i = 0; i < cols.length; i++) {
+                        const col = cols[i];
+
+                        if (
+                            col.querySelector("monster-button-bar") ||
+                            col.querySelector("monster-button")
+                        ) {
+                            continue;
+                        }
+
+                        if (col.textContent) {
+                            colTexts.push(
+                                quoteOpenChar + col.textContent.trim() + quoteCloseChar,
+                            );
+                        }
+                    }
+
+                    text = colTexts.join(delimiterChar);
+                }
+            } else {
+                if (
+                    element.querySelector("monster-button-bar") ||
+                    element.querySelector("monster-button")
+                ) {
+                    return;
+                }
+
+                text = element.textContent.trim();
+            }
+
+            if (getWindow().navigator.clipboard && text) {
+                getWindow()
+                    .navigator.clipboard.writeText(text)
+                    .then(
+                        () => {
+                        },
+                        (err) => {
+                        },
+                    );
+            }
+        }
+    };
+
+    if (self.getOption("features.doubleClickCopyToClipboard")) {
+        self[gridElementSymbol].addEventListener(
+            "dblclick",
+            eventHandlerDoubleClickCopyToClipboard,
+        );
+    }
+
+    if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
+        this[copyAllElementSymbol].addEventListener("click", (event) => {
+            event.preventDefault();
+
+            const table = [];
+            let currentRow = [];
+            let currentIndex = null;
+
+            const cols = self.getGridElements(`[data-monster-insert-reference]`);
+            const rowIndexes = new Map();
+            cols.forEach((col) => {
+                const index = col.getAttribute("data-monster-insert-reference");
+                rowIndexes.set(index, true);
+            });
+
+            rowIndexes.forEach((value, key) => {
+                const cols = self.getGridElements(
+                    `[data-monster-insert-reference="${key}"]`,
+                );
+
+                for (let i = 0; i < cols.length; i++) {
+                    const col = cols[i];
+
+                    if (
+                        col.querySelector("monster-button-bar") ||
+                        col.querySelector("monster-button")
+                    ) {
+                        continue;
+                    }
+
+                    if (col.textContent) {
+                        currentRow.push(
+                            quoteOpenChar + col.textContent.trim() + quoteCloseChar,
+                        );
+                    }
+                }
+
+                if (currentRow.length > 0) {
+                    table.push(currentRow);
+                }
+                currentRow = [];
+            });
+
+            if (table.length > 0) {
+                const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
+                if (getWindow().navigator.clipboard && text) {
+                    getWindow()
+                        .navigator.clipboard.writeText(text)
+                        .then(
+                            () => {
+                            },
+                            (err) => {
+                            },
+                        );
+                }
+            }
+        });
+    }
+
+    const selectRowCallback = (event) => {
+        const element = findTargetElementFromEvent(
+            event,
+            "data-monster-role",
+            "select-row",
+        );
+
+        if (!(element instanceof HTMLInputElement)) {
+            return;
+        }
+
+        const parentNode = element.parentNode;
+        if (!(parentNode instanceof HTMLDivElement)) {
+            return;
+        }
+
+        const key = element.parentNode.getAttribute(
+            "data-monster-insert-reference",
+        );
+
+        const row = self.getGridElements(
+            `[data-monster-insert-reference="${key}"]`,
+        );
+
+        const index = key.split("-").pop();
+
+        if (element.checked) {
+            row.forEach((col) => {
+                col.classList.add("selected");
+            });
+
+            fireCustomEvent(self, "monster-datatable-row-selected", {
+                index: index,
+            });
+        } else {
+            row.forEach((col) => {
+                col.classList.remove("selected");
+            });
+
+            fireCustomEvent(self, "monster-datatable-row-deselected", {
+                index: index,
+            });
+        }
+
+        fireCustomEvent(this, "monster-datatable-selection-changed", {});
+
+        const rows = self.getGridElements(`[data-monster-role="select-row"]`);
+        const allSelected = Array.from(rows).every((row) => row.checked);
+
+        const selectAll = this[gridHeadersElementSymbol].querySelector(
+            `[data-monster-role="select-all"]`,
+        );
+
+        getSlottedElements
+            .call(this, "[data-monster-role=row-action-button]", "bar")
+            .forEach((i, e) => {
+                const selected = self.getSelectedRows();
+                const mode = selected.length === 0 ? "hidden" : "visible";
+                if (e instanceof HTMLElement) {
+                    e.style.visibility = mode;
+                }
+            });
+
+        if (selectAll) {
+            selectAll.checked = allSelected;
+        }
+    };
+
+    this[gridElementSymbol].addEventListener("click", selectRowCallback);
+    this[gridElementSymbol].addEventListener("touch", selectRowCallback);
+
+    const selectAllCallback = (event) => {
+        const element = findTargetElementFromEvent(
+            event,
+            "data-monster-role",
+            "select-all",
+        );
+        if (element) {
+            const mode = element.checked;
+
+            const rows = this.getGridElements(`[data-monster-role="select-row"]`);
+            rows.forEach((row) => {
+                row.checked = mode;
+            });
+
+            if (mode) {
+                fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
+            } else {
+                fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
+            }
+
+            getSlottedElements
+                .call(this, "[data-monster-role=row-action-button]", "bar")
+                .forEach((i, e) => {
+                    if (e instanceof HTMLElement) {
+                        e.style.visibility = mode ? "visible" : "hidden";
+                    }
+                });
+
+            fireCustomEvent(this, "monster-datatable-selection-changed", {});
+        }
+    };
+
+    this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
+    this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
 }
 
 /**
  * @private
  */
 function initGridAndStructs(hostConfig, headerOrderMap) {
-	const rowID = this.getOption("templateMapping.row-key");
-
-	if (!this[gridElementSymbol]) {
-		addErrorAttribute(this, "no grid element found");
-		return;
-	}
-
-	let template;
-	getSlottedElements.call(this).forEach((e) => {
-		if (e instanceof HTMLTemplateElement && e.id === rowID) {
-			template = e;
-		}
-	});
-
-	if (!template) {
-		addErrorAttribute(this, "no template found, please add a template");
-		return;
-	}
-
-	const rowCount = template.content.children.length;
-
-	const headers = [];
-
-	for (let i = 0; i < rowCount; i++) {
-		let hClass = "";
-		const row = template.content.children[i];
-
-		let mode = "";
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
-			mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
-		}
-
-		let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
-		if (!grid || grid === "" || grid === "auto") {
-			grid = "minmax(0, 1fr)";
-		}
-
-		let label = "";
-		let labelKey = "";
-
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
-			label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
-			labelKey = label;
-
-			try {
-				if (label.startsWith("i18n:")) {
-					label = label.substring(5, label.length);
-					label = getDocumentTranslations().getText(label, label);
-				}
-			} catch (e) {
-				label = "i18n error " + label;
-			}
-		}
-
-		if (!label) {
-			label = i + 1 + "";
-			mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
-			labelKey = label;
-		}
-
-		if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
-			if (hostConfig[label] === false) {
-				mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
-			} else {
-				mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
-			}
-		}
-
-		let align = "";
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
-			align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
-		}
-
-		switch (align) {
-			case "center":
-				hClass = "flex-center";
-				break;
-			case "end":
-				hClass = "flex-end";
-				break;
-			case "start":
-				hClass = "flex-start";
-				break;
-			default:
-				hClass = "flex-start";
-		}
-
-		let field = "";
-		let direction = DIRECTION_NONE;
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
-			field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
-			const parts = field.split(" ").map((item) => item.trim());
-			field = parts[0];
-
-			if (headerOrderMap.has(field)) {
-				direction = headerOrderMap.get(field);
-			} else if (
-				parts.length === 2 &&
-				[DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
-			) {
-				direction = parts[1];
-			}
-		}
-
-		if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
-			hClass += " hidden";
-		}
-
-		const features = [];
-
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
-			const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" ");
-
-			fl.forEach((feature) => {
-				features.push(feature.trim());
-
-				if (feature === "select") {
-					label = "<input type='checkbox' data-monster-role='select-all' />";
-
-					while (row.firstChild) {
-						row.removeChild(row.firstChild);
-					}
-
-					const checkbox = document.createElement("input");
-					checkbox.type = "checkbox";
-					checkbox.setAttribute("data-monster-role", "select-row");
-					row.appendChild(checkbox);
-				}
-			});
-		}
-
-		let orderTemplate = "${field} ${direction}";
-		if (row.hasAttribute("data-monster-order-template")) {
-			orderTemplate = row.getAttribute("data-monster-order-template");
-		}
-
-		const header = new Header();
-		header.setInternals({
-			field: field,
-			label: label,
-			classes: hClass,
-			index: i,
-			mode: mode,
-			grid: grid,
-			labelKey: labelKey+ "-"+i,
-			direction: direction,
-			features: features,
-			orderTemplate: orderTemplate,
-		});
-
-		headers.push(header);
-	}
-
-	this.setOption("headers", headers);
-	queueMicrotask(() => {
-		storeOrderStatement.call(this, this.getOption("features.autoInit"));
-	});
+    const rowID = this.getOption("templateMapping.row-key");
+
+    if (!this[gridElementSymbol]) {
+        addErrorAttribute(this, "no grid element found");
+        return;
+    }
+
+    let template;
+    getSlottedElements.call(this).forEach((e) => {
+        if (e instanceof HTMLTemplateElement && e.id === rowID) {
+            template = e;
+        }
+    });
+
+    if (!template) {
+        addErrorAttribute(this, "no template found, please add a template");
+        return;
+    }
+
+    const rowCount = template.content.children.length;
+
+    const headers = [];
+
+    for (let i = 0; i < rowCount; i++) {
+        let hClass = "";
+        const row = template.content.children[i];
+
+        let mode = "";
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
+            mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
+        }
+
+        let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
+        if (!grid || grid === "" || grid === "auto") {
+            grid = "minmax(0, 1fr)";
+        }
+
+        let label = "";
+        let labelKey = "";
+
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
+            label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
+            labelKey = label;
+
+            try {
+                if (label.startsWith("i18n:")) {
+                    label = label.substring(5, label.length);
+                    label = getDocumentTranslations().getText(label, label);
+                }
+            } catch (e) {
+                label = "i18n error " + label;
+            }
+        }
+
+        if (!label) {
+            label = i + 1 + "";
+            mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
+            labelKey = label;
+        }
+
+        if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
+            if (hostConfig[label] === false) {
+                mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
+            } else {
+                mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
+            }
+        }
+
+        let align = "";
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
+            align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
+        }
+
+        switch (align) {
+            case "center":
+                hClass = "flex-center";
+                break;
+            case "end":
+                hClass = "flex-end";
+                break;
+            case "start":
+                hClass = "flex-start";
+                break;
+            default:
+                hClass = "flex-start";
+        }
+
+        let field = "";
+        let direction = DIRECTION_NONE;
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
+            field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
+            const parts = field.split(" ").map((item) => item.trim());
+            field = parts[0];
+
+            if (headerOrderMap.has(field)) {
+                direction = headerOrderMap.get(field);
+            } else if (
+                parts.length === 2 &&
+                [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
+            ) {
+                direction = parts[1];
+            }
+        }
+
+        if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
+            hClass += " hidden";
+        }
+
+        const features = [];
+
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
+            const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" ");
+
+            fl.forEach((feature) => {
+                features.push(feature.trim());
+
+                if (feature === "select") {
+                    label = "<input type='checkbox' data-monster-role='select-all' />";
+
+                    while (row.firstChild) {
+                        row.removeChild(row.firstChild);
+                    }
+
+                    const checkbox = document.createElement("input");
+                    checkbox.type = "checkbox";
+                    checkbox.setAttribute("data-monster-role", "select-row");
+                    row.appendChild(checkbox);
+                }
+            });
+        }
+
+        let orderTemplate = "${field} ${direction}";
+        if (row.hasAttribute("data-monster-order-template")) {
+            orderTemplate = row.getAttribute("data-monster-order-template");
+        }
+
+        const header = new Header();
+        header.setInternals({
+            field: field,
+            label: label,
+            classes: hClass,
+            index: i,
+            mode: mode,
+            grid: grid,
+            labelKey: labelKey + "-" + i,
+            direction: direction,
+            features: features,
+            orderTemplate: orderTemplate,
+        });
+
+        headers.push(header);
+    }
+
+    this.setOption("headers", headers);
+    queueMicrotask(() => {
+        storeOrderStatement.call(this, this.getOption("features.autoInit"));
+    });
 }
 
 /**
@@ -1162,101 +1166,101 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
  * @returns {object}
  */
 function getTranslations() {
-	const locale = getLocaleOfDocument();
-	switch (locale.language) {
-		case "de":
-			return {
-				theListContainsNoEntries: "Die Liste enthält keine Einträge",
-				copyAll: "Alles kopieren",
-				helpText:
-					"<p>Sie können die Werte aus einzelnen Zeilen<br>" +
-					"in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
-					"<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
-					"Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
-			};
-		case "fr":
-			return {
-				theListContainsNoEntries: "La liste ne contient aucune entrée",
-				copyAll: "Copier tout",
-				helpText:
-					"<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
-					"dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
-					"<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
-					"Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
-			};
-		case "sp":
-			return {
-				theListContainsNoEntries: "La lista no contiene entradas",
-				copyAll: "Copiar todo",
-				helpText:
-					"<p>Puedes copiar los valores de filas individuales<br>" +
-					"al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
-					"<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
-					"Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
-			};
-		case "it":
-			return {
-				theListContainsNoEntries: "L'elenco non contiene voci",
-				copyAll: "Copia tutto",
-				helpText:
-					"<p>Puoi copiare i valori dalle singole righe<br>" +
-					"negli appunti facendo doppio clic sulla colonna relativa.</p>" +
-					"<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
-					"Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
-			};
-		case "pl":
-			return {
-				theListContainsNoEntries: "Lista nie zawiera wpisów",
-				copyAll: "Kopiuj wszystko",
-				helpText:
-					"<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
-					"do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
-					"<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
-					"Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
-			};
-		case "no":
-			return {
-				theListContainsNoEntries: "Listen inneholder ingen oppføringer",
-				copyAll: "Kopier alt",
-				helpText:
-					"<p>Du kan kopiere verdier fra enkeltrader<br>" +
-					"til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
-					"<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
-					"Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
-			};
-		case "dk":
-			return {
-				theListContainsNoEntries: "Listen indeholder ingen poster",
-				copyAll: "Kopiér alt",
-				helpText:
-					"<p>Du kan kopiere værdier fra enkelte rækker<br>" +
-					"til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
-					"<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
-					"Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
-			};
-		case "sw":
-			return {
-				theListContainsNoEntries: "Listan innehåller inga poster",
-				copyAll: "Kopiera allt",
-				helpText:
-					"<p>Du kan kopiera värden från enskilda rader<br>" +
-					"till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
-					"<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
-					"Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
-			};
-
-		case "en":
-		default:
-			return {
-				theListContainsNoEntries: "The list contains no entries",
-				copyAll: "Copy all",
-				helpText:
-					"<p>You can copy the values from individual rows<br>" +
-					"to the clipboard by double-clicking on the relevant column.</p>" +
-					"<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
-					"If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
-			};
-	}
+    const locale = getLocaleOfDocument();
+    switch (locale.language) {
+        case "de":
+            return {
+                theListContainsNoEntries: "Die Liste enthält keine Einträge",
+                copyAll: "Alles kopieren",
+                helpText:
+                    "<p>Sie können die Werte aus einzelnen Zeilen<br>" +
+                    "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
+                    "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
+                    "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
+            };
+        case "fr":
+            return {
+                theListContainsNoEntries: "La liste ne contient aucune entrée",
+                copyAll: "Copier tout",
+                helpText:
+                    "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
+                    "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
+                    "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
+                    "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
+            };
+        case "sp":
+            return {
+                theListContainsNoEntries: "La lista no contiene entradas",
+                copyAll: "Copiar todo",
+                helpText:
+                    "<p>Puedes copiar los valores de filas individuales<br>" +
+                    "al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
+                    "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
+                    "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
+            };
+        case "it":
+            return {
+                theListContainsNoEntries: "L'elenco non contiene voci",
+                copyAll: "Copia tutto",
+                helpText:
+                    "<p>Puoi copiare i valori dalle singole righe<br>" +
+                    "negli appunti facendo doppio clic sulla colonna relativa.</p>" +
+                    "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
+                    "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
+            };
+        case "pl":
+            return {
+                theListContainsNoEntries: "Lista nie zawiera wpisów",
+                copyAll: "Kopiuj wszystko",
+                helpText:
+                    "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
+                    "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
+                    "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
+                    "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
+            };
+        case "no":
+            return {
+                theListContainsNoEntries: "Listen inneholder ingen oppføringer",
+                copyAll: "Kopier alt",
+                helpText:
+                    "<p>Du kan kopiere verdier fra enkeltrader<br>" +
+                    "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
+                    "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
+                    "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
+            };
+        case "dk":
+            return {
+                theListContainsNoEntries: "Listen indeholder ingen poster",
+                copyAll: "Kopiér alt",
+                helpText:
+                    "<p>Du kan kopiere værdier fra enkelte rækker<br>" +
+                    "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
+                    "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
+                    "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
+            };
+        case "sw":
+            return {
+                theListContainsNoEntries: "Listan innehåller inga poster",
+                copyAll: "Kopiera allt",
+                helpText:
+                    "<p>Du kan kopiera värden från enskilda rader<br>" +
+                    "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
+                    "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
+                    "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
+            };
+
+        case "en":
+        default:
+            return {
+                theListContainsNoEntries: "The list contains no entries",
+                copyAll: "Copy all",
+                helpText:
+                    "<p>You can copy the values from individual rows<br>" +
+                    "to the clipboard by double-clicking on the relevant column.</p>" +
+                    "<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
+                    "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
+            };
+    }
 }
 
 /**
@@ -1264,83 +1268,83 @@ function getTranslations() {
  * @return {string}
  */
 export function getStoredOrderConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "stored-order");
+    return generateUniqueConfigKey("datatable", this?.id, "stored-order");
 }
 
 /**
  * @private
  */
 function storeOrderStatement(doFetch) {
-	const headers = this.getOption("headers");
-	const delimiter = this.getOption("datasource.orderDelimiter");
-	const statement = createOrderStatement(headers, delimiter);
-	setDataSource.call(this, { order: statement }, doFetch);
-
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-	if (!(host && this.id)) {
-		return;
-	}
+    const headers = this.getOption("headers");
+    const delimiter = this.getOption("datasource.orderDelimiter");
+    const statement = createOrderStatement(headers, delimiter);
+    setDataSource.call(this, {order: statement}, doFetch);
 
-	const configKey = getStoredOrderConfigKey.call(this);
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+    if (!(host && this.id)) {
+        return;
+    }
 
-	// statement explode with , and remove all empty
-	const list = statement.split(",").filter((item) => item.trim() !== "");
-	if (list.length === 0) {
-		return;
-	}
+    const configKey = getStoredOrderConfigKey.call(this);
+
+    // statement explode with , and remove all empty
+    const list = statement.split(",").filter((item) => item.trim() !== "");
+    if (list.length === 0) {
+        return;
+    }
 
-	host.setConfig(configKey, list);
+    host.setConfig(configKey, list);
 }
 
 /**
  * @private
  */
 function updateGrid() {
-	if (!this[gridElementSymbol]) {
-		throw new Error("no grid element is defined");
-	}
-
-	let gridTemplateColumns = "";
-
-	const headers = this.getOption("headers");
-
-	let styles = "";
-
-	for (let i = 0; i < headers.length; i++) {
-		const header = headers[i];
-
-		if (
-			header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN &&
-			header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED
-		) {
-			styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
-			styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i+1}) { display: none; }\n`;
-		} else {
-			gridTemplateColumns += `${header.grid} `;
-		}
-	}
-
-	const sheet = new CSSStyleSheet();
-	if (styles !== "") sheet.replaceSync(styles);
-	this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
-
-	const bodyWidth = this.parentNode.clientWidth;
-
-	const breakpoint = this.getOption("responsive.breakpoint");
-	this[dataControlElementSymbol].classList.toggle(
-		"small",
-		bodyWidth <= breakpoint,
-	);
-
-	if (bodyWidth > breakpoint) {
-		this[gridElementSymbol].style.gridTemplateColumns =
-			`${gridTemplateColumns}`;
-		this[gridHeadersElementSymbol].style.gridTemplateColumns =
-			`${gridTemplateColumns}`;
-	} else {
-		this[gridElementSymbol].style.gridTemplateColumns = "auto";
-		this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
-	}
+    if (!this[gridElementSymbol]) {
+        throw new Error("no grid element is defined");
+    }
+
+    let gridTemplateColumns = "";
+
+    const headers = this.getOption("headers");
+
+    let styles = "";
+
+    for (let i = 0; i < headers.length; i++) {
+        const header = headers[i];
+
+        if (
+            header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN &&
+            header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED
+        ) {
+            styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
+            styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i + 1}) { display: none; }\n`;
+        } else {
+            gridTemplateColumns += `${header.grid} `;
+        }
+    }
+
+    const sheet = new CSSStyleSheet();
+    if (styles !== "") sheet.replaceSync(styles);
+    this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
+
+    const bodyWidth = this.parentNode.clientWidth;
+
+    const breakpoint = this.getOption("responsive.breakpoint");
+    this[dataControlElementSymbol].classList.toggle(
+        "small",
+        bodyWidth <= breakpoint,
+    );
+
+    if (bodyWidth > breakpoint) {
+        this[gridElementSymbol].style.gridTemplateColumns =
+            `${gridTemplateColumns}`;
+        this[gridHeadersElementSymbol].style.gridTemplateColumns =
+            `${gridTemplateColumns}`;
+    } else {
+        this[gridElementSymbol].style.gridTemplateColumns = "auto";
+        this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
+    }
 }
 
 /**
@@ -1348,20 +1352,20 @@ function updateGrid() {
  * @param {Header[]} headers
  * @param {bool} doFetch
  */
-function setDataSource({ order }, doFetch) {
-	const datasource = this[datasourceLinkedElementSymbol];
+function setDataSource({order}, doFetch) {
+    const datasource = this[datasourceLinkedElementSymbol];
 
-	if (!datasource) {
-		return;
-	}
+    if (!datasource) {
+        return;
+    }
 
-	if (isFunction(datasource?.setParameters)) {
-		datasource.setParameters({ order });
-	}
+    if (isFunction(datasource?.setParameters)) {
+        datasource.setParameters({order});
+    }
 
-	if (doFetch !== false && isFunction(datasource?.fetch)) {
-		datasource.fetch();
-	}
+    if (doFetch !== false && isFunction(datasource?.fetch)) {
+        datasource.fetch();
+    }
 }
 
 /**
@@ -1369,30 +1373,30 @@ function setDataSource({ order }, doFetch) {
  * @return {DataTable}
  */
 function initControlReferences() {
-	if (!this.shadowRoot) {
-		throw new Error("no shadow-root is defined");
-	}
+    if (!this.shadowRoot) {
+        throw new Error("no shadow-root is defined");
+    }
 
-	this[dataControlElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=control]",
-	);
+    this[dataControlElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=control]",
+    );
 
-	this[gridElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=datatable]",
-	);
+    this[gridElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=datatable]",
+    );
 
-	this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=datatable-headers]",
-	);
+    this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=datatable-headers]",
+    );
 
-	this[columnBarElementSymbol] =
-		this.shadowRoot.querySelector("monster-column-bar");
+    this[columnBarElementSymbol] =
+        this.shadowRoot.querySelector("monster-column-bar");
 
-	this[copyAllElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=copy-all]",
-	);
+    this[copyAllElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=copy-all]",
+    );
 
-	return this;
+    return this;
 }
 
 /**
@@ -1402,25 +1406,25 @@ function initControlReferences() {
  * @throws {Error} the datasource could not be initialized
  */
 function initOptionsFromArguments() {
-	const options = { datasource: {} };
-	const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
+    const options = {datasource: {}};
+    const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
 
-	if (selector) {
-		options.datasource = { selector: selector };
-	}
+    if (selector) {
+        options.datasource = {selector: selector};
+    }
 
-	options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter
+    options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter
 
-	const breakpoint = this.getAttribute(
-		ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
-	);
+    const breakpoint = this.getAttribute(
+        ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
+    );
 
-	if (breakpoint) {
-		options.responsive = {};
-		options.responsive.breakpoint = parseInt(breakpoint);
-	}
+    if (breakpoint) {
+        options.responsive = {};
+        options.responsive.breakpoint = parseInt(breakpoint);
+    }
 
-	return options;
+    return options;
 }
 
 /**
@@ -1428,7 +1432,7 @@ function initOptionsFromArguments() {
  * @return {string}
  */
 function getEmptyTemplate() {
-	return `<monster-state data-monster-role="empty-without-action">
+    return `<monster-state data-monster-role="empty-without-action">
     <div part="visual">
         <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
             <path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/>
@@ -1446,8 +1450,8 @@ function getEmptyTemplate() {
  * @return {string}
  */
 function getTemplate() {
-	// language=HTML
-	return `
+    // language=HTML
+    return `
         <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
             <template id="headers-row">
                 <div data-monster-attributes="class path:headers-row.classes,
diff --git a/source/components/datatable/filter.mjs b/source/components/datatable/filter.mjs
index 74637de76eb936d32d71b588ff04804db0a0ee62..b697a14db8af1544d34167439c7d6e7e30a69223 100644
--- a/source/components/datatable/filter.mjs
+++ b/source/components/datatable/filter.mjs
@@ -157,7 +157,7 @@ const hashChangeSymbol = Symbol("hashChange");
  * @example /examples/components/datatable/filter-advanced Advanced filter
  * @example /examples/components/datatable/filter-store Store filter
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html
  *
  * @copyright schukai GmbH
  * @summary The Filter component is used to show and handle the filter values.
diff --git a/source/components/datatable/save-button.mjs b/source/components/datatable/save-button.mjs
index 0d6120a244f97d5db125f881c47774e8de7845b5..a1188237f55fa4aa848a307dc3d76ca3c2cce9ee 100644
--- a/source/components/datatable/save-button.mjs
+++ b/source/components/datatable/save-button.mjs
@@ -67,7 +67,7 @@ const badgeElementSymbol = Symbol("badgeElement");
  *
  * @example /examples/components/datatable/save-button-simple Simple example
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html
  *
  * @copyright schukai GmbH
  * @summary This is a save button component that can be used to save changes to a datasource.
diff --git a/source/components/datatable/status.mjs b/source/components/datatable/status.mjs
index 5b41b5b441c1ff7225d50ab11adc756363c791a5..8f929c080bc90be87d0fd0f7e9c37a82b64bdc4f 100644
--- a/source/components/datatable/status.mjs
+++ b/source/components/datatable/status.mjs
@@ -52,7 +52,7 @@ const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement");
  *
  * @example /examples/components/datatable/datasource-status-simple Simple dataset status
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html
  *
  * @copyright schukai GmbH
  * @summary The Status component is used to show the current status of a datasource.
diff --git a/source/components/form/action-button.mjs b/source/components/form/action-button.mjs
index 8f233ee58dbc2bb7c53a61062dea1837555d41cc..322a00edeceb53882fa61fcc62167f6ba8cb0051 100644
--- a/source/components/form/action-button.mjs
+++ b/source/components/form/action-button.mjs
@@ -48,7 +48,7 @@ const containerElementSymbol = Symbol("containerElement");
  *
  * @example /examples/components/form/action-button
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/264.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/264.html
  *
  * @since 3.32.0
  * @copyright schukai GmbH
diff --git a/source/components/form/button.mjs b/source/components/form/button.mjs
index 8c01e2de5ecbaee1c7e8438d3b88e2c3c2ea1411..69a3052a7c4e6e9b29e503a558142ad859166181 100644
--- a/source/components/form/button.mjs
+++ b/source/components/form/button.mjs
@@ -48,8 +48,8 @@ export const buttonElementSymbol = Symbol("buttonElement");
  * @example /examples/components/form/button-simple Simple Button
  * @example /examples/components/form/button-with-click-event Button with event
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/282.html
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/282.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html
  *
  * @copyright schukai GmbH
  * @summary A beautiful button that can make your life easier and also looks good.
diff --git a/source/components/form/confirm-button.mjs b/source/components/form/confirm-button.mjs
index f55ea79e13589cfd10f556673c8598a337a14ff7..a18dbda6fade7f6c58fea8d14edd252a19c08587 100644
--- a/source/components/form/confirm-button.mjs
+++ b/source/components/form/confirm-button.mjs
@@ -50,7 +50,7 @@ const cancelButtonElementSymbol = Symbol("cancelButtonElement");
  *
  * @example /examples/components/form/confirm-button-simple simple confirm button
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html
  *
  * @since 1.5.0
  * @copyright schukai GmbH
diff --git a/source/components/form/form.mjs b/source/components/form/form.mjs
index acd5af7df8bf5b4f4454b62e267434ad2a684a07..ca812b6214f85b1508b2ec7103950f380b8cda4f 100644
--- a/source/components/form/form.mjs
+++ b/source/components/form/form.mjs
@@ -47,7 +47,8 @@ const debounceBindSymbol = Symbol("debounceBind");
  *
  * @example /examples/components/form/form-simple
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/281.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/281.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/217.html
  *
  * @since 1.0.0
  * @copyright schukai GmbH
@@ -179,39 +180,42 @@ function initEventHandler() {
 	this[debounceBindSymbol] = {};
 
 	if (this.getOption("features.writeBack") === true) {
-		const events = this.getOption("writeBack.events");
-		for (const event of events) {
-			this.addEventListener(event, (e) => {
-				if (!this.reportValidity()) {
-					this.classList.add("invalid");
-					setTimeout(() => {
-						this.classList.remove("invalid");
-					}, 1000);
-
-					return;
-				}
 
-				if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
-					try {
-						this[debounceWriteBackSymbol].touch();
+		setTimeout(() => {
+			const events = this.getOption("writeBack.events");
+			for (const event of events) {
+				this.addEventListener(event, (e) => {
+					if (!this.reportValidity()) {
+						this.classList.add("invalid");
+						setTimeout(() => {
+							this.classList.remove("invalid");
+						}, 1000);
+
 						return;
-					} catch (e) {
-						if (e.message !== "has already run") {
-							throw e;
+					}
+
+					if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
+						try {
+							this[debounceWriteBackSymbol].touch();
+							return;
+						} catch (e) {
+							if (e.message !== "has already run") {
+								throw e;
+							}
+							delete this[debounceWriteBackSymbol];
 						}
-						delete this[debounceWriteBackSymbol];
 					}
-				}
 
-				this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
-					setTimeout(() => {
-						this.write().catch((e) => {
-							addAttributeToken(this, "error", e.message || `${e}`);
-						});
-					}, 0);
+					this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
+						setTimeout(() => {
+							this.write().catch((e) => {
+								addAttributeToken(this, "error", e.message || `${e}`);
+							});
+						}, 0);
+					});
 				});
-			});
-		}
+			}
+		},0);
 	}
 
 	return this;
diff --git a/source/components/form/popper-button.mjs b/source/components/form/popper-button.mjs
index cee1df499bd6f323b73ece48d3a6cfd5a18c65bf..690ee472f83646db4cc6bfbb9dbe715e2809ed12 100644
--- a/source/components/form/popper-button.mjs
+++ b/source/components/form/popper-button.mjs
@@ -83,7 +83,7 @@ const arrowElementSymbol = Symbol("arrowElement");
  *
  * @example /examples/components/form/popper-button-simple
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html
  *
  * @since 1.5.0
  * @copyright schukai GmbH
diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs
index 993d29a52e6cbe85ad1b61240b5f80d8e6b01678..c6b72f84ca5d57e0746f328466bce27e209f6a91 100644
--- a/source/components/form/select.mjs
+++ b/source/components/form/select.mjs
@@ -349,6 +349,7 @@ class Select extends CustomControl {
         }
 
         const result = convertValueToSelection.call(this, value);
+
         setSelection
             .call(this, result.selection)
             .then(() => {
diff --git a/source/components/host/viewer.mjs b/source/components/host/viewer.mjs
index eac978ca8a0af47c91ee3e226ea0636b3961b802..96947f3f2b2b8415d3a1c7bc2ad1716ca5ec1a7e 100644
--- a/source/components/host/viewer.mjs
+++ b/source/components/host/viewer.mjs
@@ -12,272 +12,11 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import {
-	assembleMethodSymbol,
-	CustomElement,
-	registerCustomElement,
-} from "../../dom/customelement.mjs";
-import "../notify/notify.mjs";
-import { ViewerStyleSheet } from "./stylesheet/viewer.mjs";
-import { instanceSymbol } from "../../constants.mjs";
-import { isString } from "../../types/is.mjs";
-import { getGlobal } from "../../types/global.mjs";
-
+import { Viewer as NewViewer } from "../content/viewer.mjs";
 export { Viewer };
 
 /**
- * @private
- * @type {symbol}
- */
-const viewerElementSymbol = Symbol("viewerElement");
-
-/**
- * The Viewer component is used to show a PDF, HTML or Image.
- *
  * @copyright schukai GmbH
- * @summary A simple viewer component
- */
-class Viewer extends CustomElement {
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @return {symbol}
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/components/host/viewer@@instance");
-	}
-
-	/**
-	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
-	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
-	 *
-	 * The individual configuration values can be found in the table.
-	 *
-	 * @property {Object} templates Template definitions
-	 * @property {string} templates.main Main template
-	 * @property {Object} classes Css classes
-	 * @property {Object} features Feature definitions
-	 */
-	get defaults() {
-		return Object.assign({}, super.defaults, {
-			templates: {
-				main: getTemplate(),
-			},
-			content: "<slot>",
-			classes: {
-				viewer: "",
-			},
-			features: {},
-		});
-	}
-
-	/**
-	 *
-	 * @param html
-	 * @returns {Viewer}
-	 */
-	setContent(html) {
-		this.setOption("content", html);
-		return this;
-	}
-
-	/**
-	 *
-	 * @param {Blob|URL|string} data
-	 * @param {boolean} navigation
-	 * @param {boolean} toolbar
-	 * @param {boolean} scrollbar
-	 */
-	setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
-		const hashes =
-			"#toolbar=" +
-			(toolbar ? "1" : "0") +
-			"&navpanes=" +
-			(navigation ? "1" : "0") +
-			"&scrollbar=" +
-			(scrollbar ? "1" : "0");
-
-		let pdfContent = "";
-		if (isBlob(data)) {
-			pdfContent = URL.createObjectURL(data);
-			pdfContent += hashes;
-		} else if (isURL(data)) {
-			pdfContent = data;
-			// check if the url already contains the hashes
-			if (pdfContent.indexOf("#") === -1) {
-				pdfContent += hashes;
-			}
-		} else if (isString(data)) {
-			//URL.createObjectURL(data);
-			const blobObj = new Blob([atob(data)], { type: "application/pdf" });
-			const url = window.URL.createObjectURL(blobObj);
-
-			pdfContent = data;
-		} else {
-			throw new Error("Blob or URL expected");
-		}
-
-		const html =
-			'<object data="' +
-			pdfContent +
-			'" width="100%" height="100%" type="application/pdf"></object>';
-		this.setContent(html);
-	}
-
-	/**
-	 *
-	 * @param {Blob|URL|string} data
-	 */
-	setImage(data) {
-		if (isBlob(data)) {
-			data = URL.createObjectURL(data);
-		} else if (isURL(data)) {
-			// nothing to do
-		} else if (isString(data)) {
-			// nothing to do
-		} else {
-			throw new Error("Blob or URL expected");
-		}
-
-		const html = '<img src="' + data + '" alt="image" />';
-		this.setContent(html);
-	}
-
-	/**
-	 *
-	 * if the data is a string, it is interpreted as html
-	 * if the data is a url, the html is loaded from the url and set as content
-	 * if the data is an HTMLElement, the outerHTML is used as content
-	 *
-	 * @param {HTMLElement|URL|string|Blob} data
-	 */
-	setHTML(data) {
-		if (data instanceof Blob) {
-			blobToText(data)
-				.then((html) => {
-					this.setHTML(html);
-				})
-				.catch((error) => {
-					throw new Error(error);
-				});
-
-			return;
-		} else if (data instanceof HTMLElement) {
-			data = data.outerHTML;
-		} else if (isString(data)) {
-			// nothing to do
-		} else if (isURL(data)) {
-			// fetch element
-			getGlobal()
-				.fetch(data)
-				.then((response) => {
-					return response.text();
-				})
-				.then((html) => {
-					this.setHTML(html);
-				})
-				.catch((error) => {
-					throw new Error(error);
-				});
-		} else {
-			throw new Error("HTMLElement or string expected");
-		}
-
-		this.setContent(data);
-	}
-
-	/**
-	 *
-	 * @return {Viewer}
-	 */
-	[assembleMethodSymbol]() {
-		super[assembleMethodSymbol]();
-
-		initControlReferences.call(this);
-		initEventHandler.call(this);
-	}
-
-	/**
-	 *
-	 * @return {string}
-	 */
-	static getTag() {
-		return "monster-viewer";
-	}
-
-	/**
-	 * @return {CSSStyleSheet[]}
-	 */
-	static getCSSStyleSheet() {
-		return [ViewerStyleSheet];
-	}
-}
-
-/**
- * @private
- * @param variable
- * @return {boolean}
- */
-function isURL(variable) {
-	try {
-		new URL(variable);
-		return true;
-	} catch (error) {
-		return false;
-	}
-}
-
-/**
- * @private
- * @param variable
- * @return {boolean}
- */
-function isBlob(variable) {
-	return variable instanceof Blob;
-}
-
-/**
- * @private
- * @param blob
- * @return {Promise<unknown>}
- */
-function blobToText(blob) {
-	return new Promise((resolve, reject) => {
-		const reader = new FileReader();
-		reader.onloadend = () => resolve(reader.result);
-		reader.onerror = reject;
-		reader.readAsText(blob);
-	});
-}
-
-/**
- * @private
- * @return {Select}
- * @throws {Error} no shadow-root is defined
- */
-function initControlReferences() {
-	if (!this.shadowRoot) {
-		throw new Error("no shadow-root is defined");
-	}
-
-	this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
-}
-
-/**
- * @private
+ * @deprecated since 3.102.0 use Content/Viewer instead
  */
-function initEventHandler() {
-	return this;
-}
-
-/**
- * @private
- * @return {string}
- */
-function getTemplate() {
-	// language=HTML
-	return `
-        <div id="viewer" data-monster-role="viewer" part="viewer" data-monster-replace="path:content" data-monster-attributes="class path:classes.viewer">
-        </div>`;
-}
-
-registerCustomElement(Viewer);
+class Viewer extends NewViewer {}
diff --git a/source/components/layout/tabs.mjs b/source/components/layout/tabs.mjs
index 536747048149aa6616198d7db0318e74dda1e8c6..eeb104ce7322afc63ab0c55f60466698b7bcab27 100644
--- a/source/components/layout/tabs.mjs
+++ b/source/components/layout/tabs.mjs
@@ -150,9 +150,9 @@ const resizeObserverSymbol = Symbol("resizeObserver");
  * @example /examples/components/layout/tabs-active Active Tabs
  * @example /examples/components/layout/tabs-removable Removable Tabs
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/268.html
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/271.html
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/273.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/268.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/271.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/273.html
  *
  * @since 3.74.0
  * @copyright schukai GmbH
diff --git a/source/components/notify/message.mjs b/source/components/notify/message.mjs
index ed53a75bd057e016c6e1d9b7e490cd1b2b861863..4e9876d4156a882ec6eeecc487e901801cf9dc44 100644
--- a/source/components/notify/message.mjs
+++ b/source/components/notify/message.mjs
@@ -72,7 +72,7 @@ const removeEventHandlerSymbol = Symbol("removeEventHandler");
  *
  * @example /examples/components/notify/message-simple Message
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/269.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/269.html
  *
  * @since 1.0.0
  * @copyright schukai GmbH
diff --git a/source/components/notify/notify.mjs b/source/components/notify/notify.mjs
index 8f26859e20b11733f2fad29af820091105814db1..0bfeaef40f0f4549cad0094c4cb937a9ca62318e 100644
--- a/source/components/notify/notify.mjs
+++ b/source/components/notify/notify.mjs
@@ -52,7 +52,7 @@ const queueSymbol = Symbol("queue");
  * @example /examples/components/notify/notify-simple Notify
  * @example /examples/components/notify/notify-inline Inline Notify
  *
- * @issue https://localhost.alvine.dev:8443/development/issues/closed/269.html
+ * @issue https://localhost.alvine.dev:8440/development/issues/closed/269.html
  *
  * @since 1.0.0
  * @copyright schukai GmbH
diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs
index 8d4044fe9442704a56848b9f4f780556a88332fd..a9f74d2e24273b3def431981dd3334c8e2b4b88a 100644
--- a/source/dom/updater.mjs
+++ b/source/dom/updater.mjs
@@ -399,6 +399,10 @@ function retrieveAndSetValue(element) {
                 break;
 
             case "string[]":
+                if (value === "") {
+                    value = [];
+                }
+
                 value = value.split(",").map((v) => `${v}`);
                 break;