From 1cdf38090cea40ffd1e4e84a27f323be683bc6c1 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Mon, 3 Apr 2023 18:23:50 +0200
Subject: [PATCH] feat: attribure observer

---
 application/source/dom/customelement.mjs | 82 +++++++++++++++++-------
 1 file changed, 58 insertions(+), 24 deletions(-)

diff --git a/application/source/dom/customelement.mjs b/application/source/dom/customelement.mjs
index 7e7d86f88..858cfd824 100644
--- a/application/source/dom/customelement.mjs
+++ b/application/source/dom/customelement.mjs
@@ -5,19 +5,19 @@
  * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
  */
 
-import { internalSymbol } from "../constants.mjs";
-import { extend } from "../data/extend.mjs";
-import { Pathfinder } from "../data/pathfinder.mjs";
-import { Formatter } from "../text/formatter.mjs";
-
-import { parseDataURL } from "../types/dataurl.mjs";
-import { getGlobalObject } from "../types/global.mjs";
-import { isArray, isFunction, isIterable, isObject, isString } from "../types/is.mjs";
-import { Observer } from "../types/observer.mjs";
-import { ProxyObserver } from "../types/proxyobserver.mjs";
-import { validateFunction, validateInstance, validateObject, validateString } from "../types/validate.mjs";
-import { clone } from "../util/clone.mjs";
-import { addAttributeToken, getLinkedObjects, hasObjectLink } from "./attributes.mjs";
+import {internalSymbol} from "../constants.mjs";
+import {extend} from "../data/extend.mjs";
+import {Pathfinder} from "../data/pathfinder.mjs";
+import {Formatter} from "../text/formatter.mjs";
+
+import {parseDataURL} from "../types/dataurl.mjs";
+import {getGlobalObject} from "../types/global.mjs";
+import {isArray, isFunction, isIterable, isObject, isString} from "../types/is.mjs";
+import {Observer} from "../types/observer.mjs";
+import {ProxyObserver} from "../types/proxyobserver.mjs";
+import {validateFunction, validateInstance, validateObject, validateString} from "../types/validate.mjs";
+import {clone} from "../util/clone.mjs";
+import {addAttributeToken, getLinkedObjects, hasObjectLink} from "./attributes.mjs";
 import {
     ATTRIBUTE_DISABLED,
     ATTRIBUTE_ERRORMESSAGE,
@@ -25,11 +25,11 @@ import {
     ATTRIBUTE_OPTIONS_SELECTOR,
     customElementUpdaterLinkSymbol,
 } from "./constants.mjs";
-import { findDocumentTemplate, Template } from "./template.mjs";
-import { addObjectWithUpdaterToElement } from "./updater.mjs";
-import { instanceSymbol } from "../constants.mjs";
-import { getDocumentTranslations, Translations } from "../i18n/translations.mjs";
-import { getSlottedElements } from "./slotted.mjs";
+import {findDocumentTemplate, Template} from "./template.mjs";
+import {addObjectWithUpdaterToElement} from "./updater.mjs";
+import {instanceSymbol} from "../constants.mjs";
+import {getDocumentTranslations, Translations} from "../i18n/translations.mjs";
+import {getSlottedElements} from "./slotted.mjs";
 import {initOptionsFromAttributes} from "./util/init-options-from-attributes.mjs";
 
 export {
@@ -203,6 +203,7 @@ class CustomElement extends HTMLElement {
         this[internalSymbol] = new ProxyObserver({
             options: initOptionsFromAttributes(this, extend({}, this.defaults)),
         });
+        initAttributeChangeMutationObserver.call(this);        
         initOptionObserver.call(this);
         this[initMethodSymbol]();
     }
@@ -217,7 +218,9 @@ class CustomElement extends HTMLElement {
     }
 
     /**
-     * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
+     * This method determines which attributes are to be
+     * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
+     * Therefore, the `observedAttributes` property cannot be changed during runtime.
      *
      * @return {string[]}
      * @since 1.15.0
@@ -421,7 +424,8 @@ class CustomElement extends HTMLElement {
 
         try {
             value = new Pathfinder(this[internalSymbol].getRealSubject()["options"]).getVia(path);
-        } catch (e) {}
+        } catch (e) {
+        }
 
         if (value === undefined) return defaultValue;
         return value;
@@ -491,7 +495,8 @@ class CustomElement extends HTMLElement {
             try {
                 initShadowRoot.call(self);
                 elements = self.shadowRoot.childNodes;
-            } catch (e) {}
+            } catch (e) {
+            }
 
             try {
                 initCSSStylesheet.call(this);
@@ -542,7 +547,8 @@ class CustomElement extends HTMLElement {
      * @return {void}
      * @since 1.7.0
      */
-    disconnectedCallback() {}
+    disconnectedCallback() {
+    }
 
     /**
      * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
@@ -550,7 +556,8 @@ class CustomElement extends HTMLElement {
      * @return {void}
      * @since 1.7.0
      */
-    adoptedCallback() {}
+    adoptedCallback() {
+    }
 
     /**
      * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
@@ -595,6 +602,32 @@ class CustomElement extends HTMLElement {
     }
 }
 
+/**
+ * This method is called when the element is first created.
+ * 
+ * @private
+ * @this CustomElement
+ */
+function initAttributeChangeMutationObserver() {
+    const self = this;
+
+    if (self[attributeObserverSymbol] === undefined) {
+        self[attributeObserverSymbol] = {};
+    }
+
+    new MutationObserver(function (mutations) {
+        for (const mutation of mutations) {
+            if (mutation.type === "attributes") {
+                self.attributeChangedCallback(mutation.attributeName, mutation.oldValue, mutation.target.getAttribute(mutation.attributeName));
+            }
+        }
+    }).observe(self, {
+        attributes: true,
+        attributeOldValue: true,
+        attributeFilter: Object.keys(self[attributeObserverSymbol]),
+    });
+}
+
 /**
  * @this CustomElement
  * @private
@@ -787,7 +820,8 @@ function parseOptionsJSON(data) {
     try {
         let dataUrl = parseDataURL(data);
         data = dataUrl.content;
-    } catch (e) {}
+    } catch (e) {
+    }
 
     try {
         obj = JSON.parse(data);
-- 
GitLab