@cocreate/events 1.29.1 → 1.30.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +1 -1
  3. package/src/index.js +311 -371
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.30.0](https://github.com/CoCreate-app/CoCreate-events/compare/v1.29.1...v1.30.0) (2026-02-15)
2
+
3
+
4
+ ### Features
5
+
6
+ * streamline event initialization and update handling with dash notation ([aaf434a](https://github.com/CoCreate-app/CoCreate-events/commit/aaf434a56eab97dc9581833ba5e277946e2a79c7))
7
+
1
8
  ## [1.29.1](https://github.com/CoCreate-app/CoCreate-events/compare/v1.29.0...v1.29.1) (2026-02-04)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/events",
3
- "version": "1.29.1",
3
+ "version": "1.30.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
@@ -20,63 +20,76 @@
20
20
  // you must obtain a commercial license from CoCreate LLC.
21
21
  // For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
22
22
 
23
- import { queryElements, queryData } from "@cocreate/utils";
23
+ import { queryElements } from "@cocreate/utils";
24
24
  import action from "@cocreate/actions";
25
25
  import observer from "@cocreate/observer";
26
26
  import "@cocreate/element-prototype";
27
+ import { getAttribute } from "@cocreate/element-prototype/src/getAttribute";
27
28
 
28
29
  const CoCreateEvents = {
29
- elements2: new Map(),
30
+ elements: new Map(),
31
+
30
32
  init: function (prefix, events) {
31
33
  if (prefix && events) {
32
34
  this.initPrefix(prefix, events);
33
35
  } 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"]);
36
+ const defaults = {
37
+ "toggle": ["click"],
38
+ "click": ["click"],
39
+ "hover": ["mouseover", "mouseout"],
40
+ "mouseover": ["mouseover"],
41
+ "mouseout": ["mouseout"],
42
+ "input": ["input"],
43
+ "change": ["change"],
44
+ "selected": ["click"],
45
+ "onload": ["onload"],
46
+ "observe": ["observer"],
47
+ "intersection": ["intersection"],
48
+ "resize": ["onload", "resize"],
49
+ "localstorage": ["onload"],
50
+ "focus": ["focus"],
51
+ "blur": ["blur"]
52
+ };
53
+
54
+ for (const [p, e] of Object.entries(defaults)) {
55
+ this.initPrefix(p, e);
56
+ }
49
57
  }
50
58
 
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;
59
+ // Initialize from existing elements with [event-name]
60
+ const customEventEls = document.querySelectorAll("[event-name]");
61
+ const processedNames = new Set();
62
+
63
+ for (const customEventEl of customEventEls) {
64
+ let names = customEventEl.getAttribute("event-name");
65
+ if (!names) continue;
66
+
67
+ names.split(",").forEach(name => {
68
+ name = name.trim();
69
+ if (name && !processedNames.has(name)) {
70
+ processedNames.add(name);
60
71
  this.initPrefix(name);
61
72
  }
62
- }
73
+ });
63
74
  }
64
75
 
65
- const self = this;
76
+ // Observe for new elements with [event-name]
66
77
  observer.init({
67
78
  name: "CoCreateEventName",
68
79
  types: ["addedNodes"],
69
80
  selector: `[event-name]`,
70
- callback: function (mutation) {
71
- let name = mutation.target.getAttribute("event-name");
72
- self.initPrefix(name);
81
+ callback: (mutation) => {
82
+ const names = mutation.target.getAttribute("event-name");
83
+ if (names) {
84
+ names.split(",").forEach(name => {
85
+ this.initPrefix(name.trim());
86
+ });
87
+ }
73
88
  }
74
89
  });
75
90
  },
76
91
 
77
92
  initPrefix: function (prefix, events) {
78
- const self = this;
79
-
80
93
  action.init({
81
94
  name: prefix,
82
95
  endEvent: `${prefix}End`,
@@ -90,267 +103,244 @@ const CoCreateEvents = {
90
103
  );
91
104
  }
92
105
  });
93
-
94
- // We look for the prefix as an attribute, or common options
95
- let selector = `[${prefix}], [${prefix}\\.group], [${prefix}\\.query]`;
106
+
107
+ // Updated selector to use dash for options: prefix-group, prefix-query
108
+ const selector = `[${prefix}], [${prefix}-group], [${prefix}-query]`;
96
109
 
97
110
  observer.init({
98
111
  name: "CoCreateEventAttributes",
99
112
  types: ["attributes", "addedNodes"],
100
- // removed attributeFilter to allow discovery of dynamic attributes (prefix-src, prefix.option, etc.)
101
113
  selector,
102
- callback: function (mutation) {
103
- // Filter relevant mutations here since attributeFilter is gone
114
+ callback: (mutation) => {
104
115
  if (mutation.type === 'attributes') {
105
116
  if (mutation.attributeName.startsWith(prefix)) {
106
- self.initElements([mutation.target], prefix, events);
117
+ this.initElements([mutation.target], prefix, events);
107
118
  }
108
119
  } else {
109
- self.initElements([mutation.target], prefix, events);
120
+ this.initElements([mutation.target], prefix, events);
110
121
  }
111
122
  }
112
123
  });
113
124
 
114
- let elements = document.querySelectorAll(selector);
125
+ const elements = document.querySelectorAll(selector);
115
126
  this.initElements(elements, prefix, events);
116
127
  },
117
128
 
118
- elements: new Map(),
119
129
  initElements: function (elements, prefix, events = []) {
120
- const self = this;
121
130
  for (const el of elements) {
122
131
  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 };
132
+ let elData = this.elements.get(el);
133
+
134
+ if (!elData) {
135
+ elData = { [prefix]: { events } };
136
+ this.elements.set(el, elData);
137
+ } else if (!elData[prefix]) {
138
+ elData[prefix] = { events };
129
139
  } else {
130
140
  isEventable = false;
131
141
  }
132
142
 
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();
143
+ // Check for custom events defined on the element using dash
144
+ let customEventsAttr = el.getAttribute(`${prefix}-events`);
145
+ if (customEventsAttr) {
146
+ // Remove listeners for previously assumed events before overriding
147
+ events.forEach(event => el.removeEventListener(event, eventFunction));
138
148
 
139
- for (let i = 0; i < events.length; i++)
140
- el.removeEventListener(events[i], eventFunction);
141
-
142
- events = customEvents;
143
- prefixes[prefix].events = events;
149
+ events = customEventsAttr.split(",").map(e => e.trim());
150
+ elData[prefix].events = events;
144
151
  isEventable = true;
145
152
  }
146
153
 
147
154
  if (!events || !isEventable) continue;
148
155
 
156
+ // --- Handle Special Events ---
157
+
149
158
  if (events.includes("onload")) {
150
159
  this.__updateElements(el, prefix);
151
160
  }
152
161
 
153
162
  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
- }
163
+ this._initObserver(el, prefix);
195
164
  }
196
165
 
197
166
  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);
167
+ this._initResizeObserver(el, prefix);
206
168
  }
207
169
 
208
170
  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`);
226
- const once = onceAttr !== null && onceAttr !== "false";
227
-
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
-
248
- if (once) {
249
- intersectionObserver.unobserve(entry.target);
250
- }
251
- }
252
- });
253
- },
254
- { root, rootMargin, threshold: validThreshold }
255
- );
256
-
257
- intersectionObserver.observe(el);
171
+ this._initIntersectionObserver(el, prefix);
258
172
  }
259
173
 
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
- ) {
174
+ // --- Handle Standard Events ---
175
+
176
+ for (const eventName of events) {
177
+ if (["onload", "observer", "intersection", "resize"].includes(eventName)) {
267
178
  continue;
268
179
  }
269
180
 
270
- el.removeEventListener(events[i], eventFunction);
181
+ // Remove existing listener to prevent duplicates
182
+ el.removeEventListener(eventName, eventFunction);
271
183
 
272
184
  const options = {};
273
- // Updated to use dot notation for event options
185
+ // Updated to use dash for options
274
186
  const attributes = ["bubbles", "cancelable", "composed"];
275
187
 
276
- for (let j = 0; j < attributes.length; j++) {
277
- const attrValue = el.getAttribute(
278
- `${events[i]}.${attributes[j]}`
279
- );
188
+ for (const attr of attributes) {
189
+ const attrValue = el.getAttribute(`${eventName}-${attr}`);
280
190
  if (attrValue !== null) {
281
- options[attributes[j]] = attrValue !== "false";
191
+ options[attr] = attrValue !== "false";
282
192
  }
283
193
  }
284
194
 
285
- el.addEventListener(events[i], eventFunction, options);
195
+ el.addEventListener(eventName, eventFunction, options);
286
196
  }
197
+ }
287
198
 
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
- }
199
+ const self = this;
200
+ function eventFunction(event) {
201
+ const target = event.currentTarget;
202
+ if (!target) return;
203
+
204
+ const elData = self.elements.get(target);
205
+ if (!elData || !elData[prefix]) return;
318
206
 
319
- self.__updateElements(
320
- target,
321
- prefix,
322
- prefixes[prefix].events
323
- );
207
+ const prefixData = elData[prefix];
208
+
209
+ // Debounce/Prevent duplicate mouse events if needed
210
+ if (prefixData.prev === event.type && ["mouseover", "mouseout"].includes(event.type)) {
211
+ return;
212
+ }
213
+ prefixData.prev = event.type;
214
+
215
+ // Check for 'actions' blocking
216
+ const actionsAttr = target.getAttribute("actions") || "";
217
+ if (actionsAttr.includes(prefix)) return;
218
+
219
+ // Handle lifecycle controls (using dash)
220
+ const lifecycleAttributes = [
221
+ { attr: "prevent-default", method: "preventDefault" },
222
+ { attr: "stop-propagation", method: "stopPropagation" },
223
+ { attr: "stop-immediate-propagation", method: "stopImmediatePropagation" }
224
+ ];
225
+
226
+ for (const { attr, method } of lifecycleAttributes) {
227
+ const attrValue = target.getAttribute(`${prefix}-${attr}`);
228
+ if (attrValue !== null && attrValue !== "false") {
229
+ event[method]();
324
230
  }
325
231
  }
232
+
233
+ self.__updateElements(target, prefix, prefixData.events);
234
+ }
235
+ },
236
+
237
+ _initObserver: function (el, prefix) {
238
+ // Updated to dash: prefix-selector, prefix-query, prefix-attribute-filter, prefix-types
239
+ let selector = el.getAttribute(`${prefix}-selector`) || el.getAttribute(`${prefix}-query`) || "";
240
+ let attributeFilter = (el.getAttribute(`${prefix}-attribute-filter`) || "")
241
+ .split(/\s*,\s*/)
242
+ .filter(item => item);
243
+
244
+ let types = (el.getAttribute(`${prefix}-types`) || "")
245
+ .split(/\s*,\s*/)
246
+ .filter(item => item);
247
+
248
+ const config = {
249
+ types: types,
250
+ callback: () => {
251
+ this.__updateElements(el, prefix);
252
+ }
253
+ };
254
+
255
+ if (selector) {
256
+ if (!types.length && !attributeFilter.length) {
257
+ config.types.push("addedNodes");
258
+ }
259
+ config.selector = selector;
260
+ }
261
+
262
+ if (attributeFilter.length) {
263
+ config.types.push("attributes");
264
+ config.attributeFilter = attributeFilter;
326
265
  }
266
+
267
+ if (config.types.length) {
268
+ observer.init(config);
269
+ }
270
+ },
271
+
272
+ _initResizeObserver: function (el, prefix) {
273
+ const resizeObserver = new ResizeObserver((entries) => {
274
+ const lastEntry = entries[entries.length - 1];
275
+ const { width } = lastEntry.contentRect;
276
+ // Keeps dash style for this status attribute
277
+ el.setAttribute(`${prefix}-if-value`, width + "px");
278
+ this.__updateElements(el, prefix);
279
+ });
280
+ resizeObserver.observe(el);
281
+ },
282
+
283
+ _initIntersectionObserver: function (el, prefix) {
284
+ // Updated to dash
285
+ const rootSelector = el.getAttribute(`${prefix}-root`);
286
+ const rootMargin = el.getAttribute(`${prefix}-root-margin`) || "0px";
287
+ const thresholdRaw = el.getAttribute(`${prefix}-threshold`);
288
+ const threshold = thresholdRaw ? JSON.parse(thresholdRaw) : [0];
289
+ const trigger = el.getAttribute(`${prefix}-trigger`) || "both";
290
+
291
+ const root = rootSelector ? document.querySelector(rootSelector) : null;
292
+ const validThreshold = Array.isArray(threshold)
293
+ ? threshold.filter(v => v >= 0 && v <= 1)
294
+ : [0];
295
+
296
+ const once = el.getAttribute(`${prefix}-once`);
297
+ const isOnce = once !== null && once !== "false";
298
+
299
+ const intersectionObserver = new IntersectionObserver(
300
+ (entries) => {
301
+ entries.forEach((entry) => {
302
+ const isVisible = entry.isIntersecting;
303
+
304
+ if (
305
+ (trigger === "visible" && isVisible) ||
306
+ (trigger === "not-visible" && !isVisible) ||
307
+ trigger === "both"
308
+ ) {
309
+ el.setAttribute(`${prefix}-is-visible`, isVisible ? "true" : "false");
310
+ this.__updateElements(el, prefix);
311
+
312
+ if (isOnce) {
313
+ intersectionObserver.unobserve(entry.target);
314
+ }
315
+ }
316
+ });
317
+ },
318
+ { root, rootMargin, threshold: validThreshold }
319
+ );
320
+
321
+ intersectionObserver.observe(el);
327
322
  },
328
323
 
329
324
  __updateElements: async function (rootElement, prefix, events, params) {
330
325
  if (!rootElement.isConnected) return;
331
326
 
332
- // 1. Determine targets based on prefix.group option
327
+ // 1. Determine targets based on prefix-group
333
328
  let sourceElements = [rootElement];
334
- let sourceGroup = rootElement.getAttribute(`${prefix}.group`);
329
+ const sourceGroup = rootElement.getAttribute(`${prefix}-group`);
330
+
335
331
  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
- // });
332
+ sourceElements = document.querySelectorAll(`[${prefix}-group="${sourceGroup}"]`);
343
333
  }
344
334
 
345
335
  // 2. Iterate through sourceElements
346
- for (let sourceElement of sourceElements) {
336
+ for (const sourceElement of sourceElements) {
347
337
 
348
- // Handle Global Options for this sourceElement processing
338
+ // --- Global Options (Dash) ---
349
339
 
350
340
  // onEvent
351
- let onEvent = sourceElement.getAttribute(`${prefix}.on`);
341
+ const onEvent = sourceElement.getAttribute(`${prefix}-on`);
352
342
  if (onEvent) {
353
- await new Promise((resolve, reject) => {
343
+ await new Promise((resolve) => {
354
344
  const handleEvent = () => {
355
345
  document.removeEventListener(onEvent, handleEvent);
356
346
  resolve();
@@ -360,125 +350,104 @@ const CoCreateEvents = {
360
350
  }
361
351
 
362
352
  // setInterval
363
- let interval = sourceElement.getAttribute(`${prefix}.setInterval`);
364
- if (interval !== null) {
365
- let intervalMs = parseInt(interval, 10) || 0;
353
+ const intervalAttr = sourceElement.getAttribute(`${prefix}-set-interval`);
354
+ if (intervalAttr !== null) {
355
+ const intervalMs = parseInt(intervalAttr, 10) || 0;
356
+
366
357
  if (!sourceElement.eventsInterval) {
367
358
  sourceElement.eventsInterval = {};
368
359
  }
369
- let intervalEntry = sourceElement.eventsInterval[prefix];
360
+
361
+ const currentInterval = sourceElement.eventsInterval[prefix];
362
+
370
363
  if (intervalMs > 0) {
371
- if (!intervalEntry || intervalEntry.delay !== intervalMs) {
372
- if (intervalEntry) {
373
- clearInterval(intervalEntry.id);
374
- }
364
+ // Start or update interval
365
+ if (!currentInterval || currentInterval.delay !== intervalMs) {
366
+ if (currentInterval) clearInterval(currentInterval.id);
367
+
375
368
  sourceElement.eventsInterval[prefix] = {
376
369
  delay: intervalMs,
377
370
  id: setInterval(() => {
378
371
  if (!sourceElement.isConnected) {
379
- const entry =
380
- sourceElement.eventsInterval &&
381
- sourceElement.eventsInterval[prefix];
372
+ const entry = sourceElement.eventsInterval?.[prefix];
382
373
  if (entry) {
383
374
  clearInterval(entry.id);
384
375
  delete sourceElement.eventsInterval[prefix];
385
376
  }
386
377
  return;
387
378
  }
388
- // Recursive call for interval
389
- this.__updateElements(
390
- rootElement,
391
- prefix,
392
- events,
393
- params
394
- );
379
+ // Recursive call
380
+ this.__updateElements(rootElement, prefix, events, params);
395
381
  }, intervalMs)
396
382
  };
397
383
  }
398
- } else if (intervalEntry) {
399
- clearInterval(intervalEntry.id);
384
+ } else if (currentInterval) {
385
+ // Clear interval if set to 0
386
+ clearInterval(currentInterval.id);
400
387
  delete sourceElement.eventsInterval[prefix];
401
388
  }
402
389
  }
403
390
 
404
391
  // setTimeout
405
- let delayTime = sourceElement.getAttribute(`${prefix}.setTimeout`);
406
- if (delayTime) {
407
- delayTime = parseInt(delayTime, 10) || 0;
392
+ const delayAttr = sourceElement.getAttribute(`${prefix}-set-timeout`);
393
+ if (delayAttr) {
394
+ const delayTime = parseInt(delayAttr, 10) || 0;
408
395
  if (delayTime > 0) {
409
- await new Promise((resolve) => setTimeout(resolve, delayTime));
396
+ await new Promise(resolve => setTimeout(resolve, delayTime));
410
397
  }
411
398
  }
412
399
 
413
400
  if (!sourceElement.isConnected) return;
414
401
 
415
402
  // once
416
- let once = sourceElement.getAttribute(`${prefix}.once`);
417
- if (once || once === "") {
403
+ const onceAttr = sourceElement.getAttribute(`${prefix}-once`);
404
+ if (onceAttr !== null) {
418
405
  if (!sourceElement.eventsOnce) {
419
406
  sourceElement.eventsOnce = [prefix];
420
407
  } else if (sourceElement.eventsOnce.includes(prefix)) {
421
- continue;
408
+ continue; // Skip if already ran
422
409
  } else {
423
410
  sourceElement.eventsOnce.push(prefix);
424
411
  }
425
412
  }
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
413
+
414
+ // --- Resolve Targets ---
415
+ const querySelector = sourceElement.getAttribute(`${prefix}-query`);
432
416
  let targetElements = [sourceElement];
433
-
434
- // If query exists or we have params, resolve targets
417
+
435
418
  if (querySelector || params) {
436
419
  targetElements = queryElements({
437
420
  element: sourceElement,
438
421
  prefix: prefix,
439
- selector: querySelector || params
422
+ selector: querySelector || params
440
423
  });
441
424
  }
442
425
 
443
- // --- 6. Execute Action on Targets ---
444
- for (let targetElement of targetElements) {
426
+ // --- Execute Action on Targets ---
427
+ for (const targetElement of targetElements) {
445
428
  await this.setValue(
446
- rootElement,
447
- sourceElement,
429
+ rootElement,
430
+ sourceElement,
448
431
  targetElement,
449
432
  prefix,
450
433
  events
451
434
  );
452
435
  }
453
-
454
436
  }
455
437
 
456
438
  rootElement.dispatchEvent(
457
- new CustomEvent(`${prefix}End`, {
458
- detail: {}
459
- })
439
+ new CustomEvent(`${prefix}End`, { detail: {} })
460
440
  );
461
441
  },
462
442
 
463
- setValue: async function (
464
- rootElement,
465
- sourceElement,
466
- targetElement,
467
- prefix,
468
- events
469
- ) {
443
+ setValue: async function (rootElement, sourceElement, targetElement, prefix, events) {
444
+ // Handle Mouseout toggle logic
470
445
  if (events && events.includes("mouseout")) {
471
446
  if (targetElement.matches(":hover")) {
472
447
  targetElement.addEventListener(
473
448
  "mouseout",
474
449
  () => {
475
- this.setValue(
476
- rootElement,
477
- sourceElement,
478
- targetElement,
479
- prefix,
480
- events
481
- );
450
+ this.setValue(rootElement, sourceElement, targetElement, prefix, events);
482
451
  },
483
452
  { once: true }
484
453
  );
@@ -486,53 +455,29 @@ const CoCreateEvents = {
486
455
  }
487
456
  }
488
457
 
489
- let deactivate = false;
490
- if (rootElement !== sourceElement) {
491
- deactivate = true;
492
- }
458
+ const deactivate = (rootElement !== sourceElement);
493
459
 
494
- // 3. Iterate Attributes of the Source Element (el) in order
495
- for (let attribute of sourceElement.attributes) {
460
+ // Iterate Attributes
461
+ for (const attribute of sourceElement.attributes) {
496
462
  const name = attribute.name;
497
463
 
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
- }
464
+ // Optimization: Skip if shorter than prefix + dot + min char
465
+ if (name.length <= prefix.length + 1) continue;
466
+
467
+ // STRICT RULE: Only process attributes with DOT separator.
468
+ // All options use Dash, so they will be ignored here.
469
+ if (!name.startsWith(prefix) || name[prefix.length] !== '.') continue;
525
470
 
526
- // Parse Target Name and Type
527
- let targetName = name.substring(prefix.length + 1);
528
- let separator = name[prefix.length]; // '.' or '-'
471
+ // Extract target name (e.g., 'value', 'style', 'src', 'data-something')
472
+ const targetName = name.substring(prefix.length + 1);
529
473
 
530
- let value = attribute.value;
531
- if (!value && sourceElement && sourceElement.getValue) {
474
+ // Get Value
475
+ let value = sourceElement.getAttribute(name);
476
+ if (!value && sourceElement.getValue) {
532
477
  value = await sourceElement.getValue();
533
478
  }
534
479
 
535
- // Parse comma-separated values to support rotation
480
+ // Parse CSV for rotation
536
481
  let values = [];
537
482
  if (typeof value === 'string') {
538
483
  values = value.split(',').map(v => v.trim());
@@ -541,36 +486,40 @@ const CoCreateEvents = {
541
486
  } else {
542
487
  values = [value];
543
488
  }
544
-
489
+
545
490
  let oldValue = "";
546
491
  let newValue = "";
547
492
 
548
493
  switch (targetName) {
549
494
  case 'style':
550
495
  if (targetElement.getAttribute("style")) {
551
- let styles = targetElement.getAttribute("style").split(";").map(x => x.trim().replace(/\s/g, ""));
496
+ // Normalize style string for comparison
497
+ const styles = targetElement.getAttribute("style")
498
+ .split(";")
499
+ .map(x => x.trim().replace(/\s/g, ""));
500
+
552
501
  oldValue = values.find(x => styles.includes(x.replace(/\s/g, ""))) || "";
553
502
  }
554
503
  newValue = this.__getNextValue(values, oldValue, deactivate);
555
504
 
556
505
  if (oldValue) {
557
- let [prop, val] = oldValue.split(":");
506
+ const [prop] = oldValue.split(":");
558
507
  if (prop) targetElement.style[prop.trim()] = "";
559
508
  }
560
-
509
+
561
510
  if (newValue) {
562
- let styles = newValue.split(";");
563
- for(let s of styles) {
564
- let [prop, val] = s.split(":");
511
+ const styles = newValue.split(";");
512
+ for (const s of styles) {
513
+ const [prop, val] = s.split(":");
565
514
  if (prop && val) targetElement.style[prop.trim()] = val.trim();
566
515
  }
567
516
  }
568
517
  break;
569
518
 
570
519
  case 'class':
571
- let currentClasses = Array.from(targetElement.classList);
520
+ const currentClasses = Array.from(targetElement.classList);
572
521
  oldValue = values.find(val => {
573
- let requiredClasses = val.split(" ");
522
+ const requiredClasses = val.split(" ");
574
523
  return requiredClasses.every(c => currentClasses.includes(c));
575
524
  }) || "";
576
525
 
@@ -578,7 +527,7 @@ const CoCreateEvents = {
578
527
 
579
528
  if (oldValue) {
580
529
  oldValue.split(" ").forEach(c => targetElement.classList.remove(c));
581
- if (values.length === 1) return;
530
+ if (values.length === 1 && !newValue) return;
582
531
  }
583
532
 
584
533
  if (newValue) {
@@ -586,39 +535,31 @@ const CoCreateEvents = {
586
535
  }
587
536
  break;
588
537
 
589
- // UPDATED: Added lowercase cases for HTML compatibility
590
538
  case 'insertadjacenthtml':
591
539
  case 'insertadjacenttext':
592
- case 'insertAdjacentHTML':
540
+ case 'insertAdjacentHTML':
593
541
  case 'insertAdjacentText':
594
- // 1. Get Position from Attribute Value (No rotation, strict)
595
- let position = attribute.value;
596
- if (!position) return;
542
+ const position = sourceElement.getAttribute(name);;
543
+ if (!position) continue;
597
544
 
598
- // 2. Get Content from Source Element
599
545
  let content = "";
600
- if (sourceElement && sourceElement.getValue) {
546
+ if (sourceElement.getValue) {
601
547
  content = await sourceElement.getValue();
602
548
  }
603
549
 
604
- // 3. Execute Insertion
605
550
  // Check for CoCreate Rich Text integration
606
- let domTextEditor;
607
- if (targetElement.parentElement) {
608
- domTextEditor = targetElement.parentElement.closest("[contenteditable]");
609
- }
551
+ let domTextEditor = targetElement.parentElement?.closest("[contenteditable]");
610
552
 
611
553
  if (domTextEditor && typeof CoCreate !== 'undefined' && CoCreate.text) {
612
554
  CoCreate.text.insertAdjacentElement({
613
555
  domTextEditor,
614
- position: position,
556
+ position,
615
557
  target: targetElement,
616
558
  elementValue: content
617
559
  });
618
560
  } else {
619
- // Standard JS Execution
620
- // Check lowercase match since switch case might have matched that
621
- if (targetName === 'insertAdjacentHTML' || targetName === 'insertadjacenthtml') {
561
+ const isHtml = targetName.toLowerCase().includes('html');
562
+ if (isHtml) {
622
563
  targetElement.insertAdjacentHTML(position, content);
623
564
  } else {
624
565
  targetElement.insertAdjacentText(position, content);
@@ -630,11 +571,12 @@ const CoCreateEvents = {
630
571
  if (targetElement.getValue) {
631
572
  oldValue = await targetElement.getValue();
632
573
  } else {
633
- oldValue = targetElement.value;
574
+ oldValue = targetElement.value || "";
634
575
  }
576
+
635
577
  if (!values.includes(oldValue)) oldValue = "";
636
578
  newValue = this.__getNextValue(values, oldValue, deactivate);
637
-
579
+
638
580
  if (targetElement.setValue) {
639
581
  targetElement.setValue(newValue);
640
582
  } else {
@@ -643,33 +585,32 @@ const CoCreateEvents = {
643
585
  break;
644
586
 
645
587
  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
- }
588
+ // Generic handling: Try Property first, then Attribute
589
+
590
+ // 1. Try Property
591
+ if (targetElement[targetName] !== undefined) {
592
+ oldValue = targetElement[targetName];
593
+ if (!values.includes(oldValue) && typeof oldValue !== 'function') oldValue = "";
594
+
595
+ newValue = this.__getNextValue(values, oldValue, deactivate);
596
+
597
+ if (typeof targetElement[targetName] === 'function') {
598
+ targetElement[targetName](newValue);
599
+ } else {
600
+ targetElement[targetName] = newValue;
659
601
  }
660
- } else {
661
- // Dash Notation: Attribute
662
- // Fallback to attribute
602
+ }
603
+ // 2. Fallback to Attribute
604
+ else {
663
605
  oldValue = targetElement.getAttribute(targetName);
664
606
  if (!values.includes(oldValue)) oldValue = "";
607
+
665
608
  newValue = this.__getNextValue(values, oldValue, deactivate);
666
609
 
667
- // Set Attribute with Namespace check
610
+ // Handle Namespaced Attributes (e.g. svg:href) if encoded in name
668
611
  if (targetName.includes('.')) {
669
612
  const realName = targetName.replace('.', ':');
670
613
  targetElement.setAttribute(realName, newValue);
671
- } else if (targetName.includes(':')) {
672
- targetElement.setAttribute(targetName, newValue);
673
614
  } else {
674
615
  targetElement.setAttribute(targetName, newValue);
675
616
  }
@@ -677,14 +618,13 @@ const CoCreateEvents = {
677
618
  break;
678
619
  }
679
620
  }
680
-
681
621
  },
682
622
 
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) {
623
+ __getNextValue: function (values, val, deactivate) {
624
+ if (deactivate) return "";
625
+ const size = values.length;
626
+ const nn = values.indexOf(val);
627
+ if (nn === -1) {
688
628
  return values[0];
689
629
  } else {
690
630
  return values[(nn + 1) % size];