@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/setDataHandler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dotNotationToObject,
|
|
3
3
|
queryData,
|
|
4
|
+
searchData,
|
|
4
5
|
sortData,
|
|
5
6
|
getAttributes,
|
|
6
7
|
checkValue,
|
|
@@ -8,21 +9,25 @@ import {
|
|
|
8
9
|
} from "@cocreate/utils";
|
|
9
10
|
import CRUD from "@cocreate/crud-client";
|
|
10
11
|
import filter from "@cocreate/filter";
|
|
11
|
-
import { elements, forms, keys } from "./mapRegistry.js"
|
|
12
|
+
import { elements, forms, keys } from "./mapRegistry.js";
|
|
13
|
+
import { getDataElements, getDataObject } from "./getDataHandler.js";
|
|
12
14
|
|
|
13
|
-
export function setDataType(element, data, action) {
|
|
15
|
+
export async function setDataType(element, data, action) {
|
|
14
16
|
// TODO: if an array name is updated, the attibute array="" needs to be updated.
|
|
15
17
|
|
|
16
18
|
if (!data) return;
|
|
17
19
|
|
|
20
|
+
const dataElements = new Set(getDataElements(data));
|
|
21
|
+
|
|
18
22
|
let form;
|
|
19
23
|
if (element.tagName === "FORM") {
|
|
20
24
|
form = element;
|
|
21
|
-
} else if (element.parentElement){
|
|
25
|
+
} else if (element.parentElement) {
|
|
22
26
|
form = element.parentElement.closest("form");
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
if (!form) {
|
|
30
|
+
dataElements.add(element);
|
|
26
31
|
if (data.type === "object") {
|
|
27
32
|
if (data.object?.[0]?._id) {
|
|
28
33
|
element.setAttribute("object", data.object[0]._id);
|
|
@@ -32,12 +37,31 @@ export function setDataType(element, data, action) {
|
|
|
32
37
|
}
|
|
33
38
|
} else {
|
|
34
39
|
let formObject = forms.get(form);
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const fieldsets = form.querySelectorAll('fieldset[object="pending"]');
|
|
41
|
+
for (const scope of [...fieldsets, form]) {
|
|
42
|
+
if (scope.getAttribute("object") === "pending") {
|
|
43
|
+
const object = data.object?.[0];
|
|
44
|
+
if (scope.hasAttribute("array")) {
|
|
45
|
+
const arrays = scope.getAttribute("array").split(",");
|
|
46
|
+
const objectArrays = Array.isArray(object?.$array)
|
|
47
|
+
? object.$array
|
|
48
|
+
: [object?.$array];
|
|
49
|
+
if (object?._id && arrays.some((array) => {
|
|
50
|
+
const name = array.trim();
|
|
51
|
+
return name !== "" && objectArrays.includes(name);
|
|
52
|
+
})) {
|
|
53
|
+
scope.setAttribute("object", object._id);
|
|
54
|
+
}
|
|
55
|
+
} else if (object?._id) {
|
|
56
|
+
scope.setAttribute("object", object._id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
37
60
|
|
|
38
|
-
|
|
61
|
+
const typeElements = formObject?.types?.get(data.type);
|
|
39
62
|
|
|
40
|
-
for (let [el, elData] of
|
|
63
|
+
for (let [el, elData] of typeElements?.entries() || []) {
|
|
64
|
+
dataElements.add(el);
|
|
41
65
|
if (data.type === "object") {
|
|
42
66
|
if (!elData.object || elData.object === "pending") {
|
|
43
67
|
elData.object = data.object[0]._id;
|
|
@@ -48,9 +72,10 @@ export function setDataType(element, data, action) {
|
|
|
48
72
|
el.setAttribute(data.type, data[data.type].name);
|
|
49
73
|
}
|
|
50
74
|
}
|
|
51
|
-
setData(Array.from(elements.keys()), data);
|
|
52
75
|
}
|
|
53
76
|
|
|
77
|
+
if (dataElements.size) await setData(Array.from(dataElements), data);
|
|
78
|
+
|
|
54
79
|
if (action) {
|
|
55
80
|
action.element.dispatchEvent(
|
|
56
81
|
new CustomEvent("saved", {
|
|
@@ -107,14 +132,22 @@ export async function setData(element, data) {
|
|
|
107
132
|
type = data.method.split(".")[0];
|
|
108
133
|
} else if (type == "key") type = "object";
|
|
109
134
|
|
|
110
|
-
if (data
|
|
111
|
-
let sortedData = sort$in(data[type], data
|
|
135
|
+
if (data.filter && data.filter.query) {
|
|
136
|
+
let sortedData = sort$in(data[type], data.filter.query);
|
|
112
137
|
if (sortedData && sortedData.length) data[type] = sortedData;
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
for (let el of element) {
|
|
116
|
-
//
|
|
117
|
-
if (el.hasAttribute("rendered"))
|
|
141
|
+
// Keep SSR content for this element without skipping its batch siblings.
|
|
142
|
+
if (el.hasAttribute("rendered")) {
|
|
143
|
+
el.removeAttribute("rendered");
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
data.method?.endsWith(".delete") &&
|
|
148
|
+
!(el.getFilter || el.renderValue)
|
|
149
|
+
)
|
|
150
|
+
continue;
|
|
118
151
|
if (el.hasAttribute("render-json")) {
|
|
119
152
|
console.log("render-json", "");
|
|
120
153
|
}
|
|
@@ -124,11 +157,24 @@ export async function setData(element, data) {
|
|
|
124
157
|
if (action && ["database", "array", "object", "key"].includes(action))
|
|
125
158
|
continue;
|
|
126
159
|
|
|
127
|
-
|
|
128
|
-
|
|
160
|
+
const { key, isRead, isUpdate, isListen, isCrdt } = getAttributes(el);
|
|
161
|
+
if (key !== undefined && typeof key !== "string") {
|
|
162
|
+
console.error("[CoCreate elements] Ignoring element with a non-string key", {
|
|
163
|
+
key,
|
|
164
|
+
keyType: typeof key,
|
|
165
|
+
element: el
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
129
168
|
}
|
|
130
169
|
|
|
131
|
-
const
|
|
170
|
+
const hasRenderController =
|
|
171
|
+
el.hasAttribute("render-query") || el.hasAttribute("render-json");
|
|
172
|
+
|
|
173
|
+
// array/object without a key establishes CRUD identity only. Do not
|
|
174
|
+
// write the response into the element unless it explicitly controls a
|
|
175
|
+
// render target.
|
|
176
|
+
if (key === undefined && !hasRenderController) continue;
|
|
177
|
+
|
|
132
178
|
let elementType = el.getAttribute("type");
|
|
133
179
|
if (elementType !== "file" && (el.getFilter || el.renderValue))
|
|
134
180
|
await filterData(el, data, type, key);
|
|
@@ -188,6 +234,24 @@ export async function setData(element, data) {
|
|
|
188
234
|
}
|
|
189
235
|
|
|
190
236
|
async function filterData(element, data, type, key) {
|
|
237
|
+
if (key === "$length" && (data.error || !Array.isArray(data[type]))) return;
|
|
238
|
+
if (
|
|
239
|
+
data.method &&
|
|
240
|
+
!data.method.endsWith(".read") &&
|
|
241
|
+
(element.getFilter || element.renderValue)
|
|
242
|
+
) {
|
|
243
|
+
// A write payload is a document delta, not the result of this count's
|
|
244
|
+
// query. Keep the displayed count until its own filtered read completes.
|
|
245
|
+
// Re-read even for partial updates: a document may have left the query.
|
|
246
|
+
if (key === "$length" && element.getFilter && element.setFilter) {
|
|
247
|
+
if (data.error || !data[type]?.length) return;
|
|
248
|
+
element.setFilter(await element.getFilter());
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
await checkFilters(element, data, type);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
191
255
|
let operator = "",
|
|
192
256
|
value;
|
|
193
257
|
|
|
@@ -250,24 +314,18 @@ async function filterData(element, data, type, key) {
|
|
|
250
314
|
element.renderValue &&
|
|
251
315
|
data.method &&
|
|
252
316
|
data.method.endsWith(".read") &&
|
|
253
|
-
data
|
|
254
|
-
(data
|
|
317
|
+
data.filter &&
|
|
318
|
+
(data.filter.overwrite || !isRendered)
|
|
255
319
|
) {
|
|
256
320
|
await element.renderValue(data);
|
|
257
321
|
} else if (
|
|
258
|
-
(data
|
|
322
|
+
(data.filter && data.filter.loadMore) ||
|
|
259
323
|
(data.method && data.method.endsWith(".read") && isRendered)
|
|
260
324
|
) {
|
|
261
325
|
await loadMore(element, data, type);
|
|
262
|
-
} else if (
|
|
263
|
-
element.getFilter &&
|
|
264
|
-
data.method &&
|
|
265
|
-
!data.method.endsWith(".read")
|
|
266
|
-
) {
|
|
267
|
-
await checkFilters(element, data, type);
|
|
268
326
|
} else if (element.renderValue) {
|
|
269
327
|
if (key)
|
|
270
|
-
data = {[key]: data}
|
|
328
|
+
data = { [key]: data }
|
|
271
329
|
await element.renderValue(data);
|
|
272
330
|
} else if (key === "$length") {
|
|
273
331
|
element.setValue(data[type].length);
|
|
@@ -289,68 +347,84 @@ async function filterData(element, data, type, key) {
|
|
|
289
347
|
value = data[type][0];
|
|
290
348
|
} else if (data) element.setValue(data);
|
|
291
349
|
|
|
292
|
-
if (data
|
|
350
|
+
if (data.filter) {
|
|
293
351
|
let filterElement = filter.filters.get(element);
|
|
294
|
-
if (filterElement) filterElement.index = data
|
|
352
|
+
if (filterElement) filterElement.index = data.filter.index;
|
|
295
353
|
}
|
|
296
354
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
355
|
+
const evt = new CustomEvent("fetchedData", {
|
|
356
|
+
bubbles: true,
|
|
357
|
+
detail: data
|
|
358
|
+
});
|
|
300
359
|
element.dispatchEvent(evt);
|
|
360
|
+
|
|
361
|
+
if (!data[type] || !Array.isArray(data[type]) || !data[type].length) return;
|
|
301
362
|
}
|
|
302
363
|
|
|
303
364
|
async function loadMore(element, data, type, sort) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (index >= 0) {
|
|
322
|
-
clonedData[type].splice(i, 1);
|
|
323
|
-
i--; // Adjust the index to account for the removed item
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (clonedData[type].length > 0) {
|
|
327
|
-
await element.renderValue(clonedData);
|
|
365
|
+
const renderedData = await element.getData();
|
|
366
|
+
if (!renderedData || !renderedData[type]) return;
|
|
367
|
+
|
|
368
|
+
const key = type === "object" ? "_id" : "name";
|
|
369
|
+
const renderedKeys = new Set(renderedData[type].map(item => item[key]));
|
|
370
|
+
const records = data[type].filter(item => {
|
|
371
|
+
if (renderedKeys.has(item[key])) return false;
|
|
372
|
+
renderedKeys.add(item[key]);
|
|
373
|
+
return true;
|
|
374
|
+
});
|
|
375
|
+
if (records.length) {
|
|
376
|
+
await element.renderValue({
|
|
377
|
+
...data,
|
|
378
|
+
[type]: records,
|
|
379
|
+
filter: { ...data.filter, loadMore: true, overwrite: false }
|
|
380
|
+
});
|
|
328
381
|
}
|
|
329
382
|
}
|
|
330
383
|
|
|
331
384
|
async function checkFilters(element, data, type) {
|
|
385
|
+
if (type && data[type]) {
|
|
386
|
+
const applyUpdate = (record) => {
|
|
387
|
+
if (!record?.$update) return record;
|
|
388
|
+
const { $update, ...fields } = record;
|
|
389
|
+
return { ...fields, ...$update };
|
|
390
|
+
};
|
|
391
|
+
data = {
|
|
392
|
+
...data,
|
|
393
|
+
[type]: Array.isArray(data[type])
|
|
394
|
+
? data[type].map(applyUpdate)
|
|
395
|
+
: applyUpdate(data[type])
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
332
399
|
let localData;
|
|
333
400
|
if (!element.getData) {
|
|
334
|
-
// TODO:
|
|
401
|
+
// TODO: getDataObject is related to render or form, but filter could exist on an input which does not have getData
|
|
335
402
|
// TODO: generate an object of current element key: value to use with filter. because filter is used object is not defined
|
|
336
|
-
localData =
|
|
337
|
-
if (Array.isArray(localData[type]))
|
|
403
|
+
localData = getDataObject(element);
|
|
404
|
+
if (localData && Array.isArray(localData[type])) {
|
|
405
|
+
localData = localData[type][0];
|
|
406
|
+
}
|
|
338
407
|
} else localData = await element.getData();
|
|
339
408
|
|
|
340
409
|
if (!localData) return;
|
|
341
410
|
|
|
411
|
+
let filter = localData.filter
|
|
342
412
|
let newData;
|
|
343
413
|
if (type) {
|
|
344
414
|
localData = localData[type];
|
|
345
415
|
newData = data[type];
|
|
346
416
|
} else newData = data;
|
|
347
417
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
418
|
+
if (localData == null) {
|
|
419
|
+
if (element.renderValue) localData = [];
|
|
420
|
+
else return;
|
|
351
421
|
}
|
|
352
422
|
|
|
353
|
-
if (
|
|
423
|
+
if (
|
|
424
|
+
newData == null ||
|
|
425
|
+
(Array.isArray(newData) && newData.length === 0)
|
|
426
|
+
)
|
|
427
|
+
return;
|
|
354
428
|
|
|
355
429
|
if (Array.isArray(localData)) {
|
|
356
430
|
if (Array.isArray(newData)) {
|
|
@@ -361,6 +435,9 @@ async function checkFilters(element, data, type) {
|
|
|
361
435
|
checkIndex(element, data, localData, newData, type, filter);
|
|
362
436
|
}
|
|
363
437
|
} else {
|
|
438
|
+
if (data.method?.endsWith(".delete")) return;
|
|
439
|
+
|
|
440
|
+
let matched = false;
|
|
364
441
|
let primaryKey;
|
|
365
442
|
if (type === "object") {
|
|
366
443
|
primaryKey = "_id";
|
|
@@ -370,21 +447,27 @@ async function checkFilters(element, data, type) {
|
|
|
370
447
|
|
|
371
448
|
if (Array.isArray(newData)) {
|
|
372
449
|
for (let i = 0; i < newData.length; i++) {
|
|
450
|
+
if (!newData[i]) continue;
|
|
373
451
|
if (
|
|
374
452
|
typeof localData === "string" &&
|
|
375
453
|
localData === newData[i][primaryKey]
|
|
376
454
|
) {
|
|
377
455
|
localData = newData[i];
|
|
378
|
-
|
|
456
|
+
matched = true;
|
|
457
|
+
} else if (localData[primaryKey] && localData[primaryKey] === newData[i][primaryKey]) {
|
|
379
458
|
localData = dotNotationToObject(newData[i], localData);
|
|
459
|
+
matched = true;
|
|
380
460
|
}
|
|
381
461
|
}
|
|
382
462
|
} else if (typeof localData === "string" && localData === newData[primaryKey]) {
|
|
383
463
|
localData = newData;
|
|
384
|
-
|
|
464
|
+
matched = true;
|
|
465
|
+
} else if (localData[primaryKey] && localData[primaryKey] === newData[primaryKey]) {
|
|
385
466
|
localData = dotNotationToObject(newData, localData);
|
|
467
|
+
matched = true;
|
|
386
468
|
}
|
|
387
469
|
|
|
470
|
+
if (!matched) return;
|
|
388
471
|
if (element.renderValue) element.renderValue(data);
|
|
389
472
|
// render({ element, data, key: type });
|
|
390
473
|
else if (data) element.setValue(localData);
|
|
@@ -392,50 +475,51 @@ async function checkFilters(element, data, type) {
|
|
|
392
475
|
}
|
|
393
476
|
|
|
394
477
|
function checkIndex(element, data, localData, newData, type, filter) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
478
|
+
if (!newData) return;
|
|
479
|
+
const primaryKey = type === "object" ? "_id" : "name";
|
|
480
|
+
const primaryValue = newData[primaryKey];
|
|
481
|
+
let index = localData.findIndex(
|
|
482
|
+
(item) => item?.[primaryKey] === primaryValue
|
|
483
|
+
);
|
|
484
|
+
const isMatch =
|
|
485
|
+
(!filter?.query || queryData(newData, filter.query)) &&
|
|
486
|
+
(!filter?.search || searchData(newData, filter.search));
|
|
487
|
+
|
|
488
|
+
if (!data.filter) data.filter = {};
|
|
489
|
+
|
|
490
|
+
if (data.method.endsWith(".delete") || !isMatch) {
|
|
405
491
|
if (index === -1) return;
|
|
406
|
-
data
|
|
492
|
+
data.filter.remove = true;
|
|
407
493
|
localData.splice(index, 1);
|
|
408
494
|
} else {
|
|
409
495
|
if (data.method.endsWith(".create")) {
|
|
410
496
|
if (index === -1) {
|
|
411
|
-
data
|
|
412
|
-
|
|
413
|
-
else index = localData.length;
|
|
497
|
+
data.filter.create = true;
|
|
498
|
+
index = localData.length;
|
|
414
499
|
localData.push(newData);
|
|
415
500
|
}
|
|
416
501
|
} else {
|
|
417
502
|
if (index === -1) return;
|
|
418
|
-
data
|
|
419
|
-
data
|
|
503
|
+
data.filter.update = true;
|
|
504
|
+
data.filter.currentIndex = index;
|
|
420
505
|
localData[index] = dotNotationToObject(newData, localData[index]);
|
|
421
|
-
if (filter && filter.sort) {
|
|
422
|
-
localData[index].isNewData = true;
|
|
423
|
-
}
|
|
424
506
|
}
|
|
425
507
|
|
|
426
508
|
if (filter && filter.sort) {
|
|
427
509
|
localData = sortData(localData, filter.sort);
|
|
428
|
-
index = localData.findIndex(
|
|
510
|
+
index = localData.findIndex(
|
|
511
|
+
(item) => item?.[primaryKey] === primaryValue
|
|
512
|
+
);
|
|
429
513
|
}
|
|
430
514
|
}
|
|
431
515
|
|
|
432
516
|
if (index >= 0) {
|
|
433
|
-
if (data
|
|
434
|
-
delete data
|
|
435
|
-
data
|
|
517
|
+
if (data.filter.currentIndex === index)
|
|
518
|
+
delete data.filter.currentIndex;
|
|
519
|
+
data.filter.index = index;
|
|
436
520
|
if (element.renderValue) element.renderValue(data);
|
|
437
521
|
else if (data) element.setValue(localData);
|
|
438
522
|
}
|
|
439
523
|
}
|
|
440
524
|
|
|
441
|
-
export default { setDataType, setData }
|
|
525
|
+
export default { setDataType, setData }
|