@cocreate/element-prototype 1.33.2 → 1.35.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.
@@ -9,33 +9,36 @@ jobs:
9
9
  about:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - name: Checkout
13
- uses: actions/checkout@v3
14
- - name: Setup Node.js
15
- uses: actions/setup-node@v3
16
- with:
17
- node-version: 16
18
- - name: Jaid/action-sync-node-meta
19
- uses: jaid/action-sync-node-meta@v1.4.0
20
- with:
21
- direction: overwrite-github
22
- githubToken: "${{ secrets.GITHUB }}"
12
+ - name: Checkout repository
13
+ uses: actions/checkout@v7
14
+
15
+ - name: Sync package.json description to GitHub About
16
+ env:
17
+ GITHUB_TOKEN: ${{ secrets.GITHUB }}
18
+ run: |
19
+ DESC=$(jq -r '.description // empty' package.json)
20
+ if [ -n "$DESC" ]; then
21
+ echo "Updating repository description to: $DESC"
22
+ gh api -X PATCH repos/${{ github.repository }} -f description="$DESC"
23
+ else
24
+ echo "No description found in package.json."
25
+ fi
23
26
 
24
27
  release:
25
28
  runs-on: ubuntu-latest
26
29
  steps:
27
- - name: Checkout
28
- uses: actions/checkout@v4
30
+ - name: Checkout repository
31
+ uses: actions/checkout@v7
29
32
  with:
30
- fetch-depth: 0 # Required so semantic-release can trace git tags/history
33
+ fetch-depth: 0
34
+ token: ${{ secrets.GITHUB }} # Authenticates git CLI for tag/commit pushes
31
35
 
32
36
  - name: Setup Node.js
33
- uses: actions/setup-node@v4
37
+ uses: actions/setup-node@v7
34
38
  with:
35
- node-version: 22
39
+ node-version: '24'
36
40
 
37
41
  - name: Install Semantic Release & Plugins
38
- # Installs semantic-release and its plugins on the runner
39
42
  run: |
40
43
  npm install -g semantic-release \
41
44
  @semantic-release/changelog \
@@ -43,12 +46,11 @@ jobs:
43
46
  @semantic-release/github \
44
47
  @semantic-release/git
45
48
 
46
- - name: Run Semantic Release (Native)
49
+ - name: Run Semantic Release
47
50
  id: semantic
48
- # This will automatically pick up your export default config file in the repository root
49
51
  run: npx semantic-release
50
52
  env:
51
- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
53
+ GITHUB_TOKEN: "${{ secrets.GITHUB }}"
52
54
  NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
53
55
 
54
56
  outputs:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/element-prototype",
3
- "version": "1.33.2",
3
+ "version": "1.35.0",
4
4
  "description": "A simple element-prototype component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "element-prototype",
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "main": "./src/index.js",
38
38
  "dependencies": {
39
- "@cocreate/utils": "^1.44.0"
39
+ "@cocreate/utils": "^1.50.3"
40
40
  },
41
41
  "devDependencies": {
42
- "@cocreate/webpack": "^1.6.0"
42
+ "@cocreate/webpack": "^1.7.0"
43
43
  },
44
44
  "allowScripts": {
45
45
  "@cocreate/utils@1.42.2": true,
@@ -1,69 +1,105 @@
1
- import { processOperators, processOperatorsAsync } from "@cocreate/utils";
1
+ /********************************************************************************
2
+ * Copyright (C) 2026 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ ********************************************************************************/
2
18
 
3
- // Store a reference to the original getAttribute function
19
+ import { astSync, astAsync } from "@cocreate/utils";
20
+
21
+ // Store a reference to the original native getAttribute function
4
22
  const originalGetAttribute = Element.prototype.getAttribute;
5
23
 
6
- // Map to store attribute details
24
+ // Map to store attribute binding details for dynamic storage syncs
7
25
  const attributes = new Map();
8
26
 
9
- // Add an event listener for storage events to update attributes
10
- window.addEventListener("storage", updateAttributes);
11
- // Custom event to update attributes
12
- window.addEventListener("updateAttributes", function (e) {
13
- updateAttributes(e.detail);
14
- });
27
+ if (typeof window !== "undefined") {
28
+ // Add an event listener for storage changes to update local attributes dynamically
29
+ window.addEventListener("storage", updateAttributes);
30
+
31
+ // Custom event bridge to update attributes from script environments
32
+ window.addEventListener("updateAttributes", function (e) {
33
+ updateAttributes(e.detail);
34
+ });
35
+ }
15
36
 
16
37
  /**
17
- * Function to update attributes based on specific storage keys
18
- * @param {Object} e - The event object containing key and newValue
38
+ * Function to update attributes on registered elements based on specific storage keys
39
+ * @param {Object} e - The event object containing changed key and newValue
19
40
  */
20
41
  function updateAttributes(e) {
21
- const keys = ["organization_id", "user_id", "clientId", "session_id"];
22
- if (keys.includes(e.key)) {
23
- let attr = attributes.get(e.key) || [];
24
- for (let attribute of attr) {
25
- attribute.element.setAttribute(attribute.name, e.newValue);
26
- }
27
- }
42
+ if (!e) return;
43
+ const keys = ["organization_id", "user_id", "clientId", "session_id"];
44
+ if (keys.includes(e.key)) {
45
+ let attr = attributes.get(e.key) || [];
46
+ for (let attribute of attr) {
47
+ if (attribute && attribute.element) {
48
+ attribute.element.setAttribute(attribute.name, e.newValue);
49
+ }
50
+ }
51
+ }
28
52
  }
29
53
 
30
54
  /**
31
- * Custom getAttribute function that processes the attribute value
55
+ * Custom getAttribute function that processes the attribute value synchronously
32
56
  * @param {Element} element - The element from which to get the attribute
33
57
  * @param {string} name - The attribute name
34
- * @returns {string} - The processed attribute value
58
+ * @returns {string|any} - The processed attribute value
35
59
  */
36
60
  function getAttribute(element, name) {
37
- if (!(element instanceof Element)) {
38
- throw new Error("First argument must be an Element");
39
- }
40
- let value = originalGetAttribute.call(element, name);
41
- return processOperators(element, value);
61
+ if (!(element instanceof Element)) {
62
+ throw new Error("First argument must be an Element");
63
+ }
64
+
65
+ const value = originalGetAttribute.call(element, name);
66
+ if (!value || typeof value !== "string") {
67
+ return value;
68
+ }
69
+
70
+ // Evaluate formulas or step paths synchronously via the unified AST Engine
71
+ return astSync({ executeAST: value, node: element });
42
72
  }
43
73
 
44
74
  /**
45
- * Asynchronously gets an attribute and processes its value for operators.
75
+ * Asynchronously gets an attribute and processes its value through our async AST pipelines.
46
76
  * @param {Element} element - The element from which to get the attribute.
47
77
  * @param {string} name - The attribute name.
48
- * @returns {Promise<string|object>} - A promise that resolves to the processed attribute value.
78
+ * @returns {Promise<string|any>} - A promise that resolves to the processed attribute value.
49
79
  */
50
80
  async function getAttributeAsync(element, name) {
51
- if (!(element instanceof Element)) {
52
- throw new Error("First argument must be an Element");
53
- }
54
- let value = originalGetAttribute.call(element, name);
55
- return await processOperatorsAsync(element, value);
81
+ if (!(element instanceof Element)) {
82
+ throw new Error("First argument must be an Element");
83
+ }
84
+
85
+ const value = originalGetAttribute.call(element, name);
86
+ if (!value || typeof value !== "string") {
87
+ return value;
88
+ }
89
+
90
+ // Evaluate async formulas, database lookups, or template blocks asynchronously
91
+ return await astAsync({ executeAST: value, node: element });
56
92
  }
57
93
 
58
- // Override the getAttribute method on Element prototype
94
+ // Override the native getAttribute method on the global Element prototype
59
95
  Element.prototype.getAttribute = function (name) {
60
- return getAttribute(this, name); // Use the custom getAttribute function
96
+ return getAttribute(this, name);
61
97
  };
62
98
 
63
- // Add getAttributeAsync to the Element prototype
99
+ // Add getAttributeAsync directly to the global Element prototype
64
100
  Element.prototype.getAttributeAsync = function (name) {
65
- return getAttributeAsync(this, name);
101
+ return getAttributeAsync(this, name);
66
102
  };
67
103
 
68
- // Export the custom getAttribute function
69
- export { getAttribute, getAttributeAsync };
104
+ // Export the customized attribute getters
105
+ export { getAttribute, getAttributeAsync };
package/src/getValue.js CHANGED
@@ -1,178 +1,293 @@
1
- import { processOperators } from "@cocreate/utils";
1
+ /********************************************************************************
2
+ * Copyright (C) 2026 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ ********************************************************************************/
18
+
19
+ import { astSync, astAsync } from "@cocreate/utils";
2
20
 
3
21
  const storage = new Map();
4
22
 
5
23
  HTMLElement.prototype.getValue = function () {
6
- let value = getValue(this);
7
- return value;
24
+ return getValue(this);
8
25
  };
9
26
  SVGElement.prototype.getValue = function () {
10
- let value = getValue(this);
11
- return value;
27
+ return getValue(this);
12
28
  };
13
29
 
14
- // TODO: return blobs for element.src and for link.href (Not addressed)
30
+ HTMLElement.prototype.getValueAsync = async function () {
31
+ return await getValueAsync(this);
32
+ };
33
+ SVGElement.prototype.getValueAsync = async function () {
34
+ return await getValueAsync(this);
35
+ };
15
36
 
16
37
  /**
17
- * Retrieves and processes the value from an HTML element based on its type and additional attributes.
38
+ * Retrieves and processes the value from an HTML element synchronously.
18
39
  *
19
40
  * @param {HTMLElement} element - The HTML element to process for its value.
20
41
  * @param {string} valueType - Optional. The expected type that defines how the value should be processed.
21
- * @returns {any} - The processed value based on the element's type and attributes.
42
+ * @returns {any} - The processed value.
22
43
  */
23
44
  const getValue = (element, valueType) => {
24
- // If no valueType is given, attempt to retrieve it from the element's attributes
25
45
  if (!valueType) {
26
46
  valueType = element.getAttribute("value-type") || "";
27
47
  }
28
48
 
29
- // Initialize value with element's value property or its 'value' attribute, defaulting to an empty string
30
49
  let value = element.value || element.getAttribute("value") || "";
31
50
 
32
- // Handle specific cases for elements considered components, plugins, or file inputs
33
51
  if (
34
52
  element.hasAttribute("component") ||
35
- // element.hasAttribute("plugin") ||
36
53
  element.type === "file" ||
37
54
  element.getAttribute("type") === "file"
38
55
  ) {
39
- // Retrieve and delete value from storage for secure data handling
40
56
  value = storage.get(element);
41
57
  storage.delete(element);
42
58
  return value;
43
59
  }
44
60
 
45
- // Retrieve prefix and suffix from attributes for later use
46
61
  let prefix = element.getAttribute("value-prefix") || "";
47
62
  let suffix = element.getAttribute("value-suffix") || "";
48
63
 
49
- // Determine elementType using type first, fallback to tagName (both lowercase and uppercase respectively)
50
- // const elementType = element.type ? element.type.toLowerCase() : null;
51
- const elementType = element.getAttribute("type") || element.type
64
+ const elementType = element.getAttribute("type") || element.type;
52
65
  const tagName = element.tagName.toUpperCase();
53
66
 
54
- // Switch statement to handle different element types and tagNames
55
67
  switch (elementType) {
56
68
  case "checkbox":
57
- // Handles multiple checkboxes in a group using a key and applies prefix/suffix if needed
58
69
  value = handleCheckbox(element, prefix, suffix);
59
70
  break;
60
71
 
61
- case "radio":
72
+ case "radio": {
62
73
  const key = element.getAttribute("key");
63
- // Handles radio inputs by selecting the checked radio's value in the group with the same key
64
74
  let el = document.querySelector(`input[key="${key}"]:checked`);
65
75
  if (el) {
66
76
  value = el.value;
67
77
  }
68
78
  break;
79
+ }
69
80
 
70
81
  case "number":
71
- // Converts the value to a number for inputs of type number
72
82
  value = Number(value);
73
83
  break;
74
84
 
75
85
  case "range":
76
- // If a minimum is specified, returns a range array, otherwise a single number
77
86
  value = element.min
78
87
  ? [Number(element.min), Number(element.value)]
79
88
  : Number(element.value);
80
89
  break;
81
90
 
82
91
  case "password":
83
- // Encodes the value in Base64 format for password inputs for secure representation
84
92
  value = btoa(value || "");
85
93
  break;
86
94
 
87
95
  case "email":
88
- // Converts the email to lowercase to maintain a standardized format
89
96
  value = value.toLowerCase();
90
97
  break;
91
98
 
92
99
  case "url":
93
- // TODO: Implement logic to return URL parts instead of the complete URL
94
100
  break;
95
101
 
96
102
  case "time":
97
103
  case "date":
98
104
  case "datetime":
99
105
  case "datetime-local":
100
- // Processes datetime-related inputs with custom logic
101
106
  value = handleDateTime(element, value, valueType);
102
107
  break;
108
+
103
109
  default:
104
110
  switch (tagName) {
105
111
  case "INPUT":
106
- // For generic input types, use the element's value
107
112
  value = element.value;
108
113
  break;
109
114
 
110
115
  case "SELECT":
111
- // Handles multiple selection in select elements, applying prefix/suffix if required
112
116
  value = element.hasAttribute("multiple")
113
117
  ? handleMultipleSelect(element, prefix, suffix)
114
118
  : element.value;
115
119
  break;
116
120
 
117
121
  case "TEXTAREA":
118
- // For textarea elements, preference is given to the 'value' attribute if it exists
119
122
  value = element.hasAttribute("value")
120
123
  ? element.getAttribute("value")
121
124
  : element.value;
122
125
  break;
123
126
 
124
127
  case "IFRAME":
125
- // For iframes, return the document source
126
128
  value = element.srcdoc;
127
129
  break;
128
130
 
129
131
  default:
130
- // Handles cases not explicitly matched by type or tagName
131
132
  value = handleElement(element, valueType);
132
133
  break;
133
134
  }
134
135
  break;
135
136
  }
136
137
 
137
- // If the desired valueType is boolean, convert the value accordingly
138
138
  if (valueType === "boolean") {
139
139
  return value && value !== "false";
140
140
  }
141
141
 
142
- // Apply additional processing through a series of transformation functions
143
142
  if (value) {
144
- value = processOperators(element, value, ["$value"]);
143
+ value = astSync({ executeAST: value, node: element, exclude: ["$value"] });
145
144
  value = caseHandler(element, value);
146
145
  value = regex(element, value);
147
146
  value = encodeValue(value, element.getAttribute("value-encode") || "");
148
147
  value = decodeValue(value, element.getAttribute("value-decode") || "");
149
148
  }
150
149
 
151
- // Append prefix and suffix to value if applicable, before JSON parsing
152
150
  if (typeof value === "string" || typeof value === "number") {
153
151
  if (prefix || suffix) {
154
152
  value = prefix + value + suffix;
155
153
  }
156
154
  }
157
155
 
158
- // Parse the value as JSON, if possible, and convert to an array if needed
159
156
  value = parseJson(value, valueType);
160
157
  value = toArray(value, valueType);
161
158
 
162
- return value; // Return the final processed value
159
+ return value;
163
160
  };
164
161
 
165
162
  /**
166
- * Processes a checkbox or a group of checkboxes to determine their checked values.
167
- * For multiple checkboxes with the same key, their checked values are collected into an array.
163
+ * Retrieves and processes the value from an HTML element asynchronously.
168
164
  *
169
- * @param {HTMLInputElement} element - The input element of type checkbox.
170
- * @param {string} prefix - A string to prepend to each checked value.
171
- * @param {string} suffix - A string to append to each checked value.
172
- * @returns {string|boolean|Array} - The value(s) of checked checkbox(es), or `false` if none are checked.
165
+ * @param {HTMLElement} element - The HTML element to process for its value.
166
+ * @param {string} valueType - Optional. The expected type that defines how the value should be processed.
167
+ * @returns {Promise<any>} - A promise that resolves to the processed value.
168
+ */
169
+ const getValueAsync = async (element, valueType) => {
170
+ if (!valueType) {
171
+ valueType = element.getAttribute("value-type") || "";
172
+ }
173
+
174
+ let value = element.value || element.getAttribute("value") || "";
175
+
176
+ if (
177
+ element.hasAttribute("component") ||
178
+ element.type === "file" ||
179
+ element.getAttribute("type") === "file"
180
+ ) {
181
+ value = storage.get(element);
182
+ storage.delete(element);
183
+ return value;
184
+ }
185
+
186
+ let prefix = element.getAttribute("value-prefix") || "";
187
+ let suffix = element.getAttribute("value-suffix") || "";
188
+
189
+ const elementType = element.getAttribute("type") || element.type;
190
+ const tagName = element.tagName.toUpperCase();
191
+
192
+ switch (elementType) {
193
+ case "checkbox":
194
+ value = handleCheckbox(element, prefix, suffix);
195
+ break;
196
+
197
+ case "radio": {
198
+ const key = element.getAttribute("key");
199
+ let el = document.querySelector(`input[key="${key}"]:checked`);
200
+ if (el) {
201
+ value = el.value;
202
+ }
203
+ break;
204
+ }
205
+
206
+ case "number":
207
+ value = Number(value);
208
+ break;
209
+
210
+ case "range":
211
+ value = element.min
212
+ ? [Number(element.min), Number(element.value)]
213
+ : Number(element.value);
214
+ break;
215
+
216
+ case "password":
217
+ value = btoa(value || "");
218
+ break;
219
+
220
+ case "email":
221
+ value = value.toLowerCase();
222
+ break;
223
+
224
+ case "url":
225
+ break;
226
+
227
+ case "time":
228
+ case "date":
229
+ case "datetime":
230
+ case "datetime-local":
231
+ value = handleDateTime(element, value, valueType);
232
+ break;
233
+
234
+ default:
235
+ switch (tagName) {
236
+ case "INPUT":
237
+ value = element.value;
238
+ break;
239
+
240
+ case "SELECT":
241
+ value = element.hasAttribute("multiple")
242
+ ? handleMultipleSelect(element, prefix, suffix)
243
+ : element.value;
244
+ break;
245
+
246
+ case "TEXTAREA":
247
+ value = element.hasAttribute("value")
248
+ ? element.getAttribute("value")
249
+ : element.value;
250
+ break;
251
+
252
+ case "IFRAME":
253
+ value = element.srcdoc;
254
+ break;
255
+
256
+ default:
257
+ value = handleElement(element, valueType);
258
+ break;
259
+ }
260
+ break;
261
+ }
262
+
263
+ if (valueType === "boolean") {
264
+ return value && value !== "false";
265
+ }
266
+
267
+ if (value) {
268
+ value = await astAsync({ executeAST: value, node: element, exclude: ["$value"] });
269
+ value = caseHandler(element, value);
270
+ value = regex(element, value);
271
+ value = encodeValue(value, element.getAttribute("value-encode") || "");
272
+ value = decodeValue(value, element.getAttribute("value-decode") || "");
273
+ }
274
+
275
+ if (typeof value === "string" || typeof value === "number") {
276
+ if (prefix || suffix) {
277
+ value = prefix + value + suffix;
278
+ }
279
+ }
280
+
281
+ value = parseJson(value, valueType);
282
+ value = toArray(value, valueType);
283
+
284
+ return value;
285
+ };
286
+
287
+ /**
288
+ * Processes a checkbox or a group of checkboxes to determine their checked values.
173
289
  */
174
290
  const handleCheckbox = (element, prefix = "", suffix = "") => {
175
- // Retrieve all checkboxes with the same key, else just the single element
176
291
  const inputs = element.getAttribute("key")
177
292
  ? document.querySelectorAll(
178
293
  `input[type="${element.type}"][key="${element.getAttribute(
@@ -182,30 +297,16 @@ const handleCheckbox = (element, prefix = "", suffix = "") => {
182
297
  : [element];
183
298
 
184
299
  if (inputs.length === 1) {
185
- // If only one checkbox, return its value or true/false depending on its checked state
186
300
  return element.checked ? element.value || true : false;
187
301
  } else {
188
- // For multiple checkboxes, collect their checked values into an array
189
302
  return Array.from(inputs)
190
- .filter((el) => el.checked) // Filter only checked elements
191
- .map((el) => `${prefix}${el.value}${suffix}`); // Apply prefix/suffix and collect values
303
+ .filter((el) => el.checked)
304
+ .map((el) => `${prefix}${el.value}${suffix}`);
192
305
  }
193
306
  };
194
307
 
195
308
  /**
196
309
  * Handles and transforms a date/time value based on the specified valueType.
197
- * Supports operations like converting to ISO string, extracting day/month names, converting to Unix timestamp, and more.
198
- *
199
- * @param {HTMLElement} element - The DOM element containing the date/time value.
200
- * @param {string} value - The initial value which may represent a date/time.
201
- * @param {string} valueType - Specifies the type of transformation to apply to the date/time value.
202
- * @returns {any} - The transformed or processed date/time value.
203
- */
204
- /**
205
- * Handles and transforms a date/time value with a 3-step pipeline:
206
- * 1. SNAP: Adjusts date to start/end of periods (Month, Week, Year).
207
- * 2. MATH: Adds/Subtracts Years, Months, Weeks, Days, Hours, Minutes, Seconds.
208
- * 3. FORMAT: Returns the final string/number representation.
209
310
  */
210
311
  const handleDateTime = (element, value, valueType) => {
211
312
  const inputType = (element.getAttribute("type") || element.type || "").toLowerCase();
@@ -214,7 +315,6 @@ const handleDateTime = (element, value, valueType) => {
214
315
  if (value === "$now" || element.hasAttribute("now")) {
215
316
  date = new Date();
216
317
  } else if (value instanceof Date) {
217
- // If it's already a date object, clone it to avoid mutating references
218
318
  date = new Date(value.getTime());
219
319
  } else if (value) {
220
320
  if (
@@ -223,7 +323,6 @@ const handleDateTime = (element, value, valueType) => {
223
323
  value
224
324
  )
225
325
  ) {
226
- // Normalize datetime-local only when seconds are absent; preserve provided seconds.
227
326
  if (inputType === "datetime-local" && /^[0-9T:-]{16}$/.test(value)) {
228
327
  value = `${value}:00`;
229
328
  }
@@ -237,12 +336,11 @@ const handleDateTime = (element, value, valueType) => {
237
336
  }
238
337
 
239
338
  if (date instanceof Date && !isNaN(date.getTime())) {
240
- // These operations "floor" or "ceil" the date to a specific boundary.
241
339
  switch (valueType) {
242
340
  case "startOfDay":
243
341
  date.setHours(0, 0, 0, 0);
244
342
  break;
245
- case "startOfWeek":
343
+ case "startOfWeek": {
246
344
  const startWkOff = parseInt(
247
345
  element.getAttribute("week-start-day") || 0,
248
346
  10
@@ -250,7 +348,8 @@ const handleDateTime = (element, value, valueType) => {
250
348
  date.setDate(date.getDate() - date.getDay() + startWkOff);
251
349
  date.setHours(0, 0, 0, 0);
252
350
  break;
253
- case "endOfWeek":
351
+ }
352
+ case "endOfWeek": {
254
353
  const endWkOff = parseInt(
255
354
  element.getAttribute("week-start-day") || 0,
256
355
  10
@@ -258,6 +357,7 @@ const handleDateTime = (element, value, valueType) => {
258
357
  date.setDate(date.getDate() - date.getDay() + 6 + endWkOff);
259
358
  date.setHours(23, 59, 59, 999);
260
359
  break;
360
+ }
261
361
  case "startOfMonth":
262
362
  date.setDate(1);
263
363
  date.setHours(0, 0, 0, 0);
@@ -276,78 +376,68 @@ const handleDateTime = (element, value, valueType) => {
276
376
  break;
277
377
  }
278
378
 
279
- // --- PHASE 3: MATH (Modify the Date)
280
- // Helper to get integer value from attribute safely
281
379
  const getVal = (attr) =>
282
380
  parseInt(element.getAttribute(attr) || "0", 10);
283
381
 
284
- // 1. Years
285
382
  const addYears = getVal("add-years") - getVal("subtract-years");
286
383
  if (addYears) date.setFullYear(date.getFullYear() + addYears);
287
384
 
288
- // 2. Months
289
385
  const addMonths = getVal("add-months") - getVal("subtract-months");
290
386
  if (addMonths) date.setMonth(date.getMonth() + addMonths);
291
387
 
292
- // 3. Weeks
293
388
  const addWeeks = getVal("add-weeks") - getVal("subtract-weeks");
294
389
  if (addWeeks) date.setDate(date.getDate() + addWeeks * 7);
295
390
 
296
- // 4. Days
297
391
  const addDays = getVal("add-days") - getVal("subtract-days");
298
392
  if (addDays) date.setDate(date.getDate() + addDays);
299
393
 
300
- // 5. Hours
301
394
  const addHours = getVal("add-hours") - getVal("subtract-hours");
302
395
  if (addHours) date.setHours(date.getHours() + addHours);
303
396
 
304
- // 6. Minutes
305
397
  const addMinutes = getVal("add-minutes") - getVal("subtract-minutes");
306
398
  if (addMinutes) date.setMinutes(date.getMinutes() + addMinutes);
307
399
 
308
- // 7. Seconds
309
400
  const addSeconds = getVal("add-seconds") - getVal("subtract-seconds");
310
401
  if (addSeconds) date.setSeconds(date.getSeconds() + addSeconds);
311
402
 
312
-
313
- // --- PHASE 4: FORMATTING (Output the Result) ---
314
403
  switch (valueType) {
315
- case "getDayName":
404
+ case "getDayName": {
316
405
  const days = [
317
406
  "Sunday", "Monday", "Tuesday", "Wednesday",
318
407
  "Thursday", "Friday", "Saturday",
319
408
  ];
320
409
  return days[date.getDay()];
410
+ }
321
411
 
322
- case "getMonthName":
412
+ case "getMonthName": {
323
413
  const months = [
324
414
  "January", "February", "March", "April", "May", "June",
325
415
  "July", "August", "September", "October", "November", "December",
326
416
  ];
327
417
  return months[date.getMonth()];
418
+ }
328
419
 
329
420
  case "getYear":
330
421
  case "getFullYear":
331
422
  return date.getFullYear();
332
423
 
333
- case "getTime":
424
+ case "getTime": {
334
425
  let hours = date.getHours();
335
426
  const minutes = String(date.getMinutes()).padStart(2, "0");
336
427
  const ampm = hours >= 12 ? "PM" : "AM";
337
- hours = hours % 12 || 12; // Convert 0 to 12 for midnight
428
+ hours = hours % 12 || 12;
338
429
  return `${hours}:${minutes} ${ampm}`;
430
+ }
339
431
 
340
432
  case "timeTo": {
341
433
  const diffMs = date.getTime() - Date.now();
342
434
  let totalSeconds = Math.floor(Math.abs(diffMs) / 1000);
343
435
 
344
- // Fetch requested units, defaulting to all if attribute isn't present
345
436
  const unitsAttr = element.getAttribute("time-to-units") || "years,months,days,hours,minutes,seconds";
346
437
  const requestedUnits = unitsAttr.split(',').map(u => u.trim().toLowerCase());
347
438
 
348
439
  const result = {};
349
440
 
350
- // Using the exact same time approximations as your timeAgo feature
351
441
  const unitValues = {
352
442
  years: 31536000,
353
443
  months: 2592000,
@@ -358,14 +448,13 @@ const handleDateTime = (element, value, valueType) => {
358
448
  seconds: 1
359
449
  };
360
450
 
361
- // The order matters here (largest to smallest) so we "consume" time properly
362
451
  const order = ["years", "months", "weeks", "days", "hours", "minutes", "seconds"];
363
452
 
364
453
  for (const unit of order) {
365
454
  if (requestedUnits.includes(unit)) {
366
455
  const val = Math.floor(totalSeconds / unitValues[unit]);
367
456
  result[unit] = val;
368
- totalSeconds -= val * unitValues[unit]; // subtract the accounted time
457
+ totalSeconds -= val * unitValues[unit];
369
458
  }
370
459
  }
371
460
 
@@ -376,7 +465,6 @@ const handleDateTime = (element, value, valueType) => {
376
465
  const diffMs = Date.now() - date.getTime();
377
466
  const absSecs = Math.floor(Math.abs(diffMs) / 1000);
378
467
 
379
- // --- LIMIT CHECK ---
380
468
  const limitAttr = element.getAttribute("time-ago-limit");
381
469
  if (limitAttr) {
382
470
  const match = limitAttr.match(/^(\d+)([a-z]+)$/i);
@@ -386,18 +474,17 @@ const handleDateTime = (element, value, valueType) => {
386
474
  let multiplier = 0;
387
475
 
388
476
  switch (limitUnit) {
389
- case 's': multiplier = 1; break; // seconds
390
- case 'm': multiplier = 60; break; // minutes
391
- case 'h': multiplier = 3600; break; // hours
392
- case 'd': multiplier = 86400; break; // days
393
- case 'w': multiplier = 604800; break; // weeks
394
- case 'mo': multiplier = 2592000; break; // months
395
- case 'y': multiplier = 31536000; break; // years
477
+ case 's': multiplier = 1; break;
478
+ case 'm': multiplier = 60; break;
479
+ case 'h': multiplier = 3600; break;
480
+ case 'd': multiplier = 86400; break;
481
+ case 'w': multiplier = 604800; break;
482
+ case 'mo': multiplier = 2592000; break;
483
+ case 'y': multiplier = 31536000; break;
396
484
  }
397
485
 
398
486
  const limitInSeconds = amount * multiplier;
399
487
 
400
- // If the time elapsed is greater than the limit, fallback to standard date string
401
488
  if (absSecs > limitInSeconds) {
402
489
  const locale = element.getAttribute("locale") || "en-US";
403
490
  return date.toLocaleString(locale);
@@ -421,19 +508,18 @@ const handleDateTime = (element, value, valueType) => {
421
508
  case "toUnixTimestamp":
422
509
  return Math.floor(date.getTime() / 1000);
423
510
 
424
- case "toLocaleString":
511
+ case "toLocaleString": {
425
512
  const locale = element.getAttribute("locale") || "en-US";
426
513
  return date.toLocaleString(locale);
514
+ }
427
515
 
428
516
  default:
429
- // Handle generic methods if specified
430
517
  if (valueType && typeof date[valueType] === "function") {
431
518
  return date[valueType]();
432
519
  }
433
520
 
434
521
  const pad = (n) => String(n).padStart(2, "0");
435
522
  if (inputType === "datetime-local") {
436
- // Return a datetime-local compatible string with seconds to avoid invalid values
437
523
  return `${date.getFullYear()}-${pad(
438
524
  date.getMonth() + 1
439
525
  )}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(
@@ -444,102 +530,64 @@ const handleDateTime = (element, value, valueType) => {
444
530
  }
445
531
  }
446
532
  };
533
+
447
534
  /**
448
- * Processes a <select> HTML element with the "multiple" attribute to retrieve an array of selected option values.
449
- * Each selected value can have a prefix or suffix added to it if specified.
450
- *
451
- * @param {HTMLSelectElement} element - The select element with multiple options that are possibly selected.
452
- * @param {string} prefix - A string to prepend to each selected option value.
453
- * @param {string} suffix - A string to append to each selected option value.
454
- * @returns {string[]} - An array of selected option values, each optionally prefixed and suffixed.
535
+ * Processes a <select> element with the "multiple" attribute.
455
536
  */
456
537
  const handleMultipleSelect = (element, prefix, suffix) => {
457
- // Initialize an empty array to hold the values of selected options.
458
538
  let value = [];
459
-
460
- // Retrieve all selected options from the select element.
461
539
  let options = element.selectedOptions;
462
540
 
463
- // Iterate over each selected option.
464
541
  for (let i = 0; i < options.length; i++) {
465
- // Retrieve the value of the current option.
466
542
  let optionValue = options[i].value;
467
-
468
- // If a prefix or suffix is provided, modify the option value accordingly.
469
543
  if (prefix || suffix) {
470
544
  optionValue = prefix + optionValue + suffix;
471
545
  }
472
-
473
- // Add the processed option value to the array.
474
546
  value.push(optionValue);
475
547
  }
476
548
 
477
- // Return the array of processed option values.
478
549
  return value;
479
550
  };
480
551
 
481
552
  /**
482
- * Retrieves a specific value from an HTML element based on the specified valueType.
483
- * If the element has a "value" attribute, it returns its value. Otherwise, it processes
484
- * the element to extract or format the desired information based on the valueType.
485
- *
486
- * @param {HTMLElement} element - The DOM element to process.
487
- * @param {string} valueType - The type of value to retrieve (e.g., "text", "outerHTML").
488
- * @returns {string} - The extracted value based on the specified valueType.
553
+ * Retrieves a value from a standard element.
489
554
  */
490
555
  const handleElement = (element, valueType) => {
491
- // Check if the element has a "value" attribute; if so, return its value.
492
556
  if (element.hasAttribute("value")) {
493
557
  return element.getAttribute("value");
494
558
  }
495
559
 
496
- // Use the original element as the target by default.
497
560
  let targetElement = element;
498
- // Retrieve the "value-remove-query" attribute used to specify selectors that should be removed.
499
561
  const excludeSelector = element.getAttribute("value-remove-query");
500
562
 
501
- // If there is an excludeSelector, clone the element and remove elements matching the selector.
502
563
  if (excludeSelector) {
503
- // Clone the element to avoid modifying the original DOM.
504
564
  targetElement = element.cloneNode(true);
505
- // Find and remove all elements that match the excludeSelector from the cloned element.
506
565
  targetElement
507
566
  .querySelectorAll(excludeSelector)
508
567
  .forEach((el) => el.remove());
509
568
  }
510
569
 
511
- // Determine the value to return based on the valueType.
512
570
  switch (valueType) {
513
571
  case "text":
514
- // Return the text content of the target element.
515
572
  return targetElement.innerText;
516
573
  case "outerHTML":
517
574
  case "element":
518
575
  case "node":
519
- // For these cases, return the outer HTML of the target element.
520
576
  return targetElement.outerHTML;
521
577
  default:
522
- // By default, return the inner HTML of the target element.
523
578
  return targetElement.innerHTML;
524
579
  }
525
580
  };
526
581
 
527
582
  /**
528
- * Modifies the case of `value` based on specific attributes in `element`.
529
- * Converts to lowercase or uppercase if attributes are present and not "false".
530
- *
531
- * @param {HTMLElement} element - The DOM element to check for attributes.
532
- * @param {string} value - The value to adjust based on the attributes.
533
- * @returns {string} - The adjusted value.
583
+ * Adjusts casing transformations on attributes.
534
584
  */
535
585
  function caseHandler(element, value) {
536
- // Convert to lowercase if "value-lowercase" is set and not "false"
537
586
  const lowercase = element.getAttribute("value-lowercase");
538
587
  if (lowercase !== null && lowercase !== "false") {
539
588
  value = value.toLowerCase();
540
589
  }
541
590
 
542
- // Convert to uppercase if "value-uppercase" is set and not "false"
543
591
  const uppercase = element.getAttribute("value-uppercase");
544
592
  if (uppercase !== null && uppercase !== "false") {
545
593
  value = value.toUpperCase();
@@ -549,18 +597,11 @@ function caseHandler(element, value) {
549
597
  }
550
598
 
551
599
  /**
552
- * Processes the value by applying regex-based transformations specified by certain attributes on an element.
553
- * Attributes dictate regex operations such as replace, match, split, etc.
554
- *
555
- * @param {HTMLElement} element - The DOM element whose attributes specify regex operations.
556
- * @param {string} value - The value to be transformed based on regex operations.
557
- * @returns {string} - The transformed value, or the original value if an error occurs.
600
+ * Transforms output based on Regular Expression definitions.
558
601
  */
559
602
  function regex(element, value) {
560
603
  try {
561
- const attributes = element.attributes; // Retrieve all attributes of the element
562
-
563
- // Define a list of attributes that specify regex operations
604
+ const attributes = element.attributes;
564
605
  const regexAttribute = [
565
606
  "value-replace",
566
607
  "value-replaceall",
@@ -572,30 +613,19 @@ function regex(element, value) {
572
613
  "value-exec"
573
614
  ];
574
615
 
575
- // Iterate over element attributes and process ones related to regex operations
576
616
  for (let i = 0; i < attributes.length; i++) {
577
- // Stop processing if value is null or undefined
578
617
  if (value === null || value === undefined) break;
579
-
580
- // Skip attributes that do not relate to regex operations
581
618
  if (!regexAttribute.includes(attributes[i].name)) continue;
582
619
 
583
- let regexAttributeValue = attributes[i].value; // The attribute value containing regex pattern
584
-
585
- // Skip processing for empty regex attributes
620
+ let regexAttributeValue = attributes[i].value;
586
621
  if (!regexAttributeValue) continue;
587
622
 
588
- // Parse the regex pattern and replacement from the attribute value
589
623
  let { regex, replacement } = regexParser(regexAttributeValue);
590
-
591
- // Use parsed regex if available
592
624
  if (regex) regexAttributeValue = regex;
593
625
 
594
- // Default to an empty string for replacement if not specified
595
626
  replacement =
596
627
  replacement || element.getAttribute("value-replacement") || "";
597
628
 
598
- // Execute the determined regex operation
599
629
  switch (attributes[i].name) {
600
630
  case "value-replace":
601
631
  value = value.replace(regexAttributeValue, replacement);
@@ -606,143 +636,107 @@ function regex(element, value) {
606
636
  case "value-test":
607
637
  value = regex.test(value);
608
638
  break;
609
- case "value-match":
639
+ case "value-match": {
610
640
  const matches = value.match(regexAttributeValue);
611
641
  if (matches) {
612
- value = matches[1] || matches[0]; // Use capturing group if available
642
+ value = matches[1] || matches[0];
613
643
  }
614
644
  break;
645
+ }
615
646
  case "value-split":
616
647
  value = value.split(regexAttributeValue);
617
648
  break;
618
649
  case "value-lastindex":
619
- regex.lastIndex = 0; // Ensure the starting index is reset
650
+ regex.lastIndex = 0;
620
651
  regex.test(value);
621
652
  value = regex.lastIndex;
622
653
  break;
623
654
  case "value-search":
624
655
  value = value.search(regexAttributeValue);
625
656
  break;
626
- case "value-exec":
657
+ case "value-exec": {
627
658
  const execResult = regex.exec(value);
628
659
  if (execResult) {
629
- value = execResult[1] || execResult[2] || execResult[0]; // Prioritize capturing groups
660
+ value = execResult[1] || execResult[2] || execResult[0];
630
661
  }
631
662
  break;
632
- default:
633
- // Unknown attribute, ignored
634
- break;
663
+ }
635
664
  }
636
665
  }
637
666
 
638
- // Return the transformed value
639
667
  return value;
640
668
  } catch (error) {
641
- // Log a warning since the transformation error is non-critical, but should be noted
642
669
  console.warn(
643
670
  "Warning: Transformation error during regex operation:",
644
671
  error,
645
672
  element
646
673
  );
647
- return value; // Return the original value to maintain application flow
674
+ return value;
648
675
  }
649
676
  }
650
677
 
651
- /**
652
- * Parses a string to extract a regular expression and a replacement string.
653
- * Assumes the input string may contain a regex pattern in the form "/pattern/flags"
654
- * and an optional replacement string separated by a comma.
655
- *
656
- * @param {string} string - The input string potentially containing a regex pattern and a replacement.
657
- * @returns {Object} An object containing the `regex` (RegExp object) and `replacement` (string).
658
- */
659
678
  function regexParser(string) {
660
679
  let regex, replacement;
661
-
662
- // Match a regex pattern defined as /pattern/flags within the input string.
663
- // It captures the pattern and the flags (e.g., 'g' for global, 'i' for case-insensitive).
664
680
  let regexMatch = string.match(/\/(.+)\/([gimuy]*)/);
665
681
 
666
- // If a regex pattern is found in the string, proceed to create the RegExp object
667
682
  if (regexMatch) {
668
- // Create a new RegExp object using the captured pattern and flags
669
683
  regex = new RegExp(regexMatch[1], regexMatch[2]);
670
-
671
- // Split the input string on ", " to separate the pattern from a replacement string
672
684
  const splitReplace = string.split(", ");
673
-
674
- // Check if there's a replacement string provided. If so, trim the surrounding spaces.
675
685
  replacement =
676
686
  splitReplace.length > 1 ? splitReplace[1].slice(1, -1) : "";
677
687
  }
678
688
 
679
- // Return an object containing the parsed regex and replacement string
680
689
  return { regex, replacement };
681
690
  }
682
691
 
683
692
  /**
684
- * Encodes the given value using the specified encoding type.
685
- *
686
- * @param {string} value - The value to be encoded.
687
- * @param {string} encodingType - The type of encoding to apply. Accepted values include "url", "uri-component", "base64", "html-entities", and "json".
688
- * @returns {string} - The encoded value or the original value if encoding type is unsupported.
693
+ * Encodes calculated structures securely.
689
694
  */
690
695
  function encodeValue(value, encodingType) {
691
696
  if (!encodingType) {
692
697
  return value;
693
698
  }
694
699
 
695
- // Determine the encoding method based on the case-insensitive encodingType
696
700
  switch (encodingType.toLowerCase()) {
697
701
  case "url":
698
702
  case "uri":
699
- // Encode spaces as "%20" explicitly and use encodeURI for overall URL encoding (excluding special characters like '?', '&', '/')
700
703
  return encodeURI(value.replace(/ /g, "%20"));
701
704
 
702
705
  case "uri-component":
703
- // Encode spaces as "%20" explicitly and use encodeURIComponent for more thorough encoding, including special characters
704
706
  return encodeURIComponent(value.replace(/ /g, "%20"));
705
707
 
706
708
  case "base64":
707
709
  case "atob":
708
710
  try {
709
- // Create a TextEncoder to convert the value into a Uint8Array, then encode it in base64
710
711
  const encoder = new TextEncoder();
711
712
  const uint8Array = encoder.encode(value);
712
713
  return btoa(String.fromCharCode(...uint8Array));
713
714
  } catch (error) {
714
715
  console.warn(`Failed to encode as Base64: ${error.message}`);
715
- return value; // Return the original value as a fallback
716
+ return value;
716
717
  }
717
718
 
718
719
  case "html-entities":
719
- // Replace specific HTML entities with their corresponding character codes using regex
720
720
  return value.replace(/[\u00A0-\u9999<>\&]/g, (i) => {
721
721
  return `&#${i.charCodeAt(0)};`;
722
722
  });
723
723
 
724
724
  case "json":
725
725
  try {
726
- // Convert the value to a JSON string representation
727
726
  return JSON.stringify(value);
728
727
  } catch (error) {
729
728
  console.warn(`Failed to convert to JSON: ${error.message}`);
730
- return value; // Return the original value as a fallback
729
+ return value;
731
730
  }
732
731
 
733
732
  default:
734
- // Log a warning for unsupported encoding types and return the original value
735
733
  console.warn(`Unsupported encoding type: ${encodingType}`);
736
734
  return value;
737
735
  }
738
736
  }
739
737
 
740
738
  /**
741
- * Decodes the given value using the specified decoding type.
742
- *
743
- * @param {string} value - The value to be decoded.
744
- * @param {string} decodingType - The type of decoding to apply. Accepted values include "url", "uri-component", "base64", "html-entities", and "json".
745
- * @returns {string} - The decoded value or the original value if decoding failed.
739
+ * Decodes calculated structures securely.
746
740
  */
747
741
  function decodeValue(value, decodingType) {
748
742
  if (!decodingType) {
@@ -758,7 +752,7 @@ function decodeValue(value, decodingType) {
758
752
  return decodeURIComponent(value);
759
753
 
760
754
  case "base64":
761
- case "btoa": // Alias for Base64 decoding
755
+ case "btoa":
762
756
  try {
763
757
  const decodedArray = Uint8Array.from(atob(value), (c) =>
764
758
  c.charCodeAt(0)
@@ -767,99 +761,74 @@ function decodeValue(value, decodingType) {
767
761
  return decoder.decode(decodedArray);
768
762
  } catch (error) {
769
763
  console.warn(`Failed to decode Base64: ${error.message}`);
770
- return value; // Return the original value as a fallback
764
+ return value;
771
765
  }
772
766
 
773
- case "html-entities":
767
+ case "html-entities": {
774
768
  const tempElement = document.createElement("div");
775
769
  tempElement.innerHTML = value;
776
770
  return tempElement.textContent;
771
+ }
777
772
 
778
773
  case "json":
779
774
  try {
780
775
  return JSON.parse(value);
781
776
  } catch (error) {
782
777
  console.warn(`Failed to parse JSON: ${error.message}`);
783
- return value; // Return the original value as a fallback
778
+ return value;
784
779
  }
785
780
 
786
781
  default:
787
782
  console.warn(`Unsupported decoding type: ${decodingType}`);
788
- return value; // Return the original value as a fallback
783
+ return value;
789
784
  }
790
785
  }
791
786
 
792
- /**
793
- * Parses a string into a JSON object if the provided valueType suggests it might be JSON.
794
- * Handles objects, arrays, or types starting with 'array'. If parsing fails,
795
- * attempts to extract a JSON structure using regex and tries again.
796
- *
797
- * @param {string} value - The string value that might contain JSON data.
798
- * @param {string} valueType - A string indicating the expected type of the value (e.g., "object", "json", "array").
799
- * @returns {any} The parsed JSON object/array or the original value if parsing fails.
800
- */
801
787
  function parseJson(value, valueType) {
802
- // Check if the value is present and if the valueType suggests it could be a JSON structure
803
788
  if (
804
- value && // Ensure there is a value to parse
805
- (valueType === "object" || // Check if the type is explicitly an object
806
- valueType === "json" || // Check if the type is explicitly JSON
807
- valueType.startsWith("array")) // Check if the type indicates an array or complex array structure
789
+ value &&
790
+ (valueType === "object" ||
791
+ valueType === "json" ||
792
+ valueType.startsWith("array"))
808
793
  ) {
809
794
  try {
810
- // Attempt to parse the value directly as JSON
811
795
  value = JSON.parse(value);
812
796
  } catch {
813
- // If direct JSON parsing fails, attempt to extract a potential JSON structure using a regex
814
- const jsonRegex = /(\{[\s\S]*}|\[[\s\S]*\])/; // Regex to find JSON objects or arrays
815
- const match = value.match(jsonRegex); // Search the value for JSON-like patterns
797
+ const jsonRegex = /(\{[\s\S]*}|\[[\s\S]*\])/;
798
+ const match = value.match(jsonRegex);
816
799
 
817
800
  if (match) {
818
801
  try {
819
- // If a pattern is found, attempt to parse the extracted potential JSON
820
802
  value = JSON.parse(match[0]);
821
803
  } catch {
822
- // Warn if parsing still fails after extracting potential JSON
823
804
  console.warn(
824
805
  "Warning: Failed to parse JSON after extraction. Returning original value."
825
806
  );
826
807
  }
827
808
  } else {
828
- // Warn if no valid JSON structure is found in the string
829
809
  console.warn(
830
810
  "Warning: No valid JSON structure found in the string. Returning original value."
831
811
  );
832
812
  }
833
813
  }
834
814
  }
835
- return value; // Return the transformed value, or the original if no transformation occurs
815
+ return value;
836
816
  }
837
817
 
838
- /**
839
- * Processes a value by converting it to an array or extracting specific array-related elements.
840
- * @param {any} value - The value to be processed, potentially an object or primitive.
841
- * @param {string} valueType - A string indicating the type of conversion or extraction to perform.
842
- * @returns {Array} - The processed value as an array, or a transformed array-like structure.
843
- */
844
818
  function toArray(value, valueType) {
845
- // Now handle array-specific logic if valueType starts with 'array'
846
819
  if (valueType.startsWith("array")) {
847
- // If the value isn't already an array, convert it accordingly
848
820
  if (!Array.isArray(value)) {
849
- // If the parsed value is an object, apply array conversion based on operators
850
- if (typeof value === "object") {
821
+ if (typeof value === "object" && value !== null) {
851
822
  if (valueType === "array.$keys") {
852
- value = Object.keys(value); // Extracts keys
823
+ value = Object.keys(value);
853
824
  } else if (valueType === "array.$values") {
854
- value = Object.values(value); // Extracts values
825
+ value = Object.values(value);
855
826
  } else if (valueType === "array.$entries") {
856
- value = Object.entries(value); // Extracts entries as [key, value]
827
+ value = Object.entries(value);
857
828
  } else {
858
- // Default behavior: wrap the object in an array
859
829
  value = [value];
860
830
  }
861
831
  } else {
862
- // If it's not an object (i.e., a primitive), wrap the value in an array
863
832
  value = [value];
864
833
  }
865
834
  }
@@ -867,4 +836,4 @@ function toArray(value, valueType) {
867
836
  return value;
868
837
  }
869
838
 
870
- export { getValue, storage };
839
+ export { getValue, getValueAsync, storage };
@@ -1,18 +1,22 @@
1
- import utils from "@cocreate/utils";
1
+ import { queryElements as baseQueryElements } from "@cocreate/utils";
2
2
 
3
3
  /**
4
4
  * Custom method to query elements in the context of a specific DOM element.
5
5
  *
6
- * @param {Object} options - Options to customize the query.
7
- * @returns {NodeListOf<Element>} - A list of elements found based on the query criteria.
6
+ * @this {Element} - The DOM element context.
7
+ * @param {Object} [options={}] - Options to customize the query.
8
+ * @returns {NodeListOf<Element>} A list of elements found based on the query criteria.
8
9
  */
9
- function queryElements(options = {}) {
10
- // Use `this` as the element context and pass additional options to utils.queryElements
11
- return utils.queryElements({ element: this, ...options });
12
- }
10
+ export function queryElements(options = {}) {
11
+ // Ensure 'this' is a valid Element when used as a prototype method
12
+ if (!(this instanceof Element)) {
13
+ throw new TypeError("queryElements must be called on an Element instance");
14
+ }
13
15
 
14
- // Attach the method to Element's prototype to allow its use as an instance method
15
- Element.prototype.queryElements = queryElements;
16
+ return baseQueryElements({ element: this, ...options });
17
+ }
16
18
 
17
- // Export the function for direct use in other parts of your application
18
- export { queryElements };
19
+ // Attach the method to Element's prototype for instance method usage
20
+ if (!Element.prototype.queryElements) {
21
+ Element.prototype.queryElements = queryElements;
22
+ }