@cocreate/events 1.29.0 → 1.29.1

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +1 -1
  3. package/src/index.js +660 -1116
package/src/index.js CHANGED
@@ -26,1128 +26,672 @@ import observer from "@cocreate/observer";
26
26
  import "@cocreate/element-prototype";
27
27
 
28
28
  const CoCreateEvents = {
29
- elements2: new Map(),
30
- init: function (prefix, events) {
31
- if (prefix && events) {
32
- this.initPrefix(prefix, events);
33
- } else {
34
- this.initPrefix("toggle", ["click"]);
35
- this.initPrefix("click", ["click"]);
36
- this.initPrefix("hover", ["mouseover", "mouseout"]);
37
- this.initPrefix("mouseover", ["mouseover"]);
38
- this.initPrefix("mouseout", ["mouseout"]);
39
- this.initPrefix("input", ["input"]);
40
- this.initPrefix("change", ["change"]);
41
- this.initPrefix("selected", ["click"]);
42
- this.initPrefix("onload", ["onload"]);
43
- this.initPrefix("observe", ["observer"]);
44
- this.initPrefix("intersection", ["intersection"]);
45
- this.initPrefix("resize", ["onload", "resize"]);
46
- this.initPrefix("localstorage", ["onload"]);
47
- this.initPrefix("focus", ["focus"]);
48
- this.initPrefix("blur", ["blur"]);
49
- }
50
-
51
- let customEventEls = document.querySelectorAll("[event-name]");
52
- let names = {};
53
- for (let customEventEl of customEventEls) {
54
- let name = customEventEl.getAttribute("event-name");
55
- name = name.split(",");
56
- for (let i = 0; i < name.length; i++) {
57
- name[i].trim();
58
- if (!names[name]) {
59
- names[name] = name;
60
- this.initPrefix(name);
61
- }
62
- }
63
- }
64
-
65
- const self = this;
66
- observer.init({
67
- name: "CoCreateEventName",
68
- types: ["addedNodes"],
69
- selector: `[event-name]`,
70
- callback: function (mutation) {
71
- let name = mutation.target.getAttribute("event-name");
72
- self.initPrefix(name);
73
- }
74
- });
75
- },
76
-
77
- initPrefix: function (prefix, events) {
78
- const self = this;
79
-
80
- action.init({
81
- name: prefix,
82
- endEvent: `${prefix}End`,
83
- callback: (data) => {
84
- this.__updateElements(
85
- data.element,
86
- prefix,
87
- null,
88
- null,
89
- data.params
90
- );
91
- }
92
- });
93
-
94
- let selector = `[${prefix}], [${prefix}-key], [${prefix}-attribute], [${prefix}-value], [${prefix}-action], [${prefix}-query]`;
95
-
96
- observer.init({
97
- name: "CoCreateEventattributes",
98
- types: ["attributes", "addedNodes"],
99
- attributeFilter: [
100
- `${prefix}-key`,
101
- `${prefix}-value`,
102
- `${prefix}-query`,
103
- `${prefix}-events`,
104
- `${prefix}-bubbles`,
105
- `${prefix}-cancelable`,
106
- `${prefix}-composed`,
107
- `${prefix}-preventDefault`,
108
- `${prefix}-stopPropagation`,
109
- `${prefix}-stopImmediatePropagation`
110
- ],
111
- selector,
112
- callback: function (mutation) {
113
- self.initElements([mutation.target], prefix, events);
114
- }
115
- });
116
-
117
- let elements = document.querySelectorAll(selector);
118
- this.initElements(elements, prefix, events);
119
- },
120
-
121
- elements: new Map(),
122
- initElements: function (elements, prefix, events = []) {
123
- const self = this;
124
- for (const el of elements) {
125
- let isEventable = true;
126
- let prefixes = this.elements.get(el);
127
- if (!prefixes) {
128
- prefixes = { [prefix]: { events } };
129
- this.elements.set(el, prefixes);
130
- } else if (!prefixes[prefix]) {
131
- prefixes[prefix] = { events };
132
- } else {
133
- isEventable = false;
134
- }
135
-
136
- let customEvents = el.getAttribute(`${prefix}-events`);
137
- if (customEvents) {
138
- customEvents = customEvents.split(",");
139
- for (let i = 0; i < customEvents.length; i++)
140
- customEvents[i] = customEvents[i].trim();
141
-
142
- for (let i = 0; i < events.length; i++)
143
- el.removeEventListener(events[i], eventFunction);
144
-
145
- events = customEvents;
146
- prefixes[prefix].events = events;
147
- isEventable = true;
148
- }
149
-
150
- if (!events || !isEventable) continue;
151
-
152
- if (events.includes("onload")) {
153
- this.__updateElements(el, prefix);
154
- }
155
-
156
- if (events.includes("observer")) {
157
- let target;
158
- for (let attribute of el.attributes) {
159
- if (attribute.name === "observe-target") {
160
- target = attribute.value;
161
- break;
162
- } else if ([`${prefix}-query`].includes(attribute.name)) {
163
- target = attribute.value;
164
- break;
165
- }
166
- }
167
-
168
- let attributeFilter = (
169
- el.getAttribute(`${prefix}-attributes`) || ""
170
- )
171
- .split(/\s*,\s*/)
172
- .filter((item) => item);
173
-
174
- let observeAttribute = (
175
- el.getAttribute(`${prefix}-observe`) || ""
176
- )
177
- .split(/\s*,\s*/)
178
- .filter((item) => item);
179
-
180
- let observerConfig = {
181
- types: observeAttribute,
182
- callback: function (mutation) {
183
- self.__updateElements(el, prefix, mutation.target);
184
- }
185
- };
186
-
187
- if (target) {
188
- if (
189
- target &&
190
- !observeAttribute.length &&
191
- !attributeFilter.length
192
- ) {
193
- observerConfig.types.push("addedNodes");
194
- }
195
- observerConfig.target = target;
196
- }
197
-
198
- if (attributeFilter.length) {
199
- observerConfig.types.push("attributes");
200
- observerConfig.attributeFilter = attributeFilter;
201
- }
202
-
203
- if (observerConfig.types.length) observer.init(observerConfig);
204
- }
205
-
206
- if (events.includes("resize")) {
207
- const resizeObserver = new ResizeObserver((entries) => {
208
- const lastEntry = entries[entries.length - 1];
209
- const { width, height } = lastEntry.contentRect;
210
- el.setAttribute(`${prefix}-if-value`, width + "px");
211
- self.__updateElements(el, prefix);
212
- });
213
- resizeObserver.observe(el);
214
- }
215
-
216
- if (events.includes("intersection")) {
217
- const rootSelector = el.getAttribute(`${prefix}-root`);
218
- const rootMargin =
219
- el.getAttribute(`${prefix}-root-margin`) || "0px";
220
- const threshold = el.getAttribute(`${prefix}-threshold`)
221
- ? JSON.parse(el.getAttribute(`${prefix}-threshold`))
222
- : [0];
223
- const trigger = el.getAttribute(`${prefix}-trigger`) || "both"; // Default to "both"
224
-
225
- // Resolve root from the selector or use null for viewport
226
- const root = rootSelector
227
- ? document.querySelector(rootSelector)
228
- : null;
229
-
230
- // Validate and filter threshold values
231
- const validThreshold = Array.isArray(threshold)
232
- ? threshold.filter((value) => value >= 0 && value <= 1)
233
- : [0];
234
-
235
- // Returns true if attribute is present AND does not equal "false"
236
- const onceAttr = el.getAttribute(`${prefix}-once`);
29
+ elements2: new Map(),
30
+ init: function (prefix, events) {
31
+ if (prefix && events) {
32
+ this.initPrefix(prefix, events);
33
+ } else {
34
+ this.initPrefix("toggle", ["click"]);
35
+ this.initPrefix("click", ["click"]);
36
+ this.initPrefix("hover", ["mouseover", "mouseout"]);
37
+ this.initPrefix("mouseover", ["mouseover"]);
38
+ this.initPrefix("mouseout", ["mouseout"]);
39
+ this.initPrefix("input", ["input"]);
40
+ this.initPrefix("change", ["change"]);
41
+ this.initPrefix("selected", ["click"]);
42
+ this.initPrefix("onload", ["onload"]);
43
+ this.initPrefix("observe", ["observer"]);
44
+ this.initPrefix("intersection", ["intersection"]);
45
+ this.initPrefix("resize", ["onload", "resize"]);
46
+ this.initPrefix("localstorage", ["onload"]);
47
+ this.initPrefix("focus", ["focus"]);
48
+ this.initPrefix("blur", ["blur"]);
49
+ }
50
+
51
+ let customEventEls = document.querySelectorAll("[event-name]");
52
+ let names = {};
53
+ for (let customEventEl of customEventEls) {
54
+ let name = customEventEl.getAttribute("event-name");
55
+ name = name.split(",");
56
+ for (let i = 0; i < name.length; i++) {
57
+ name[i].trim();
58
+ if (!names[name]) {
59
+ names[name] = name;
60
+ this.initPrefix(name);
61
+ }
62
+ }
63
+ }
64
+
65
+ const self = this;
66
+ observer.init({
67
+ name: "CoCreateEventName",
68
+ types: ["addedNodes"],
69
+ selector: `[event-name]`,
70
+ callback: function (mutation) {
71
+ let name = mutation.target.getAttribute("event-name");
72
+ self.initPrefix(name);
73
+ }
74
+ });
75
+ },
76
+
77
+ initPrefix: function (prefix, events) {
78
+ const self = this;
79
+
80
+ action.init({
81
+ name: prefix,
82
+ endEvent: `${prefix}End`,
83
+ callback: (data) => {
84
+ this.__updateElements(
85
+ data.element,
86
+ prefix,
87
+ null,
88
+ null,
89
+ data.params
90
+ );
91
+ }
92
+ });
93
+
94
+ // We look for the prefix as an attribute, or common options
95
+ let selector = `[${prefix}], [${prefix}\\.group], [${prefix}\\.query]`;
96
+
97
+ observer.init({
98
+ name: "CoCreateEventAttributes",
99
+ types: ["attributes", "addedNodes"],
100
+ // removed attributeFilter to allow discovery of dynamic attributes (prefix-src, prefix.option, etc.)
101
+ selector,
102
+ callback: function (mutation) {
103
+ // Filter relevant mutations here since attributeFilter is gone
104
+ if (mutation.type === 'attributes') {
105
+ if (mutation.attributeName.startsWith(prefix)) {
106
+ self.initElements([mutation.target], prefix, events);
107
+ }
108
+ } else {
109
+ self.initElements([mutation.target], prefix, events);
110
+ }
111
+ }
112
+ });
113
+
114
+ let elements = document.querySelectorAll(selector);
115
+ this.initElements(elements, prefix, events);
116
+ },
117
+
118
+ elements: new Map(),
119
+ initElements: function (elements, prefix, events = []) {
120
+ const self = this;
121
+ for (const el of elements) {
122
+ let isEventable = true;
123
+ let prefixes = this.elements.get(el);
124
+ if (!prefixes) {
125
+ prefixes = { [prefix]: { events } };
126
+ this.elements.set(el, prefixes);
127
+ } else if (!prefixes[prefix]) {
128
+ prefixes[prefix] = { events };
129
+ } else {
130
+ isEventable = false;
131
+ }
132
+
133
+ let customEvents = el.getAttribute(`${prefix}.events`);
134
+ if (customEvents) {
135
+ customEvents = customEvents.split(",");
136
+ for (let i = 0; i < customEvents.length; i++)
137
+ customEvents[i] = customEvents[i].trim();
138
+
139
+ for (let i = 0; i < events.length; i++)
140
+ el.removeEventListener(events[i], eventFunction);
141
+
142
+ events = customEvents;
143
+ prefixes[prefix].events = events;
144
+ isEventable = true;
145
+ }
146
+
147
+ if (!events || !isEventable) continue;
148
+
149
+ if (events.includes("onload")) {
150
+ this.__updateElements(el, prefix);
151
+ }
152
+
153
+ if (events.includes("observer")) {
154
+ let observerSelector = (
155
+ el.getAttribute(`${prefix}.selector`) ||
156
+ el.getAttribute(`${prefix}.query`) || ""
157
+ )
158
+
159
+ let attributeFilter = (
160
+ el.getAttribute(`${prefix}.attributeFilter`) || ""
161
+ )
162
+ .split(/\s*,\s*/)
163
+ .filter((item) => item);
164
+
165
+ let observerTypes = (
166
+ el.getAttribute(`${prefix}.types`) || ""
167
+ )
168
+ .split(/\s*,\s*/)
169
+ .filter((item) => item);
170
+
171
+ let observerConfig = {
172
+ types: observerTypes,
173
+ callback: function (mutation) {
174
+ self.__updateElements(el, prefix);
175
+ }
176
+ };
177
+
178
+ if (observerSelector) {
179
+ if (!observerTypes.length &&
180
+ !attributeFilter.length
181
+ ) {
182
+ observerConfig.types.push("addedNodes");
183
+ }
184
+ observerConfig.selector = observerSelector;
185
+ }
186
+
187
+ if (attributeFilter.length) {
188
+ observerConfig.types.push("attributes");
189
+ observerConfig.attributeFilter = attributeFilter;
190
+ }
191
+
192
+ if (observerConfig.types.length) {
193
+ observer.init(observerConfig);
194
+ }
195
+ }
196
+
197
+ if (events.includes("resize")) {
198
+ const resizeObserver = new ResizeObserver((entries) => {
199
+ const lastEntry = entries[entries.length - 1];
200
+ const { width, height } = lastEntry.contentRect;
201
+ // Note: We're setting a standard attribute here for use by logic later
202
+ el.setAttribute(`${prefix}-if-value`, width + "px");
203
+ self.__updateElements(el, prefix);
204
+ });
205
+ resizeObserver.observe(el);
206
+ }
207
+
208
+ if (events.includes("intersection")) {
209
+ const rootSelector = el.getAttribute(`${prefix}.root`);
210
+ const rootMargin =
211
+ el.getAttribute(`${prefix}.rootMargin`) || "0px";
212
+ const threshold = el.getAttribute(`${prefix}.threshold`)
213
+ ? JSON.parse(el.getAttribute(`${prefix}.threshold`))
214
+ : [0];
215
+ const trigger = el.getAttribute(`${prefix}.trigger`) || "both"; // Default to "both"
216
+
217
+ const root = rootSelector
218
+ ? document.querySelector(rootSelector)
219
+ : null;
220
+
221
+ const validThreshold = Array.isArray(threshold)
222
+ ? threshold.filter((value) => value >= 0 && value <= 1)
223
+ : [0];
224
+
225
+ const onceAttr = el.getAttribute(`${prefix}.once`);
237
226
  const once = onceAttr !== null && onceAttr !== "false";
238
227
 
239
- // Create IntersectionObserver
240
- const intersectionObserver = new IntersectionObserver(
241
- (entries) => {
242
- entries.forEach((entry) => {
243
- const isVisible = entry.isIntersecting;
244
-
245
- // Determine if we should trigger based on the "trigger" attribute
246
- if (
247
- (trigger === "visible" && isVisible) ||
248
- (trigger === "not-visible" && !isVisible) ||
249
- trigger === "both"
250
- ) {
251
- // Set an attribute to reflect visibility state
252
- el.setAttribute(
253
- `${prefix}-is-visible`,
254
- isVisible ? "true" : "false"
255
- );
256
-
257
- // Call __updateElements
258
- self.__updateElements(
259
- el,
260
- prefix
261
- );
262
- // Disconnect if once is true
228
+ const intersectionObserver = new IntersectionObserver(
229
+ (entries) => {
230
+ entries.forEach((entry) => {
231
+ const isVisible = entry.isIntersecting;
232
+
233
+ if (
234
+ (trigger === "visible" && isVisible) ||
235
+ (trigger === "not-visible" && !isVisible) ||
236
+ trigger === "both"
237
+ ) {
238
+ el.setAttribute(
239
+ `${prefix}-is-visible`,
240
+ isVisible ? "true" : "false"
241
+ );
242
+
243
+ self.__updateElements(
244
+ el,
245
+ prefix
246
+ );
247
+
263
248
  if (once) {
264
249
  intersectionObserver.unobserve(entry.target);
265
250
  }
266
- }
267
- });
268
- },
269
- { root, rootMargin, threshold: validThreshold }
270
- );
271
-
272
- intersectionObserver.observe(el);
273
- }
274
-
275
- for (let i = 0; i < events.length; i++) {
276
- // Skip specific lifecycle events
277
- if (
278
- events[i] === "onload" ||
279
- events[i] === "observer" ||
280
- events[i] === "intersection" ||
281
- events[i] === "resize"
282
- ) {
283
- continue;
284
- }
285
-
286
- // Remove any existing event listener
287
- el.removeEventListener(events[i], eventFunction);
288
-
289
- // Initialize an empty options object
290
- const options = {};
291
-
292
- // Attributes to check dynamically
293
- const attributes = ["bubbles", "cancelable", "composed"];
294
-
295
- for (let j = 0; j < attributes.length; j++) {
296
- const attrValue = el.getAttribute(
297
- `${events[i]}-${attributes[j]}`
298
- );
299
- if (attrValue !== null) {
300
- options[attributes[j]] = attrValue !== "false";
301
- }
302
- }
303
-
304
- // Add the new event listener with the dynamically constructed options
305
- el.addEventListener(events[i], eventFunction, options);
306
- }
307
-
308
- function eventFunction(event) {
309
- // TODO: apply debounce.
310
- // let debounce;
311
- // clearTimeout(debounce);
312
- // debounce = setTimeout(function() {
313
- const target = event.currentTarget;
314
- if (target) {
315
- let prefixes = self.elements.get(target);
316
- if (
317
- prefixes[prefix].prev === event.type &&
318
- ["mouseover", "mouseout"].includes(event.type)
319
- )
320
- return;
321
- else prefixes[prefix].prev = event.type;
322
-
323
- let attribute = target.getAttribute("actions") || "";
324
- if (attribute.includes(prefix)) return;
325
- // if (target.closest(`[actions*="${prefix}"]`))
326
- // return;
327
-
328
- // Define the lifecycle attributes (matching JavaScript event methods)
329
- const lifecycleAttributes = [
330
- "preventDefault",
331
- "stopPropagation",
332
- "stopImmediatePropagation"
333
- ];
334
-
335
- // Handle lifecycle attributes dynamically
336
- for (let i = 0; i < lifecycleAttributes.length; i++) {
337
- const attr = `${prefix}-${lifecycleAttributes[i]}`; // Add prefix dynamically
338
- const attrValue = target.getAttribute(attr); // Directly get the attribute value
339
-
340
- // If attribute exists and is not explicitly "false", apply the corresponding event method
341
- if (attrValue !== null && attrValue !== "false") {
342
- event[lifecycleAttributes[i]]();
343
- }
344
- }
345
-
346
- self.__updateElements(
347
- target,
348
- prefix,
349
- null,
350
- prefixes[prefix].events
351
- );
352
- }
353
- }
354
- }
355
- },
356
-
357
- __updateElements: async function (el, prefix, target, events, params) {
358
- if (!el.isConnected) return;
359
-
360
- let elements = [el];
361
- let targetGroup = el.getAttribute(`${prefix}-group`);
362
- if (targetGroup) {
363
- elements = document.querySelectorAll(
364
- `[${prefix}-group="${targetGroup}"]`
365
- );
366
- }
367
-
368
- for (let element of elements) {
369
- // const requestAnimationFrameAttr = element.getAttribute(
370
- // `${prefix}-requestAnimationFrame`
371
- // );
372
-
373
- // if (
374
- // requestAnimationFrameAttr !== null &&
375
- // requestAnimationFrameAttr !== "false"
376
- // ) {
377
- // // Double requestAnimationFrame
378
- // await new Promise((resolve) => {
379
- // requestAnimationFrame(() => {
380
- // requestAnimationFrame(() => {
381
- // resolve();
382
- // });
383
- // });
384
- // });
385
- // }
386
-
387
- let interval = element.getAttribute(`${prefix}-setInterval`);
388
- if (interval !== null) {
389
- let intervalMs = parseInt(interval, 10) || 0;
390
- if (!element.eventsInterval) {
391
- element.eventsInterval = {};
392
- }
393
- let intervalEntry = element.eventsInterval[prefix];
394
- if (intervalMs > 0) {
395
- if (!intervalEntry || intervalEntry.delay !== intervalMs) {
396
- if (intervalEntry) {
397
- clearInterval(intervalEntry.id);
398
- }
399
- element.eventsInterval[prefix] = {
400
- delay: intervalMs,
401
- id: setInterval(() => {
402
- if (!element.isConnected) {
403
- const entry =
404
- element.eventsInterval &&
405
- element.eventsInterval[prefix];
406
- if (entry) {
407
- clearInterval(entry.id);
408
- delete element.eventsInterval[prefix];
409
- }
410
- return;
411
- }
412
- self.__updateElements(
413
- element,
414
- prefix,
415
- target,
416
- events,
417
- params
418
- );
419
- }, intervalMs)
420
- };
421
- }
422
- } else if (intervalEntry) {
423
- clearInterval(intervalEntry.id);
424
- delete element.eventsInterval[prefix];
425
- }
426
- }
427
-
428
- let delay = element.getAttribute(`${prefix}-setTimeout`);
429
- if (delay) {
430
- delay = parseInt(delay, 10) || 0;
431
- if (delay > 0) {
432
- await new Promise((resolve) => setTimeout(resolve, delay));
433
- }
434
- }
435
-
436
- if (!element.isConnected) return;
437
-
438
- let once = element.getAttribute(`${prefix}-once`);
439
- if (once || once === "") {
440
- if (!element.eventsOnce) {
441
- element.eventsOnce = [prefix];
442
- } else if (element.eventsOnce.includes(prefix)) {
443
- continue;
444
- } else {
445
- element.eventsOnce.push(prefix);
446
- }
447
- }
448
-
449
- // TODO: support empty value when prefix-attribute defined, add and remove the attribute
450
- let targetAttribute = element.getAttribute(`${prefix}-attribute`);
451
- let targetPosition = element.getAttribute(
452
- `${prefix}-insert-adjacent`
453
- );
454
-
455
- let onEvent = element.getAttribute(`${prefix}-on`);
456
- if (onEvent) {
457
- await new Promise((resolve, reject) => {
458
- const handleEvent = () => {
459
- document.removeEventListener(onEvent, handleEvent);
460
- resolve();
461
- };
462
- document.addEventListener(onEvent, handleEvent);
463
- });
464
- }
465
-
466
- let values;
467
- if (prefix === "localstorage") {
468
- let key = element.getAttribute("localstorage-get");
469
- if (key) {
470
- values = localStorage.getItem(key);
471
- } else if ((key = element.getAttribute("localstorage-set"))) {
472
- values = await element.getValue();
473
- if (values) {
474
- localStorage.setItem(key, values);
475
- }
476
- }
477
-
478
- if (!key) return;
479
- } else {
480
- values = element.getAttribute(`${prefix}-value`);
481
-
482
- if (values === null) {
483
- let valueElements = queryElements({
484
- element,
485
- prefix: `${prefix}-value`
486
- });
487
- if (valueElements.length) {
488
- let elementValues = [];
489
- for (let i = 0; i < valueElements.length; i++) {
490
- elementValues.push(valueElements[i].getValue());
491
- }
492
- if (elementValues.length) {
493
- values = elementValues;
494
- }
495
- }
496
- }
497
-
498
- if (values === null) {
499
- values = element.getAttribute(`${prefix}-if-value`);
500
- }
501
- if (values === null) {
502
- values = element.getAttribute(prefix);
503
- }
504
- }
505
-
506
- if (values || values === "") {
507
- if (typeof values === "string") {
508
- values = values.split(",");
509
- } else if (!Array.isArray(values)) {
510
- values = [values];
511
- }
512
- } else {
513
- values = await element.getValue();
514
- if (!Array.isArray(values)) {
515
- values = [values];
516
- }
517
- }
518
-
519
- if ((targetAttribute && !values) || values.length === 0) {
520
- return;
521
- }
522
-
523
- let ifCondition = element.getAttribute(`${prefix}-if`);
524
- let elseIfCondition = element.getAttribute(`${prefix}-else-if`);
525
- let elseCondition = element.getAttribute(`${prefix}-else`);
526
-
527
- let ifValue = element.getAttribute(`${prefix}-if-value`);
528
- if (ifCondition) {
529
- if (!ifValue && ifValue !== "")
530
- ifValue = (await element.getValue()) || values;
531
- //values // await element.getValue()
532
- else if (ifValue || ifValue === "") {
533
- ifValue = [ifValue];
534
- } else {
535
- ifValue = values;
536
- }
537
-
538
- if (!Array.isArray(ifValue)) {
539
- ifValue = [ifValue];
540
- }
541
- }
542
-
543
- if (ifCondition) {
544
- // If the 'if' condition is not met, stop processing this element.
545
- if (!evaluateCondition(ifCondition, ifValue)) {
546
- return;
547
- }
548
- } else if (elseCondition) {
549
- // If there's no 'if' condition but there is an 'else' condition,
550
- // stop processing if the 'else' condition is met.
551
- // This creates an "execute unless" behavior for the 'else' attribute.
552
- if (evaluateCondition(elseIfCondition, ifValue)) {
553
- return;
554
- }
555
- }
556
-
557
- let targetText = element.getAttribute(`${prefix}-text`);
558
- let targetHtml = element.getAttribute(`${prefix}-html`);
559
- let targetKey = element.getAttribute(`${prefix}-key`);
560
-
561
- if (prefix === "selected") {
562
- if (el !== element) {
563
- element.removeAttribute("selected");
564
- } else {
565
- element.setAttribute("selected", "");
566
- }
567
- }
568
-
569
- let deactivate = false;
570
- if (el !== element) {
571
- deactivate = true;
572
- }
573
- if (events && events.includes("click")) {
574
- // TODO: handling of attributes that represent states aria-expanded, aria-selected etc.
575
- }
576
-
577
- // values = values.map(x => x.trim());
578
- values = values.map((x) => {
579
- if (typeof x === "string") x = x.trim(); // Update x with the trimmed value
580
- let prop = element.getAttribute(`${prefix}-property`);
581
- if (prop) {
582
- x = `${prop}:${x}`; // Update x with prop if it exists
583
- }
584
- return x; // Return the updated x
585
- });
586
- let targetElements;
587
- if (element.hasAttribute(`${prefix}-query`)) {
588
- targetElements = queryElements({
589
- element,
590
- prefix,
591
- selector: params
592
- });
593
- } else {
594
- targetElements = [element];
595
- }
596
-
597
- let action =
598
- element.getAttribute(`${prefix}-action`) ||
599
- element.getAttribute(`${prefix}-method`);
600
- for (let i = 0; i < targetElements.length; i++) {
601
- if (action) {
602
- if (targetElements[i][action]) {
603
- targetElements[i][action]();
604
- }
605
- } else if (
606
- !targetAttribute &&
607
- targetAttribute !== "" &&
608
- !targetPosition &&
609
- ["click", "focus", "blur"].includes(prefix)
610
- ) {
611
- // TODO: click causes an infinite loop if targetElement[i] is a child of element do to event propactaion
612
- if (
613
- (targetElements[i] === element &&
614
- element.hasAttribute(prefix)) ||
615
- (params &&
616
- targetElements[i] !== element &&
617
- !element.contains(targetElements[i]))
618
- )
619
- targetElements[i][prefix]();
620
- } else {
621
- this.setValue(
622
- prefix,
623
- targetElements[i],
624
- targetAttribute,
625
- values,
626
- targetKey,
627
- deactivate,
628
- events,
629
- targetPosition
630
- );
631
- }
632
- }
633
- }
634
-
635
- el.dispatchEvent(
636
- new CustomEvent(`${prefix}End`, {
637
- detail: {}
638
- })
639
- );
640
- },
641
-
642
- setValue: async function (
643
- prefix,
644
- element,
645
- attrName,
646
- values,
647
- key,
648
- deactivate,
649
- events,
650
- targetPosition
651
- ) {
652
- if (events && events.includes("mouseout")) {
653
- if (element.matches(":hover")) {
654
- element.addEventListener(
655
- "mouseout",
656
- () => {
657
- this.setValue(
658
- prefix,
659
- element,
660
- attrName,
661
- values,
662
- key,
663
- deactivate,
664
- events,
665
- targetPosition
666
- );
667
- },
668
- { once: true }
669
- );
670
- return;
671
- }
672
- }
673
-
674
- let attrValues, oldValue;
675
- if (key) {
676
- key = `{{${key}}}`;
677
- // let value = values[0]
678
- replaceKey(prefix, element, key, values);
679
- } else {
680
- // TODO: if html in iframe
681
- let domTextEditor;
682
- if (element.parentElement) {
683
- domTextEditor =
684
- element.parentElement.closest("[contenteditable]");
685
- }
686
-
687
- if (attrName === "style") {
688
- if (element.getAttribute(attrName)) {
689
- attrValues = element
690
- .getAttribute(attrName)
691
- .split(";")
692
- .map((x) => x.trim());
693
- attrValues = attrValues.map(function (item, index) {
694
- return item.replace(" ", "");
695
- });
696
- oldValue =
697
- values.filter((x) => attrValues.includes(x))[0] || "";
698
- }
699
- let newValue = this.__getNextValue(values, oldValue);
700
-
701
- if (oldValue) {
702
- let [property, value] = oldValue.split(":");
703
- element.style[property] = "";
704
- }
705
-
706
- if (newValue != "") {
707
- newValue = newValue.split(";").map((x) => x.trim());
708
- for (let style of newValue) {
709
- let [property, value] = style.split(":");
710
- element.style[property] = value;
711
- }
712
- }
713
- } else {
714
- if (attrName === "value") {
715
- oldValue = await element.getValue();
716
- } else if (
717
- attrName === "text" ||
718
- attrName === "html" ||
719
- element.getAttribute(attrName)
720
- ) {
721
- attrValues = element
722
- .getAttribute(attrName)
723
- .split(" ")
724
- .map((x) => x.trim());
725
- oldValue =
726
- values.filter((x) => attrValues.includes(x))[0] || "";
727
- }
728
-
729
- let newValue = this.__getNextValue(values, oldValue);
730
- if (deactivate) newValue = "";
731
- if (attrName === "class") {
732
- if (oldValue) {
733
- oldValue.split(" ").forEach((className) => {
734
- element.classList.remove(className);
735
- });
736
- if (values.length === 1) {
737
- return;
738
- }
739
- }
740
-
741
- if (newValue) {
742
- newValue.split(" ").forEach((className) => {
743
- element.classList.add(className);
744
- });
745
- }
746
- } else if (attrName === "value") {
747
- element.setValue(newValue, false);
748
- } else if (
749
- ["$click", "$focus", "$blur", "$save", "$read"].includes(
750
- attrName
751
- )
752
- ) {
753
- element[attrName.substring(1)]();
754
- } else if (attrName) {
755
- // TODO: removeAttribute vs setting empty value, how best to define. operator $remove ???
756
- if (newValue === "" && element.hasAttribute(attrName)) {
757
- element.removeAttribute(attrName);
758
- } else {
759
- element.setAttribute(attrName, newValue);
760
- }
761
- } else if (targetPosition) {
762
- if (domTextEditor) {
763
- CoCreate.text.insertAdjacentElement({
764
- domTextEditor,
765
- position: targetPosition,
766
- target: element,
767
- elementValue: newValue
768
- });
769
- } else if (targetPosition === "replace") {
770
- element.insertAdjacentHTML("beforebegin", newValue); // Insert before oldElement
771
- element.remove(); // Remove oldElement
772
- } else {
773
- element.insertAdjacentHTML(targetPosition, newValue);
774
- }
775
- } else {
776
- if (
777
- !attrName &&
778
- ["click", "focus", "blur"].includes(newValue)
779
- ) {
780
- element[newValue]();
781
- } else if (["click", "focus", "blur"].includes(attrName)) {
782
- element[attrName]();
783
- }
784
- }
785
- }
786
- }
787
- },
788
-
789
- __getNextValue: function (values, val) {
790
- let size = values.length;
791
- let nn = values.indexOf(val);
792
- if (nn == -1) {
793
- return values[0];
794
- } else {
795
- return values[(nn + 1) % size];
796
- }
797
- }
251
+ }
252
+ });
253
+ },
254
+ { root, rootMargin, threshold: validThreshold }
255
+ );
256
+
257
+ intersectionObserver.observe(el);
258
+ }
259
+
260
+ for (let i = 0; i < events.length; i++) {
261
+ if (
262
+ events[i] === "onload" ||
263
+ events[i] === "observer" ||
264
+ events[i] === "intersection" ||
265
+ events[i] === "resize"
266
+ ) {
267
+ continue;
268
+ }
269
+
270
+ el.removeEventListener(events[i], eventFunction);
271
+
272
+ const options = {};
273
+ // Updated to use dot notation for event options
274
+ const attributes = ["bubbles", "cancelable", "composed"];
275
+
276
+ for (let j = 0; j < attributes.length; j++) {
277
+ const attrValue = el.getAttribute(
278
+ `${events[i]}.${attributes[j]}`
279
+ );
280
+ if (attrValue !== null) {
281
+ options[attributes[j]] = attrValue !== "false";
282
+ }
283
+ }
284
+
285
+ el.addEventListener(events[i], eventFunction, options);
286
+ }
287
+
288
+ function eventFunction(event) {
289
+ const target = event.currentTarget;
290
+ if (target) {
291
+ let prefixes = self.elements.get(target);
292
+ if (
293
+ prefixes[prefix].prev === event.type &&
294
+ ["mouseover", "mouseout"].includes(event.type)
295
+ )
296
+ return;
297
+ else prefixes[prefix].prev = event.type;
298
+
299
+ // Updated to use dot notation for 'actions' attribute check if applicable
300
+ let attribute = target.getAttribute("actions") || "";
301
+ if (attribute.includes(prefix)) return;
302
+
303
+ // Handle lifecycle using dot notation
304
+ const lifecycleAttributes = [
305
+ "preventDefault",
306
+ "stopPropagation",
307
+ "stopImmediatePropagation"
308
+ ];
309
+
310
+ for (let i = 0; i < lifecycleAttributes.length; i++) {
311
+ const attr = `${prefix}.${lifecycleAttributes[i]}`;
312
+ const attrValue = target.getAttribute(attr);
313
+
314
+ if (attrValue !== null && attrValue !== "false") {
315
+ event[lifecycleAttributes[i]]();
316
+ }
317
+ }
318
+
319
+ self.__updateElements(
320
+ target,
321
+ prefix,
322
+ prefixes[prefix].events
323
+ );
324
+ }
325
+ }
326
+ }
327
+ },
328
+
329
+ __updateElements: async function (rootElement, prefix, events, params) {
330
+ if (!rootElement.isConnected) return;
331
+
332
+ // 1. Determine targets based on prefix.group option
333
+ let sourceElements = [rootElement];
334
+ let sourceGroup = rootElement.getAttribute(`${prefix}.group`);
335
+ if (sourceGroup) {
336
+ // Looking for targets that identify with this group
337
+ // Using dot notation for the target marker as well for consistency
338
+ sourceElements = document.querySelectorAll(`[${prefix}\\.group="${sourceGroup}"]`);
339
+ // sourceElements = queryElements({
340
+ // element: rootElement,
341
+ // selector: `[${prefix}\\.group="${sourceGroup}"]`
342
+ // });
343
+ }
344
+
345
+ // 2. Iterate through sourceElements
346
+ for (let sourceElement of sourceElements) {
347
+
348
+ // Handle Global Options for this sourceElement processing
349
+
350
+ // onEvent
351
+ let onEvent = sourceElement.getAttribute(`${prefix}.on`);
352
+ if (onEvent) {
353
+ await new Promise((resolve, reject) => {
354
+ const handleEvent = () => {
355
+ document.removeEventListener(onEvent, handleEvent);
356
+ resolve();
357
+ };
358
+ document.addEventListener(onEvent, handleEvent);
359
+ });
360
+ }
361
+
362
+ // setInterval
363
+ let interval = sourceElement.getAttribute(`${prefix}.setInterval`);
364
+ if (interval !== null) {
365
+ let intervalMs = parseInt(interval, 10) || 0;
366
+ if (!sourceElement.eventsInterval) {
367
+ sourceElement.eventsInterval = {};
368
+ }
369
+ let intervalEntry = sourceElement.eventsInterval[prefix];
370
+ if (intervalMs > 0) {
371
+ if (!intervalEntry || intervalEntry.delay !== intervalMs) {
372
+ if (intervalEntry) {
373
+ clearInterval(intervalEntry.id);
374
+ }
375
+ sourceElement.eventsInterval[prefix] = {
376
+ delay: intervalMs,
377
+ id: setInterval(() => {
378
+ if (!sourceElement.isConnected) {
379
+ const entry =
380
+ sourceElement.eventsInterval &&
381
+ sourceElement.eventsInterval[prefix];
382
+ if (entry) {
383
+ clearInterval(entry.id);
384
+ delete sourceElement.eventsInterval[prefix];
385
+ }
386
+ return;
387
+ }
388
+ // Recursive call for interval
389
+ this.__updateElements(
390
+ rootElement,
391
+ prefix,
392
+ events,
393
+ params
394
+ );
395
+ }, intervalMs)
396
+ };
397
+ }
398
+ } else if (intervalEntry) {
399
+ clearInterval(intervalEntry.id);
400
+ delete sourceElement.eventsInterval[prefix];
401
+ }
402
+ }
403
+
404
+ // setTimeout
405
+ let delayTime = sourceElement.getAttribute(`${prefix}.setTimeout`);
406
+ if (delayTime) {
407
+ delayTime = parseInt(delayTime, 10) || 0;
408
+ if (delayTime > 0) {
409
+ await new Promise((resolve) => setTimeout(resolve, delayTime));
410
+ }
411
+ }
412
+
413
+ if (!sourceElement.isConnected) return;
414
+
415
+ // once
416
+ let once = sourceElement.getAttribute(`${prefix}.once`);
417
+ if (once || once === "") {
418
+ if (!sourceElement.eventsOnce) {
419
+ sourceElement.eventsOnce = [prefix];
420
+ } else if (sourceElement.eventsOnce.includes(prefix)) {
421
+ continue;
422
+ } else {
423
+ sourceElement.eventsOnce.push(prefix);
424
+ }
425
+ }
426
+
427
+ // --- Resolve Targets (Look up query) ---
428
+ // We check for query on the sourceElement to see if targets should be redirected
429
+ let querySelector = sourceElement.getAttribute(`${prefix}.query`);
430
+
431
+ // Reset targets to sourceElement by default or use query
432
+ let targetElements = [sourceElement];
433
+
434
+ // If query exists or we have params, resolve targets
435
+ if (querySelector || params) {
436
+ targetElements = queryElements({
437
+ element: sourceElement,
438
+ prefix: prefix,
439
+ selector: querySelector || params
440
+ });
441
+ }
442
+
443
+ // --- 6. Execute Action on Targets ---
444
+ for (let targetElement of targetElements) {
445
+ await this.setValue(
446
+ rootElement,
447
+ sourceElement,
448
+ targetElement,
449
+ prefix,
450
+ events
451
+ );
452
+ }
453
+
454
+ }
455
+
456
+ rootElement.dispatchEvent(
457
+ new CustomEvent(`${prefix}End`, {
458
+ detail: {}
459
+ })
460
+ );
461
+ },
462
+
463
+ setValue: async function (
464
+ rootElement,
465
+ sourceElement,
466
+ targetElement,
467
+ prefix,
468
+ events
469
+ ) {
470
+ if (events && events.includes("mouseout")) {
471
+ if (targetElement.matches(":hover")) {
472
+ targetElement.addEventListener(
473
+ "mouseout",
474
+ () => {
475
+ this.setValue(
476
+ rootElement,
477
+ sourceElement,
478
+ targetElement,
479
+ prefix,
480
+ events
481
+ );
482
+ },
483
+ { once: true }
484
+ );
485
+ return;
486
+ }
487
+ }
488
+
489
+ let deactivate = false;
490
+ if (rootElement !== sourceElement) {
491
+ deactivate = true;
492
+ }
493
+
494
+ // 3. Iterate Attributes of the Source Element (el) in order
495
+ for (let attribute of sourceElement.attributes) {
496
+ const name = attribute.name;
497
+
498
+ // Optimization: Skip attributes that don't match the prefix
499
+ // UPDATED: All checks below use lowercase because DOM attribute iteration is lowercase
500
+ if (!name.startsWith(prefix)
501
+ || name.length <= prefix.length + 1
502
+ || name === `${prefix}.on`
503
+ || name === `${prefix}.settimeout`
504
+ || name === `${prefix}.setinterval`
505
+ || name === `${prefix}.once`
506
+ || name === `${prefix}.group`
507
+ || name === `${prefix}.query`
508
+ || name === `${prefix}.root`
509
+ || name === `${prefix}.rootmargin`
510
+ || name === `${prefix}.threshold`
511
+ || name === `${prefix}.trigger`
512
+ || name === `${prefix}.selector`
513
+ || name === `${prefix}.attributefilter`
514
+ || name === `${prefix}.types`
515
+ || name === `${prefix}.bubbles`
516
+ || name === `${prefix}.cancelable`
517
+ || name === `${prefix}.composed`
518
+ || name === `${prefix}.preventdefault`
519
+ || name === `${prefix}.stoppropagation`
520
+ || name === `${prefix}.stopimmediatepropagation`
521
+ // Skip .position option as well since it's used as config
522
+ || name === `${prefix}.position`) {
523
+ continue;
524
+ }
525
+
526
+ // Parse Target Name and Type
527
+ let targetName = name.substring(prefix.length + 1);
528
+ let separator = name[prefix.length]; // '.' or '-'
529
+
530
+ let value = attribute.value;
531
+ if (!value && sourceElement && sourceElement.getValue) {
532
+ value = await sourceElement.getValue();
533
+ }
534
+
535
+ // Parse comma-separated values to support rotation
536
+ let values = [];
537
+ if (typeof value === 'string') {
538
+ values = value.split(',').map(v => v.trim());
539
+ } else if (Array.isArray(value)) {
540
+ values = value;
541
+ } else {
542
+ values = [value];
543
+ }
544
+
545
+ let oldValue = "";
546
+ let newValue = "";
547
+
548
+ switch (targetName) {
549
+ case 'style':
550
+ if (targetElement.getAttribute("style")) {
551
+ let styles = targetElement.getAttribute("style").split(";").map(x => x.trim().replace(/\s/g, ""));
552
+ oldValue = values.find(x => styles.includes(x.replace(/\s/g, ""))) || "";
553
+ }
554
+ newValue = this.__getNextValue(values, oldValue, deactivate);
555
+
556
+ if (oldValue) {
557
+ let [prop, val] = oldValue.split(":");
558
+ if (prop) targetElement.style[prop.trim()] = "";
559
+ }
560
+
561
+ if (newValue) {
562
+ let styles = newValue.split(";");
563
+ for(let s of styles) {
564
+ let [prop, val] = s.split(":");
565
+ if (prop && val) targetElement.style[prop.trim()] = val.trim();
566
+ }
567
+ }
568
+ break;
569
+
570
+ case 'class':
571
+ let currentClasses = Array.from(targetElement.classList);
572
+ oldValue = values.find(val => {
573
+ let requiredClasses = val.split(" ");
574
+ return requiredClasses.every(c => currentClasses.includes(c));
575
+ }) || "";
576
+
577
+ newValue = this.__getNextValue(values, oldValue, deactivate);
578
+
579
+ if (oldValue) {
580
+ oldValue.split(" ").forEach(c => targetElement.classList.remove(c));
581
+ if (values.length === 1) return;
582
+ }
583
+
584
+ if (newValue) {
585
+ newValue.split(" ").forEach(c => targetElement.classList.add(c));
586
+ }
587
+ break;
588
+
589
+ // UPDATED: Added lowercase cases for HTML compatibility
590
+ case 'insertadjacenthtml':
591
+ case 'insertadjacenttext':
592
+ case 'insertAdjacentHTML':
593
+ case 'insertAdjacentText':
594
+ // 1. Get Position from Attribute Value (No rotation, strict)
595
+ let position = attribute.value;
596
+ if (!position) return;
597
+
598
+ // 2. Get Content from Source Element
599
+ let content = "";
600
+ if (sourceElement && sourceElement.getValue) {
601
+ content = await sourceElement.getValue();
602
+ }
603
+
604
+ // 3. Execute Insertion
605
+ // Check for CoCreate Rich Text integration
606
+ let domTextEditor;
607
+ if (targetElement.parentElement) {
608
+ domTextEditor = targetElement.parentElement.closest("[contenteditable]");
609
+ }
610
+
611
+ if (domTextEditor && typeof CoCreate !== 'undefined' && CoCreate.text) {
612
+ CoCreate.text.insertAdjacentElement({
613
+ domTextEditor,
614
+ position: position,
615
+ target: targetElement,
616
+ elementValue: content
617
+ });
618
+ } else {
619
+ // Standard JS Execution
620
+ // Check lowercase match since switch case might have matched that
621
+ if (targetName === 'insertAdjacentHTML' || targetName === 'insertadjacenthtml') {
622
+ targetElement.insertAdjacentHTML(position, content);
623
+ } else {
624
+ targetElement.insertAdjacentText(position, content);
625
+ }
626
+ }
627
+ break;
628
+
629
+ case 'value':
630
+ if (targetElement.getValue) {
631
+ oldValue = await targetElement.getValue();
632
+ } else {
633
+ oldValue = targetElement.value;
634
+ }
635
+ if (!values.includes(oldValue)) oldValue = "";
636
+ newValue = this.__getNextValue(values, oldValue, deactivate);
637
+
638
+ if (targetElement.setValue) {
639
+ targetElement.setValue(newValue);
640
+ } else {
641
+ targetElement.value = newValue;
642
+ }
643
+ break;
644
+
645
+ default:
646
+ if (separator === '.') {
647
+ // Dot Notation: Property or Method
648
+ if (targetElement[targetName] !== undefined) {
649
+ // It's a property or function
650
+ oldValue = targetElement[targetName];
651
+ if (!values.includes(oldValue)) oldValue = "";
652
+ newValue = this.__getNextValue(values, oldValue, deactivate);
653
+
654
+ if (typeof targetElement[targetName] === 'function') {
655
+ targetElement[targetName](newValue);
656
+ } else {
657
+ targetElement[targetName] = newValue;
658
+ }
659
+ }
660
+ } else {
661
+ // Dash Notation: Attribute
662
+ // Fallback to attribute
663
+ oldValue = targetElement.getAttribute(targetName);
664
+ if (!values.includes(oldValue)) oldValue = "";
665
+ newValue = this.__getNextValue(values, oldValue, deactivate);
666
+
667
+ // Set Attribute with Namespace check
668
+ if (targetName.includes('.')) {
669
+ const realName = targetName.replace('.', ':');
670
+ targetElement.setAttribute(realName, newValue);
671
+ } else if (targetName.includes(':')) {
672
+ targetElement.setAttribute(targetName, newValue);
673
+ } else {
674
+ targetElement.setAttribute(targetName, newValue);
675
+ }
676
+ }
677
+ break;
678
+ }
679
+ }
680
+
681
+ },
682
+
683
+ __getNextValue: function (values, val, deativate) {
684
+ if (deativate) return "";
685
+ let size = values.length;
686
+ let nn = values.indexOf(val);
687
+ if (nn == -1) {
688
+ return values[0];
689
+ } else {
690
+ return values[(nn + 1) % size];
691
+ }
692
+ }
798
693
  };
799
694
 
800
- const eventElements = new Map();
801
- const eventKeys = new Map();
802
- const elementAttributes = new Map();
803
-
804
- function replaceKey(prefix, element, key, values) {
805
- let currentValue = key;
806
-
807
- if (eventElements.has(element)) {
808
- currentValue = eventElements.get(element).get(key);
809
- }
810
- let value = CoCreateEvents.__getNextValue(values, currentValue);
811
- eventKeys.set(key, value);
812
- eventElements.set(element, eventKeys);
813
-
814
- let attributes = elementAttributes.get(element);
815
- if (!attributes) {
816
- attributes = {};
817
- elementAttributes.set(element, attributes);
818
- }
819
-
820
- for (let attribute of element.attributes) {
821
- let newName = attribute.name;
822
- let newValue = attribute.value;
823
- let attrName = attribute.name;
824
- let attrValue = attribute.value;
825
- let setAttr = false;
826
-
827
- if (attrName === "clone-data") console.log("clone-data");
828
- if (
829
- attrValue.includes(currentValue) ||
830
- (attributes[attrName] &&
831
- attributes[attrName].valueKeys &&
832
- attributes[attrName].valueKeys[key])
833
- ) {
834
- if (!attributes[attrName])
835
- attributes[attrName] = { originalValue: attrValue };
836
- else if (!attributes[attrName].originalValue)
837
- attributes[attrName].originalValue = attrValue;
838
- if (!attributes[attrName].valueKeys)
839
- attributes[attrName].valueKeys = { [key]: value };
840
- else attributes[attrName].valueKeys[key] = value;
841
-
842
- newValue = attributes[attrName].originalValue;
843
- for (let valueKey of Object.keys(attributes[attrName].valueKeys)) {
844
- const valueKeyRegex = new RegExp(valueKey, "g");
845
- newValue = newValue.replace(valueKeyRegex, value);
846
- }
847
- setAttr = true;
848
- }
849
- // if (attrName.includes(currentValue) || attributes[attrName] && attributes[attrName].nameKeys && attributes[attrName].nameKeys[key]){
850
- // if (!attributes[attrName])
851
- // attributes[attrName] = { originalName: attrName }
852
- // else if (!attributes[attrName].originalName)
853
- // attributes[attrName].originalName = attrName
854
-
855
- // if (!attributes[attrName].nameKeys)
856
- // attributes[attrName].nameKeys = {[key]: value}
857
-
858
- // newName = attributes[attrName].originalName
859
- // for (let nameKey of Object.keys(attributes[attrName].nameKeys)) {
860
- // let r
861
- // if (attrName === nameKey)
862
- // r = nameKey
863
- // else
864
- // r = attributes[attrName].nameKeys[nameKey]
865
-
866
- // const nameKeyRegex = new RegExp(r, "g");
867
- // newName = newName.replace(nameKeyRegex, value);
868
- // }
869
-
870
- // attributes[attrName].nameKeys[key] = value;
871
-
872
- // element.removeAttribute(attrName);
873
- // setAttr = true;
874
-
875
- // }
876
- if (setAttr) element.setAttribute(newName, newValue);
877
- }
878
- let html = element.innerHTML;
879
- if (
880
- html.indexOf(currentValue) !== -1 ||
881
- (element.htmlKeys && element.htmlKeys[key])
882
- ) {
883
- if (!element.originalHtml) element.originalHtml = html;
884
- if (!element.htmlKeys) element.htmlKeys = { [key]: value };
885
- else element.htmlKeys[key] = value;
886
-
887
- let newHTML = element.originalHtml;
888
- if (newHTML) {
889
- for (let elementKey of Object.keys(element.htmlKeys)) {
890
- const htmlKeyRegex = new RegExp(elementKey, "g");
891
- newHTML = newHTML.replace(htmlKeyRegex, value);
892
- }
893
-
894
- element.innerHTML = newHTML;
895
- }
896
- }
897
- }
898
-
899
- function evaluateCondition(condition, values) {
900
- let orConditions = condition.split(/\s*\|\|\s*/);
901
-
902
- if (orConditions.length > 1) {
903
- return orConditions.some((cond) => checkCondition(cond, values));
904
- } else {
905
- let andConditions = condition.split(/\s*&&\s*/);
906
- return andConditions.every((cond) => checkCondition(cond, values));
907
- }
908
- }
909
-
910
- function checkCondition(condition, value) {
911
- let isNegated = condition.startsWith("!");
912
- if (isNegated) {
913
- condition = condition.substring(1);
914
- }
915
- const operatorMatch = condition.match(/(<=|>=|<|>)(.+)/);
916
- if (operatorMatch) condition = operatorMatch[2].trim();
917
-
918
- let parse = true;
919
- if (condition === "true") {
920
- return !!value[0];
921
- } else if (condition === "false") {
922
- return !value[0];
923
- }
924
- // else if (condition === "[]" && typeof value[0] === "string") {
925
- // parse = false;
926
- // }
927
-
928
- // TODO: why parse updated conditin to boolean false
929
- // if (parse && condition !== "false") condition = parseCondition(condition);
930
-
931
- if (
932
- typeof condition === "number" ||
933
- (typeof condition === "string" && /\d/.test(condition))
934
- ) {
935
- condition = parseNumberCondition(condition);
936
- } else if (typeof value[0] === "object" && typeof condition === "string") {
937
- condition = parseJsonCondition(condition);
938
- }
939
-
940
- let result;
941
-
942
- if (condition === "$template") {
943
- result = /{{\s*([\w\W]+)\s*}}/g.test(value[0]);
944
- } else if (Array.isArray(value) && !(typeof condition === "object")) {
945
- if (operatorMatch) {
946
- condition = parseFloat(condition);
947
- result = value.some((v) => {
948
- v = parseFloat(v);
949
- switch (operatorMatch[1]) {
950
- case "<":
951
- return v < condition;
952
- case ">":
953
- return v > condition;
954
- case "<=":
955
- return v <= condition;
956
- case ">=":
957
- return v >= condition;
958
- default:
959
- return false;
960
- }
961
- });
962
- } else result = value.includes(condition);
963
- } else if (Array.isArray(value)) {
964
- // TODO: handle comparing array to array, vs querying the array items for a match
965
- result = queryData(value, condition);
966
- } else if (
967
- typeof value === "object" &&
968
- value !== null &&
969
- typeof parsedCond === "object"
970
- ) {
971
- result = queryData(value, condition);
972
- } else {
973
- result = value === condition;
974
- }
975
-
976
- result = isNegated ? !result : result;
977
- return result;
978
- }
979
-
980
- function parseJsonCondition(condition) {
981
- try {
982
- // Attempt to parse the condition as JSON
983
- let parsedJson = JSON.parse(condition);
984
- return parsedJson;
985
- } catch (e) {
986
- return condition;
987
- }
988
- }
989
- function parseNumberCondition(condition) {
990
- if (typeof condition === "string") {
991
- const num = parseFloat(condition);
992
- return !isNaN(num) ? num : condition;
993
- }
994
- return condition;
995
- }
996
-
997
- // Helper function for delay using Promises
998
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
999
-
1000
- /**
1001
- * Waits for a specific element property to meet a readiness condition,
1002
- * based on retry attributes defined on the element.
1003
- * Incorporates optional exponential backoff for retry delays.
1004
- * Returns a Promise that resolves to true if the condition is met within
1005
- * the allowed attempts, and false otherwise.
1006
- *
1007
- * @param {HTMLElement} element The element with the onload-retry-* attributes.
1008
- * @returns {Promise<boolean>} A Promise resolving to true if ready, false if timed out.
1009
- */
1010
- async function checkElementReadinessWithBackoff(element) {
1011
- const propertyName = element.getAttribute("onload-retry-property");
1012
-
1013
- if (!propertyName) {
1014
- return true; // No retry needed
1015
- }
1016
-
1017
- // --- Retry Parameters ---
1018
- const maxAttempts =
1019
- parseInt(element.getAttribute("onload-retry-attempts") || "3", 10) || 3;
1020
- // Initial delay (used for the first wait and as the base for backoff)
1021
- const initialDelayMs =
1022
- parseInt(element.getAttribute("onload-retry-delay") || "100", 10) ||
1023
- 100;
1024
- // Exponential backoff factor (e.g., 2 for doubling). <= 1 means fixed delay.
1025
- // Default to 1 (no backoff) if missing or invalid.
1026
- const backoffFactor =
1027
- parseFloat(
1028
- element.getAttribute("onload-retry-backoff-factor") || "1"
1029
- ) || 1;
1030
- // Optional maximum delay cap. Use Infinity if missing, invalid, or <= 0.
1031
- const maxDelayMs = parseInt(
1032
- element.getAttribute("onload-retry-max-delay") || "0",
1033
- 10
1034
- );
1035
- const effectiveMaxDelay =
1036
- maxDelayMs && maxDelayMs > 0 ? maxDelayMs : Infinity;
1037
-
1038
- console.log(
1039
- `Checking readiness for property "${propertyName}" on element ${
1040
- element.id || ""
1041
- }. Max attempts: ${maxAttempts}, Initial Delay: ${initialDelayMs}ms, Backoff Factor: ${backoffFactor}, Max Delay: ${
1042
- effectiveMaxDelay === Infinity ? "None" : effectiveMaxDelay + "ms"
1043
- }.`
1044
- );
1045
-
1046
- // Initialize the delay to be used for the *first* wait.
1047
- let currentDelay = initialDelayMs;
1048
-
1049
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1050
- console.log(
1051
- `Attempt ${attempt}/${maxAttempts}: Checking "${propertyName}"...`
1052
- );
1053
- try {
1054
- const propertyValue = element[propertyName];
1055
- // Example check: Adjust condition as needed
1056
- if (propertyValue !== undefined && propertyValue > 0) {
1057
- console.log(
1058
- `Property "${propertyName}" ready on attempt ${attempt}. Value: ${propertyValue}`
1059
- );
1060
- return true; // Condition met
1061
- }
1062
- } catch (error) {
1063
- console.error(
1064
- `Error accessing property "${propertyName}" on attempt ${attempt}:`,
1065
- error
1066
- );
1067
- }
1068
-
1069
- // If condition not met and more attempts remain:
1070
- if (attempt < maxAttempts) {
1071
- // Determine the actual delay for this wait, applying the cap
1072
- const delayForThisWait = Math.min(currentDelay, effectiveMaxDelay);
1073
-
1074
- console.log(
1075
- `Waiting ${Math.round(
1076
- delayForThisWait
1077
- )}ms before next attempt...`
1078
- );
1079
- await delay(delayForThisWait);
1080
-
1081
- // Calculate the delay for the *next* potential wait, only if backoff is active
1082
- if (backoffFactor > 1) {
1083
- currentDelay = currentDelay * backoffFactor;
1084
- // Note: We apply the cap just before the actual delay,
1085
- // but the base 'currentDelay' keeps growing exponentially.
1086
- // Alternatively, you could cap 'currentDelay' itself here:
1087
- // currentDelay = Math.min(currentDelay * backoffFactor, effectiveMaxDelay);
1088
- // Let's stick to capping just before delay() for now.
1089
- }
1090
- // If backoffFactor <= 1, currentDelay remains initialDelayMs (implicitly handled by initialization)
1091
- }
1092
- }
1093
-
1094
- // If loop finishes without condition being met
1095
- console.warn(
1096
- `Readiness check failed for property "${propertyName}" on element ${
1097
- element.id || ""
1098
- } after ${maxAttempts} attempts.`
1099
- );
1100
- return false; // Timed out
1101
- }
1102
-
1103
- // --- Example Usage (calling code doesn't change) ---
1104
-
1105
- async function handleElementProcessingWithBackoff(element) {
1106
- // Call the function that now supports backoff
1107
- const isReady = await checkElementReadinessWithBackoff(element);
1108
-
1109
- if (isReady) {
1110
- console.log(
1111
- `Element ${element.id || ""} is ready. Proceeding with actions.`
1112
- );
1113
- // --- Execute subsequent actions ---
1114
- if (element.hasAttribute("onload-attribute")) {
1115
- /* ... */
1116
- }
1117
- if (element.hasAttribute("onload-query")) {
1118
- /* ... */
1119
- }
1120
- } else {
1121
- console.warn(
1122
- `Skipping actions for element ${
1123
- element.id || ""
1124
- } because readiness check failed.`
1125
- );
1126
- }
1127
- }
1128
-
1129
- // --- Example Elements ---
1130
-
1131
- // With Backoff (Delays: 100, 200, 400, 800 - capped)
1132
- // <span id="elem1" onload-retry-property="scrollWidth"
1133
- // onload-retry-attempts="5" onload-retry-delay="100"
1134
- // onload-retry-backoff-factor="2" onload-retry-max-delay="500">
1135
- // Content 1
1136
- // </span>
1137
- // Waits would be: 100ms, 200ms, 400ms, 500ms (capped)
1138
-
1139
- // No Backoff (factor missing/invalid/<=1)
1140
- // <span id="elem2" onload-retry-property="clientHeight"
1141
- // onload-retry-attempts="4" onload-retry-delay="300">
1142
- // Content 2
1143
- // </span>
1144
- // Waits would be: 300ms, 300ms, 300ms
1145
-
1146
- // const element1 = document.getElementById('elem1');
1147
- // if (element1) handleElementProcessingWithBackoff(element1);
1148
- // const element2 = document.getElementById('elem2');
1149
- // if (element2) handleElementProcessingWithBackoff(element2);
1150
-
1151
695
  CoCreateEvents.init();
1152
696
 
1153
- export default CoCreateEvents;
697
+ export default CoCreateEvents;