@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.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- 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
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
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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:
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
//
|
|
95
|
-
|
|
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:
|
|
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
|
-
|
|
117
|
+
this.initElements([mutation.target], prefix, events);
|
|
107
118
|
}
|
|
108
119
|
} else {
|
|
109
|
-
|
|
120
|
+
this.initElements([mutation.target], prefix, events);
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
});
|
|
113
124
|
|
|
114
|
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
181
|
+
// Remove existing listener to prevent duplicates
|
|
182
|
+
el.removeEventListener(eventName, eventFunction);
|
|
271
183
|
|
|
272
184
|
const options = {};
|
|
273
|
-
// Updated to use
|
|
185
|
+
// Updated to use dash for options
|
|
274
186
|
const attributes = ["bubbles", "cancelable", "composed"];
|
|
275
187
|
|
|
276
|
-
for (
|
|
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[
|
|
191
|
+
options[attr] = attrValue !== "false";
|
|
282
192
|
}
|
|
283
193
|
}
|
|
284
194
|
|
|
285
|
-
el.addEventListener(
|
|
195
|
+
el.addEventListener(eventName, eventFunction, options);
|
|
286
196
|
}
|
|
197
|
+
}
|
|
287
198
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
327
|
+
// 1. Determine targets based on prefix-group
|
|
333
328
|
let sourceElements = [rootElement];
|
|
334
|
-
|
|
329
|
+
const sourceGroup = rootElement.getAttribute(`${prefix}-group`);
|
|
330
|
+
|
|
335
331
|
if (sourceGroup) {
|
|
336
|
-
|
|
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 (
|
|
336
|
+
for (const sourceElement of sourceElements) {
|
|
347
337
|
|
|
348
|
-
//
|
|
338
|
+
// --- Global Options (Dash) ---
|
|
349
339
|
|
|
350
340
|
// onEvent
|
|
351
|
-
|
|
341
|
+
const onEvent = sourceElement.getAttribute(`${prefix}-on`);
|
|
352
342
|
if (onEvent) {
|
|
353
|
-
await new Promise((resolve
|
|
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
|
-
|
|
364
|
-
if (
|
|
365
|
-
|
|
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
|
-
|
|
360
|
+
|
|
361
|
+
const currentInterval = sourceElement.eventsInterval[prefix];
|
|
362
|
+
|
|
370
363
|
if (intervalMs > 0) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
|
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 (
|
|
399
|
-
|
|
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
|
-
|
|
406
|
-
if (
|
|
407
|
-
delayTime = parseInt(
|
|
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(
|
|
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
|
-
|
|
417
|
-
if (
|
|
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
|
|
428
|
-
|
|
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
|
-
// ---
|
|
444
|
-
for (
|
|
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
|
-
|
|
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
|
-
|
|
490
|
-
if (rootElement !== sourceElement) {
|
|
491
|
-
deactivate = true;
|
|
492
|
-
}
|
|
458
|
+
const deactivate = (rootElement !== sourceElement);
|
|
493
459
|
|
|
494
|
-
//
|
|
495
|
-
for (
|
|
460
|
+
// Iterate Attributes
|
|
461
|
+
for (const attribute of sourceElement.attributes) {
|
|
496
462
|
const name = attribute.name;
|
|
497
463
|
|
|
498
|
-
// Optimization: Skip
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
//
|
|
527
|
-
|
|
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
|
-
|
|
531
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
506
|
+
const [prop] = oldValue.split(":");
|
|
558
507
|
if (prop) targetElement.style[prop.trim()] = "";
|
|
559
508
|
}
|
|
560
|
-
|
|
509
|
+
|
|
561
510
|
if (newValue) {
|
|
562
|
-
|
|
563
|
-
for(
|
|
564
|
-
|
|
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
|
-
|
|
520
|
+
const currentClasses = Array.from(targetElement.classList);
|
|
572
521
|
oldValue = values.find(val => {
|
|
573
|
-
|
|
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
|
-
|
|
595
|
-
|
|
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
|
|
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
|
|
556
|
+
position,
|
|
615
557
|
target: targetElement,
|
|
616
558
|
elementValue: content
|
|
617
559
|
});
|
|
618
560
|
} else {
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
}
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
//
|
|
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,
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (nn
|
|
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];
|