@cocreate/elements 1.41.0 → 1.42.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/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ # [1.42.0](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.41.1...v1.42.0) (2025-04-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * $relativePath handeling ([e9d41df](https://github.com/CoCreate-app/CoCreate-elements/commit/e9d41df24291083acc2bd5fa928c6b8b621e8f7c))
7
+ * handeling onRest ([ba102ce](https://github.com/CoCreate-app/CoCreate-elements/commit/ba102cedec35c41f1a974d0f62de3670ed40105e))
8
+ * update observer obseve param to type and and attributeName to attributeFilter ([3ce1a06](https://github.com/CoCreate-app/CoCreate-elements/commit/3ce1a0615fd16eeacc24b89c722593d7a470a566))
9
+ * update to how action is handled ([25f2654](https://github.com/CoCreate-app/CoCreate-elements/commit/25f26548618397c3cbae0469ff284ca1be5a8a7b))
10
+
11
+
12
+ ### Features
13
+
14
+ * value-selectors to get value from target element ([e3ae15b](https://github.com/CoCreate-app/CoCreate-elements/commit/e3ae15b5e0d2d953399bd45dc82ea44aa7aa6cde))
15
+
16
+ ## [1.41.1](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.41.0...v1.41.1) (2025-01-18)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * path handling ([1276f2d](https://github.com/CoCreate-app/CoCreate-elements/commit/1276f2d3988ec556ce95d3166596526d9d5851c3))
22
+ * sum handling ([86f1e74](https://github.com/CoCreate-app/CoCreate-elements/commit/86f1e74af2e14b51b925e36fd22b2935796073fa))
23
+
1
24
  # [1.41.0](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.40.1...v1.41.0) (2025-01-02)
2
25
 
3
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/elements",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "Fetch content into any html element by using html5 attributes to query data stored in CoCreate headless CMS. If data is changed it will instantly reflect in html tags as changes are broadcasted in realtime",
5
5
  "keywords": [
6
6
  "elements",
package/src/fetchSrc.js CHANGED
@@ -22,159 +22,137 @@ async function initElements(elements) {
22
22
  if (!src || /{{\s*([\w\W]+)\s*}}/g.test(src)) continue;
23
23
 
24
24
  let initialize = initializing.get(element);
25
- if (!initialize || initialize.src != src) {
26
- initializing.set(element, { src });
27
- if (src) {
28
- try {
29
- let response = await fetch(src);
30
- if (
31
- element.tagName === "AUDIO" ||
32
- element.tagName === "VIDEO"
33
- ) {
34
- try {
35
- let mediaConfig = await response.json();
36
- let sourceBuffer;
37
-
38
- const mediaSource = new MediaSource();
39
- element.src = URL.createObjectURL(mediaSource);
40
- mediaSource.addEventListener(
41
- "sourceopen",
42
- async () => {
43
- sourceBuffer = mediaSource.addSourceBuffer(
44
- `${mediaConfig["content-type"]}; codecs="${mediaConfig.codecs}"`
45
- );
46
-
47
- sourceBuffer.addEventListener(
48
- "updateend",
49
- () => {
50
- console.log(
51
- "Update ended, buffered ranges:",
52
- sourceBuffer.buffered
53
- );
54
- if (
55
- !sourceBuffer.updating &&
56
- mediaSource.readyState ===
57
- "open"
58
- ) {
59
- mediaSource.endOfStream();
60
-
61
- element.addEventListener(
62
- "ended",
63
- () => {
64
- URL.revokeObjectURL(
65
- element.src
66
- );
67
- }
68
- );
69
- }
70
- }
71
- );
72
- sourceBuffer.addEventListener(
73
- "error",
74
- (e) => {
75
- console.error(
76
- "SourceBuffer error event:",
77
- e
78
- );
79
- console.log(
80
- "MediaSource readyState:",
81
- mediaSource.readyState
82
- );
83
- console.log(
84
- "SourceBuffer updating:",
85
- sourceBuffer.updating
86
- );
87
- console.log(
88
- "SourceBuffer buffered ranges:",
89
- sourceBuffer.buffered
90
- );
91
- // Any other state information you can log
92
- }
93
- );
94
-
95
- getSegment(
96
- sourceBuffer,
97
- mediaConfig.segments[0]
98
- );
99
- }
100
- );
101
-
102
- mediaSource.addEventListener("sourceended", () => {
103
- console.log("MediaSource ended event fired");
104
- });
105
-
106
- mediaSource.addEventListener("sourceclose", () => {
107
- console.log("MediaSource close event fired");
108
- });
109
-
110
- // Event listener for seeking
111
- element.addEventListener("seeking", () => {
112
- let currentTime = element.currentTime;
113
- let chunkIndex = Math.floor(
114
- currentTime / chunkDuration
115
- );
116
- });
117
-
118
- // Event listener for seeked
119
- element.addEventListener("seeked", () => {
120
- let currentTime = element.currentTime;
121
- if (isBuffered(element, currentTime)) return;
122
-
123
- let query = {
124
- start: { $lte: currentTime },
125
- end: { $gte: currentTime }
126
- };
127
-
128
- let segments = queryData(
129
- mediaConfig.segments,
130
- query
131
- );
132
- for (let segment of segments) {
133
- getSegment(sourceBuffer, segment);
134
- }
135
- });
136
-
137
- element.addEventListener("timeupdate", function () {
138
- const bufferEnd = element.buffered.end(0);
139
- const currentTime = element.currentTime;
140
- const threshold = 10; // seconds before buffer end to fetch the next segment
141
-
142
- if (bufferEnd - currentTime < threshold) {
143
- // Time to fetch the next segment
144
- getSegment(sourceBuffer, mediaConfig);
145
- }
146
- });
147
- } catch (error) {
148
- element.src = src;
149
- // let blob = await response.json();
150
- // URL.createObjectURL(blob);
151
- }
152
- } else if (element.hasAttribute("rendered")) {
153
- element.removeAttribute("rendered");
154
- // let path = element.getAttribute("path");
155
- // if (path) {
156
- // let elements = element.querySelectorAll("[src]");
157
- // for (let i = 0; i < elements.length; i++) {
158
- // text = text.replaceAll("{{path}}", path);
159
- // }
160
- // }
161
- } else {
162
- let text = await response.text();
163
- if (text) {
164
- let path = element.getAttribute("path");
165
- if (path) text = text.replaceAll("{{path}}", path);
166
- element.setValue(text);
167
- initializing.delete(element);
168
- }
25
+ if (initialize && initialize.src === src) continue;
26
+
27
+ initializing.set(element, { src });
28
+ try {
29
+ let state = localStorage.getItem("state"); // Assume this is your state object
30
+ let response = await fetch(src, {
31
+ headers: {
32
+ "X-State": state // Custom header to send state
33
+ }
34
+ });
35
+
36
+ if (element.tagName === "AUDIO" || element.tagName === "VIDEO") {
37
+ initMediaSource(element, src);
38
+ } else {
39
+ element.removeAttribute("rendered");
40
+
41
+ let text = await response.text();
42
+ if (text) {
43
+ let pathElement = element.closest("[path]");
44
+ if (pathElement) {
45
+ let path =
46
+ pathElement.getAttribute("path") || getPath();
47
+ if (path) text = text.replaceAll("$relativePath", path);
169
48
  }
170
- } catch (err) {
171
- console.log("FetchSrc error:" + err);
49
+ element.setValue(text);
50
+ initializing.delete(element);
172
51
  }
173
52
  }
53
+ } catch (err) {
54
+ console.log("FetchSrc error:" + err);
174
55
  }
175
56
  }
176
57
  }
177
58
 
59
+ function getPath() {
60
+ let currentPath = window.location.pathname.replace(/\/[^\/]*$/, ""); // Remove file from path
61
+ let depth = currentPath.split("/").filter(Boolean).length; // Count depth levels
62
+
63
+ return depth > 0 ? "../".repeat(depth) : "./"; // Return the correct relative path
64
+ }
65
+
66
+ async function initMediaSource(element, src) {
67
+ try {
68
+ let mediaConfig = await response.json();
69
+ let sourceBuffer;
70
+
71
+ const mediaSource = new MediaSource();
72
+ element.src = URL.createObjectURL(mediaSource);
73
+ mediaSource.addEventListener("sourceopen", async () => {
74
+ sourceBuffer = mediaSource.addSourceBuffer(
75
+ `${mediaConfig["content-type"]}; codecs="${mediaConfig.codecs}"`
76
+ );
77
+
78
+ sourceBuffer.addEventListener("updateend", () => {
79
+ console.log(
80
+ "Update ended, buffered ranges:",
81
+ sourceBuffer.buffered
82
+ );
83
+ if (
84
+ !sourceBuffer.updating &&
85
+ mediaSource.readyState === "open"
86
+ ) {
87
+ mediaSource.endOfStream();
88
+
89
+ element.addEventListener("ended", () => {
90
+ URL.revokeObjectURL(element.src);
91
+ });
92
+ }
93
+ });
94
+
95
+ sourceBuffer.addEventListener("error", (e) => {
96
+ console.error("SourceBuffer error event:", e);
97
+ console.log("MediaSource readyState:", mediaSource.readyState);
98
+ console.log("SourceBuffer updating:", sourceBuffer.updating);
99
+ console.log(
100
+ "SourceBuffer buffered ranges:",
101
+ sourceBuffer.buffered
102
+ );
103
+ // Any other state information you can log
104
+ });
105
+
106
+ getSegment(sourceBuffer, mediaConfig.segments[0]);
107
+ });
108
+
109
+ mediaSource.addEventListener("sourceended", () => {
110
+ console.log("MediaSource ended event fired");
111
+ });
112
+
113
+ mediaSource.addEventListener("sourceclose", () => {
114
+ console.log("MediaSource close event fired");
115
+ });
116
+
117
+ // Event listener for seeking
118
+ element.addEventListener("seeking", () => {
119
+ let currentTime = element.currentTime;
120
+ let chunkIndex = Math.floor(currentTime / chunkDuration);
121
+ });
122
+
123
+ // Event listener for seeked
124
+ element.addEventListener("seeked", () => {
125
+ let currentTime = element.currentTime;
126
+ if (isBuffered(element, currentTime)) return;
127
+
128
+ let query = {
129
+ start: { $lte: currentTime },
130
+ end: { $gte: currentTime }
131
+ };
132
+
133
+ let segments = queryData(mediaConfig.segments, query);
134
+ for (let segment of segments) {
135
+ getSegment(sourceBuffer, segment);
136
+ }
137
+ });
138
+
139
+ element.addEventListener("timeupdate", function () {
140
+ const bufferEnd = element.buffered.end(0);
141
+ const currentTime = element.currentTime;
142
+ const threshold = 10; // seconds before buffer end to fetch the next segment
143
+
144
+ if (bufferEnd - currentTime < threshold) {
145
+ // Time to fetch the next segment
146
+ getSegment(sourceBuffer, mediaConfig);
147
+ }
148
+ });
149
+ } catch (error) {
150
+ element.src = src;
151
+ // let blob = await response.json();
152
+ // URL.createObjectURL(blob);
153
+ }
154
+ }
155
+
178
156
  async function getSegment(sourceBuffer, segment) {
179
157
  // TODO: use socket/crud/file with room using the url to get one or more segments and append
180
158
  let segments = [];
@@ -262,17 +240,30 @@ function base64ToArrayBuffer(base64) {
262
240
 
263
241
  observer.init({
264
242
  name: "CoCreateSrc",
265
- observe: ["addedNodes"],
243
+ types: ["addedNodes"],
266
244
  selector: selector,
267
245
  callback: function (mutation) {
268
246
  init(mutation.target);
269
247
  }
270
248
  });
271
249
 
250
+ observer.init({
251
+ name: "CoCreateSrcOperator",
252
+ types: ["addedNodes"],
253
+ selector: "img",
254
+ // selector: "img, video, audio, script, input, iframe, frame, link, source",
255
+ callback: function (mutation) {
256
+ let src = mutation.target.getAttribute("src");
257
+ if (src) {
258
+ mutation.target.setAttribute("src", src);
259
+ }
260
+ }
261
+ });
262
+
272
263
  observer.init({
273
264
  name: "CoCreateSrcAttributes",
274
- observe: ["attributes"],
275
- attributeName: ["src"],
265
+ types: ["attributes"],
266
+ attributeFilter: ["src"],
276
267
  selector: selector,
277
268
  callback: function (mutation) {
278
269
  if (
package/src/form.js CHANGED
@@ -21,11 +21,18 @@ function init(elements) {
21
21
  setAttribute(element);
22
22
  disableAutoFill(element);
23
23
  setValue(element);
24
+
24
25
  element.addEventListener("submit", function (event) {
25
26
  if (!element.hasAttribute("action")) {
26
27
  event.preventDefault();
27
28
  }
28
29
  });
30
+
31
+ // Handle form reset event
32
+ element.addEventListener("reset", function (event) {
33
+ event.preventDefault(); // Prevent default reset
34
+ reset({ form: event.target }); // Call custom reset logic
35
+ });
29
36
  }
30
37
  }
31
38
 
@@ -93,7 +100,9 @@ function disableAutoFill(element) {
93
100
  /**
94
101
  * @param form
95
102
  */
96
- function reset(form) {
103
+ function reset(action) {
104
+ let form = action.form;
105
+
97
106
  // Convert the form elements collection to an array
98
107
  const formElementsArray = Array.from(form.elements);
99
108
 
@@ -127,7 +136,13 @@ function reset(form) {
127
136
  element.value ||
128
137
  element.getAttribute("value")
129
138
  );
130
- if (!resetType || resetType === "value") element.setValue("");
139
+ if (!resetType || resetType === "value") {
140
+ if (element.contentEditable === "true") {
141
+ element.innerHTML = "";
142
+ } else {
143
+ element.setValue("");
144
+ }
145
+ }
131
146
  }
132
147
 
133
148
  if (form.hasAttribute("object")) form.setAttribute("object", "");
@@ -141,11 +156,13 @@ function reset(form) {
141
156
  });
142
157
 
143
158
  // Dispatch a custom reset event
144
- document.dispatchEvent(
145
- new CustomEvent("reset", {
146
- detail: {}
147
- })
148
- );
159
+ if (action.element) {
160
+ action.element.dispatchEvent(
161
+ new CustomEvent("reset", {
162
+ detail: {}
163
+ })
164
+ );
165
+ }
149
166
  }
150
167
 
151
168
  function setValue(form) {
@@ -166,15 +183,15 @@ function setValue(form) {
166
183
 
167
184
  Observer.init({
168
185
  name: "CoCreateForm",
169
- observe: ["addedNodes"],
186
+ types: ["addedNodes"],
170
187
  selector: "form",
171
188
  callback: (mutation) => init(mutation.target)
172
189
  });
173
190
 
174
191
  Observer.init({
175
192
  name: "CoCreateForm",
176
- observe: ["attributes"],
177
- attributeName: getAttributeNames([
193
+ types: ["attributes"],
194
+ attributeFilter: getAttributeNames([
178
195
  "organization_id",
179
196
  "host",
180
197
  "storage",
@@ -190,7 +207,7 @@ Observer.init({
190
207
 
191
208
  Observer.init({
192
209
  name: "CoCreateFormElements",
193
- observe: ["addedNodes"],
210
+ types: ["addedNodes"],
194
211
  selector:
195
212
  "[organization_id], [host], [storage], [database], [array], [index], [object], [key]",
196
213
  callback: function (mutation) {
@@ -203,7 +220,7 @@ Action.init({
203
220
  name: "reset",
204
221
  endEvent: "reset",
205
222
  callback: (action) => {
206
- if (action.form) reset(action.form);
223
+ if (action.form) reset(action);
207
224
  }
208
225
  });
209
226
 
package/src/index.js CHANGED
@@ -40,6 +40,7 @@ import { render, renderValue, renderedNodes } from "@cocreate/render";
40
40
  import "@cocreate/element-prototype";
41
41
  import "./fetchSrc";
42
42
  import { reset } from "./form";
43
+ import "./value.js";
43
44
 
44
45
  const selector = "[storage], [database], [array], [render-json]";
45
46
  const elements = new Map();
@@ -462,7 +463,12 @@ async function filterData(element, data, type, key) {
462
463
  }
463
464
 
464
465
  let property = key;
465
- if (operator) property = property.replace(operator + ".", "");
466
+ if (operator) {
467
+ property = property.replace(operator + ".", "");
468
+ if (operator === "$sum") {
469
+ value = 0;
470
+ }
471
+ }
466
472
  if (Array.isArray(data[type])) {
467
473
  let Data = [],
468
474
  isObject;
@@ -480,8 +486,12 @@ async function filterData(element, data, type, key) {
480
486
  }
481
487
  let docValue = getValueFromObject(doc, property);
482
488
  if (docValue) {
483
- if (Array.isArray(docValue)) Data.push(...docValue);
484
- else {
489
+ if (operator === "$sum" && typeof docValue === "number") {
490
+ if (typeof value !== "number") value = 0;
491
+ value += docValue || 0;
492
+ } else if (Array.isArray(docValue)) {
493
+ Data.push(...docValue);
494
+ } else {
485
495
  isObject = true;
486
496
  Data.push(docValue);
487
497
  }
@@ -496,11 +506,11 @@ async function filterData(element, data, type, key) {
496
506
  // data = { [property]: Data[0] }
497
507
  // } else
498
508
  if (!operator) data = Data;
499
- else if (operator === "$sum") {
500
- value = Data.reduce((accumulator, currentValue) => {
501
- return accumulator + currentValue;
502
- }, 0);
503
- }
509
+ // else if (operator === "$sum") {
510
+ // value = data[type].reduce((accumulator, currentValue) => {
511
+ // return accumulator + currentValue;
512
+ // }, 0);
513
+ // }
504
514
  } else {
505
515
  data = { [property]: data[type][property] };
506
516
  }
@@ -1006,7 +1016,7 @@ async function getData(form) {
1006
1016
  return dataArray;
1007
1017
  }
1008
1018
 
1009
- async function save(element) {
1019
+ async function save(element, action) {
1010
1020
  if (!element) return;
1011
1021
  let data, value;
1012
1022
  let upsert = element.getAttribute("upsert");
@@ -1112,19 +1122,28 @@ async function save(element) {
1112
1122
  (data[i].type !== "object" &&
1113
1123
  data[i].method.endsWith(".update")))
1114
1124
  ) {
1115
- setTypeValue(element, data[i]);
1116
- } else if (data[i])
1117
- document.dispatchEvent(
1118
- new CustomEvent("saved", {
1119
- detail: data[i]
1120
- })
1121
- );
1125
+ setTypeValue(element, data[i], action);
1126
+ } else if (data[i]) {
1127
+ if (action) {
1128
+ action.element.dispatchEvent(
1129
+ new CustomEvent("saved", {
1130
+ detail: data
1131
+ })
1132
+ );
1133
+ } else {
1134
+ document.dispatchEvent(
1135
+ new CustomEvent("saved", {
1136
+ detail: data
1137
+ })
1138
+ );
1139
+ }
1140
+ }
1122
1141
  }
1123
1142
 
1124
1143
  return Data;
1125
1144
  }
1126
1145
 
1127
- function setTypeValue(element, data) {
1146
+ function setTypeValue(element, data, action) {
1128
1147
  // TODO: if an array name is updated, the attibute array="" needs to be updated.
1129
1148
 
1130
1149
  if (!data) return;
@@ -1160,7 +1179,7 @@ function setTypeValue(element, data) {
1160
1179
  }
1161
1180
  setData(Array.from(elements.keys()), data);
1162
1181
 
1163
- // const state_ids = new Map();
1182
+ // const state_ids = new Map()
1164
1183
 
1165
1184
  // let state_id = form.getAttribute('state_id');
1166
1185
  // if (state_id) {
@@ -1213,11 +1232,19 @@ function setTypeValue(element, data) {
1213
1232
  // }
1214
1233
  }
1215
1234
 
1216
- document.dispatchEvent(
1217
- new CustomEvent("saved", {
1218
- detail: data
1219
- })
1220
- );
1235
+ if (action) {
1236
+ action.element.dispatchEvent(
1237
+ new CustomEvent("saved", {
1238
+ detail: data
1239
+ })
1240
+ );
1241
+ } else {
1242
+ document.dispatchEvent(
1243
+ new CustomEvent("saved", {
1244
+ detail: data
1245
+ })
1246
+ );
1247
+ }
1221
1248
  }
1222
1249
 
1223
1250
  async function remove(element) {
@@ -1296,7 +1323,7 @@ function initSocket() {
1296
1323
 
1297
1324
  Observer.init({
1298
1325
  name: "render",
1299
- observe: ["addedNodes"],
1326
+ types: ["addedNodes"],
1300
1327
  selector: "[render-clone]",
1301
1328
  callback: function (mutation) {
1302
1329
  if (!mutation.parentElement.hasAttribute("dnd")) return;
@@ -1445,16 +1472,16 @@ function dndCrudSend(data, crudType) {
1445
1472
 
1446
1473
  Observer.init({
1447
1474
  name: "CoCreateElementsChildList",
1448
- observe: ["childList"],
1475
+ types: ["addedNodes"],
1449
1476
  selector: selector,
1450
1477
  callback: function (mutation) {
1451
- init(mutation.addedNodes);
1478
+ init([mutation.target]);
1452
1479
  }
1453
1480
  });
1454
1481
 
1455
1482
  Observer.init({
1456
1483
  name: "CoCreateElementsRemovedNodes",
1457
- observe: ["removedNodes"],
1484
+ types: ["removedNodes"],
1458
1485
  selector: selector,
1459
1486
  callback: function (mutation) {
1460
1487
  // if (mutation.target.parentElement) return
@@ -1472,8 +1499,8 @@ Observer.init({
1472
1499
 
1473
1500
  Observer.init({
1474
1501
  name: "CoCreateElementsAttributes",
1475
- observe: ["attributes"],
1476
- attributeName: [
1502
+ types: ["attributes"],
1503
+ attributeFilter: [
1477
1504
  "organization_id",
1478
1505
  "host",
1479
1506
  "storage",
@@ -1499,7 +1526,7 @@ Actions.init([
1499
1526
  name: "save",
1500
1527
  endEvent: "saved",
1501
1528
  callback: (action) => {
1502
- if (action.form) save(action.form);
1529
+ if ((action.form, action)) save(action.form, action);
1503
1530
  }
1504
1531
  },
1505
1532
  {
@@ -1542,7 +1569,7 @@ Actions.init([
1542
1569
  data[data.type] = { _id: data[data.type] };
1543
1570
 
1544
1571
  let response = await CRUD.send(data);
1545
- document.dispatchEvent(
1572
+ action.element.dispatchEvent(
1546
1573
  new CustomEvent("deleted", {
1547
1574
  detail: response
1548
1575
  })
package/src/value.js ADDED
@@ -0,0 +1,115 @@
1
+ import Observer from "@cocreate/observer";
2
+ import "@cocreate/element-prototype";
3
+
4
+ const selector =
5
+ "[value-selector], [value-closest], [value-parent], [value-next], [value-previous], [value-document], [value-frame], [value-top]";
6
+
7
+ /**
8
+ * Initializes elements with value-* attributes to enable dynamic value retrieval and setting.
9
+ *
10
+ * @param {HTMLElement|HTMLCollection|NodeList|Array<HTMLElement>} [element] - Optional element(s) to initialize. If not provided, initializes all matching elements in the document.
11
+ */
12
+ async function init(element) {
13
+ if (
14
+ element &&
15
+ !(element instanceof HTMLCollection) &&
16
+ !(element instanceof NodeList) &&
17
+ !Array.isArray(element)
18
+ ) {
19
+ element = [element];
20
+ } else if (!element) {
21
+ element = document.querySelectorAll(selector);
22
+ }
23
+
24
+ for (let i = 0; i < element.length; i++) {
25
+ initElement(element[i]);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Initializes a single element with value-* attributes.
31
+ *
32
+ * @param {HTMLElement} element - The element to initialize.
33
+ */
34
+ function initElement(element) {
35
+ let targets = element.queryElements();
36
+
37
+ valueHandler(element, targets, true);
38
+ }
39
+
40
+ /**
41
+ * Handles the retrieval and setting of values based on value-* attributes.
42
+ *
43
+ * @param {HTMLElement} element - The element to set the value on.
44
+ * @param {object} targets - An object containing elements retrieved by queryElements().
45
+ */
46
+ function valueHandler(element, targets = [], initialize) {
47
+ let values = [];
48
+ // TODO: consdier the potential of targets being an array of elements, should value be an array of the values?
49
+ for (let i = 0; i < targets.length; i++) {
50
+ if (!targets[i] || !targets[i].isConnected) {
51
+ targets.splice(i, 1); // Remove the element
52
+ i -= 1; // Adjust the index
53
+ continue; // Skip to the next iteration
54
+ }
55
+
56
+ if (initialize) {
57
+ // Remove existing listener (if any)
58
+ targets[i].removeEventListener("input", elementValueHandler);
59
+
60
+ // Add new listener
61
+ targets[i].addEventListener("input", elementValueHandler);
62
+ }
63
+
64
+ values.push(targets[i].getValue());
65
+ }
66
+
67
+ if (!values.length) {
68
+ return;
69
+ } else if (values.length === 1) {
70
+ values = values[0];
71
+ }
72
+
73
+ let attribute = element.getAttribute("value-attribute");
74
+ if (attribute) {
75
+ element.setAttribute(attribute, values);
76
+ } else {
77
+ element.setValue(values);
78
+ }
79
+
80
+ function elementValueHandler(e) {
81
+ valueHandler(element, targets);
82
+ }
83
+ }
84
+
85
+ init();
86
+
87
+ Observer.init({
88
+ name: "CoCreateElementsValueAdded",
89
+ types: ["addedNodes"],
90
+ selector: selector,
91
+ callback: function (mutation) {
92
+ init(mutation.target);
93
+ }
94
+ });
95
+
96
+ Observer.init({
97
+ name: "CoCreateElementsAttributes",
98
+ types: ["attributes"],
99
+ attributeFilter: [
100
+ "value-selector",
101
+ "value-closest",
102
+ "value-parent",
103
+ "value-next",
104
+ "value-previous",
105
+ "value-document",
106
+ "value-frame",
107
+ "value-top"
108
+ ],
109
+ callback: function (mutation) {
110
+ let currentValue = mutation.target.getAttribute(mutation.attributeName);
111
+ if (currentValue !== mutation.oldValue) {
112
+ initElement(mutation.target);
113
+ }
114
+ }
115
+ });