@cocreate/element-prototype 1.35.0 → 2.0.0

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/src/setValue.js CHANGED
@@ -1,282 +1,337 @@
1
- import { storage } from "./getValue.js";
1
+ import { getValueAttribute, transformValue, convertValue } from "@cocreate/utils";
2
2
 
3
3
  HTMLElement.prototype.setValue = function (value, dispatch) {
4
- setValue(this, value, dispatch);
4
+ setValue(this, value, dispatch);
5
5
  };
6
6
  SVGElement.prototype.setValue = function (value, dispatch) {
7
- setValue(this, value, dispatch);
7
+ setValue(this, value, dispatch);
8
8
  };
9
9
 
10
- // TODO: check if using a a switch case will provide better performance
11
10
  const setValue = (el, value, dispatch) => {
12
- let bubbles = el.hasAttribute("value-bubbles");
13
- let valueDispatch = el.hasAttribute("value-dispatch");
14
- if (valueDispatch || valueDispatch === "") {
15
- if (value === "$false" || value === undefined || value === null)
16
- return dispatchEvents(el, bubbles, dispatch);
17
- }
18
-
19
- if (value === null || value === undefined) return;
20
- if (el.hasAttribute("component"))
21
- return storage.set(el, value);
22
- else if (typeof value === "object") value = JSON.stringify(value, null, 2);
23
-
24
- if (["date", "time", "datetime", "datetime-local"].includes(el.getAttribute("type") || el.type )) {
11
+ let bubbles = ![null, false, "false"].includes(getValueAttribute(el, "bubbles", "set-value"));
12
+ let valueDispatch = ![null, false, "false"].includes(getValueAttribute(el, "dispatch", "set-value"));
13
+ if (valueDispatch || valueDispatch === "") {
14
+ if (value === "$false" || value === undefined || value === null)
15
+ return dispatchEvents(el, bubbles, dispatch);
16
+ }
17
+
18
+ if (value === null || value === undefined) return;
19
+ value = transformValue(el, value, "set-value");
20
+ value = convertValue(value, getValueAttribute(el, "type", "set-value") || "");
21
+
22
+ if (typeof value === "object" && !el.hasAttribute("component")) {
23
+ value = JSON.stringify(value, null, 2);
24
+ }
25
+
26
+ const nativeValueElement = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
27
+ if (!nativeValueElement && (
28
+ el.hasAttribute("component") ||
29
+ ((el.__cocreateEditor || el.hasAttribute("editor") ||
30
+ (el.hasAttribute("contenteditable") && el.getAttribute("contenteditable") !== "false")) &&
31
+ el.tagName !== "IFRAME")
32
+ )) {
33
+ el.value = value;
34
+ return dispatchEvents(el, bubbles, dispatch, value);
35
+ }
36
+
37
+ if (["date", "time", "datetime", "datetime-local"].includes(el.getAttribute("type") || el.type)) {
25
38
  if (value) {
26
39
  const date = new Date(value);
27
- // If 'use-utc' is present, we shift the time forward by the timezone offset.
28
- if (el.hasAttribute("use-utc")) {
29
- date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
30
- }
40
+ // If 'use-utc' is present, we shift the time forward by the timezone offset.
41
+ if (el.hasAttribute("use-utc")) {
42
+ date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
43
+ }
31
44
 
32
45
  if (el.tagName === "INPUT") {
33
- if (!isNaN(date.getTime())) {
34
- // We no longer need ternary operators or if/else blocks here.
35
- const year = date.getFullYear();
36
- const month = String(date.getMonth() + 1).padStart(2, "0");
37
- const day = String(date.getDate()).padStart(2, "0");
38
- const hours = String(date.getHours()).padStart(2, "0");
39
- const minutes = String(date.getMinutes()).padStart(2, "0");
40
-
41
- // 5. Set Value
42
- if (el.type === "time") {
43
- el.value = `${hours}:${minutes}`;
44
- } else if (el.type === "date") {
45
- el.value = `${year}-${month}-${day}`;
46
- } else {
47
- el.value = `${year}-${month}-${day}T${hours}:${minutes}`;
48
- }
49
- } else {
50
- console.warn(`Invalid date for input ${el.name}:`, value);
51
- el.value = "";
52
- }
53
- return dispatchEvents(el, bubbles, dispatch);
46
+ if (!isNaN(date.getTime())) {
47
+ // We no longer need ternary operators or if/else blocks here.
48
+ const year = date.getFullYear();
49
+ const month = String(date.getMonth() + 1).padStart(2, "0");
50
+ const day = String(date.getDate()).padStart(2, "0");
51
+ const hours = String(date.getHours()).padStart(2, "0");
52
+ const minutes = String(date.getMinutes()).padStart(2, "0");
53
+
54
+ // 5. Set Value
55
+ if (el.type === "time") {
56
+ el.value = `${hours}:${minutes}`;
57
+ } else if (el.type === "date") {
58
+ el.value = `${year}-${month}-${day}`;
59
+ } else {
60
+ el.value = `${year}-${month}-${day}T${hours}:${minutes}`;
61
+ }
62
+ } else {
63
+ console.warn(`Invalid date for input ${el.name}:`, value);
64
+ el.value = "";
65
+ }
66
+ return dispatchEvents(el, bubbles, dispatch);
54
67
  } else if (!isNaN(date.getTime())) {
55
- value = date.toLocaleString()
56
- }
68
+ value = date.toLocaleString()
69
+ }
57
70
  } else {
58
71
  el.value = "";
59
72
  }
60
73
  }
61
-
62
- let valueType = el.getAttribute("value-type");
63
- let prefix = el.getAttribute("value-prefix") || "";
64
- if (prefix) value = value.toString().replace(prefix, "");
65
-
66
- let suffix = el.getAttribute("value-suffix") || "";
67
- if (suffix) value = value.toString().replace(suffix, "");
68
-
69
- // TODO: el.options vs rendenring options from src
70
- if (
71
- el.tagName == "INPUT" ||
72
- el.tagName == "TEXTAREA" ||
73
- el.tagName == "SELECT"
74
- ) {
75
- // TODO: attribute config undefined when used with onload-value
76
- let isCrdt = el.getAttribute("crdt");
77
- if (isCrdt == "true" || el.type === "file") return;
78
-
79
- if (el.type == "checkbox") {
80
- let inputs = [el];
81
- let key = el.getAttribute("key");
82
- if (key)
83
- inputs = document.querySelectorAll(
84
- `input[type="${el.type}"][key="${key}"]`
85
- );
86
-
87
- for (let i = 0; i < inputs.length; i++) {
88
- let inputValue = inputs[i].getAttribute("value");
89
- if (inputs[i].value) {
90
- if (value === true || value === false)
91
- inputs[i].checked = value;
92
- else if (value.includes(inputValue) || value === "on") {
93
- inputs[i].checked = true;
94
- } else {
95
- inputs[i].checked = false;
96
- }
97
- } else {
98
- if (
99
- value === "true" ||
100
- value === true ||
101
- value === "checked"
102
- )
103
- inputs[i].checked = true;
104
- else inputs[i].checked = false;
105
- }
106
- }
107
- } else if (el.type === "radio") {
108
- const wasChecked = el.checked;
109
- if (el.value == value) {
110
- el.checked = true;
111
- } else {
112
- el.checked = false;
113
- }
114
- // Only continue if checked state changed
115
- if (el.checked === wasChecked) return;
116
- } else if (el.type === "password") {
117
- el.value = __decryptPassword(value);
118
- } else if (el.tagName == "SELECT") {
119
- let options = el.options;
120
- for (let i = 0; i < options.length; i++) {
121
- if (value.includes && value.includes(options[i].value)) {
122
- options[i].selected = "selected";
123
- } else {
124
- options[i].selected = "";
125
- }
126
- }
127
- } else {
128
- if (el.value === value && !valueDispatch) {
129
- return;
130
- }
131
-
132
- el.value = value;
133
- }
134
- // dispatchEvents(el, bubbles, dispatch);
135
- } else if (el.tagName === "IMG" || el.tagName === "SOURCE") {
136
- el.src = value;
137
- } else if (el.tagName === "IFRAME") {
138
- el.srcdoc = value;
139
- } else if (el.tagName === "SCRIPT") {
140
- setScript(el, value);
141
- } else if (el.hasAttribute("value")) {
142
- el.setAttribute("value", value);
143
- } else {
144
- if (el.hasAttribute("contenteditable") && el == document.activeElement)
145
- return;
146
-
147
- if (valueType == "string" || valueType == "text")
148
- el.textContent = value;
149
- else {
150
- let newElement = document.createElement("div");
151
- newElement.innerHTML = value;
152
- setState(newElement);
153
-
154
- let CoCreateJS = newElement.querySelector(
155
- 'script[src*="CoCreate.js"], script[src*="CoCreate.min.js"]'
156
- );
157
- if (CoCreateJS) CoCreateJS.remove();
158
-
159
- let CoCreateCSS = newElement.querySelector(
160
- 'link[href*="CoCreate.css"], link[href*="CoCreate.min.css"]'
161
- );
162
- if (CoCreateCSS) CoCreateCSS.remove();
163
-
164
- let css = newElement.querySelector("link[array], link[object]");
165
- if (css) css.remove();
166
-
167
- if (valueType == "outerHTML") {
168
- let parentNode = el.parentNode;
169
- if (parentNode) {
170
- if (newElement.children.length > 0) {
171
- let fragment = document.createDocumentFragment();
172
- while (newElement.firstChild) {
173
- fragment.appendChild(newElement.firstChild);
174
- }
175
- parentNode.replaceChild(fragment, el);
176
- } else {
177
- parentNode.replaceChild(newElement, el);
178
- }
179
- }
180
- } else el.innerHTML = newElement.innerHTML;
181
-
182
- let scripts = el.querySelectorAll("script");
183
- for (let script of scripts) {
184
- setScript(script);
185
- }
186
- }
187
-
188
- if (el.hasAttribute("value")) {
189
- el.setAttribute("value", value);
190
- }
191
- }
192
-
193
- // if (el.getAttribute("contenteditable")) dispatchEvents(el, bubbles, dispatch);
194
-
195
- if (el.tagName == "HEAD" || el.tagName == "BODY") {
196
- el.removeAttribute("array");
197
- el.removeAttribute("object");
198
- el.removeAttribute("state_id");
199
-
200
- let scripts = el.querySelectorAll("script");
201
- for (let script of scripts) {
202
- setScript(script);
203
- }
204
- }
205
-
206
- dispatchEvents(el, bubbles, dispatch);
74
+
75
+ let valueType = getValueAttribute(el, "type", "set-value");
76
+ let prefix = getValueAttribute(el, "prefix", "set-value") || "";
77
+ if (prefix) value = el.hasAttribute("set-value-prefix")
78
+ ? prefix + value
79
+ : value.toString().replace(prefix, "");
80
+
81
+ let suffix = getValueAttribute(el, "suffix", "set-value") || "";
82
+ if (suffix) value = el.hasAttribute("set-value-suffix")
83
+ ? value + suffix
84
+ : value.toString().replace(suffix, "");
85
+
86
+ // TODO: el.options vs rendenring options from src
87
+ if (
88
+ el.tagName == "INPUT" ||
89
+ el.tagName == "TEXTAREA" ||
90
+ el.tagName == "SELECT"
91
+ ) {
92
+ if (el.type === "file") return;
93
+
94
+ if (el.type == "checkbox") {
95
+ let inputs = [el];
96
+ let key = el.getAttribute("key");
97
+ if (key)
98
+ inputs = document.querySelectorAll(
99
+ `input[type="${el.type}"][key="${key}"]`
100
+ );
101
+
102
+ for (let i = 0; i < inputs.length; i++) {
103
+ let inputValue = inputs[i].getAttribute("value");
104
+ if (inputs[i].value) {
105
+ if (value === true || value === false)
106
+ inputs[i].checked = value;
107
+ else if (value.includes(inputValue) || value === "on") {
108
+ inputs[i].checked = true;
109
+ } else {
110
+ inputs[i].checked = false;
111
+ }
112
+ } else {
113
+ if (
114
+ value === "true" ||
115
+ value === true ||
116
+ value === "checked"
117
+ )
118
+ inputs[i].checked = true;
119
+ else inputs[i].checked = false;
120
+ }
121
+ }
122
+ } else if (el.type === "radio") {
123
+ const wasChecked = el.checked;
124
+ if (el.value == value) {
125
+ el.checked = true;
126
+ } else {
127
+ el.checked = false;
128
+ }
129
+ // Only continue if checked state changed
130
+ if (el.checked === wasChecked) return;
131
+ } else if (el.type === "password") {
132
+ el.value = __decryptPassword(value);
133
+ } else if (el.tagName == "SELECT") {
134
+ let options = el.options;
135
+ for (let i = 0; i < options.length; i++) {
136
+ if (value.includes && value.includes(options[i].value)) {
137
+ options[i].selected = "selected";
138
+ } else {
139
+ options[i].selected = "";
140
+ }
141
+ }
142
+ } else {
143
+ if (el.value === value && !valueDispatch) {
144
+ return;
145
+ }
146
+
147
+ el.value = value;
148
+ }
149
+ // dispatchEvents(el, bubbles, dispatch);
150
+ } else if (el.tagName === "IMG" || el.tagName === "SOURCE") {
151
+ el.src = value;
152
+ } else if (el.tagName === "IFRAME") {
153
+ if (el.hasAttribute("src")) el.src = value;
154
+ else if (el.hasAttribute("srcdoc")) el.srcdoc = value;
155
+ else {
156
+ const doc = el.contentDocument;
157
+ const parsedDocument = new DOMParser().parseFromString(
158
+ String(value),
159
+ "text/html"
160
+ );
161
+ const sourceRoot = parsedDocument.documentElement;
162
+ const targetRoot = doc.documentElement;
163
+
164
+ for (const attribute of Array.from(sourceRoot.attributes))
165
+ targetRoot.setAttribute(attribute.name, attribute.value);
166
+
167
+ targetRoot.replaceChildren(...Array.from(sourceRoot.childNodes));
168
+
169
+ for (const script of targetRoot.querySelectorAll("script"))
170
+ setScript(script, undefined, true);
171
+ }
172
+ } else if (el.tagName === "SCRIPT") {
173
+ setScript(el, value);
174
+ } else if (el.hasAttribute("value")) {
175
+ el.setAttribute("value", value);
176
+ } else {
177
+ if (el.hasAttribute("contenteditable") && el == document.activeElement)
178
+ return;
179
+
180
+ if (valueType == "string" || valueType == "text" || valueType == "textContent")
181
+ el.textContent = value;
182
+ else if (valueType == "innerText")
183
+ el.innerText = value;
184
+ else {
185
+ let newElement = document.createElement("div");
186
+ newElement.innerHTML = value;
187
+
188
+ let CoCreateJS = newElement.querySelector(
189
+ 'script[src*="CoCreate.js"], script[src*="CoCreate.min.js"]'
190
+ );
191
+ if (CoCreateJS) CoCreateJS.remove();
192
+
193
+ let CoCreateCSS = newElement.querySelector(
194
+ 'link[href*="CoCreate.css"], link[href*="CoCreate.min.css"]'
195
+ );
196
+ if (CoCreateCSS) CoCreateCSS.remove();
197
+
198
+ let css = newElement.querySelector("link[array], link[object]");
199
+ if (css) css.remove();
200
+
201
+ if (document.head) {
202
+ const stylesheets = newElement.querySelectorAll(
203
+ 'link[rel~="stylesheet"][href]'
204
+ );
205
+ for (const stylesheet of stylesheets) {
206
+ const exists = Array.from(
207
+ document.head.querySelectorAll(
208
+ 'link[rel~="stylesheet"][href]'
209
+ )
210
+ ).some(
211
+ (link) =>
212
+ link.href === stylesheet.href &&
213
+ link.media === stylesheet.media
214
+ );
215
+ if (exists) stylesheet.remove();
216
+ else document.head.appendChild(stylesheet);
217
+ }
218
+ }
219
+
220
+ if (valueType?.toLowerCase() === "outerhtml") {
221
+ let parentNode = el.parentNode;
222
+ if (parentNode) {
223
+ if (newElement.children.length > 0) {
224
+ let fragment = document.createDocumentFragment();
225
+ while (newElement.firstChild) {
226
+ fragment.appendChild(newElement.firstChild);
227
+ }
228
+ parentNode.replaceChild(fragment, el);
229
+ } else {
230
+ parentNode.replaceChild(newElement, el);
231
+ }
232
+ }
233
+ } else el.innerHTML = newElement.innerHTML;
234
+
235
+ let scripts = el.querySelectorAll("script");
236
+ for (let script of scripts) {
237
+ setScript(script);
238
+ }
239
+ }
240
+
241
+ if (el.hasAttribute("value")) {
242
+ el.setAttribute("value", value);
243
+ }
244
+ }
245
+
246
+ // if (el.getAttribute("contenteditable")) dispatchEvents(el, bubbles, dispatch);
247
+
248
+ if (el.tagName == "HEAD" || el.tagName == "BODY") {
249
+ el.removeAttribute("array");
250
+ el.removeAttribute("object");
251
+ el.removeAttribute("state_id");
252
+
253
+ let scripts = el.querySelectorAll("script");
254
+ for (let script of scripts) {
255
+ setScript(script);
256
+ }
257
+ }
258
+
259
+ dispatchEvents(el, bubbles, dispatch, value);
207
260
  };
208
261
 
209
- function setState(el) {
210
- if (CoCreate.state) {
211
- let stateElements = el.querySelectorAll("[state_id]");
212
- if (stateElements) CoCreate.state.initElements(stateElements);
213
- }
214
- }
262
+ function setScript(script, value, allowCrossOrigin = false) {
263
+ let srcAttribute = script.src;
264
+ if (srcAttribute && !allowCrossOrigin) {
265
+ let pageOrigin = window.location.origin;
266
+ let srcOrigin;
267
+
268
+ try {
269
+ srcOrigin = new URL(srcAttribute, document.baseURI).origin;
270
+ } catch (e) {
271
+ // Handle invalid URLs
272
+ console.error("Invalid URL in src attribute:", srcAttribute);
273
+ return;
274
+ }
275
+ if (pageOrigin !== srcOrigin) return;
276
+ }
215
277
 
216
- function setScript(script, value) {
217
- let srcAttribute = script.src;
218
- if (srcAttribute) {
219
- let pageOrigin = window.location.origin;
220
- let srcOrigin;
221
-
222
- try {
223
- srcOrigin = new URL(srcAttribute, document.baseURI).origin;
224
- } catch (e) {
225
- // Handle invalid URLs
226
- console.error("Invalid URL in src attribute:", srcAttribute);
227
- return;
228
- }
229
- if (pageOrigin !== srcOrigin) return;
230
- }
231
-
232
- let newScript = document.createElement("script");
233
- for (let attr of script.attributes) {
234
- newScript.setAttribute(attr.name, attr.value);
235
- }
236
- newScript.innerHTML = script.innerHTML;
237
- if (value) {
238
- if (script.hasAttribute("src")) newScript.src = value;
239
- else newScript.innerHTML = value;
240
- }
241
- try {
242
- script.replaceWith(newScript);
243
- } catch (error) {
244
- console.log(error);
245
- }
278
+ let newScript = document.createElement("script");
279
+ for (let attr of script.attributes) {
280
+ newScript.setAttribute(attr.name, attr.value);
281
+ }
282
+ newScript.innerHTML = script.innerHTML;
283
+ if (value) {
284
+ if (script.hasAttribute("src")) newScript.src = value;
285
+ else newScript.innerHTML = value;
286
+ }
287
+ try {
288
+ script.replaceWith(newScript);
289
+ } catch (error) {
290
+ console.log(error);
291
+ }
246
292
  }
247
293
 
248
294
  function __decryptPassword(str) {
249
- if (!str) return "";
250
- let decode_str = atob(str);
251
- return decode_str;
295
+ if (!str) return "";
296
+ const value = String(str);
297
+ try {
298
+ return atob(value);
299
+ } catch (error) {
300
+ // Password fields may also receive plain-text credentials from CRUD.
301
+ // Preserve those values instead of aborting the response render.
302
+ if (error.name !== "InvalidCharacterError") throw error;
303
+ return value;
304
+ }
252
305
  }
253
306
 
254
- function dispatchEvents(el, bubbles = true, skip = true) {
255
- let inputEvent = new CustomEvent("input", {
256
- bubbles,
257
- detail: {
258
- skip
259
- }
260
- });
261
-
262
- Object.defineProperty(inputEvent, "target", {
263
- writable: false,
264
- value: el
265
- });
266
- el.dispatchEvent(inputEvent);
267
-
268
- let changeEvent = new CustomEvent("change", {
269
- bubbles,
270
- detail: {
271
- skip
272
- }
273
- });
274
-
275
- Object.defineProperty(changeEvent, "target", {
276
- writable: false,
277
- value: el
278
- });
279
- el.dispatchEvent(changeEvent);
307
+ function dispatchEvents(el, bubbles = true, skip = true, value) {
308
+ let inputEvent = new CustomEvent("input", {
309
+ bubbles,
310
+ detail: {
311
+ skip,
312
+ value
313
+ }
314
+ });
315
+
316
+ Object.defineProperty(inputEvent, "target", {
317
+ writable: false,
318
+ value: el
319
+ });
320
+ el.dispatchEvent(inputEvent);
321
+
322
+ let changeEvent = new CustomEvent("change", {
323
+ bubbles,
324
+ detail: {
325
+ skip,
326
+ value
327
+ }
328
+ });
329
+
330
+ Object.defineProperty(changeEvent, "target", {
331
+ writable: false,
332
+ value: el
333
+ });
334
+ el.dispatchEvent(changeEvent);
280
335
  }
281
336
 
282
337
  export { setValue };