@schukai/monster 3.110.1 → 3.110.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [3.110.2] - 2025-02-19
6
+
7
+ ### Bug Fixes
8
+
9
+ - **reload:** set shadowRoot to false
10
+
11
+
12
+
5
13
  ## [3.110.1] - 2025-02-19
6
14
 
7
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.110.1"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.110.2"}
@@ -69,7 +69,7 @@ class Reload extends CustomElement {
69
69
  * @property {Object} templates Template definitions
70
70
  * @property {string} templates.main Main template
71
71
  * @property {string} url url to fetch
72
- * @property {string} reload onshow, always
72
+ * @property {string} reload onshow, always (onshow is default and means that the content is loaded when the element is visible, always means that the content is always loaded)
73
73
  * @property {string} filter css selector
74
74
  * @property {Object} fetch fetch options for the request
75
75
  * @property {string} fetch.redirect error, follow, manual
@@ -87,7 +87,7 @@ class Reload extends CustomElement {
87
87
  templates: {
88
88
  main: getTemplate.call(this),
89
89
  },
90
- shadowMode: null,
90
+ shadowMode: false,
91
91
  url: null,
92
92
  reload: "onshow",
93
93
  filter: null,
@@ -171,7 +171,13 @@ class Reload extends CustomElement {
171
171
  this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`);
172
172
  }
173
173
 
174
- return loadContent.call(this);
174
+ try {
175
+ return loadContent.call(this);
176
+ } catch (e) {
177
+ addErrorAttribute(this, e);
178
+ return Promise.reject(e)
179
+ }
180
+
175
181
  }
176
182
  }
177
183
 
@@ -290,7 +296,7 @@ function loadContent() {
290
296
  }
291
297
  })
292
298
  .catch((e) => {
293
- throw e;
299
+ addErrorAttribute(this, e);
294
300
  });
295
301
  }
296
302
 
@@ -16,124 +16,117 @@ import { isString } from "../../../types/is.mjs";
16
16
  import { fireCustomEvent } from "../../../dom/events.mjs";
17
17
  import { validateInstance, validateString } from "../../../types/validate.mjs";
18
18
 
19
+ /**
20
+ * Traverse the element's ancestors to find an existing Shadow DOM.
21
+ *
22
+ * @param {HTMLElement} element - The starting element.
23
+ * @returns {ShadowRoot|null} - The found Shadow DOM or null if none exists.
24
+ */
19
25
  function findShadowRoot(element) {
20
- if (element instanceof ShadowRoot) return element;
21
- if (!element.parentNode) return null;
22
- return findShadowRoot(element.parentNode);
26
+ while (element) {
27
+ if (element?.shadowRoot) {
28
+ return element?.shadowRoot;
29
+ }
30
+ element = element?.parentNode;
31
+ }
32
+ return null;
23
33
  }
24
34
 
25
35
  /**
26
- * @private
27
- * @param {HTMLElement} element
28
- * @param {string|URL} url
29
- * @param {Object} options fetch options
30
- * @param {Object} filter fetch options
31
- * @return {Promise<Object>}
32
- * @throws {Error} we won't be able to read the data
33
- * @throws {Error} client error
34
- * @throws {Error} undefined status or type
35
- * @throws {TypeError} value is not an instance of
36
- * @throws {TypeError} value is not a string
36
+ * Loads content from a URL and assigns it to an element.
37
+ * Optionally, the loaded content can be filtered using a CSS selector.
38
+ * Additionally, any <script> elements within the content are extracted and executed.
39
+ *
40
+ * @param {HTMLElement} element - The target element to insert the content.
41
+ * @param {string|URL} url - The URL from which to load the content.
42
+ * @param {Object} options - Options for the fetch call.
43
+ * @param {string} [filter] - Optional CSS selector to filter the loaded content.
44
+ * @returns {Promise<Object>} A promise that resolves to an object containing { content: string, type: string | null }.
45
+ * @throws {Error} When the content cannot be read or the response contains an error.
46
+ * @throws {TypeError} When the provided parameters do not match the expected types.
37
47
  */
38
48
  function loadAndAssignContent(element, url, options, filter) {
39
49
  return loadContent(url, options).then((response) => {
40
50
  let content = response.content;
41
51
 
52
+ // Optional filtering: if a valid, non-empty CSS selector is provided,
53
+ // only the matching elements will be retained.
42
54
  if (isString(filter) && filter !== "") {
43
- const t = document.createElement("div");
44
- const c = document.createElement("div");
45
- c.innerHTML = content;
46
- for (const [, node] of c.querySelectorAll(filter).entries()) {
47
- t.appendChild(node);
48
- }
49
-
50
- content = t.innerHTML;
55
+ const filteredContainer = document.createElement("div");
56
+ const tempContainer = document.createElement("div");
57
+ tempContainer.innerHTML = content;
58
+ const matchingNodes = tempContainer.querySelectorAll(filter);
59
+ matchingNodes.forEach((node) => {
60
+ filteredContainer.appendChild(node);
61
+ });
62
+ content = filteredContainer.innerHTML;
51
63
  }
52
64
 
53
- const t = document.createElement("div");
54
- t.innerHTML = content;
55
-
56
- const scripts = t.querySelectorAll("script");
57
- for (const [, script] of scripts.entries()) {
58
- const s = document.createElement("script");
59
- s.innerHTML = script.innerHTML;
60
- if (script.src) s.src = script.src;
61
- if (script.type) s.type = script.type;
62
- if (script.async) s.async = script.async;
63
- if (script.defer) s.defer = script.defer;
64
- if (script.crossOrigin) s.crossOrigin = script.crossOrigin;
65
- if (script.integrity) s.integrity = script.integrity;
66
- if (script.referrerPolicy) s.referrerPolicy = script.referrerPolicy;
67
- document.head.appendChild(s);
68
- t.removeChild(script);
69
- }
65
+ // Temporary container for processing the content and extracting scripts
66
+ const tempDiv = document.createElement("div");
67
+ tempDiv.innerHTML = content;
68
+
69
+ // Extract and execute all <script> elements by appending them to the document head
70
+ const scriptElements = tempDiv.querySelectorAll("script");
71
+ scriptElements.forEach((oldScript) => {
72
+ const newScript = document.createElement("script");
73
+ if (oldScript.src) newScript.src = oldScript.src;
74
+ if (oldScript.type) newScript.type = oldScript.type;
75
+ if (oldScript.async) newScript.async = oldScript.async;
76
+ if (oldScript.defer) newScript.defer = oldScript.defer;
77
+ if (oldScript.crossOrigin) newScript.crossOrigin = oldScript.crossOrigin;
78
+ if (oldScript.integrity) newScript.integrity = oldScript.integrity;
79
+ if (oldScript.referrerPolicy) newScript.referrerPolicy = oldScript.referrerPolicy;
80
+ newScript.textContent = oldScript.textContent;
81
+ document.head.appendChild(newScript);
82
+ if (oldScript.parentNode) {
83
+ oldScript.parentNode.removeChild(oldScript);
84
+ }
85
+ });
70
86
 
71
- validateInstance(element, HTMLElement).innerHTML = t.innerHTML;
87
+ // Assign the processed content to the target element
88
+ validateInstance(element, HTMLElement).innerHTML = tempDiv.innerHTML;
72
89
 
73
- const root = findShadowRoot(element);
74
- if (root !== null) {
75
- element = root.host;
90
+ // If the element is within a Shadow DOM, use the host as the event target
91
+ const shadowRoot = findShadowRoot(element);
92
+ const eventTarget = shadowRoot !== null ? shadowRoot.host : element;
93
+ if (eventTarget instanceof HTMLElement) {
94
+ fireCustomEvent(eventTarget, "monster-fetched", { url });
76
95
  }
77
96
 
78
- fireCustomEvent(element, "monster-fetched", {
79
- url,
80
- });
81
-
82
97
  return response;
83
98
  });
84
99
  }
85
100
 
86
101
  /**
87
- * @private
88
- * @param {string|URL} url
89
- * @param {Object} options fetch options
90
- * @return {Promise<string>}
91
- * @throws {Error} we won't be able to read the data
92
- * @throws {Error} client error
93
- * @throws {Error} undefined status or type
94
- * @throws {TypeError} value is not a string
102
+ * Loads content from a URL using fetch and returns an object with the loaded content
103
+ * and the Content-Type header.
104
+ *
105
+ * @param {string|URL} url - The URL from which to load the content.
106
+ * @param {Object} options - Options for the fetch call.
107
+ * @returns {Promise<Object>} A promise that resolves to an object { content: string, type: string | null }.
108
+ * @throws {Error} When the content cannot be read or the response contains an error.
109
+ * @throws {TypeError} When the URL cannot be validated as a string.
95
110
  */
96
111
  function loadContent(url, options) {
97
112
  if (url instanceof URL) {
98
113
  url = url.toString();
99
114
  }
100
-
101
115
  return fetch(validateString(url), options).then((response) => {
102
- // The ok read-only property of the Response interface contains a
103
- // Boolean stating whether the response was successful (status in the range 200-299) or not.
104
- if (response?.ok !== true) {
105
- // @see https://developer.mozilla.org/en-US/docs/Web/API/Response/type
106
- if (
107
- ["error", "opaque", "opaqueredirect"].indexOf(response?.type) !== -1
108
- ) {
109
- throw new Error(
110
- `we won't be able to read the data (${response?.type})`,
111
- );
116
+ if (!response.ok) {
117
+ if (["error", "opaque", "opaqueredirect"].includes(response.type)) {
118
+ throw new Error(`we won't be able to read the data (${response.type})`);
112
119
  }
113
-
114
- const statusClass = `${response?.status}`.substring(0, 1);
115
- switch (statusClass) {
116
- case "4":
117
- throw new Error(`client error ${response?.statusText}`);
118
- break;
119
- default:
120
- throw new Error(
121
- `undefined status (${response?.status} / ${response?.statusText}) or type (${response?.type})`,
122
- );
120
+ const statusClass = String(response.status).charAt(0);
121
+ if (statusClass === "4") {
122
+ throw new Error(`client error ${response.statusText}`);
123
123
  }
124
+ throw new Error(`undefined status (${response.status} / ${response.statusText}) or type (${response.type})`);
124
125
  }
125
-
126
- return new Promise(function (resolve, reject) {
127
- response
128
- .text()
129
- .then((content) => {
130
- resolve({
131
- content,
132
- type: response.headers.get("Content-Type"),
133
- });
134
- })
135
- .catch(reject);
136
- });
126
+ return response.text().then((content) => ({
127
+ content,
128
+ type: response.headers.get("Content-Type"),
129
+ }));
137
130
  });
138
131
  }
139
132