@headless-tree/core 0.0.0-20250509212452 → 0.0.0-20250511185653
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 +1 -1
- package/lib/cjs/features/async-data-loader/feature.js +2 -15
- package/lib/cjs/features/async-data-loader/types.d.ts +0 -4
- package/lib/cjs/features/checkboxes/feature.js +29 -63
- package/lib/cjs/features/checkboxes/types.d.ts +3 -3
- package/lib/cjs/features/sync-data-loader/feature.js +0 -2
- package/lib/cjs/features/sync-data-loader/types.d.ts +2 -2
- package/lib/esm/features/async-data-loader/feature.js +2 -15
- package/lib/esm/features/async-data-loader/types.d.ts +0 -4
- package/lib/esm/features/checkboxes/feature.js +29 -63
- package/lib/esm/features/checkboxes/types.d.ts +3 -3
- package/lib/esm/features/sync-data-loader/feature.js +0 -2
- package/lib/esm/features/sync-data-loader/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/features/async-data-loader/feature.ts +2 -21
- package/src/features/async-data-loader/types.ts +0 -4
- package/src/features/checkboxes/checkboxes.spec.ts +134 -0
- package/src/features/checkboxes/feature.ts +33 -64
- package/src/features/checkboxes/types.ts +3 -3
- package/src/features/sync-data-loader/feature.ts +0 -3
- package/src/features/sync-data-loader/types.ts +2 -2
- package/src/features/tree/types.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -29,17 +29,14 @@ const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, functio
|
|
|
29
29
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
30
30
|
(_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
|
|
31
31
|
(_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
|
|
32
|
-
return item;
|
|
33
32
|
});
|
|
34
33
|
const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
34
|
var _a, _b, _c, _d, _e;
|
|
36
35
|
const config = tree.getConfig();
|
|
37
36
|
const dataRef = getDataRef(tree);
|
|
38
|
-
let childrenIds;
|
|
39
|
-
// TODO is folder check?
|
|
40
37
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
41
38
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
42
|
-
childrenIds = children.map((c) => c.id);
|
|
39
|
+
const childrenIds = children.map((c) => c.id);
|
|
43
40
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
44
41
|
children.forEach(({ id, data }) => {
|
|
45
42
|
var _a, _b, _c;
|
|
@@ -53,7 +50,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
53
50
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
|
|
54
51
|
}
|
|
55
52
|
else {
|
|
56
|
-
childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
53
|
+
const childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
57
54
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
58
55
|
(_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
|
|
59
56
|
tree.rebuildTree();
|
|
@@ -61,7 +58,6 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
61
58
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
62
59
|
(_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
|
|
63
60
|
(_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
|
|
64
|
-
return childrenIds;
|
|
65
61
|
});
|
|
66
62
|
exports.asyncDataLoaderFeature = {
|
|
67
63
|
key: "async-data-loader",
|
|
@@ -87,7 +83,6 @@ exports.asyncDataLoaderFeature = {
|
|
|
87
83
|
});
|
|
88
84
|
}),
|
|
89
85
|
waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
90
|
-
// TODO replace inner implementation with load() fns
|
|
91
86
|
tree.retrieveChildrenIds(itemId);
|
|
92
87
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
93
88
|
return;
|
|
@@ -101,14 +96,6 @@ exports.asyncDataLoaderFeature = {
|
|
|
101
96
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
102
97
|
});
|
|
103
98
|
}),
|
|
104
|
-
loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
105
|
-
var _b;
|
|
106
|
-
return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
|
|
107
|
-
}),
|
|
108
|
-
loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
109
|
-
var _b;
|
|
110
|
-
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
111
|
-
}),
|
|
112
99
|
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
113
100
|
var _a, _b;
|
|
114
101
|
const config = tree.getConfig();
|
|
@@ -27,12 +27,8 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
27
27
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
28
|
};
|
|
29
29
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
30
|
-
/** @deprecated use loadItemData instead */
|
|
31
30
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
32
|
-
/** @deprecated use loadChildrenIds instead */
|
|
33
31
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
34
|
-
loadItemData: (itemId: string) => Promise<T>;
|
|
35
|
-
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
36
32
|
};
|
|
37
33
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
38
34
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -1,46 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.checkboxesFeature = void 0;
|
|
13
4
|
const utils_1 = require("../../utils");
|
|
14
5
|
const types_1 = require("./types");
|
|
15
|
-
|
|
16
|
-
* Cases for checking:
|
|
17
|
-
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
18
|
-
* - Check an explicitly unchecked item in a checked folder
|
|
19
|
-
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
20
|
-
*
|
|
21
|
-
* Cases for unchecking:
|
|
22
|
-
* - Uncheck a checked item in an indeterminate folder
|
|
23
|
-
* - Uncheck an explicitly unchecked item in an checked folder
|
|
24
|
-
*/
|
|
25
|
-
const fetchAllDescendants = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
-
const children = yield tree.loadChildrenIds(itemId);
|
|
27
|
-
return [
|
|
28
|
-
itemId,
|
|
29
|
-
...(yield Promise.all(children.map((child) => fetchAllDescendants(tree, child)))).flat(),
|
|
30
|
-
];
|
|
31
|
-
});
|
|
6
|
+
const errors_1 = require("../../utilities/errors");
|
|
32
7
|
const getAllLoadedDescendants = (tree, itemId) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
8
|
+
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
9
|
+
return [itemId];
|
|
10
|
+
}
|
|
11
|
+
return tree
|
|
12
|
+
.retrieveChildrenIds(itemId)
|
|
13
|
+
.map((child) => getAllLoadedDescendants(tree, child))
|
|
14
|
+
.flat();
|
|
38
15
|
};
|
|
39
16
|
exports.checkboxesFeature = {
|
|
40
17
|
key: "checkboxes",
|
|
41
18
|
overwrites: ["selection"],
|
|
42
19
|
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
43
|
-
getDefaultConfig: (defaultConfig, tree) =>
|
|
20
|
+
getDefaultConfig: (defaultConfig, tree) => {
|
|
21
|
+
var _a;
|
|
22
|
+
const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
|
|
23
|
+
if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
|
|
24
|
+
(0, errors_1.throwError)(`!canCheckFolders not supported with async trees`);
|
|
25
|
+
}
|
|
26
|
+
return Object.assign({ setCheckedItems: (0, utils_1.makeStateUpdater)("checkedItems", tree), canCheckFolders: hasAsyncLoader !== null && hasAsyncLoader !== void 0 ? hasAsyncLoader : false }, defaultConfig);
|
|
27
|
+
},
|
|
44
28
|
stateHandlerNames: {
|
|
45
29
|
checkedItems: "setCheckedItems",
|
|
46
30
|
},
|
|
@@ -50,37 +34,33 @@ exports.checkboxesFeature = {
|
|
|
50
34
|
},
|
|
51
35
|
},
|
|
52
36
|
itemInstance: {
|
|
53
|
-
getCheckboxProps: ({ item
|
|
37
|
+
getCheckboxProps: ({ item }) => {
|
|
54
38
|
const checkedState = item.getCheckedState();
|
|
55
|
-
// console.log("prop", itemId, checkedState);
|
|
56
39
|
return {
|
|
57
40
|
onChange: item.toggleCheckedState,
|
|
58
41
|
checked: checkedState === types_1.CheckedState.Checked,
|
|
59
42
|
ref: (r) => {
|
|
60
43
|
if (r) {
|
|
61
|
-
// console.log("ref", itemId, checkedState);
|
|
62
44
|
r.indeterminate = checkedState === types_1.CheckedState.Indeterminate;
|
|
63
45
|
}
|
|
64
46
|
},
|
|
65
47
|
};
|
|
66
48
|
},
|
|
67
|
-
toggleCheckedState: (
|
|
49
|
+
toggleCheckedState: ({ item }) => {
|
|
68
50
|
if (item.getCheckedState() === types_1.CheckedState.Checked) {
|
|
69
|
-
|
|
51
|
+
item.setUnchecked();
|
|
70
52
|
}
|
|
71
53
|
else {
|
|
72
|
-
|
|
54
|
+
item.setChecked();
|
|
73
55
|
}
|
|
74
|
-
}
|
|
56
|
+
},
|
|
75
57
|
getCheckedState: ({ item, tree, itemId }) => {
|
|
76
|
-
// TODO checkedcache
|
|
77
58
|
const { checkedItems } = tree.getState();
|
|
78
59
|
if (checkedItems.includes(itemId)) {
|
|
79
60
|
return types_1.CheckedState.Checked;
|
|
80
61
|
}
|
|
81
|
-
if (item.isFolder()) {
|
|
62
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
82
63
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
83
|
-
console.log("descendants of ", itemId, descendants);
|
|
84
64
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
85
65
|
return types_1.CheckedState.Checked;
|
|
86
66
|
}
|
|
@@ -88,41 +68,27 @@ exports.checkboxesFeature = {
|
|
|
88
68
|
return types_1.CheckedState.Indeterminate;
|
|
89
69
|
}
|
|
90
70
|
}
|
|
91
|
-
// if (
|
|
92
|
-
// item.isFolder() &&
|
|
93
|
-
// checkedItems.some((checkedItem) =>
|
|
94
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
95
|
-
// )
|
|
96
|
-
// ) {
|
|
97
|
-
// // TODO for every descendent, not every checked item
|
|
98
|
-
// return checkedItems.every((checkedItem) =>
|
|
99
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
100
|
-
// )
|
|
101
|
-
// ? CheckedState.Checked
|
|
102
|
-
// : CheckedState.Indeterminate;
|
|
103
|
-
// }
|
|
104
71
|
return types_1.CheckedState.Unchecked;
|
|
105
72
|
},
|
|
106
|
-
setChecked: (
|
|
73
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
107
74
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
108
75
|
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
109
76
|
}
|
|
110
77
|
else {
|
|
111
|
-
const descendants = yield fetchAllDescendants(tree, itemId);
|
|
112
78
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
113
79
|
...items,
|
|
114
|
-
...
|
|
80
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
115
81
|
]);
|
|
116
82
|
}
|
|
117
|
-
}
|
|
118
|
-
setUnchecked: (
|
|
83
|
+
},
|
|
84
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
119
85
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
120
86
|
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
121
87
|
}
|
|
122
88
|
else {
|
|
123
|
-
|
|
124
|
-
|
|
89
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
90
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
125
91
|
}
|
|
126
|
-
}
|
|
92
|
+
},
|
|
127
93
|
},
|
|
128
94
|
};
|
|
@@ -16,9 +16,9 @@ export type CheckboxesFeatureDef<T> = {
|
|
|
16
16
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
17
|
};
|
|
18
18
|
itemInstance: {
|
|
19
|
-
setChecked: () =>
|
|
20
|
-
setUnchecked: () =>
|
|
21
|
-
toggleCheckedState: () =>
|
|
19
|
+
setChecked: () => void;
|
|
20
|
+
setUnchecked: () => void;
|
|
21
|
+
toggleCheckedState: () => void;
|
|
22
22
|
getCheckedState: () => CheckedState;
|
|
23
23
|
getCheckboxProps: () => Record<string, any>;
|
|
24
24
|
};
|
|
@@ -40,8 +40,6 @@ exports.syncDataLoaderFeature = {
|
|
|
40
40
|
}
|
|
41
41
|
return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
|
|
42
42
|
},
|
|
43
|
-
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
44
|
-
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
45
43
|
},
|
|
46
44
|
itemInstance: {
|
|
47
45
|
isLoading: () => false,
|
|
@@ -18,8 +18,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
18
18
|
dataLoader: TreeDataLoader<T>;
|
|
19
19
|
};
|
|
20
20
|
treeInstance: {
|
|
21
|
-
retrieveItemData: (itemId: string
|
|
22
|
-
retrieveChildrenIds: (itemId: string
|
|
21
|
+
retrieveItemData: (itemId: string) => T;
|
|
22
|
+
retrieveChildrenIds: (itemId: string) => string[];
|
|
23
23
|
};
|
|
24
24
|
itemInstance: {
|
|
25
25
|
isLoading: () => boolean;
|
|
@@ -26,17 +26,14 @@ const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, functio
|
|
|
26
26
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
27
27
|
(_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
|
|
28
28
|
(_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
|
|
29
|
-
return item;
|
|
30
29
|
});
|
|
31
30
|
const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
31
|
var _a, _b, _c, _d, _e;
|
|
33
32
|
const config = tree.getConfig();
|
|
34
33
|
const dataRef = getDataRef(tree);
|
|
35
|
-
let childrenIds;
|
|
36
|
-
// TODO is folder check?
|
|
37
34
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
38
35
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
39
|
-
childrenIds = children.map((c) => c.id);
|
|
36
|
+
const childrenIds = children.map((c) => c.id);
|
|
40
37
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
41
38
|
children.forEach(({ id, data }) => {
|
|
42
39
|
var _a, _b, _c;
|
|
@@ -50,7 +47,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
50
47
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
|
|
51
48
|
}
|
|
52
49
|
else {
|
|
53
|
-
childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
50
|
+
const childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
54
51
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
55
52
|
(_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
|
|
56
53
|
tree.rebuildTree();
|
|
@@ -58,7 +55,6 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
58
55
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
59
56
|
(_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
|
|
60
57
|
(_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
|
|
61
|
-
return childrenIds;
|
|
62
58
|
});
|
|
63
59
|
export const asyncDataLoaderFeature = {
|
|
64
60
|
key: "async-data-loader",
|
|
@@ -84,7 +80,6 @@ export const asyncDataLoaderFeature = {
|
|
|
84
80
|
});
|
|
85
81
|
}),
|
|
86
82
|
waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
87
|
-
// TODO replace inner implementation with load() fns
|
|
88
83
|
tree.retrieveChildrenIds(itemId);
|
|
89
84
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
90
85
|
return;
|
|
@@ -98,14 +93,6 @@ export const asyncDataLoaderFeature = {
|
|
|
98
93
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
99
94
|
});
|
|
100
95
|
}),
|
|
101
|
-
loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
102
|
-
var _b;
|
|
103
|
-
return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
|
|
104
|
-
}),
|
|
105
|
-
loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
106
|
-
var _b;
|
|
107
|
-
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
108
|
-
}),
|
|
109
96
|
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
110
97
|
var _a, _b;
|
|
111
98
|
const config = tree.getConfig();
|
|
@@ -27,12 +27,8 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
27
27
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
28
|
};
|
|
29
29
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
30
|
-
/** @deprecated use loadItemData instead */
|
|
31
30
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
32
|
-
/** @deprecated use loadChildrenIds instead */
|
|
33
31
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
34
|
-
loadItemData: (itemId: string) => Promise<T>;
|
|
35
|
-
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
36
32
|
};
|
|
37
33
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
38
34
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -1,43 +1,27 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { makeStateUpdater } from "../../utils";
|
|
11
2
|
import { CheckedState } from "./types";
|
|
12
|
-
|
|
13
|
-
* Cases for checking:
|
|
14
|
-
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
15
|
-
* - Check an explicitly unchecked item in a checked folder
|
|
16
|
-
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
17
|
-
*
|
|
18
|
-
* Cases for unchecking:
|
|
19
|
-
* - Uncheck a checked item in an indeterminate folder
|
|
20
|
-
* - Uncheck an explicitly unchecked item in an checked folder
|
|
21
|
-
*/
|
|
22
|
-
const fetchAllDescendants = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
-
const children = yield tree.loadChildrenIds(itemId);
|
|
24
|
-
return [
|
|
25
|
-
itemId,
|
|
26
|
-
...(yield Promise.all(children.map((child) => fetchAllDescendants(tree, child)))).flat(),
|
|
27
|
-
];
|
|
28
|
-
});
|
|
3
|
+
import { throwError } from "../../utilities/errors";
|
|
29
4
|
const getAllLoadedDescendants = (tree, itemId) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
5
|
+
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
6
|
+
return [itemId];
|
|
7
|
+
}
|
|
8
|
+
return tree
|
|
9
|
+
.retrieveChildrenIds(itemId)
|
|
10
|
+
.map((child) => getAllLoadedDescendants(tree, child))
|
|
11
|
+
.flat();
|
|
35
12
|
};
|
|
36
13
|
export const checkboxesFeature = {
|
|
37
14
|
key: "checkboxes",
|
|
38
15
|
overwrites: ["selection"],
|
|
39
16
|
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
40
|
-
getDefaultConfig: (defaultConfig, tree) =>
|
|
17
|
+
getDefaultConfig: (defaultConfig, tree) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
|
|
20
|
+
if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
|
|
21
|
+
throwError(`!canCheckFolders not supported with async trees`);
|
|
22
|
+
}
|
|
23
|
+
return Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree), canCheckFolders: hasAsyncLoader !== null && hasAsyncLoader !== void 0 ? hasAsyncLoader : false }, defaultConfig);
|
|
24
|
+
},
|
|
41
25
|
stateHandlerNames: {
|
|
42
26
|
checkedItems: "setCheckedItems",
|
|
43
27
|
},
|
|
@@ -47,37 +31,33 @@ export const checkboxesFeature = {
|
|
|
47
31
|
},
|
|
48
32
|
},
|
|
49
33
|
itemInstance: {
|
|
50
|
-
getCheckboxProps: ({ item
|
|
34
|
+
getCheckboxProps: ({ item }) => {
|
|
51
35
|
const checkedState = item.getCheckedState();
|
|
52
|
-
// console.log("prop", itemId, checkedState);
|
|
53
36
|
return {
|
|
54
37
|
onChange: item.toggleCheckedState,
|
|
55
38
|
checked: checkedState === CheckedState.Checked,
|
|
56
39
|
ref: (r) => {
|
|
57
40
|
if (r) {
|
|
58
|
-
// console.log("ref", itemId, checkedState);
|
|
59
41
|
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
60
42
|
}
|
|
61
43
|
},
|
|
62
44
|
};
|
|
63
45
|
},
|
|
64
|
-
toggleCheckedState: (
|
|
46
|
+
toggleCheckedState: ({ item }) => {
|
|
65
47
|
if (item.getCheckedState() === CheckedState.Checked) {
|
|
66
|
-
|
|
48
|
+
item.setUnchecked();
|
|
67
49
|
}
|
|
68
50
|
else {
|
|
69
|
-
|
|
51
|
+
item.setChecked();
|
|
70
52
|
}
|
|
71
|
-
}
|
|
53
|
+
},
|
|
72
54
|
getCheckedState: ({ item, tree, itemId }) => {
|
|
73
|
-
// TODO checkedcache
|
|
74
55
|
const { checkedItems } = tree.getState();
|
|
75
56
|
if (checkedItems.includes(itemId)) {
|
|
76
57
|
return CheckedState.Checked;
|
|
77
58
|
}
|
|
78
|
-
if (item.isFolder()) {
|
|
59
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
79
60
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
80
|
-
console.log("descendants of ", itemId, descendants);
|
|
81
61
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
82
62
|
return CheckedState.Checked;
|
|
83
63
|
}
|
|
@@ -85,41 +65,27 @@ export const checkboxesFeature = {
|
|
|
85
65
|
return CheckedState.Indeterminate;
|
|
86
66
|
}
|
|
87
67
|
}
|
|
88
|
-
// if (
|
|
89
|
-
// item.isFolder() &&
|
|
90
|
-
// checkedItems.some((checkedItem) =>
|
|
91
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
92
|
-
// )
|
|
93
|
-
// ) {
|
|
94
|
-
// // TODO for every descendent, not every checked item
|
|
95
|
-
// return checkedItems.every((checkedItem) =>
|
|
96
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
97
|
-
// )
|
|
98
|
-
// ? CheckedState.Checked
|
|
99
|
-
// : CheckedState.Indeterminate;
|
|
100
|
-
// }
|
|
101
68
|
return CheckedState.Unchecked;
|
|
102
69
|
},
|
|
103
|
-
setChecked: (
|
|
70
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
104
71
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
105
72
|
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
106
73
|
}
|
|
107
74
|
else {
|
|
108
|
-
const descendants = yield fetchAllDescendants(tree, itemId);
|
|
109
75
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
110
76
|
...items,
|
|
111
|
-
...
|
|
77
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
112
78
|
]);
|
|
113
79
|
}
|
|
114
|
-
}
|
|
115
|
-
setUnchecked: (
|
|
80
|
+
},
|
|
81
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
116
82
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
117
83
|
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
118
84
|
}
|
|
119
85
|
else {
|
|
120
|
-
|
|
121
|
-
|
|
86
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
87
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
122
88
|
}
|
|
123
|
-
}
|
|
89
|
+
},
|
|
124
90
|
},
|
|
125
91
|
};
|
|
@@ -16,9 +16,9 @@ export type CheckboxesFeatureDef<T> = {
|
|
|
16
16
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
17
|
};
|
|
18
18
|
itemInstance: {
|
|
19
|
-
setChecked: () =>
|
|
20
|
-
setUnchecked: () =>
|
|
21
|
-
toggleCheckedState: () =>
|
|
19
|
+
setChecked: () => void;
|
|
20
|
+
setUnchecked: () => void;
|
|
21
|
+
toggleCheckedState: () => void;
|
|
22
22
|
getCheckedState: () => CheckedState;
|
|
23
23
|
getCheckboxProps: () => Record<string, any>;
|
|
24
24
|
};
|
|
@@ -37,8 +37,6 @@ export const syncDataLoaderFeature = {
|
|
|
37
37
|
}
|
|
38
38
|
return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
|
|
39
39
|
},
|
|
40
|
-
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
41
|
-
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
42
40
|
},
|
|
43
41
|
itemInstance: {
|
|
44
42
|
isLoading: () => false,
|
|
@@ -18,8 +18,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
18
18
|
dataLoader: TreeDataLoader<T>;
|
|
19
19
|
};
|
|
20
20
|
treeInstance: {
|
|
21
|
-
retrieveItemData: (itemId: string
|
|
22
|
-
retrieveChildrenIds: (itemId: string
|
|
21
|
+
retrieveItemData: (itemId: string) => T;
|
|
22
|
+
retrieveChildrenIds: (itemId: string) => string[];
|
|
23
23
|
};
|
|
24
24
|
itemInstance: {
|
|
25
25
|
isLoading: () => boolean;
|
package/package.json
CHANGED
|
@@ -22,19 +22,15 @@ const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
22
22
|
|
|
23
23
|
dataRef.current.awaitingItemDataLoading?.[itemId].forEach((cb) => cb());
|
|
24
24
|
delete dataRef.current.awaitingItemDataLoading?.[itemId];
|
|
25
|
-
return item;
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
29
28
|
const config = tree.getConfig();
|
|
30
29
|
const dataRef = getDataRef(tree);
|
|
31
|
-
let childrenIds: string[];
|
|
32
|
-
|
|
33
|
-
// TODO is folder check?
|
|
34
30
|
|
|
35
31
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
36
32
|
const children = await config.dataLoader.getChildrenWithData(itemId);
|
|
37
|
-
childrenIds = children.map((c) => c.id);
|
|
33
|
+
const childrenIds = children.map((c) => c.id);
|
|
38
34
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
39
35
|
children.forEach(({ id, data }) => {
|
|
40
36
|
dataRef.current.itemData[id] = data;
|
|
@@ -49,7 +45,7 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
49
45
|
loadingItemData.filter((id) => !childrenIds.includes(id)),
|
|
50
46
|
);
|
|
51
47
|
} else {
|
|
52
|
-
childrenIds = await config.dataLoader.getChildren(itemId);
|
|
48
|
+
const childrenIds = await config.dataLoader.getChildren(itemId);
|
|
53
49
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
54
50
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
55
51
|
tree.rebuildTree();
|
|
@@ -61,7 +57,6 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
61
57
|
|
|
62
58
|
dataRef.current.awaitingItemChildrensLoading?.[itemId]?.forEach((cb) => cb());
|
|
63
59
|
delete dataRef.current.awaitingItemChildrensLoading?.[itemId];
|
|
64
|
-
return childrenIds;
|
|
65
60
|
};
|
|
66
61
|
|
|
67
62
|
export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
@@ -99,7 +94,6 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
99
94
|
},
|
|
100
95
|
|
|
101
96
|
waitForItemChildrenLoaded: async ({ tree }, itemId) => {
|
|
102
|
-
// TODO replace inner implementation with load() fns
|
|
103
97
|
tree.retrieveChildrenIds(itemId);
|
|
104
98
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
105
99
|
return;
|
|
@@ -112,19 +106,6 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
112
106
|
});
|
|
113
107
|
},
|
|
114
108
|
|
|
115
|
-
loadItemData: async ({ tree }, itemId) => {
|
|
116
|
-
return (
|
|
117
|
-
getDataRef(tree).current.itemData[itemId] ??
|
|
118
|
-
(await loadItemData(tree, itemId))
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
loadChildrenIds: async ({ tree }, itemId) => {
|
|
122
|
-
return (
|
|
123
|
-
getDataRef(tree).current.childrenIds[itemId] ??
|
|
124
|
-
(await loadChildrenIds(tree, itemId))
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
|
|
128
109
|
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
129
110
|
const config = tree.getConfig();
|
|
130
111
|
const dataRef = getDataRef(tree);
|
|
@@ -32,12 +32,8 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
32
32
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
33
33
|
};
|
|
34
34
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
35
|
-
/** @deprecated use loadItemData instead */
|
|
36
35
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
37
|
-
/** @deprecated use loadChildrenIds instead */
|
|
38
36
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
39
|
-
loadItemData: (itemId: string) => Promise<T>;
|
|
40
|
-
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
41
37
|
};
|
|
42
38
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
43
39
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { TestTree } from "../../test-utils/test-tree";
|
|
3
|
+
import { checkboxesFeature } from "./feature";
|
|
4
|
+
import { CheckedState } from "./types";
|
|
5
|
+
|
|
6
|
+
const factory = TestTree.default({})
|
|
7
|
+
.withFeatures(checkboxesFeature)
|
|
8
|
+
.suits.sync().tree;
|
|
9
|
+
|
|
10
|
+
describe("core-feature/checkboxes", () => {
|
|
11
|
+
it("should initialize with no checked items", async () => {
|
|
12
|
+
const tree = await factory.createTestCaseTree();
|
|
13
|
+
expect(tree.instance.getState().checkedItems).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should check items", async () => {
|
|
17
|
+
const tree = await factory.createTestCaseTree();
|
|
18
|
+
tree.item("x111").setChecked();
|
|
19
|
+
tree.item("x112").setChecked();
|
|
20
|
+
expect(tree.instance.getState().checkedItems).toEqual(["x111", "x112"]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should uncheck an item", async () => {
|
|
24
|
+
const tree = await factory
|
|
25
|
+
.with({ state: { checkedItems: ["x111"] } })
|
|
26
|
+
.createTestCaseTree();
|
|
27
|
+
tree.item("x111").setUnchecked();
|
|
28
|
+
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should toggle checked state", async () => {
|
|
32
|
+
const tree = await factory.createTestCaseTree();
|
|
33
|
+
const item = tree.item("x111");
|
|
34
|
+
|
|
35
|
+
item.toggleCheckedState();
|
|
36
|
+
expect(tree.instance.getState().checkedItems).toContain("x111");
|
|
37
|
+
|
|
38
|
+
item.toggleCheckedState();
|
|
39
|
+
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("props", () => {
|
|
43
|
+
it("should toggle checked state", async () => {
|
|
44
|
+
const tree = await factory.createTestCaseTree();
|
|
45
|
+
const item = tree.item("x111");
|
|
46
|
+
|
|
47
|
+
item.getCheckboxProps().onChange();
|
|
48
|
+
expect(tree.instance.getState().checkedItems).toContain("x111");
|
|
49
|
+
|
|
50
|
+
item.getCheckboxProps().onChange();
|
|
51
|
+
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should return checked state in props", async () => {
|
|
55
|
+
const tree = await factory.createTestCaseTree();
|
|
56
|
+
tree.item("x111").setChecked();
|
|
57
|
+
expect(tree.item("x111").getCheckboxProps().checked).toBe(true);
|
|
58
|
+
expect(tree.item("x112").getCheckboxProps().checked).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should create indeterminate state", async () => {
|
|
62
|
+
const tree = await factory.createTestCaseTree();
|
|
63
|
+
tree.item("x111").setChecked();
|
|
64
|
+
const refObject = { indeterminate: undefined };
|
|
65
|
+
tree.item("x11").getCheckboxProps().ref(refObject);
|
|
66
|
+
expect(refObject.indeterminate).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should not create indeterminate state", async () => {
|
|
70
|
+
const tree = await factory.createTestCaseTree();
|
|
71
|
+
const refObject = { indeterminate: undefined };
|
|
72
|
+
tree.item("x11").getCheckboxProps().ref(refObject);
|
|
73
|
+
expect(refObject.indeterminate).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should handle folder checking when canCheckFolders is true", async () => {
|
|
78
|
+
const tree = await factory
|
|
79
|
+
.with({ canCheckFolders: true })
|
|
80
|
+
.createTestCaseTree();
|
|
81
|
+
|
|
82
|
+
tree.item("x11").setChecked();
|
|
83
|
+
expect(tree.instance.getState().checkedItems).toContain("x11");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle folder checking when canCheckFolders is false", async () => {
|
|
87
|
+
const tree = await factory.createTestCaseTree();
|
|
88
|
+
|
|
89
|
+
tree.item("x11").setChecked();
|
|
90
|
+
expect(tree.instance.getState().checkedItems).toEqual(
|
|
91
|
+
expect.arrayContaining(["x111", "x112", "x113", "x114"]),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should turn folder indeterminate", async () => {
|
|
96
|
+
const tree = await factory.createTestCaseTree();
|
|
97
|
+
|
|
98
|
+
tree.item("x111").setChecked();
|
|
99
|
+
expect(tree.item("x11").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should turn folder checked if all children are checked", async () => {
|
|
103
|
+
const tree = await factory
|
|
104
|
+
.with({
|
|
105
|
+
isItemFolder: (item) => item.getItemData().length < 4,
|
|
106
|
+
})
|
|
107
|
+
.createTestCaseTree();
|
|
108
|
+
|
|
109
|
+
tree.item("x11").setChecked();
|
|
110
|
+
tree.item("x12").setChecked();
|
|
111
|
+
tree.item("x13").setChecked();
|
|
112
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
113
|
+
tree.do.selectItem("x14");
|
|
114
|
+
tree.item("x141").setChecked();
|
|
115
|
+
tree.item("x142").setChecked();
|
|
116
|
+
tree.item("x143").setChecked();
|
|
117
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
118
|
+
tree.item("x144").setChecked();
|
|
119
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Checked);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should return correct checked state for items", async () => {
|
|
123
|
+
const tree = await factory.createTestCaseTree();
|
|
124
|
+
const item = tree.instance.getItemInstance("x111");
|
|
125
|
+
|
|
126
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
127
|
+
|
|
128
|
+
item.setChecked();
|
|
129
|
+
expect(item.getCheckedState()).toBe(CheckedState.Checked);
|
|
130
|
+
|
|
131
|
+
item.setUnchecked();
|
|
132
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -1,42 +1,19 @@
|
|
|
1
1
|
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
3
|
import { CheckedState } from "./types";
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
* Cases for checking:
|
|
7
|
-
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
8
|
-
* - Check an explicitly unchecked item in a checked folder
|
|
9
|
-
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
10
|
-
*
|
|
11
|
-
* Cases for unchecking:
|
|
12
|
-
* - Uncheck a checked item in an indeterminate folder
|
|
13
|
-
* - Uncheck an explicitly unchecked item in an checked folder
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const fetchAllDescendants = async <T>(
|
|
17
|
-
tree: TreeInstance<T>,
|
|
18
|
-
itemId: string,
|
|
19
|
-
): Promise<string[]> => {
|
|
20
|
-
const children = await tree.loadChildrenIds(itemId);
|
|
21
|
-
return [
|
|
22
|
-
itemId,
|
|
23
|
-
...(
|
|
24
|
-
await Promise.all(
|
|
25
|
-
children.map((child) => fetchAllDescendants(tree, child)),
|
|
26
|
-
)
|
|
27
|
-
).flat(),
|
|
28
|
-
];
|
|
29
|
-
};
|
|
4
|
+
import { throwError } from "../../utilities/errors";
|
|
30
5
|
|
|
31
6
|
const getAllLoadedDescendants = <T>(
|
|
32
7
|
tree: TreeInstance<T>,
|
|
33
8
|
itemId: string,
|
|
34
9
|
): string[] => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
10
|
+
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
11
|
+
return [itemId];
|
|
12
|
+
}
|
|
13
|
+
return tree
|
|
14
|
+
.retrieveChildrenIds(itemId)
|
|
15
|
+
.map((child) => getAllLoadedDescendants(tree, child))
|
|
16
|
+
.flat();
|
|
40
17
|
};
|
|
41
18
|
|
|
42
19
|
export const checkboxesFeature: FeatureImplementation = {
|
|
@@ -49,10 +26,19 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
49
26
|
...initialState,
|
|
50
27
|
}),
|
|
51
28
|
|
|
52
|
-
getDefaultConfig: (defaultConfig, tree) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
29
|
+
getDefaultConfig: (defaultConfig, tree) => {
|
|
30
|
+
const hasAsyncLoader = defaultConfig.features?.some(
|
|
31
|
+
(f) => f.key === "async-data-loader",
|
|
32
|
+
);
|
|
33
|
+
if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
|
|
34
|
+
throwError(`!canCheckFolders not supported with async trees`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
38
|
+
canCheckFolders: hasAsyncLoader ?? false,
|
|
39
|
+
...defaultConfig,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
56
42
|
|
|
57
43
|
stateHandlerNames: {
|
|
58
44
|
checkedItems: "setCheckedItems",
|
|
@@ -65,40 +51,36 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
65
51
|
},
|
|
66
52
|
|
|
67
53
|
itemInstance: {
|
|
68
|
-
getCheckboxProps: ({ item
|
|
54
|
+
getCheckboxProps: ({ item }) => {
|
|
69
55
|
const checkedState = item.getCheckedState();
|
|
70
|
-
// console.log("prop", itemId, checkedState);
|
|
71
56
|
return {
|
|
72
57
|
onChange: item.toggleCheckedState,
|
|
73
58
|
checked: checkedState === CheckedState.Checked,
|
|
74
59
|
ref: (r: any) => {
|
|
75
60
|
if (r) {
|
|
76
|
-
// console.log("ref", itemId, checkedState);
|
|
77
61
|
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
78
62
|
}
|
|
79
63
|
},
|
|
80
64
|
};
|
|
81
65
|
},
|
|
82
66
|
|
|
83
|
-
toggleCheckedState:
|
|
67
|
+
toggleCheckedState: ({ item }) => {
|
|
84
68
|
if (item.getCheckedState() === CheckedState.Checked) {
|
|
85
|
-
|
|
69
|
+
item.setUnchecked();
|
|
86
70
|
} else {
|
|
87
|
-
|
|
71
|
+
item.setChecked();
|
|
88
72
|
}
|
|
89
73
|
},
|
|
90
74
|
|
|
91
75
|
getCheckedState: ({ item, tree, itemId }) => {
|
|
92
|
-
// TODO checkedcache
|
|
93
76
|
const { checkedItems } = tree.getState();
|
|
94
77
|
|
|
95
78
|
if (checkedItems.includes(itemId)) {
|
|
96
79
|
return CheckedState.Checked;
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
if (item.isFolder()) {
|
|
82
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
100
83
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
101
|
-
console.log("descendants of ", itemId, descendants);
|
|
102
84
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
103
85
|
return CheckedState.Checked;
|
|
104
86
|
}
|
|
@@ -107,43 +89,30 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
107
89
|
}
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
// if (
|
|
111
|
-
// item.isFolder() &&
|
|
112
|
-
// checkedItems.some((checkedItem) =>
|
|
113
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
114
|
-
// )
|
|
115
|
-
// ) {
|
|
116
|
-
// // TODO for every descendent, not every checked item
|
|
117
|
-
// return checkedItems.every((checkedItem) =>
|
|
118
|
-
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
119
|
-
// )
|
|
120
|
-
// ? CheckedState.Checked
|
|
121
|
-
// : CheckedState.Indeterminate;
|
|
122
|
-
// }
|
|
123
|
-
|
|
124
92
|
return CheckedState.Unchecked;
|
|
125
93
|
},
|
|
126
94
|
|
|
127
|
-
setChecked:
|
|
95
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
128
96
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
129
97
|
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
130
98
|
} else {
|
|
131
|
-
const descendants = await fetchAllDescendants(tree, itemId);
|
|
132
99
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
133
100
|
...items,
|
|
134
|
-
...
|
|
101
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
135
102
|
]);
|
|
136
103
|
}
|
|
137
104
|
},
|
|
138
105
|
|
|
139
|
-
setUnchecked:
|
|
106
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
140
107
|
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
141
108
|
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
142
109
|
items.filter((id) => id !== itemId),
|
|
143
110
|
);
|
|
144
111
|
} else {
|
|
145
|
-
|
|
146
|
-
|
|
112
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
113
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
114
|
+
items.filter((id) => !descendants.includes(id)),
|
|
115
|
+
);
|
|
147
116
|
}
|
|
148
117
|
},
|
|
149
118
|
},
|
|
@@ -18,9 +18,9 @@ export type CheckboxesFeatureDef<T> = {
|
|
|
18
18
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
19
19
|
};
|
|
20
20
|
itemInstance: {
|
|
21
|
-
setChecked: () =>
|
|
22
|
-
setUnchecked: () =>
|
|
23
|
-
toggleCheckedState: () =>
|
|
21
|
+
setChecked: () => void;
|
|
22
|
+
setUnchecked: () => void;
|
|
23
|
+
toggleCheckedState: () => void;
|
|
24
24
|
getCheckedState: () => CheckedState;
|
|
25
25
|
getCheckboxProps: () => Record<string, any>;
|
|
26
26
|
};
|
|
@@ -47,9 +47,6 @@ export const syncDataLoaderFeature: FeatureImplementation = {
|
|
|
47
47
|
(c) => c.data,
|
|
48
48
|
);
|
|
49
49
|
},
|
|
50
|
-
|
|
51
|
-
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
52
|
-
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
53
50
|
},
|
|
54
51
|
|
|
55
52
|
itemInstance: {
|
|
@@ -17,8 +17,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
17
17
|
dataLoader: TreeDataLoader<T>;
|
|
18
18
|
};
|
|
19
19
|
treeInstance: {
|
|
20
|
-
retrieveItemData: (itemId: string
|
|
21
|
-
retrieveChildrenIds: (itemId: string
|
|
20
|
+
retrieveItemData: (itemId: string) => T;
|
|
21
|
+
retrieveChildrenIds: (itemId: string) => string[];
|
|
22
22
|
};
|
|
23
23
|
itemInstance: {
|
|
24
24
|
isLoading: () => boolean;
|
|
@@ -20,7 +20,7 @@ export type TreeFeatureDef<T> = {
|
|
|
20
20
|
focusedItem: string | null;
|
|
21
21
|
};
|
|
22
22
|
config: {
|
|
23
|
-
isItemFolder: (item: ItemInstance<T>) => boolean;
|
|
23
|
+
isItemFolder: (item: ItemInstance<T>) => boolean; // TODO:breaking use item data as payload
|
|
24
24
|
getItemName: (item: ItemInstance<T>) => string;
|
|
25
25
|
|
|
26
26
|
onPrimaryAction?: (item: ItemInstance<T>) => void;
|