@cocreate/events 1.26.0 → 1.27.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,21 @@
1
+ # [1.27.0](https://github.com/CoCreate-app/CoCreate-events/compare/v1.26.0...v1.27.0) (2025-04-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * console.log() ([2174c1e](https://github.com/CoCreate-app/CoCreate-events/commit/2174c1e0a9fa0b777e073de30aed4f29ec59f8f6))
7
+ * formating ([58b8550](https://github.com/CoCreate-app/CoCreate-events/commit/58b8550c96c30153a94e24ce93b39914eed2fba3))
8
+ * removed console.log() ([f44cb8f](https://github.com/CoCreate-app/CoCreate-events/commit/f44cb8fdfec3b3b1787e830cd1b3bab3a1ef3fa5))
9
+ * renamed prefix-delay to prefix-setTimout ([84e511a](https://github.com/CoCreate-app/CoCreate-events/commit/84e511af8bcb521e5e8e7bc481a6f202fca04930))
10
+ * Selctor === "" returning null ([c49bc35](https://github.com/CoCreate-app/CoCreate-events/commit/c49bc35cd8a8ff9232229ee64d71bc4e32d3d6fc))
11
+ * selectors ([ccb2dee](https://github.com/CoCreate-app/CoCreate-events/commit/ccb2dee2a682bcdf601047f0783c5c44ebcfe5ed))
12
+ * update observer obseve param to type and and attributeName to attributeFilter ([44372d4](https://github.com/CoCreate-app/CoCreate-events/commit/44372d4ceba70360507cd0c94df43f4ee877be99))
13
+
14
+
15
+ ### Features
16
+
17
+ * support "*-preventDefault", "*-stopPropagation", "*-stopImmediatePropagation" ([5b10663](https://github.com/CoCreate-app/CoCreate-events/commit/5b106633f425001ca6ec7d65dc0bd00ad6368b64))
18
+
1
19
  # [1.26.0](https://github.com/CoCreate-app/CoCreate-events/compare/v1.25.0...v1.26.0) (2025-01-18)
2
20
 
3
21
 
@@ -1,31 +1,27 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <title>Value | CoCreateJS</title>
3
+ <head>
4
+ <title>Value | CoCreateJS</title>
5
5
 
6
- <!-- CoCreate Favicon -->
7
- <link
8
- rel="icon"
9
- type="image/png"
10
- sizes="32x32"
11
- href="../assets/favicon.ico" />
12
- <link rel="manifest" href="/manifest.webmanifest" />
13
- </head>
14
- <body>
15
- <div click-selector="#testing" click-value="hello, bye" click-key="ty">
16
- <span id="testing" class="{{ty}}">other content</span>
17
- </div>
6
+ <!-- CoCreate Favicon -->
7
+ <link
8
+ rel="icon"
9
+ type="image/png"
10
+ sizes="32x32"
11
+ href="../assets/favicon.ico" />
12
+ <link rel="manifest" href="/manifest.webmanifest" />
13
+ </head>
14
+ <body>
15
+ <div click-selector="#testing" click-value="hello, bye" click-key="ty">
16
+ <span id="testing" class="{{ty}}">other content</span>
17
+ </div>
18
18
 
19
- <form realtime="false">
20
- <input
21
- input-selector="#testing2"
22
- input-attribute="value"
23
- value-type="array" />
19
+ <form>
20
+ <input input-selector="#testing2" input-attribute="value" />
21
+ <input id="testing2" />
22
+ </form>
24
23
 
25
- <input id="testing2" class="{{ty}}" />
26
- </form>
27
-
28
- <!-- <script src="../../../CoCreateJS/dist/CoCreate.js"></script> -->
29
- <script src="https://CoCreate.app/dist/CoCreate.js"></script>
30
- </body>
24
+ <!-- <script src="../../../CoCreateJS/dist/CoCreate.js"></script> -->
25
+ <script src="https://CoCreate.app/dist/CoCreate.js"></script>
26
+ </body>
31
27
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/events",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "A simple events component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "events",
package/src/index.js CHANGED
@@ -63,7 +63,7 @@ const CoCreateEvents = {
63
63
  const self = this;
64
64
  observer.init({
65
65
  name: "CoCreateEventName",
66
- observe: ["addedNodes"],
66
+ types: ["addedNodes"],
67
67
  selector: `[event-name]`,
68
68
  callback: function (mutation) {
69
69
  let name = mutation.target.getAttribute("event-name");
@@ -89,12 +89,12 @@ const CoCreateEvents = {
89
89
  }
90
90
  });
91
91
 
92
- let selector = `[${prefix}], [${prefix}-key], [${prefix}-attribute], [${prefix}-value], [${prefix}-action], [${prefix}-selector], [${prefix}-closest], [${prefix}-parent], [${prefix}-next], [${prefix}-previous]`;
92
+ let selector = `[${prefix}], [${prefix}-key], [${prefix}-attribute], [${prefix}-value], [${prefix}-action], [${prefix}-selector], [${prefix}-closest], [${prefix}-parent], [${prefix}-next], [${prefix}-previous], [${prefix}-preventDefault], [${prefix}-stopPropagation], [${prefix}-stopImmediatePropagation]`;
93
93
 
94
94
  observer.init({
95
95
  name: "CoCreateEventattributes",
96
- observe: ["attributes", "addedNodes"],
97
- attributeName: [
96
+ types: ["attributes", "addedNodes"],
97
+ attributeFilter: [
98
98
  `${prefix}-key`,
99
99
  `${prefix}-value`,
100
100
  `${prefix}-selector`,
@@ -102,7 +102,13 @@ const CoCreateEvents = {
102
102
  `${prefix}-parent`,
103
103
  `${prefix}-next`,
104
104
  `${prefix}-previous`,
105
- `${prefix}-events`
105
+ `${prefix}-events`,
106
+ `${prefix}-bubbles`,
107
+ `${prefix}-cancelable`,
108
+ `${prefix}-composed`,
109
+ `${prefix}-preventDefault`,
110
+ `${prefix}-stopPropagation`,
111
+ `${prefix}-stopImmediatePropagation`
106
112
  ],
107
113
  selector,
108
114
  callback: function (mutation) {
@@ -125,7 +131,9 @@ const CoCreateEvents = {
125
131
  this.elements.set(el, prefixes);
126
132
  } else if (!prefixes[prefix]) {
127
133
  prefixes[prefix] = { events };
128
- } else isEventable = false;
134
+ } else {
135
+ isEventable = false;
136
+ }
129
137
 
130
138
  let customEvents = el.getAttribute(`${prefix}-events`);
131
139
  if (customEvents) {
@@ -140,8 +148,25 @@ const CoCreateEvents = {
140
148
  prefixes[prefix].events = events;
141
149
  isEventable = true;
142
150
  }
151
+
143
152
  if (!events || !isEventable) continue;
144
- if (events.includes("onload")) this.__updateElements(el, prefix);
153
+
154
+ // let originalSelector = ""; // i think we need to extract the selector and remove the operators
155
+ // if (originalSelector) {
156
+ // observer.init({
157
+ // types: ["addedNodes"],
158
+ // callback: function (mutation) {
159
+ // const els = el.queryElements();
160
+ // if (els && els.includes(mutation.target)) {
161
+ // self.__updateElements();
162
+ // }
163
+ // }
164
+ // });
165
+ // }
166
+
167
+ if (events.includes("onload")) {
168
+ this.__updateElements(el, prefix);
169
+ }
145
170
 
146
171
  if (events.includes("observer")) {
147
172
  let target;
@@ -163,7 +188,7 @@ const CoCreateEvents = {
163
188
  }
164
189
  }
165
190
 
166
- let attributeName = (
191
+ let attributeFilter = (
167
192
  el.getAttribute(`${prefix}-attributes`) || ""
168
193
  )
169
194
  .split(/\s*,\s*/)
@@ -176,7 +201,7 @@ const CoCreateEvents = {
176
201
  .filter((item) => item);
177
202
 
178
203
  let observerConfig = {
179
- observe: observeAttribute,
204
+ types: observeAttribute,
180
205
  callback: function (mutation) {
181
206
  self.__updateElements(el, prefix, mutation.target);
182
207
  }
@@ -186,20 +211,19 @@ const CoCreateEvents = {
186
211
  if (
187
212
  target &&
188
213
  !observeAttribute.length &&
189
- !attributeName.length
214
+ !attributeFilter.length
190
215
  ) {
191
- observerConfig.observe.push("addedNodes");
216
+ observerConfig.types.push("addedNodes");
192
217
  }
193
218
  observerConfig.target = target;
194
219
  }
195
220
 
196
- if (attributeName.length) {
197
- observerConfig.observe.push("attributes");
198
- observerConfig.attributeName = attributeName;
221
+ if (attributeFilter.length) {
222
+ observerConfig.types.push("attributes");
223
+ observerConfig.attributeFilter = attributeFilter;
199
224
  }
200
225
 
201
- if (observerConfig.observe.length)
202
- observer.init(observerConfig);
226
+ if (observerConfig.types.length) observer.init(observerConfig);
203
227
  }
204
228
 
205
229
  if (events.includes("resize")) {
@@ -270,19 +294,40 @@ const CoCreateEvents = {
270
294
  }
271
295
 
272
296
  for (let i = 0; i < events.length; i++) {
297
+ // Skip specific lifecycle events
273
298
  if (
274
- events[i] !== "onload" &&
275
- events[i] !== "observer" &&
276
- events[i] !== "intersection" &&
277
- events[i] !== "resize"
299
+ events[i] === "onload" ||
300
+ events[i] === "observer" ||
301
+ events[i] === "intersection" ||
302
+ events[i] === "resize"
278
303
  ) {
279
- el.removeEventListener(events[i], eventFunction);
280
- el.addEventListener(events[i], eventFunction);
304
+ continue;
305
+ }
306
+
307
+ // Remove any existing event listener
308
+ el.removeEventListener(events[i], eventFunction);
309
+
310
+ // Initialize an empty options object
311
+ const options = {};
312
+
313
+ // Attributes to check dynamically
314
+ const attributes = ["bubbles", "cancelable", "composed"];
315
+
316
+ for (let j = 0; j < attributes.length; j++) {
317
+ const attrValue = el.getAttribute(
318
+ `${events[i]}-${attributes[j]}`
319
+ );
320
+ if (attrValue !== null) {
321
+ options[attributes[j]] = attrValue !== "false";
322
+ }
281
323
  }
324
+
325
+ // Add the new event listener with the dynamically constructed options
326
+ el.addEventListener(events[i], eventFunction, options);
282
327
  }
283
328
 
284
329
  function eventFunction(event) {
285
- // TODO: apply debounce
330
+ // TODO: apply debounce.
286
331
  // let debounce;
287
332
  // clearTimeout(debounce);
288
333
  // debounce = setTimeout(function() {
@@ -300,6 +345,25 @@ const CoCreateEvents = {
300
345
  if (attribute.includes(prefix)) return;
301
346
  // if (target.closest(`[actions*="${prefix}"]`))
302
347
  // return;
348
+
349
+ // Define the lifecycle attributes (matching JavaScript event methods)
350
+ const lifecycleAttributes = [
351
+ "preventDefault",
352
+ "stopPropagation",
353
+ "stopImmediatePropagation"
354
+ ];
355
+
356
+ // Handle lifecycle attributes dynamically
357
+ for (let i = 0; i < lifecycleAttributes.length; i++) {
358
+ const attr = `${prefix}-${lifecycleAttributes[i]}`; // Add prefix dynamically
359
+ const attrValue = target.getAttribute(attr); // Directly get the attribute value
360
+
361
+ // If attribute exists and is not explicitly "false", apply the corresponding event method
362
+ if (attrValue !== null && attrValue !== "false") {
363
+ event[lifecycleAttributes[i]]();
364
+ }
365
+ }
366
+
303
367
  self.__updateElements(
304
368
  target,
305
369
  prefix,
@@ -341,7 +405,26 @@ const CoCreateEvents = {
341
405
  }
342
406
 
343
407
  for (let element of elements) {
344
- let delay = element.getAttribute(`${prefix}-delay`);
408
+ // const requestAnimationFrameAttr = element.getAttribute(
409
+ // `${prefix}-requestAnimationFrame`
410
+ // );
411
+
412
+ // if (
413
+ // requestAnimationFrameAttr !== null &&
414
+ // requestAnimationFrameAttr !== "false"
415
+ // ) {
416
+ // // Double requestAnimationFrame
417
+ // await new Promise((resolve) => {
418
+ // requestAnimationFrame(() => {
419
+ // requestAnimationFrame(() => {
420
+ // resolve();
421
+ // });
422
+ // });
423
+ // });
424
+ // }
425
+
426
+ // TODO: Handle setInterval logic
427
+ let delay = element.getAttribute(`${prefix}-setTimeout`);
345
428
 
346
429
  if (delay) {
347
430
  delay = parseInt(delay, 10) || 0;
@@ -431,13 +514,15 @@ const CoCreateEvents = {
431
514
  let elseCondition = element.getAttribute(`${prefix}-else`);
432
515
 
433
516
  let ifValue = element.getAttribute(`${prefix}-if-value`);
434
- if (!ifValue && ifValue !== "")
435
- ifValue = (await element.getValue()) || values;
436
- //values // await element.getValue()
437
- else if (ifValue || ifValue === "") ifValue = [ifValue];
438
- else ifValue = values;
439
-
440
- if (!Array.isArray(ifValue)) ifValue = [ifValue];
517
+ if (ifCondition) {
518
+ if (!ifValue && ifValue !== "")
519
+ ifValue = (await element.getValue()) || values;
520
+ //values // await element.getValue()
521
+ else if (ifValue || ifValue === "") ifValue = [ifValue];
522
+ else ifValue = values;
523
+
524
+ if (!Array.isArray(ifValue)) ifValue = [ifValue];
525
+ }
441
526
 
442
527
  //TODO: improved resize toggling of values
443
528
  // let hasCondition = this.elements2.get(element)
@@ -485,7 +570,11 @@ const CoCreateEvents = {
485
570
  prefix,
486
571
  selector: params
487
572
  });
488
- if (targetElements === false) targetElements = [element];
573
+
574
+ if (targetElements === false) {
575
+ targetElements = [element];
576
+ }
577
+
489
578
  let action = element.getAttribute(`${prefix}-action`);
490
579
  for (let i = 0; i < targetElements.length; i++) {
491
580
  if (action) {
@@ -520,7 +609,7 @@ const CoCreateEvents = {
520
609
  }
521
610
  }
522
611
 
523
- document.dispatchEvent(
612
+ el.dispatchEvent(
524
613
  new CustomEvent(`${prefix}End`, {
525
614
  detail: {}
526
615
  })
@@ -565,7 +654,7 @@ const CoCreateEvents = {
565
654
  // let value = values[0]
566
655
  replaceKey(prefix, element, key, values);
567
656
  } else {
568
- // TODo: if html in iframe
657
+ // TODO: if html in iframe
569
658
  let domTextEditor;
570
659
  if (element.parentElement) {
571
660
  domTextEditor =
@@ -628,7 +717,7 @@ const CoCreateEvents = {
628
717
  element.classList.add(newValue);
629
718
  }
630
719
  } else if (attrName === "value") {
631
- element.setValue(newValue);
720
+ element.setValue(newValue, false);
632
721
  } else if (
633
722
  ["$click", "$focus", "$blur", "$save", "$read"].includes(
634
723
  attrName
@@ -871,6 +960,160 @@ function parseNumberCondition(condition) {
871
960
  : condition;
872
961
  }
873
962
 
963
+ // Helper function for delay using Promises
964
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
965
+
966
+ /**
967
+ * Waits for a specific element property to meet a readiness condition,
968
+ * based on retry attributes defined on the element.
969
+ * Incorporates optional exponential backoff for retry delays.
970
+ * Returns a Promise that resolves to true if the condition is met within
971
+ * the allowed attempts, and false otherwise.
972
+ *
973
+ * @param {HTMLElement} element The element with the onload-retry-* attributes.
974
+ * @returns {Promise<boolean>} A Promise resolving to true if ready, false if timed out.
975
+ */
976
+ async function checkElementReadinessWithBackoff(element) {
977
+ const propertyName = element.getAttribute("onload-retry-property");
978
+
979
+ if (!propertyName) {
980
+ return true; // No retry needed
981
+ }
982
+
983
+ // --- Retry Parameters ---
984
+ const maxAttempts =
985
+ parseInt(element.getAttribute("onload-retry-attempts") || "3", 10) || 3;
986
+ // Initial delay (used for the first wait and as the base for backoff)
987
+ const initialDelayMs =
988
+ parseInt(element.getAttribute("onload-retry-delay") || "100", 10) ||
989
+ 100;
990
+ // Exponential backoff factor (e.g., 2 for doubling). <= 1 means fixed delay.
991
+ // Default to 1 (no backoff) if missing or invalid.
992
+ const backoffFactor =
993
+ parseFloat(
994
+ element.getAttribute("onload-retry-backoff-factor") || "1"
995
+ ) || 1;
996
+ // Optional maximum delay cap. Use Infinity if missing, invalid, or <= 0.
997
+ const maxDelayMs = parseInt(
998
+ element.getAttribute("onload-retry-max-delay") || "0",
999
+ 10
1000
+ );
1001
+ const effectiveMaxDelay =
1002
+ maxDelayMs && maxDelayMs > 0 ? maxDelayMs : Infinity;
1003
+
1004
+ console.log(
1005
+ `Checking readiness for property "${propertyName}" on element ${
1006
+ element.id || ""
1007
+ }. Max attempts: ${maxAttempts}, Initial Delay: ${initialDelayMs}ms, Backoff Factor: ${backoffFactor}, Max Delay: ${
1008
+ effectiveMaxDelay === Infinity ? "None" : effectiveMaxDelay + "ms"
1009
+ }.`
1010
+ );
1011
+
1012
+ // Initialize the delay to be used for the *first* wait.
1013
+ let currentDelay = initialDelayMs;
1014
+
1015
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1016
+ console.log(
1017
+ `Attempt ${attempt}/${maxAttempts}: Checking "${propertyName}"...`
1018
+ );
1019
+ try {
1020
+ const propertyValue = element[propertyName];
1021
+ // Example check: Adjust condition as needed
1022
+ if (propertyValue !== undefined && propertyValue > 0) {
1023
+ console.log(
1024
+ `Property "${propertyName}" ready on attempt ${attempt}. Value: ${propertyValue}`
1025
+ );
1026
+ return true; // Condition met
1027
+ }
1028
+ } catch (error) {
1029
+ console.error(
1030
+ `Error accessing property "${propertyName}" on attempt ${attempt}:`,
1031
+ error
1032
+ );
1033
+ }
1034
+
1035
+ // If condition not met and more attempts remain:
1036
+ if (attempt < maxAttempts) {
1037
+ // Determine the actual delay for this wait, applying the cap
1038
+ const delayForThisWait = Math.min(currentDelay, effectiveMaxDelay);
1039
+
1040
+ console.log(
1041
+ `Waiting ${Math.round(
1042
+ delayForThisWait
1043
+ )}ms before next attempt...`
1044
+ );
1045
+ await delay(delayForThisWait);
1046
+
1047
+ // Calculate the delay for the *next* potential wait, only if backoff is active
1048
+ if (backoffFactor > 1) {
1049
+ currentDelay = currentDelay * backoffFactor;
1050
+ // Note: We apply the cap just before the actual delay,
1051
+ // but the base 'currentDelay' keeps growing exponentially.
1052
+ // Alternatively, you could cap 'currentDelay' itself here:
1053
+ // currentDelay = Math.min(currentDelay * backoffFactor, effectiveMaxDelay);
1054
+ // Let's stick to capping just before delay() for now.
1055
+ }
1056
+ // If backoffFactor <= 1, currentDelay remains initialDelayMs (implicitly handled by initialization)
1057
+ }
1058
+ }
1059
+
1060
+ // If loop finishes without condition being met
1061
+ console.warn(
1062
+ `Readiness check failed for property "${propertyName}" on element ${
1063
+ element.id || ""
1064
+ } after ${maxAttempts} attempts.`
1065
+ );
1066
+ return false; // Timed out
1067
+ }
1068
+
1069
+ // --- Example Usage (calling code doesn't change) ---
1070
+
1071
+ async function handleElementProcessingWithBackoff(element) {
1072
+ // Call the function that now supports backoff
1073
+ const isReady = await checkElementReadinessWithBackoff(element);
1074
+
1075
+ if (isReady) {
1076
+ console.log(
1077
+ `Element ${element.id || ""} is ready. Proceeding with actions.`
1078
+ );
1079
+ // --- Execute subsequent actions ---
1080
+ if (element.hasAttribute("onload-attribute")) {
1081
+ /* ... */
1082
+ }
1083
+ if (element.hasAttribute("onload-next")) {
1084
+ /* ... */
1085
+ }
1086
+ } else {
1087
+ console.warn(
1088
+ `Skipping actions for element ${
1089
+ element.id || ""
1090
+ } because readiness check failed.`
1091
+ );
1092
+ }
1093
+ }
1094
+
1095
+ // --- Example Elements ---
1096
+
1097
+ // With Backoff (Delays: 100, 200, 400, 800 - capped)
1098
+ // <span id="elem1" onload-retry-property="scrollWidth"
1099
+ // onload-retry-attempts="5" onload-retry-delay="100"
1100
+ // onload-retry-backoff-factor="2" onload-retry-max-delay="500">
1101
+ // Content 1
1102
+ // </span>
1103
+ // Waits would be: 100ms, 200ms, 400ms, 500ms (capped)
1104
+
1105
+ // No Backoff (factor missing/invalid/<=1)
1106
+ // <span id="elem2" onload-retry-property="clientHeight"
1107
+ // onload-retry-attempts="4" onload-retry-delay="300">
1108
+ // Content 2
1109
+ // </span>
1110
+ // Waits would be: 300ms, 300ms, 300ms
1111
+
1112
+ // const element1 = document.getElementById('elem1');
1113
+ // if (element1) handleElementProcessingWithBackoff(element1);
1114
+ // const element2 = document.getElementById('elem2');
1115
+ // if (element2) handleElementProcessingWithBackoff(element2);
1116
+
874
1117
  CoCreateEvents.init();
875
1118
 
876
1119
  export default CoCreateEvents;