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