@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/initHandler.js
CHANGED
|
@@ -6,8 +6,15 @@ import { getAttributes } from "@cocreate/utils";
|
|
|
6
6
|
import { initDataKey, getData, getDataObject } from "./getDataHandler.js";
|
|
7
7
|
import { read, save } from "./crudHandler.js";
|
|
8
8
|
import { setData } from "./setDataHandler.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
elements,
|
|
11
|
+
forms,
|
|
12
|
+
keys,
|
|
13
|
+
debounce,
|
|
14
|
+
elementSubscriptions,
|
|
15
|
+
subscriptions
|
|
16
|
+
} from "./mapRegistry.js";
|
|
17
|
+
import { unsubscribe } from "./subscribeHandler.js";
|
|
11
18
|
|
|
12
19
|
const selector = "[storage], [database], [array], [render-json]";
|
|
13
20
|
|
|
@@ -36,36 +43,31 @@ export async function init(element) {
|
|
|
36
43
|
elements.has(element[i]) ||
|
|
37
44
|
element[i].tagName === "FORM" ||
|
|
38
45
|
element[i].getAttribute("crud") === "false"
|
|
39
|
-
)
|
|
46
|
+
) {
|
|
40
47
|
continue;
|
|
48
|
+
}
|
|
41
49
|
|
|
42
50
|
promises.push(
|
|
43
51
|
(async () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const el = element[i];
|
|
53
|
+
try {
|
|
54
|
+
const data = await initElement(el);
|
|
55
|
+
if (data) {
|
|
56
|
+
const dataKey = initDataKey(el, data);
|
|
57
|
+
// read() already batches elements by dataKey. Schedule each
|
|
58
|
+
// ready element without waiting for unrelated async fields.
|
|
59
|
+
await read(el, data, dataKey);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// Allow initialization to be retried if filter setup failed.
|
|
63
|
+
if (elements.get(el) === "") elements.delete(el);
|
|
64
|
+
console.error("[CoCreateElements] Failed to initialize element:", el, error);
|
|
48
65
|
}
|
|
49
66
|
})()
|
|
50
67
|
);
|
|
51
68
|
}
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
let dataObjects = new Map();
|
|
55
|
-
|
|
56
|
-
for (let res of results) {
|
|
57
|
-
if (res) dataObjects.set(res.string, res.data);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (dataObjects && dataObjects.size > 0) {
|
|
61
|
-
for (let key of dataObjects.keys()) {
|
|
62
|
-
let keyEntry = keys.get(key);
|
|
63
|
-
if (keyEntry) {
|
|
64
|
-
let { data, elements: mappedElements } = keyEntry;
|
|
65
|
-
read(Array.from(mappedElements.keys()), data, { string: key });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
70
|
+
await Promise.all(promises);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/**
|
|
@@ -76,14 +78,17 @@ export async function init(element) {
|
|
|
76
78
|
*/
|
|
77
79
|
export async function initElement(el) {
|
|
78
80
|
let data = getDataObject(el);
|
|
79
|
-
if (!data || !data.type)
|
|
81
|
+
if (!data || !data.type) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
80
84
|
|
|
81
85
|
if (
|
|
82
86
|
data.object &&
|
|
83
87
|
data.object !== "pending" &&
|
|
84
88
|
!/^[0-9a-fA-F]{24}$/.test(data.object)
|
|
85
|
-
)
|
|
89
|
+
) {
|
|
86
90
|
return;
|
|
91
|
+
}
|
|
87
92
|
|
|
88
93
|
if (!elements.has(el)) {
|
|
89
94
|
elements.set(el, "");
|
|
@@ -124,10 +129,10 @@ export async function initElement(el) {
|
|
|
124
129
|
await filter.init(el);
|
|
125
130
|
|
|
126
131
|
if (el.getFilter) {
|
|
127
|
-
data
|
|
132
|
+
data.filter = await el.getFilter();
|
|
128
133
|
|
|
129
134
|
el.setFilter = (filter) => {
|
|
130
|
-
data
|
|
135
|
+
data.filter = filter;
|
|
131
136
|
el.hasRead = false;
|
|
132
137
|
read(el, data);
|
|
133
138
|
};
|
|
@@ -160,16 +165,13 @@ export async function remove(element) {
|
|
|
160
165
|
|
|
161
166
|
form = form.elements;
|
|
162
167
|
for (let formEl of form.keys()) {
|
|
168
|
+
removeSubscription(formEl);
|
|
163
169
|
let key = elements.get(formEl);
|
|
164
170
|
if (key) {
|
|
165
171
|
let keyEntry = keys.get(key);
|
|
166
172
|
if (keyEntry) {
|
|
167
173
|
keyEntry.elements.delete(formEl);
|
|
168
|
-
if (!keyEntry.elements.size)
|
|
169
|
-
// Last element watching this key was removed -> trigger socket unsubscribe
|
|
170
|
-
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
171
|
-
keys.delete(key);
|
|
172
|
-
}
|
|
174
|
+
if (!keyEntry.elements.size) keys.delete(key);
|
|
173
175
|
}
|
|
174
176
|
elements.delete(formEl);
|
|
175
177
|
}
|
|
@@ -180,15 +182,13 @@ export async function remove(element) {
|
|
|
180
182
|
let keyEntry = keys.get(key);
|
|
181
183
|
if (keyEntry) {
|
|
182
184
|
keyEntry.elements.delete(el);
|
|
183
|
-
if (!keyEntry.elements.size)
|
|
184
|
-
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
185
|
-
keys.delete(key);
|
|
186
|
-
}
|
|
185
|
+
if (!keyEntry.elements.size) keys.delete(key);
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
188
|
elements.delete(el);
|
|
190
189
|
forms.delete(el);
|
|
191
190
|
} else {
|
|
191
|
+
removeSubscription(el);
|
|
192
192
|
let key = elements.get(el);
|
|
193
193
|
if (key) {
|
|
194
194
|
if (el.getFilter) filter.filters.delete(el);
|
|
@@ -198,11 +198,7 @@ export async function remove(element) {
|
|
|
198
198
|
keyEntry.elements.delete(el);
|
|
199
199
|
elements.delete(el);
|
|
200
200
|
|
|
201
|
-
if (!keyEntry.elements.size)
|
|
202
|
-
// Last element watching this key was removed -> trigger socket unsubscribe
|
|
203
|
-
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
204
|
-
keys.delete(key);
|
|
205
|
-
}
|
|
201
|
+
if (!keyEntry.elements.size) keys.delete(key);
|
|
206
202
|
} else {
|
|
207
203
|
elements.delete(el);
|
|
208
204
|
}
|
|
@@ -220,11 +216,27 @@ export async function remove(element) {
|
|
|
220
216
|
}
|
|
221
217
|
}
|
|
222
218
|
|
|
219
|
+
function removeSubscription(element) {
|
|
220
|
+
const subscription = elementSubscriptions.get(element);
|
|
221
|
+
if (!subscription) return;
|
|
222
|
+
|
|
223
|
+
const subscriptionEntry = subscriptions.get(subscription);
|
|
224
|
+
if (subscriptionEntry) {
|
|
225
|
+
subscriptionEntry.elements.delete(element);
|
|
226
|
+
if (!subscriptionEntry.elements.size) {
|
|
227
|
+
unsubscribe(subscriptionEntry.subscription);
|
|
228
|
+
subscriptions.delete(subscription);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
elementSubscriptions.delete(element);
|
|
233
|
+
}
|
|
234
|
+
|
|
223
235
|
/**
|
|
224
236
|
* Initializes global CRUD socket response listeners.
|
|
225
237
|
*/
|
|
226
238
|
export function initSocket() {
|
|
227
|
-
const type = ["storage", "database", "array", "index", "object"
|
|
239
|
+
const type = ["storage", "database", "array", "index", "object"];
|
|
228
240
|
const method = ["create", "update", "delete"];
|
|
229
241
|
|
|
230
242
|
for (let i = 0; i < type.length; i++) {
|
|
@@ -233,9 +245,11 @@ export function initSocket() {
|
|
|
233
245
|
|
|
234
246
|
CRUD.listen(action, function (data) {
|
|
235
247
|
if (
|
|
236
|
-
data.
|
|
248
|
+
data.key &&
|
|
249
|
+
!data.updateDB &&
|
|
237
250
|
data.status === "received" &&
|
|
238
|
-
|
|
251
|
+
!data.method.includes(".delete") &&
|
|
252
|
+
CRUD.socket.frameId === data.frameId
|
|
239
253
|
)
|
|
240
254
|
return;
|
|
241
255
|
if (data.rendered) return;
|
|
@@ -253,6 +267,7 @@ Observer.init({
|
|
|
253
267
|
types: ["addedNodes"],
|
|
254
268
|
selector: selector,
|
|
255
269
|
callback: function (mutation) {
|
|
270
|
+
if (mutation.move) return;
|
|
256
271
|
init([mutation.target]);
|
|
257
272
|
}
|
|
258
273
|
});
|
|
@@ -262,7 +277,7 @@ Observer.init({
|
|
|
262
277
|
types: ["removedNodes"],
|
|
263
278
|
selector: selector,
|
|
264
279
|
callback: function (mutation) {
|
|
265
|
-
if (mutation.
|
|
280
|
+
if (mutation.move) return;
|
|
266
281
|
remove(mutation.target);
|
|
267
282
|
}
|
|
268
283
|
});
|
|
@@ -273,6 +288,8 @@ Observer.init({
|
|
|
273
288
|
attributeFilter: [
|
|
274
289
|
"organization_id",
|
|
275
290
|
"host",
|
|
291
|
+
"namespace",
|
|
292
|
+
"room",
|
|
276
293
|
"storage",
|
|
277
294
|
"database",
|
|
278
295
|
"array",
|
|
@@ -290,6 +307,39 @@ Observer.init({
|
|
|
290
307
|
}
|
|
291
308
|
});
|
|
292
309
|
|
|
310
|
+
Observer.init({
|
|
311
|
+
name: "CoCreateElementsReadAttribute",
|
|
312
|
+
types: ["attributes"],
|
|
313
|
+
attributeFilter: ["read"],
|
|
314
|
+
selector,
|
|
315
|
+
callback: async function (mutation) {
|
|
316
|
+
const el = mutation.target;
|
|
317
|
+
const currentValue = el.getAttribute("read");
|
|
318
|
+
if (currentValue === mutation.oldValue) return;
|
|
319
|
+
|
|
320
|
+
if (currentValue === "false") {
|
|
321
|
+
el.hasRead = false;
|
|
322
|
+
for (const [key, pending] of debounce) {
|
|
323
|
+
pending.elements?.delete(el);
|
|
324
|
+
if (pending.elements && !pending.elements.size) {
|
|
325
|
+
clearTimeout(pending.timer);
|
|
326
|
+
debounce.delete(key);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!elements.has(el)) {
|
|
333
|
+
await init([el]);
|
|
334
|
+
} else if (el.getFilter && el.setFilter) {
|
|
335
|
+
el.setFilter(await el.getFilter());
|
|
336
|
+
} else {
|
|
337
|
+
el.hasRead = false;
|
|
338
|
+
await read(el);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
293
343
|
init();
|
|
294
344
|
|
|
295
|
-
export default { init, initElement, initSocket, remove };
|
|
345
|
+
export default { init, initElement, initSocket, remove };
|
package/src/mapRegistry.js
CHANGED
|
@@ -2,5 +2,14 @@ export const elements = new WeakMap();
|
|
|
2
2
|
export const forms = new WeakMap();
|
|
3
3
|
export const keys = new Map();
|
|
4
4
|
export const debounce = new Map();
|
|
5
|
+
export const elementSubscriptions = new WeakMap();
|
|
6
|
+
export const subscriptions = new Map();
|
|
5
7
|
|
|
6
|
-
export default {
|
|
8
|
+
export default {
|
|
9
|
+
elements,
|
|
10
|
+
forms,
|
|
11
|
+
keys,
|
|
12
|
+
debounce,
|
|
13
|
+
elementSubscriptions,
|
|
14
|
+
subscriptions
|
|
15
|
+
}
|