@cocreate/elements 1.46.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 +753 -700
- package/CONTRIBUTING.md +6 -6
- package/README.md +469 -46
- package/demo/index.html +1 -1
- package/docs/index.html +7 -7
- package/package.json +12 -11
- package/src/crudHandler.js +153 -47
- package/src/dataHandler.js +7 -0
- package/src/dataObject.js +54 -0
- package/src/formHandler.js +226 -170
- package/src/formSelectors.js +1 -0
- package/src/getDataHandler.js +97 -94
- package/src/hrefHandler.js +104 -0
- package/src/iframeHandler.js +90 -0
- package/src/index.js +446 -12
- package/src/initHandler.js +96 -46
- package/src/mapRegistry.js +10 -1
- package/src/setDataHandler.js +171 -87
- package/src/socketHandler.js +328 -0
- package/src/socketSelectors.js +3 -0
- package/src/srcHandler.js +69 -32
- package/src/subscribeHandler.js +64 -64
- package/src/valueQuery.js +3 -4
- package/src/valueQuerySelector.js +1 -0
- package/test/deferred-src.test.cjs +11 -0
- package/test/form-enter.test.cjs +48 -0
- package/test/get-data.test.cjs +100 -0
- package/test/href-handler.test.cjs +132 -0
- package/test/init-read.test.cjs +145 -0
- package/test/lazy-loading.test.cjs +168 -0
- package/test/load-more.test.cjs +19 -0
- package/test/save-array.test.cjs +31 -0
- package/test/save-completion.test.cjs +36 -0
- package/test/set-data-type.test.cjs +119 -0
- package/test/ssr-response.test.cjs +108 -0
- package/webpack.config.js +1 -0
- package/src/dndHandler.js +0 -155
package/src/getDataHandler.js
CHANGED
|
@@ -1,53 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
elements,
|
|
3
|
+
forms,
|
|
4
|
+
keys,
|
|
5
|
+
elementSubscriptions,
|
|
6
|
+
subscriptions
|
|
7
|
+
} from "./mapRegistry.js";
|
|
8
|
+
import { getSubscription } from "./subscribeHandler.js";
|
|
9
|
+
import { read, save } from "./crudHandler.js";
|
|
5
10
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
11
|
+
import { environment, getDataObject } from "./dataObject.js";
|
|
12
|
+
export { getDataObject } from "./dataObject.js";
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Infers execution type (object, index, array, database, storage, data) dynamically.
|
|
12
|
-
*
|
|
13
|
-
* @param {Element} element
|
|
14
|
-
* @returns {Object|undefined} Normalized data payload object
|
|
15
|
-
*/
|
|
16
|
-
export function getDataObject(element) {
|
|
17
|
-
const data = getAttributes(element);
|
|
18
|
-
const crudType = ["storage", "database", "array", "index", "object"];
|
|
19
|
-
|
|
20
|
-
for (let i = 0; i < crudType.length; i++) {
|
|
21
|
-
if (!checkValue(data[crudType[i]])) return;
|
|
14
|
+
function registerFormElement(form, element, data) {
|
|
15
|
+
if (!form || !data?.type) return;
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
data[crudType[i]] = [];
|
|
17
|
+
if (!form.save) form.save = () => save(form);
|
|
18
|
+
if (!form.getData) form.getData = () => getData(form);
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
}
|
|
20
|
+
let formObject = forms.get(form);
|
|
21
|
+
if (!formObject) {
|
|
22
|
+
formObject = {
|
|
23
|
+
elements: new Map(),
|
|
24
|
+
types: new Map()
|
|
25
|
+
};
|
|
26
|
+
forms.set(form, formObject);
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
data.type
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
data.type = "index";
|
|
44
|
-
} else if (data.array || data.array === "") data.type = "array";
|
|
45
|
-
else if (data.database || data.database === "") data.type = "database";
|
|
46
|
-
else if (data.storage || data.storage === "") data.type = "storage";
|
|
47
|
-
else if (data.data) data.type = "data";
|
|
48
|
-
|
|
49
|
-
delete data.isRealtime;
|
|
50
|
-
return data;
|
|
29
|
+
formObject.elements.set(element, data);
|
|
30
|
+
if (!formObject.types.has(data.type)) {
|
|
31
|
+
formObject.types.set(data.type, new Map());
|
|
32
|
+
}
|
|
33
|
+
formObject.types.get(data.type).set(element, data);
|
|
51
34
|
}
|
|
52
35
|
|
|
53
36
|
/**
|
|
@@ -59,7 +42,10 @@ export function getDataObject(element) {
|
|
|
59
42
|
* @returns {{ string: string, object: Object }}
|
|
60
43
|
*/
|
|
61
44
|
export function initDataKey(element, data) {
|
|
45
|
+
if (!data.database && environment) data.database = environment;
|
|
46
|
+
|
|
62
47
|
const dataKey = getDataKey(data);
|
|
48
|
+
const subscription = getSubscription(data);
|
|
63
49
|
|
|
64
50
|
if (keys.has(dataKey.string)) {
|
|
65
51
|
keys.get(dataKey.string).elements.set(element, "");
|
|
@@ -69,34 +55,27 @@ export function initDataKey(element, data) {
|
|
|
69
55
|
data,
|
|
70
56
|
dataKey
|
|
71
57
|
});
|
|
72
|
-
subscribe(dataKey, data);
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
formObject.elements.set(element, data);
|
|
86
|
-
if (formObject.types.has(data.type)) {
|
|
87
|
-
formObject.types.get(data.type).set(element, data);
|
|
88
|
-
} else {
|
|
89
|
-
formObject.types.set(data.type, new Map([[element, data]]));
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
forms.set(form, {
|
|
93
|
-
elements: new Map([[element, data]]),
|
|
94
|
-
types: new Map([[data.type, new Map([[element, data]])]])
|
|
95
|
-
});
|
|
96
|
-
}
|
|
60
|
+
if (subscription) {
|
|
61
|
+
let subscriptionEntry = subscriptions.get(subscription.string);
|
|
62
|
+
if (subscriptionEntry) {
|
|
63
|
+
subscriptionEntry.elements.add(element);
|
|
64
|
+
} else {
|
|
65
|
+
subscriptionEntry = {
|
|
66
|
+
elements: new Set([element]),
|
|
67
|
+
subscription
|
|
68
|
+
};
|
|
69
|
+
subscriptions.set(subscription.string, subscriptionEntry);
|
|
97
70
|
}
|
|
71
|
+
elementSubscriptions.set(element, subscription.string);
|
|
98
72
|
}
|
|
99
73
|
|
|
74
|
+
elements.set(element, dataKey.string);
|
|
75
|
+
|
|
76
|
+
const form = element.form || element.closest?.("form");
|
|
77
|
+
registerFormElement(form, element, data);
|
|
78
|
+
|
|
100
79
|
if (!element.read) element.read = () => read(element);
|
|
101
80
|
if (!element.save) element.save = () => save(element);
|
|
102
81
|
|
|
@@ -113,6 +92,8 @@ export function initDataKey(element, data) {
|
|
|
113
92
|
*/
|
|
114
93
|
export function getDataKey(data) {
|
|
115
94
|
const dataKey = {};
|
|
95
|
+
if (!data.database && environment) data.database = environment;
|
|
96
|
+
|
|
116
97
|
const attributes = [
|
|
117
98
|
"host",
|
|
118
99
|
"organization_id",
|
|
@@ -126,13 +107,13 @@ export function getDataKey(data) {
|
|
|
126
107
|
"object",
|
|
127
108
|
"key",
|
|
128
109
|
"updateKey",
|
|
129
|
-
"
|
|
110
|
+
"filter",
|
|
130
111
|
"upsert",
|
|
131
112
|
"namespace",
|
|
132
113
|
"room",
|
|
133
114
|
"broadcast",
|
|
134
115
|
"broadcastSender",
|
|
135
|
-
"
|
|
116
|
+
"broadcastClient"
|
|
136
117
|
];
|
|
137
118
|
|
|
138
119
|
for (const attribute of attributes) {
|
|
@@ -193,7 +174,7 @@ export function getDataKeys(data) {
|
|
|
193
174
|
"array",
|
|
194
175
|
"index",
|
|
195
176
|
"object",
|
|
196
|
-
"
|
|
177
|
+
"filter"
|
|
197
178
|
];
|
|
198
179
|
|
|
199
180
|
for (const [keyString, sortedKey] of keys.entries()) {
|
|
@@ -201,8 +182,8 @@ export function getDataKeys(data) {
|
|
|
201
182
|
|
|
202
183
|
for (const key of targetKeys) {
|
|
203
184
|
if (
|
|
204
|
-
!data
|
|
205
|
-
key === "
|
|
185
|
+
!data.filter &&
|
|
186
|
+
key === "filter" &&
|
|
206
187
|
sortedKey.data.hasOwnProperty(key)
|
|
207
188
|
) {
|
|
208
189
|
hasMatch = true;
|
|
@@ -211,25 +192,28 @@ export function getDataKeys(data) {
|
|
|
211
192
|
hasMatch = false;
|
|
212
193
|
break;
|
|
213
194
|
}
|
|
195
|
+
|
|
196
|
+
// Filtered object elements watch a collection query, not one
|
|
197
|
+
// specific object. Creates and updates contain the affected
|
|
198
|
+
// object(s), so their object shape will not match the empty
|
|
199
|
+
// object scope used when the filtered element was registered.
|
|
200
|
+
if (key === "object" && sortedKey.data.filter) continue;
|
|
201
|
+
|
|
214
202
|
if (
|
|
215
203
|
Array.isArray(sortedKey.data[key]) &&
|
|
216
204
|
Array.isArray(data[key])
|
|
217
205
|
) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return data[key].some((obj) => obj._id === value._id);
|
|
224
|
-
} else {
|
|
225
|
-
return data[key].includes(value);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
if (!matches) {
|
|
230
|
-
hasMatch = false;
|
|
231
|
-
break;
|
|
206
|
+
const matches = sortedKey.data[key].some((value) => {
|
|
207
|
+
if (key === "object") {
|
|
208
|
+
return data[key].some((obj) => obj._id === value._id);
|
|
209
|
+
} else {
|
|
210
|
+
return data[key].includes(value);
|
|
232
211
|
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (!matches) {
|
|
215
|
+
hasMatch = false;
|
|
216
|
+
break;
|
|
233
217
|
}
|
|
234
218
|
} else if (sortedKey.data[key] !== data[key]) {
|
|
235
219
|
hasMatch = false;
|
|
@@ -264,7 +248,7 @@ function formatPayload(payload, value, element) {
|
|
|
264
248
|
} else payload.object = { ...payload.object, ...value };
|
|
265
249
|
} else payload.object[payload.key] = value;
|
|
266
250
|
}
|
|
267
|
-
if (payload.isUpsert && payload
|
|
251
|
+
if (payload.isUpsert && payload.filter)
|
|
268
252
|
payload.method = "object.update";
|
|
269
253
|
} else payload[payload.type] = value;
|
|
270
254
|
} else if (payload[payload.type] && payload.key) {
|
|
@@ -352,19 +336,27 @@ function formatPayload(payload, value, element) {
|
|
|
352
336
|
*/
|
|
353
337
|
export async function getData(form) {
|
|
354
338
|
const dataKeys = new Map();
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
339
|
+
const selector = "[storage], [database], [array], [object]";
|
|
340
|
+
|
|
341
|
+
// form.elements includes controls associated through form="id", even when
|
|
342
|
+
// the controller lives elsewhere in the document.
|
|
343
|
+
for (const element of Array.from(form.elements || [])) {
|
|
344
|
+
if (!element.matches(selector) || element.closest("[template]")) continue;
|
|
345
|
+
|
|
346
|
+
// A dataKey identifies the document scope and intentionally excludes
|
|
347
|
+
// the field-level `key`. Reusing the registry entry's data here would
|
|
348
|
+
// therefore give every control the key of the first registered field.
|
|
349
|
+
const data = getDataObject(element);
|
|
350
|
+
registerFormElement(form, element, data);
|
|
362
351
|
}
|
|
363
352
|
|
|
353
|
+
const formObject = forms.get(form);
|
|
354
|
+
if (!formObject) return [];
|
|
364
355
|
|
|
365
356
|
for (const type of formObject.types.values()) {
|
|
366
357
|
for (const [element, data] of type.entries()) {
|
|
367
358
|
if (
|
|
359
|
+
element.closest("[template]") ||
|
|
368
360
|
!element.hasAttribute("key") ||
|
|
369
361
|
element.getAttribute("save") === "false"
|
|
370
362
|
) {
|
|
@@ -384,6 +376,17 @@ export async function getData(form) {
|
|
|
384
376
|
const value = await element.getValue();
|
|
385
377
|
if (value === undefined) continue;
|
|
386
378
|
|
|
379
|
+
// A form can expose multiple file sources (for example, device and
|
|
380
|
+
// camera) while only one source has a pending selection. An unused
|
|
381
|
+
// file input must not produce an empty object.create operation.
|
|
382
|
+
if (
|
|
383
|
+
element.matches('input[type="file"]') &&
|
|
384
|
+
Array.isArray(value) &&
|
|
385
|
+
value.length === 0
|
|
386
|
+
) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
387
390
|
const payload = { ...data };
|
|
388
391
|
const dataKey = elements.get(element);
|
|
389
392
|
|
|
@@ -414,4 +417,4 @@ export default {
|
|
|
414
417
|
getDataKeys,
|
|
415
418
|
getData,
|
|
416
419
|
getDataObject
|
|
417
|
-
};
|
|
420
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Actions from "@cocreate/actions";
|
|
2
|
+
import { queryElements } from "@cocreate/utils";
|
|
3
|
+
|
|
4
|
+
const selector = "[href], [target]";
|
|
5
|
+
|
|
6
|
+
function init() {
|
|
7
|
+
document.link ||= { islink: "true" };
|
|
8
|
+
document.addEventListener("click", linkEvent, true);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Delegation also handles links inserted after this feature loads.
|
|
12
|
+
function linkEvent(event) {
|
|
13
|
+
const target = event.target.closest?.(selector);
|
|
14
|
+
if (!target || target.hasAttribute("download") || target.closest("[actions]")) return;
|
|
15
|
+
open(target, event);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function open(target, event) {
|
|
19
|
+
if (!target) return;
|
|
20
|
+
const doc = target.ownerDocument || document;
|
|
21
|
+
if (doc.link?.islink === "false" || doc.link?.islink === false) return;
|
|
22
|
+
if (!target.getAttribute("href")) return;
|
|
23
|
+
event?.preventDefault();
|
|
24
|
+
openLink(target);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function openLink(link) {
|
|
28
|
+
const href = link.getAttribute("href");
|
|
29
|
+
const target = link.getAttribute("target");
|
|
30
|
+
|
|
31
|
+
if (!target || target === "_self") {
|
|
32
|
+
const currentUrl = new URL(location.href);
|
|
33
|
+
const targetUrl = new URL(href, location.href);
|
|
34
|
+
const currentPath = currentUrl.pathname.replace("/index.html", "/");
|
|
35
|
+
const targetPath = targetUrl.pathname.replace("/index.html", "/");
|
|
36
|
+
if (currentPath !== targetPath || currentUrl.hash !== targetUrl.hash) {
|
|
37
|
+
location.href = href;
|
|
38
|
+
}
|
|
39
|
+
} else if (target === "_window") {
|
|
40
|
+
window.open(href, "_blank", "location=yes,height=570,width=520,scrollbars=yes,status=yes");
|
|
41
|
+
} else {
|
|
42
|
+
window.open(href, target);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Actions passes its chain element; resolve its associated link before opening it.
|
|
47
|
+
function href(element, context) {
|
|
48
|
+
const link = element.closest("[href], [target], [state_to]") ||
|
|
49
|
+
element.querySelector("[href], [target], [state_to]");
|
|
50
|
+
if (!link || link.hasAttribute("download")) return;
|
|
51
|
+
return open(link, context?.event);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function linkDocuments(element) {
|
|
55
|
+
const targets = element.hasAttribute("link-query")
|
|
56
|
+
? queryElements({ element, prefix: "link" }) : [document];
|
|
57
|
+
return new Set(targets.map(target => target.nodeType === 9 ? target :
|
|
58
|
+
target.contentDocument || target.document || target.ownerDocument).filter(Boolean));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function disableHref(element) {
|
|
62
|
+
for (const doc of linkDocuments(element)) {
|
|
63
|
+
doc.link = { islink: "false" };
|
|
64
|
+
doc.removeEventListener("click", linkEvent, true);
|
|
65
|
+
doc.addEventListener("click", preventDefault, true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function enableHref(element) {
|
|
70
|
+
for (const doc of linkDocuments(element)) {
|
|
71
|
+
doc.link = { islink: "true" };
|
|
72
|
+
doc.removeEventListener("click", preventDefault, true);
|
|
73
|
+
doc.addEventListener("click", linkEvent, true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function preventDefault(event) {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Actions.init([
|
|
82
|
+
{
|
|
83
|
+
name: "disableHref",
|
|
84
|
+
endEvent: "disableHref",
|
|
85
|
+
callback: action => {
|
|
86
|
+
disableHref(action.element);
|
|
87
|
+
action.end();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "enableHref",
|
|
92
|
+
endEvent: "enableHref",
|
|
93
|
+
callback: action => {
|
|
94
|
+
enableHref(action.element);
|
|
95
|
+
action.end();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
Actions.href = href;
|
|
101
|
+
init();
|
|
102
|
+
|
|
103
|
+
export { open, disableHref, enableHref };
|
|
104
|
+
export default { open, disableHref, enableHref };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import observer from "@cocreate/observer";
|
|
2
|
+
import { relayEvents } from "@cocreate/utils/iframe";
|
|
3
|
+
|
|
4
|
+
export function clickedElement(frames) {
|
|
5
|
+
for (const frame of frames) {
|
|
6
|
+
try {
|
|
7
|
+
const frameDocument = frame.contentDocument;
|
|
8
|
+
if (frameDocument.clickedElement === undefined) {
|
|
9
|
+
frameDocument.addEventListener("click", (e) => {
|
|
10
|
+
frame.clickedElement = e.target;
|
|
11
|
+
frameDocument.clickedElement = e.target;
|
|
12
|
+
const ClickedElementEvent = frame.ownerDocument.defaultView.CustomEvent;
|
|
13
|
+
frame.dispatchEvent(new ClickedElementEvent("clickedElement", {
|
|
14
|
+
bubbles: true,
|
|
15
|
+
detail: {
|
|
16
|
+
cursor: { x: e.clientX, y: e.clientY },
|
|
17
|
+
document: frameDocument
|
|
18
|
+
}
|
|
19
|
+
}));
|
|
20
|
+
});
|
|
21
|
+
frameDocument.clickedElement = null;
|
|
22
|
+
}
|
|
23
|
+
} catch (iframeError) {
|
|
24
|
+
console.log(
|
|
25
|
+
`Cross-origin frame handling failed for: ${frame}`,
|
|
26
|
+
iframeError
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function hoveredElement(frames) {
|
|
33
|
+
for (const frame of frames) {
|
|
34
|
+
try {
|
|
35
|
+
const frameDocument = frame.contentDocument;
|
|
36
|
+
if (frameDocument.hoveredElement === undefined) {
|
|
37
|
+
frameDocument.hoveredElement = null;
|
|
38
|
+
frame.hoveredElement = null;
|
|
39
|
+
|
|
40
|
+
const emitHoveredElement = (event) => {
|
|
41
|
+
const HoveredElementEvent = frame.ownerDocument.defaultView.CustomEvent;
|
|
42
|
+
frame.dispatchEvent(new HoveredElementEvent("hoveredElement", {
|
|
43
|
+
bubbles: true,
|
|
44
|
+
detail: {
|
|
45
|
+
cursor: { x: event.clientX, y: event.clientY },
|
|
46
|
+
document: frameDocument
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
frameDocument.addEventListener("mouseover", (event) => {
|
|
52
|
+
frame.hoveredElement = event.target;
|
|
53
|
+
frameDocument.hoveredElement = event.target;
|
|
54
|
+
emitHoveredElement(event);
|
|
55
|
+
});
|
|
56
|
+
frameDocument.addEventListener("mouseout", (event) => {
|
|
57
|
+
const nextElement = event.relatedTarget;
|
|
58
|
+
frame.hoveredElement = nextElement;
|
|
59
|
+
frameDocument.hoveredElement = nextElement;
|
|
60
|
+
emitHoveredElement(event);
|
|
61
|
+
});
|
|
62
|
+
frameDocument.addEventListener("mousemove", (event) => {
|
|
63
|
+
frame.hoveredElement = event.target;
|
|
64
|
+
frameDocument.hoveredElement = event.target;
|
|
65
|
+
emitHoveredElement(event);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
} catch (iframeError) {
|
|
69
|
+
console.log(
|
|
70
|
+
`Cross-origin frame handling failed for: ${frame}`,
|
|
71
|
+
iframeError
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clickedElement(document.querySelectorAll("iframe"));
|
|
78
|
+
hoveredElement(document.querySelectorAll("iframe"));
|
|
79
|
+
relayEvents(document.querySelectorAll("iframe"));
|
|
80
|
+
|
|
81
|
+
observer.init({
|
|
82
|
+
name: "CoCreateIframes",
|
|
83
|
+
types: ["addedNodes"],
|
|
84
|
+
selector: "iframe",
|
|
85
|
+
callback: (mutation) => {
|
|
86
|
+
clickedElement([mutation.target]);
|
|
87
|
+
hoveredElement([mutation.target]);
|
|
88
|
+
relayEvents([mutation.target]);
|
|
89
|
+
}
|
|
90
|
+
});
|