@cocreate/events 1.34.0 → 2.0.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/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +317 -274
- package/CONTRIBUTING.md +6 -6
- package/README.md +485 -46
- package/demo/click.html +1 -1
- package/demo/hover.html +1 -1
- package/demo/index.html +1 -1
- package/demo/observer.html +1 -1
- package/demo/selected.html +2 -2
- package/demo/set-value.html +1 -1
- package/docs/index.html +7 -7
- package/package.json +8 -8
- package/src/index.js +355 -61
- package/test/method-invocation.test.cjs +465 -0
package/src/index.js
CHANGED
|
@@ -18,17 +18,58 @@
|
|
|
18
18
|
// Commercial Licensing Information:
|
|
19
19
|
// For commercial use of this software without the copyleft provisions of the AGPLv3,
|
|
20
20
|
// you must obtain a commercial license from CoCreate LLC.
|
|
21
|
-
// For details, visit <https://
|
|
21
|
+
// For details, visit <https://CoCreateJS.com/licenses/> or contact us at sales@CoCreateJS.com.
|
|
22
22
|
|
|
23
|
-
import { queryElements } from "@cocreate/utils";
|
|
23
|
+
import { queryElements, resolveProperty, astAsync } from "@cocreate/utils";
|
|
24
24
|
import action from "@cocreate/actions";
|
|
25
25
|
import observer from "@cocreate/observer";
|
|
26
26
|
import "@cocreate/element-prototype";
|
|
27
27
|
import { getAttribute } from "@cocreate/element-prototype/src/getAttribute.js";
|
|
28
28
|
|
|
29
|
+
function resolvePropertyPath(object, path) {
|
|
30
|
+
const segments = path.split(".");
|
|
31
|
+
let target = object;
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < segments.length; index++) {
|
|
34
|
+
if (target == null) return null;
|
|
35
|
+
const key = resolveProperty(target, segments[index]) || segments[index];
|
|
36
|
+
if (index === segments.length - 1)
|
|
37
|
+
return { target, key, value: target[key] };
|
|
38
|
+
target = target[key];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
const CoCreateEvents = {
|
|
30
45
|
elements: new Map(),
|
|
31
46
|
|
|
47
|
+
// Known targets initialize for every prefix without an additional marker.
|
|
48
|
+
// Arbitrary prefix.* properties and methods still opt in through
|
|
49
|
+
// [prefix], [prefix-group], or [prefix-query].
|
|
50
|
+
knownTargets: [
|
|
51
|
+
"class",
|
|
52
|
+
"src",
|
|
53
|
+
"href",
|
|
54
|
+
"value",
|
|
55
|
+
"hidden",
|
|
56
|
+
"disabled",
|
|
57
|
+
"checked",
|
|
58
|
+
"selected",
|
|
59
|
+
"aria-expanded",
|
|
60
|
+
"aria-hidden",
|
|
61
|
+
"aria-pressed",
|
|
62
|
+
"aria-current",
|
|
63
|
+
"textContent",
|
|
64
|
+
"innerHTML",
|
|
65
|
+
"save"
|
|
66
|
+
],
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @public
|
|
71
|
+
* @environment client
|
|
72
|
+
*/
|
|
32
73
|
init: function (prefix, events) {
|
|
33
74
|
if (prefix && events) {
|
|
34
75
|
this.initPrefix(prefix, events);
|
|
@@ -48,7 +89,8 @@ const CoCreateEvents = {
|
|
|
48
89
|
"resize": ["onload", "resize"],
|
|
49
90
|
"localstorage": ["onload"],
|
|
50
91
|
"focus": ["focus"],
|
|
51
|
-
"blur": ["blur"]
|
|
92
|
+
"blur": ["blur"],
|
|
93
|
+
"error": ["error"]
|
|
52
94
|
};
|
|
53
95
|
|
|
54
96
|
for (const [p, e] of Object.entries(defaults)) {
|
|
@@ -59,11 +101,11 @@ const CoCreateEvents = {
|
|
|
59
101
|
// Initialize from existing elements with [event-name]
|
|
60
102
|
const customEventEls = document.querySelectorAll("[event-name]");
|
|
61
103
|
const processedNames = new Set();
|
|
62
|
-
|
|
104
|
+
|
|
63
105
|
for (const customEventEl of customEventEls) {
|
|
64
106
|
let names = customEventEl.getAttribute("event-name");
|
|
65
107
|
if (!names) continue;
|
|
66
|
-
|
|
108
|
+
|
|
67
109
|
names.split(",").forEach(name => {
|
|
68
110
|
name = name.trim();
|
|
69
111
|
if (name && !processedNames.has(name)) {
|
|
@@ -89,6 +131,11 @@ const CoCreateEvents = {
|
|
|
89
131
|
});
|
|
90
132
|
},
|
|
91
133
|
|
|
134
|
+
/**
|
|
135
|
+
*
|
|
136
|
+
* @public
|
|
137
|
+
* @environment client
|
|
138
|
+
*/
|
|
92
139
|
initPrefix: function (prefix, events) {
|
|
93
140
|
action.init({
|
|
94
141
|
name: prefix,
|
|
@@ -105,7 +152,14 @@ const CoCreateEvents = {
|
|
|
105
152
|
});
|
|
106
153
|
|
|
107
154
|
// Updated selector to use dash for options: prefix-group, prefix-query
|
|
108
|
-
const
|
|
155
|
+
const knownTargets = this.knownTargets.map(target => `[${prefix}\\.${target}]`);
|
|
156
|
+
const selector = [
|
|
157
|
+
`[${prefix}]`,
|
|
158
|
+
`[${prefix}-group]`,
|
|
159
|
+
`[${prefix}-query]`,
|
|
160
|
+
`[${prefix}-element]`,
|
|
161
|
+
...knownTargets
|
|
162
|
+
].join(", ");
|
|
109
163
|
|
|
110
164
|
observer.init({
|
|
111
165
|
name: "CoCreateEventAttributes",
|
|
@@ -122,10 +176,20 @@ const CoCreateEvents = {
|
|
|
122
176
|
}
|
|
123
177
|
});
|
|
124
178
|
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* @public
|
|
182
|
+
* @environment client
|
|
183
|
+
*/
|
|
125
184
|
const elements = document.querySelectorAll(selector);
|
|
126
185
|
this.initElements(elements, prefix, events);
|
|
127
186
|
},
|
|
128
187
|
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
* @public
|
|
191
|
+
* @environment client
|
|
192
|
+
*/
|
|
129
193
|
initElements: function (elements, prefix, events = []) {
|
|
130
194
|
for (const el of elements) {
|
|
131
195
|
let isEventable = true;
|
|
@@ -206,14 +270,29 @@ const CoCreateEvents = {
|
|
|
206
270
|
|
|
207
271
|
const prefixData = elData[prefix];
|
|
208
272
|
|
|
273
|
+
// A new checked state must propagate even after an earlier
|
|
274
|
+
// input/change event; CRUD updates use the same event path.
|
|
275
|
+
const selectionChanged =
|
|
276
|
+
(target.type === "checkbox" || target.type === "radio") &&
|
|
277
|
+
prefixData.checked !== target.checked;
|
|
278
|
+
if (
|
|
279
|
+
event.detail?.skip === true && prefixData.prev === event.type &&
|
|
280
|
+
["input", "change"].includes(event.type) && !selectionChanged
|
|
281
|
+
) {
|
|
282
|
+
prefixData.prev = "";
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
209
286
|
// Debounce/Prevent duplicate mouse events if needed
|
|
210
287
|
if (prefixData.prev === event.type && ["mouseover", "mouseout"].includes(event.type)) {
|
|
211
288
|
return;
|
|
212
289
|
}
|
|
213
290
|
prefixData.prev = event.type;
|
|
291
|
+
if (target.type === "checkbox" || target.type === "radio")
|
|
292
|
+
prefixData.checked = target.checked;
|
|
214
293
|
|
|
215
294
|
// Check for 'actions' blocking
|
|
216
|
-
const actionsAttr = target.
|
|
295
|
+
const actionsAttr = target.attributes.getNamedItem("actions")?.value || "";
|
|
217
296
|
if (actionsAttr.includes(prefix)) return;
|
|
218
297
|
|
|
219
298
|
// Handle lifecycle controls (using dash)
|
|
@@ -222,7 +301,7 @@ const CoCreateEvents = {
|
|
|
222
301
|
{ attr: "stop-propagation", method: "stopPropagation" },
|
|
223
302
|
{ attr: "stop-immediate-propagation", method: "stopImmediatePropagation" }
|
|
224
303
|
];
|
|
225
|
-
|
|
304
|
+
|
|
226
305
|
for (const { attr, method } of lifecycleAttributes) {
|
|
227
306
|
const attrValue = target.getAttribute(`${prefix}-${attr}`);
|
|
228
307
|
if (attrValue !== null && attrValue !== "false") {
|
|
@@ -234,13 +313,18 @@ const CoCreateEvents = {
|
|
|
234
313
|
}
|
|
235
314
|
},
|
|
236
315
|
|
|
316
|
+
/**
|
|
317
|
+
*
|
|
318
|
+
* @public
|
|
319
|
+
* @environment client
|
|
320
|
+
*/
|
|
237
321
|
_initObserver: function (el, prefix) {
|
|
238
322
|
// Updated to dash: prefix-selector, prefix-query, prefix-attribute-filter, prefix-types
|
|
239
323
|
let selector = el.getAttribute(`${prefix}-selector`) || el.getAttribute(`${prefix}-query`) || "";
|
|
240
324
|
let attributeFilter = (el.getAttribute(`${prefix}-attribute-filter`) || "")
|
|
241
325
|
.split(/\s*,\s*/)
|
|
242
326
|
.filter(item => item);
|
|
243
|
-
|
|
327
|
+
|
|
244
328
|
let types = (el.getAttribute(`${prefix}-types`) || "")
|
|
245
329
|
.split(/\s*,\s*/)
|
|
246
330
|
.filter(item => item);
|
|
@@ -269,17 +353,29 @@ const CoCreateEvents = {
|
|
|
269
353
|
}
|
|
270
354
|
},
|
|
271
355
|
|
|
356
|
+
/**
|
|
357
|
+
*
|
|
358
|
+
* @public
|
|
359
|
+
* @environment client
|
|
360
|
+
*/
|
|
272
361
|
_initResizeObserver: function (el, prefix) {
|
|
273
362
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
274
363
|
const lastEntry = entries[entries.length - 1];
|
|
275
364
|
const { width } = lastEntry.contentRect;
|
|
276
|
-
|
|
277
|
-
el.
|
|
365
|
+
const widthValue = width + "px";
|
|
366
|
+
el.width = widthValue;
|
|
367
|
+
// Keeps dash style for conditional resize bindings.
|
|
368
|
+
el.setAttribute(`${prefix}-if-value`, widthValue);
|
|
278
369
|
this.__updateElements(el, prefix);
|
|
279
370
|
});
|
|
280
371
|
resizeObserver.observe(el);
|
|
281
372
|
},
|
|
282
373
|
|
|
374
|
+
/**
|
|
375
|
+
*
|
|
376
|
+
* @public
|
|
377
|
+
* @environment client
|
|
378
|
+
*/
|
|
283
379
|
_initIntersectionObserver: function (el, prefix) {
|
|
284
380
|
// Updated to dash
|
|
285
381
|
const rootSelector = el.getAttribute(`${prefix}-root`);
|
|
@@ -289,8 +385,8 @@ const CoCreateEvents = {
|
|
|
289
385
|
const trigger = el.getAttribute(`${prefix}-trigger`) || "both";
|
|
290
386
|
|
|
291
387
|
const root = rootSelector ? document.querySelector(rootSelector) : null;
|
|
292
|
-
const validThreshold = Array.isArray(threshold)
|
|
293
|
-
? threshold.filter(v => v >= 0 && v <= 1)
|
|
388
|
+
const validThreshold = Array.isArray(threshold)
|
|
389
|
+
? threshold.filter(v => v >= 0 && v <= 1)
|
|
294
390
|
: [0];
|
|
295
391
|
|
|
296
392
|
const once = el.getAttribute(`${prefix}-once`);
|
|
@@ -321,20 +417,25 @@ const CoCreateEvents = {
|
|
|
321
417
|
intersectionObserver.observe(el);
|
|
322
418
|
},
|
|
323
419
|
|
|
420
|
+
/**
|
|
421
|
+
*
|
|
422
|
+
* @public
|
|
423
|
+
* @environment client
|
|
424
|
+
*/
|
|
324
425
|
__updateElements: async function (rootElement, prefix, events, params) {
|
|
325
426
|
if (!rootElement.isConnected) return;
|
|
326
427
|
|
|
327
428
|
// 1. Determine targets based on prefix-group
|
|
328
429
|
let sourceElements = [rootElement];
|
|
329
430
|
const sourceGroup = rootElement.getAttribute(`${prefix}-group`);
|
|
330
|
-
|
|
431
|
+
|
|
331
432
|
if (sourceGroup) {
|
|
332
433
|
sourceElements = document.querySelectorAll(`[${prefix}-group="${sourceGroup}"]`);
|
|
333
434
|
}
|
|
334
435
|
|
|
335
436
|
// 2. Iterate through sourceElements
|
|
336
437
|
for (const sourceElement of sourceElements) {
|
|
337
|
-
|
|
438
|
+
|
|
338
439
|
// --- Global Options (Dash) ---
|
|
339
440
|
|
|
340
441
|
// onEvent
|
|
@@ -353,18 +454,18 @@ const CoCreateEvents = {
|
|
|
353
454
|
const intervalAttr = sourceElement.getAttribute(`${prefix}-set-interval`);
|
|
354
455
|
if (intervalAttr !== null) {
|
|
355
456
|
const intervalMs = parseInt(intervalAttr, 10) || 0;
|
|
356
|
-
|
|
457
|
+
|
|
357
458
|
if (!sourceElement.eventsInterval) {
|
|
358
459
|
sourceElement.eventsInterval = {};
|
|
359
460
|
}
|
|
360
|
-
|
|
461
|
+
|
|
361
462
|
const currentInterval = sourceElement.eventsInterval[prefix];
|
|
362
463
|
|
|
363
464
|
if (intervalMs > 0) {
|
|
364
465
|
// Start or update interval
|
|
365
466
|
if (!currentInterval || currentInterval.delay !== intervalMs) {
|
|
366
467
|
if (currentInterval) clearInterval(currentInterval.id);
|
|
367
|
-
|
|
468
|
+
|
|
368
469
|
sourceElement.eventsInterval[prefix] = {
|
|
369
470
|
delay: intervalMs,
|
|
370
471
|
id: setInterval(() => {
|
|
@@ -401,7 +502,7 @@ const CoCreateEvents = {
|
|
|
401
502
|
|
|
402
503
|
// once
|
|
403
504
|
const onceAttr = sourceElement.getAttribute(`${prefix}-once`);
|
|
404
|
-
if (onceAttr !== null) {
|
|
505
|
+
if (onceAttr !== null) {
|
|
405
506
|
if (!sourceElement.eventsOnce) {
|
|
406
507
|
sourceElement.eventsOnce = [prefix];
|
|
407
508
|
} else if (sourceElement.eventsOnce.includes(prefix)) {
|
|
@@ -412,7 +513,9 @@ const CoCreateEvents = {
|
|
|
412
513
|
}
|
|
413
514
|
|
|
414
515
|
// --- Resolve Targets ---
|
|
415
|
-
const querySelector = sourceElement.getAttribute(`${prefix}-query`);
|
|
516
|
+
// const querySelector = sourceElement.getAttribute(`${prefix}-query`);
|
|
517
|
+
const querySelector =
|
|
518
|
+
sourceElement.attributes[`${prefix}-query`]?.value;
|
|
416
519
|
let targetElements = [sourceElement];
|
|
417
520
|
|
|
418
521
|
if (querySelector || params) {
|
|
@@ -440,6 +543,11 @@ const CoCreateEvents = {
|
|
|
440
543
|
);
|
|
441
544
|
},
|
|
442
545
|
|
|
546
|
+
/**
|
|
547
|
+
*
|
|
548
|
+
* @public
|
|
549
|
+
* @environment client
|
|
550
|
+
*/
|
|
443
551
|
setValue: async function (rootElement, sourceElement, targetElement, prefix, events) {
|
|
444
552
|
// Handle Mouseout toggle logic
|
|
445
553
|
if (events && events.includes("mouseout")) {
|
|
@@ -457,28 +565,62 @@ const CoCreateEvents = {
|
|
|
457
565
|
|
|
458
566
|
const deactivate = (rootElement !== sourceElement);
|
|
459
567
|
|
|
460
|
-
//
|
|
568
|
+
// Expand the prefix object in place, preserving attribute order.
|
|
569
|
+
const attributes = [];
|
|
461
570
|
for (const attribute of sourceElement.attributes) {
|
|
571
|
+
if (attribute.name === prefix) {
|
|
572
|
+
const rawValue = attribute.value?.trim();
|
|
573
|
+
let assignments = null;
|
|
574
|
+
let assignmentsFromAst = false;
|
|
575
|
+
|
|
576
|
+
// An empty prefix remains an initialization marker.
|
|
577
|
+
if (!rawValue) continue;
|
|
578
|
+
if (rawValue?.startsWith("$")) {
|
|
579
|
+
assignments = await sourceElement.getAttributeAsync(attribute.name);
|
|
580
|
+
assignmentsFromAst = true;
|
|
581
|
+
} else if (rawValue.startsWith("{")) {
|
|
582
|
+
assignments = JSON.parse(rawValue);
|
|
583
|
+
} else {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (!assignments || typeof assignments !== 'object') continue;
|
|
587
|
+
|
|
588
|
+
for (const [name, value] of Object.entries(assignments)) {
|
|
589
|
+
attributes.push({
|
|
590
|
+
name: `${prefix}.${name}`,
|
|
591
|
+
value,
|
|
592
|
+
fromObject: !assignmentsFromAst
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
attributes.push(attribute);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Execute both forms through the existing assignment handlers.
|
|
601
|
+
for (const attribute of attributes) {
|
|
462
602
|
const name = attribute.name;
|
|
463
|
-
|
|
603
|
+
|
|
464
604
|
// Optimization: Skip if shorter than prefix + dot + min char
|
|
465
605
|
if (name.length <= prefix.length + 1) continue;
|
|
466
|
-
|
|
606
|
+
|
|
467
607
|
// STRICT RULE: Only process attributes with DOT separator.
|
|
468
608
|
// All options use Dash, so they will be ignored here.
|
|
469
609
|
if (!name.startsWith(prefix) || name[prefix.length] !== '.') continue;
|
|
470
610
|
|
|
471
611
|
// Extract target name (e.g., 'value', 'style', 'src', 'data-something')
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// Get Value
|
|
475
|
-
let value = sourceElement.getAttribute(name);
|
|
476
|
-
if (value === "$value" && sourceElement.getValue) {
|
|
477
|
-
value = await sourceElement.getValue();
|
|
478
|
-
}
|
|
612
|
+
let targetName = name.substring(prefix.length + 1);
|
|
613
|
+
const explicitProperty = targetName.startsWith("$");
|
|
479
614
|
|
|
480
|
-
// Parse CSV for rotation
|
|
481
615
|
let values = [];
|
|
616
|
+
const value = attribute.fromObject
|
|
617
|
+
? await astAsync({
|
|
618
|
+
executeAST: attribute.value,
|
|
619
|
+
node: sourceElement,
|
|
620
|
+
sandbox: { $this: sourceElement }
|
|
621
|
+
})
|
|
622
|
+
: sourceElement.getAttribute(name);
|
|
623
|
+
// Parse CSV for rotation
|
|
482
624
|
if (typeof value === 'string') {
|
|
483
625
|
values = value.split(',').map(v => v.trim());
|
|
484
626
|
} else if (Array.isArray(value)) {
|
|
@@ -486,18 +628,25 @@ const CoCreateEvents = {
|
|
|
486
628
|
} else {
|
|
487
629
|
values = [value];
|
|
488
630
|
}
|
|
631
|
+
// Object values and dotted attribute values resolve through AST.
|
|
489
632
|
|
|
490
633
|
let oldValue = "";
|
|
491
634
|
let newValue = "";
|
|
492
635
|
|
|
493
636
|
switch (targetName) {
|
|
637
|
+
case 'type':
|
|
638
|
+
oldValue = targetElement.getAttribute('type') || '';
|
|
639
|
+
newValue = this.__getNextValue(values, oldValue, deactivate);
|
|
640
|
+
targetElement.setAttribute('type', newValue);
|
|
641
|
+
break;
|
|
642
|
+
|
|
494
643
|
case 'style':
|
|
495
644
|
if (targetElement.getAttribute("style")) {
|
|
496
645
|
// Normalize style string for comparison
|
|
497
646
|
const styles = targetElement.getAttribute("style")
|
|
498
647
|
.split(";")
|
|
499
648
|
.map(x => x.trim().replace(/\s/g, ""));
|
|
500
|
-
|
|
649
|
+
|
|
501
650
|
oldValue = values.find(x => styles.includes(x.replace(/\s/g, ""))) || "";
|
|
502
651
|
}
|
|
503
652
|
newValue = this.__getNextValue(values, oldValue, deactivate);
|
|
@@ -527,7 +676,7 @@ const CoCreateEvents = {
|
|
|
527
676
|
|
|
528
677
|
if (oldValue) {
|
|
529
678
|
oldValue.split(" ").forEach(c => targetElement.classList.remove(c));
|
|
530
|
-
if (values.length === 1 && !newValue) return;
|
|
679
|
+
if (values.length === 1 && !newValue) return;
|
|
531
680
|
}
|
|
532
681
|
|
|
533
682
|
if (newValue) {
|
|
@@ -537,26 +686,60 @@ const CoCreateEvents = {
|
|
|
537
686
|
|
|
538
687
|
case 'insertadjacenthtml':
|
|
539
688
|
case 'insertadjacenttext':
|
|
540
|
-
case 'insertAdjacentHTML':
|
|
689
|
+
case 'insertAdjacentHTML':
|
|
541
690
|
case 'insertAdjacentText':
|
|
542
|
-
|
|
543
|
-
|
|
691
|
+
if (!value) continue;
|
|
692
|
+
const position = String(value).toLowerCase();
|
|
544
693
|
|
|
545
694
|
let content = "";
|
|
546
|
-
if (sourceElement.
|
|
547
|
-
content =
|
|
695
|
+
if (sourceElement.hasAttribute(`${prefix}-value`)) {
|
|
696
|
+
content = sourceElement.getAttribute(`${prefix}-value`);
|
|
697
|
+
} else {
|
|
698
|
+
const contentSelector = sourceElement.attributes
|
|
699
|
+
.getNamedItem(`${prefix}-element`)?.value;
|
|
700
|
+
let contentSource = sourceElement;
|
|
701
|
+
if (contentSelector !== undefined) {
|
|
702
|
+
[contentSource] = queryElements({
|
|
703
|
+
element: sourceElement,
|
|
704
|
+
prefix,
|
|
705
|
+
selector: contentSelector
|
|
706
|
+
});
|
|
707
|
+
if (!contentSource) continue;
|
|
708
|
+
}
|
|
709
|
+
if (contentSource.getValue) content = contentSource.getValue();
|
|
548
710
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
711
|
+
if (content && typeof content.then === 'function') {
|
|
712
|
+
content = await content;
|
|
713
|
+
}
|
|
714
|
+
content ??= "";
|
|
715
|
+
|
|
716
|
+
// Resolve adjacency to canonical source coordinates for editors.
|
|
717
|
+
const currentEditor = targetElement.editor;
|
|
718
|
+
const editorDetails = typeof currentEditor?.getStart === "function"
|
|
719
|
+
? currentEditor : targetElement.editorDetails;
|
|
720
|
+
const editorElement = editorDetails?.element || editorDetails?.editorElement;
|
|
721
|
+
if (editorElement?.editor?.updateText) {
|
|
722
|
+
const point = editorDetails.getInsertionPoint?.(position);
|
|
723
|
+
if (point) {
|
|
724
|
+
await point.editor.updateText({ start: point.offset, length: 0, value: String(content) });
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
if (typeof editorDetails.getInsertionPoint === "function")
|
|
728
|
+
throw new RangeError("Editor insertion position is not valid for this element.");
|
|
729
|
+
const outerRange = editorDetails.getOuterRange?.();
|
|
730
|
+
const contentRange = editorDetails.getContentRange?.();
|
|
731
|
+
const legacyBoundary = {
|
|
732
|
+
beforebegin: "start", afterbegin: "contentStart",
|
|
733
|
+
beforeend: "contentEnd", afterend: "end"
|
|
734
|
+
}[position];
|
|
735
|
+
const start = position === "beforebegin" ? outerRange?.start ?? editorDetails.start
|
|
736
|
+
: position === "afterbegin" ? contentRange?.start ?? editorDetails.contentStart
|
|
737
|
+
: position === "beforeend" ? contentRange?.end ?? editorDetails.contentEnd
|
|
738
|
+
: position === "afterend" ? outerRange?.end ?? editorDetails.end
|
|
739
|
+
: undefined;
|
|
740
|
+
if (!legacyBoundary || !Number.isInteger(start))
|
|
741
|
+
throw new RangeError("Editor insertion requires a current source range for the target.");
|
|
742
|
+
await editorElement.editor.updateText({ start, length: 0, value: String(content) });
|
|
560
743
|
} else {
|
|
561
744
|
const isHtml = targetName.toLowerCase().includes('html');
|
|
562
745
|
if (isHtml) {
|
|
@@ -567,6 +750,30 @@ const CoCreateEvents = {
|
|
|
567
750
|
}
|
|
568
751
|
break;
|
|
569
752
|
|
|
753
|
+
case 'insertadjacentelement':
|
|
754
|
+
case 'insertAdjacentElement':
|
|
755
|
+
const elementPosition = value;
|
|
756
|
+
const elementSelector =
|
|
757
|
+
sourceElement.attributes.getNamedItem(`${prefix}-element`)?.value;
|
|
758
|
+
if (!elementPosition || !elementSelector) continue;
|
|
759
|
+
|
|
760
|
+
const elementsToInsert = queryElements({
|
|
761
|
+
element: sourceElement,
|
|
762
|
+
prefix,
|
|
763
|
+
selector: elementSelector
|
|
764
|
+
});
|
|
765
|
+
const elementToInsert = elementsToInsert[0];
|
|
766
|
+
if (
|
|
767
|
+
elementToInsert &&
|
|
768
|
+
typeof targetElement.insertAdjacentElement === 'function'
|
|
769
|
+
) {
|
|
770
|
+
targetElement.insertAdjacentElement(
|
|
771
|
+
elementPosition,
|
|
772
|
+
elementToInsert
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
|
|
570
777
|
case 'value':
|
|
571
778
|
if (targetElement.getValue) {
|
|
572
779
|
oldValue = await targetElement.getValue();
|
|
@@ -578,7 +785,14 @@ const CoCreateEvents = {
|
|
|
578
785
|
newValue = this.__getNextValue(values, oldValue, deactivate);
|
|
579
786
|
|
|
580
787
|
if (targetElement.setValue) {
|
|
581
|
-
|
|
788
|
+
if (
|
|
789
|
+
targetElement === sourceElement &&
|
|
790
|
+
["input", "change"].includes(prefix)
|
|
791
|
+
) {
|
|
792
|
+
targetElement.setValue(newValue, true);
|
|
793
|
+
} else {
|
|
794
|
+
targetElement.setValue(newValue);
|
|
795
|
+
}
|
|
582
796
|
} else {
|
|
583
797
|
targetElement.value = newValue;
|
|
584
798
|
}
|
|
@@ -586,25 +800,54 @@ const CoCreateEvents = {
|
|
|
586
800
|
|
|
587
801
|
default:
|
|
588
802
|
// Generic handling: Try Property first, then Attribute
|
|
589
|
-
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
|
|
803
|
+
|
|
804
|
+
// $ always selects a property, including one not yet defined.
|
|
805
|
+
let propertyName;
|
|
806
|
+
let propertyTarget = targetElement;
|
|
807
|
+
let propertyValue;
|
|
808
|
+
if (explicitProperty || (
|
|
809
|
+
propertyName = resolveProperty(targetElement, targetName)
|
|
810
|
+
) !== undefined) {
|
|
811
|
+
if (explicitProperty) {
|
|
812
|
+
const propertyPath = resolvePropertyPath(
|
|
813
|
+
targetElement,
|
|
814
|
+
targetName.substring(1)
|
|
815
|
+
);
|
|
816
|
+
if (propertyPath) {
|
|
817
|
+
propertyTarget = propertyPath.target;
|
|
818
|
+
propertyName = propertyPath.key;
|
|
819
|
+
propertyValue = propertyPath.value;
|
|
820
|
+
} else {
|
|
821
|
+
targetName = targetName.substring(1);
|
|
822
|
+
propertyName = resolveProperty(targetElement, targetName);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
targetName = propertyName ?? targetName;
|
|
826
|
+
propertyValue ??= propertyTarget[targetName];
|
|
827
|
+
oldValue = propertyValue;
|
|
593
828
|
if (!values.includes(oldValue) && typeof oldValue !== 'function') oldValue = "";
|
|
594
|
-
|
|
829
|
+
|
|
595
830
|
newValue = this.__getNextValue(values, oldValue, deactivate);
|
|
596
831
|
|
|
597
|
-
if (typeof
|
|
598
|
-
|
|
832
|
+
if (typeof propertyValue === 'function') {
|
|
833
|
+
// A non-$ method binding with value=false disables
|
|
834
|
+
// the action. Explicit $ bindings may still
|
|
835
|
+
// intentionally pass false as an argument.
|
|
836
|
+
if (
|
|
837
|
+
explicitProperty ||
|
|
838
|
+
(newValue !== false && newValue !== "false")
|
|
839
|
+
) {
|
|
840
|
+
propertyValue.call(propertyTarget, newValue);
|
|
841
|
+
}
|
|
599
842
|
} else {
|
|
600
|
-
|
|
843
|
+
propertyTarget[targetName] = newValue;
|
|
601
844
|
}
|
|
602
|
-
}
|
|
845
|
+
}
|
|
603
846
|
// 2. Fallback to Attribute
|
|
604
847
|
else {
|
|
605
848
|
oldValue = targetElement.getAttribute(targetName);
|
|
606
849
|
if (!values.includes(oldValue)) oldValue = "";
|
|
607
|
-
|
|
850
|
+
|
|
608
851
|
newValue = this.__getNextValue(values, oldValue, deactivate);
|
|
609
852
|
|
|
610
853
|
// Handle Namespaced Attributes (e.g. svg:href) if encoded in name
|
|
@@ -615,6 +858,11 @@ const CoCreateEvents = {
|
|
|
615
858
|
}
|
|
616
859
|
},
|
|
617
860
|
|
|
861
|
+
/**
|
|
862
|
+
*
|
|
863
|
+
* @public
|
|
864
|
+
* @environment client
|
|
865
|
+
*/
|
|
618
866
|
__getNextValue: function (values, val, deactivate) {
|
|
619
867
|
if (deactivate) return "";
|
|
620
868
|
const size = values.length;
|
|
@@ -629,4 +877,50 @@ const CoCreateEvents = {
|
|
|
629
877
|
|
|
630
878
|
CoCreateEvents.init();
|
|
631
879
|
|
|
632
|
-
export default CoCreateEvents;
|
|
880
|
+
export default CoCreateEvents;
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
*
|
|
884
|
+
* @name event-name
|
|
885
|
+
* @kind member
|
|
886
|
+
* @attribute
|
|
887
|
+
* @environment client
|
|
888
|
+
*/
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
*
|
|
892
|
+
* @name style
|
|
893
|
+
* @kind member
|
|
894
|
+
* @attribute
|
|
895
|
+
* @environment client
|
|
896
|
+
*/
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
*
|
|
900
|
+
* @name type
|
|
901
|
+
* @kind member
|
|
902
|
+
* @attribute
|
|
903
|
+
* @environment client
|
|
904
|
+
*/
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* A simple events component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.
|
|
908
|
+
*
|
|
909
|
+
* @module events
|
|
910
|
+
* @environment client
|
|
911
|
+
*/
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Declarative selectors recognized by this module.
|
|
915
|
+
*
|
|
916
|
+
* @name declarative-selectors
|
|
917
|
+
* @kind member
|
|
918
|
+
* @selectors
|
|
919
|
+
* @environment client
|
|
920
|
+
* @example
|
|
921
|
+
* ```json
|
|
922
|
+
* [
|
|
923
|
+
* "[event-name]"
|
|
924
|
+
* ]
|
|
925
|
+
* ```
|
|
926
|
+
*/
|