@headless-tree/core 0.0.0-20250508233916 → 0.0.0-20250509212452
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 +2 -1
- package/lib/cjs/core/create-tree.js +9 -0
- package/lib/cjs/features/async-data-loader/feature.js +90 -64
- package/lib/cjs/features/async-data-loader/types.d.ts +12 -3
- package/lib/cjs/features/checkboxes/feature.d.ts +2 -0
- package/lib/cjs/features/checkboxes/feature.js +128 -0
- package/lib/cjs/features/checkboxes/types.d.ts +26 -0
- package/lib/cjs/features/checkboxes/types.js +9 -0
- package/lib/cjs/features/main/types.d.ts +2 -0
- package/lib/cjs/features/sync-data-loader/feature.js +2 -0
- package/lib/cjs/features/sync-data-loader/types.d.ts +2 -2
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/types/core.d.ts +2 -1
- package/lib/esm/core/create-tree.js +9 -0
- package/lib/esm/features/async-data-loader/feature.js +90 -64
- package/lib/esm/features/async-data-loader/types.d.ts +12 -3
- package/lib/esm/features/checkboxes/feature.d.ts +2 -0
- package/lib/esm/features/checkboxes/feature.js +125 -0
- package/lib/esm/features/checkboxes/types.d.ts +26 -0
- package/lib/esm/features/checkboxes/types.js +6 -0
- package/lib/esm/features/main/types.d.ts +2 -0
- package/lib/esm/features/sync-data-loader/feature.js +2 -0
- package/lib/esm/features/sync-data-loader/types.d.ts +2 -2
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/types/core.d.ts +2 -1
- package/package.json +1 -1
- package/src/core/create-tree.ts +13 -0
- package/src/features/async-data-loader/async-data-loader.spec.ts +36 -0
- package/src/features/async-data-loader/feature.ts +103 -68
- package/src/features/async-data-loader/types.ts +14 -3
- package/src/features/checkboxes/feature.ts +150 -0
- package/src/features/checkboxes/types.ts +28 -0
- package/src/features/main/types.ts +2 -0
- package/src/features/sync-data-loader/feature.ts +3 -0
- package/src/features/sync-data-loader/types.ts +2 -2
- package/src/index.ts +2 -0
- package/src/types/core.ts +2 -0
|
@@ -1,7 +1,69 @@
|
|
|
1
|
-
import { FeatureImplementation } from "../../types/core";
|
|
1
|
+
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
2
|
import { AsyncDataLoaderDataRef } from "./types";
|
|
3
3
|
import { makeStateUpdater } from "../../utils";
|
|
4
4
|
|
|
5
|
+
const getDataRef = <T>(tree: TreeInstance<T>) => {
|
|
6
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
7
|
+
dataRef.current.itemData ??= {};
|
|
8
|
+
dataRef.current.childrenIds ??= {};
|
|
9
|
+
return dataRef;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
13
|
+
const config = tree.getConfig();
|
|
14
|
+
const dataRef = getDataRef(tree);
|
|
15
|
+
|
|
16
|
+
const item = await config.dataLoader.getItem(itemId);
|
|
17
|
+
dataRef.current.itemData[itemId] = item;
|
|
18
|
+
config.onLoadedItem?.(itemId, item);
|
|
19
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
20
|
+
loadingItemData.filter((id) => id !== itemId),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
dataRef.current.awaitingItemDataLoading?.[itemId].forEach((cb) => cb());
|
|
24
|
+
delete dataRef.current.awaitingItemDataLoading?.[itemId];
|
|
25
|
+
return item;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
29
|
+
const config = tree.getConfig();
|
|
30
|
+
const dataRef = getDataRef(tree);
|
|
31
|
+
let childrenIds: string[];
|
|
32
|
+
|
|
33
|
+
// TODO is folder check?
|
|
34
|
+
|
|
35
|
+
if ("getChildrenWithData" in config.dataLoader) {
|
|
36
|
+
const children = await config.dataLoader.getChildrenWithData(itemId);
|
|
37
|
+
childrenIds = children.map((c) => c.id);
|
|
38
|
+
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
39
|
+
children.forEach(({ id, data }) => {
|
|
40
|
+
dataRef.current.itemData[id] = data;
|
|
41
|
+
config.onLoadedItem?.(id, data);
|
|
42
|
+
dataRef.current.awaitingItemDataLoading?.[id].forEach((cb) => cb());
|
|
43
|
+
delete dataRef.current.awaitingItemDataLoading?.[id];
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
config.onLoadedChildren?.(itemId, childrenIds);
|
|
47
|
+
tree.rebuildTree();
|
|
48
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
49
|
+
loadingItemData.filter((id) => !childrenIds.includes(id)),
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
childrenIds = await config.dataLoader.getChildren(itemId);
|
|
53
|
+
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
54
|
+
config.onLoadedChildren?.(itemId, childrenIds);
|
|
55
|
+
tree.rebuildTree();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) =>
|
|
59
|
+
loadingItemChildrens.filter((id) => id !== itemId),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
dataRef.current.awaitingItemChildrensLoading?.[itemId]?.forEach((cb) => cb());
|
|
63
|
+
delete dataRef.current.awaitingItemChildrensLoading?.[itemId];
|
|
64
|
+
return childrenIds;
|
|
65
|
+
};
|
|
66
|
+
|
|
5
67
|
export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
6
68
|
key: "async-data-loader",
|
|
7
69
|
|
|
@@ -37,6 +99,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
37
99
|
},
|
|
38
100
|
|
|
39
101
|
waitForItemChildrenLoaded: async ({ tree }, itemId) => {
|
|
102
|
+
// TODO replace inner implementation with load() fns
|
|
40
103
|
tree.retrieveChildrenIds(itemId);
|
|
41
104
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
42
105
|
return;
|
|
@@ -49,50 +112,46 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
49
112
|
});
|
|
50
113
|
},
|
|
51
114
|
|
|
52
|
-
|
|
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
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
53
129
|
const config = tree.getConfig();
|
|
54
|
-
const dataRef =
|
|
55
|
-
dataRef.current.itemData ??= {};
|
|
56
|
-
dataRef.current.childrenIds ??= {};
|
|
130
|
+
const dataRef = getDataRef(tree);
|
|
57
131
|
|
|
58
132
|
if (dataRef.current.itemData[itemId]) {
|
|
59
133
|
return dataRef.current.itemData[itemId];
|
|
60
134
|
}
|
|
61
135
|
|
|
62
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
136
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
63
137
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
64
138
|
...loadingItemData,
|
|
65
139
|
itemId,
|
|
66
140
|
]);
|
|
67
141
|
|
|
68
|
-
(
|
|
69
|
-
const item = await config.dataLoader.getItem(itemId);
|
|
70
|
-
dataRef.current.itemData[itemId] = item;
|
|
71
|
-
config.onLoadedItem?.(itemId, item);
|
|
72
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
73
|
-
loadingItemData.filter((id) => id !== itemId),
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
dataRef.current.awaitingItemDataLoading?.[itemId].forEach((cb) =>
|
|
77
|
-
cb(),
|
|
78
|
-
);
|
|
79
|
-
delete dataRef.current.awaitingItemDataLoading?.[itemId];
|
|
80
|
-
})();
|
|
142
|
+
loadItemData(tree, itemId);
|
|
81
143
|
}
|
|
82
144
|
|
|
83
145
|
return config.createLoadingItemData?.() ?? null;
|
|
84
146
|
},
|
|
85
147
|
|
|
86
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
87
|
-
const
|
|
88
|
-
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
89
|
-
dataRef.current.itemData ??= {};
|
|
90
|
-
dataRef.current.childrenIds ??= {};
|
|
148
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
149
|
+
const dataRef = getDataRef(tree);
|
|
91
150
|
if (dataRef.current.childrenIds[itemId]) {
|
|
92
151
|
return dataRef.current.childrenIds[itemId];
|
|
93
152
|
}
|
|
94
153
|
|
|
95
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
154
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
96
155
|
return [];
|
|
97
156
|
}
|
|
98
157
|
|
|
@@ -101,41 +160,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
101
160
|
(loadingItemChildrens) => [...loadingItemChildrens, itemId],
|
|
102
161
|
);
|
|
103
162
|
|
|
104
|
-
(
|
|
105
|
-
if ("getChildrenWithData" in config.dataLoader) {
|
|
106
|
-
const children = await config.dataLoader.getChildrenWithData(itemId);
|
|
107
|
-
const childrenIds = children.map((c) => c.id);
|
|
108
|
-
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
109
|
-
children.forEach(({ id, data }) => {
|
|
110
|
-
dataRef.current.itemData[id] = data;
|
|
111
|
-
config.onLoadedItem?.(id, data);
|
|
112
|
-
dataRef.current.awaitingItemDataLoading?.[id].forEach((cb) => cb());
|
|
113
|
-
delete dataRef.current.awaitingItemDataLoading?.[id];
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
config.onLoadedChildren?.(itemId, childrenIds);
|
|
117
|
-
tree.rebuildTree();
|
|
118
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
119
|
-
loadingItemData.filter((id) => !childrenIds.includes(id)),
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
const childrenIds = await config.dataLoader.getChildren(itemId);
|
|
123
|
-
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
124
|
-
config.onLoadedChildren?.(itemId, childrenIds);
|
|
125
|
-
tree.rebuildTree();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
tree.applySubStateUpdate(
|
|
129
|
-
"loadingItemChildrens",
|
|
130
|
-
(loadingItemChildrens) =>
|
|
131
|
-
loadingItemChildrens.filter((id) => id !== itemId),
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
dataRef.current.awaitingItemChildrensLoading?.[itemId]?.forEach((cb) =>
|
|
135
|
-
cb(),
|
|
136
|
-
);
|
|
137
|
-
delete dataRef.current.awaitingItemChildrensLoading?.[itemId];
|
|
138
|
-
})();
|
|
163
|
+
loadChildrenIds(tree, itemId);
|
|
139
164
|
|
|
140
165
|
return [];
|
|
141
166
|
},
|
|
@@ -145,15 +170,25 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
145
170
|
isLoading: ({ tree, item }) =>
|
|
146
171
|
tree.getState().loadingItemData.includes(item.getItemMeta().itemId) ||
|
|
147
172
|
tree.getState().loadingItemChildrens.includes(item.getItemMeta().itemId),
|
|
148
|
-
invalidateItemData: ({ tree, itemId }) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
invalidateItemData: async ({ tree, itemId }, optimistic) => {
|
|
174
|
+
if (!optimistic) {
|
|
175
|
+
delete getDataRef(tree).current.itemData?.[itemId];
|
|
176
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
177
|
+
...loadingItemData,
|
|
178
|
+
itemId,
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
await loadItemData(tree, itemId);
|
|
152
182
|
},
|
|
153
|
-
invalidateChildrenIds: ({ tree, itemId }) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
invalidateChildrenIds: async ({ tree, itemId }, optimistic) => {
|
|
184
|
+
if (!optimistic) {
|
|
185
|
+
delete getDataRef(tree).current.childrenIds?.[itemId];
|
|
186
|
+
tree.applySubStateUpdate(
|
|
187
|
+
"loadingItemChildrens",
|
|
188
|
+
(loadingItemChildrens) => [...loadingItemChildrens, itemId],
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
await loadChildrenIds(tree, itemId);
|
|
157
192
|
},
|
|
158
193
|
updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
|
|
159
194
|
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
@@ -32,13 +32,24 @@ 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 */
|
|
35
36
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
37
|
+
/** @deprecated use loadChildrenIds instead */
|
|
36
38
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
39
|
+
loadItemData: (itemId: string) => Promise<T>;
|
|
40
|
+
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
37
41
|
};
|
|
38
42
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
39
|
-
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
44
|
+
* @param optimistic If true, the item will not trigger a state update on `loadingItemData`, and
|
|
45
|
+
* the tree will continue to display the old data until the new data has loaded. */
|
|
46
|
+
invalidateItemData: (optimistic?: boolean) => Promise<void>;
|
|
47
|
+
|
|
48
|
+
/** Invalidate fetched children ids for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
49
|
+
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
50
|
+
* the tree will continue to display the old data until the new data has loaded. */
|
|
51
|
+
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
52
|
+
|
|
42
53
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
43
54
|
isLoading: () => boolean;
|
|
44
55
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
|
+
import { makeStateUpdater } from "../../utils";
|
|
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
|
+
};
|
|
30
|
+
|
|
31
|
+
const getAllLoadedDescendants = <T>(
|
|
32
|
+
tree: TreeInstance<T>,
|
|
33
|
+
itemId: string,
|
|
34
|
+
): string[] => {
|
|
35
|
+
const children = tree.retrieveChildrenIds(itemId, true);
|
|
36
|
+
return [
|
|
37
|
+
itemId,
|
|
38
|
+
...children.map((child) => getAllLoadedDescendants(tree, child)).flat(),
|
|
39
|
+
];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const checkboxesFeature: FeatureImplementation = {
|
|
43
|
+
key: "checkboxes",
|
|
44
|
+
|
|
45
|
+
overwrites: ["selection"],
|
|
46
|
+
|
|
47
|
+
getInitialState: (initialState) => ({
|
|
48
|
+
checkedItems: [],
|
|
49
|
+
...initialState,
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
getDefaultConfig: (defaultConfig, tree) => ({
|
|
53
|
+
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
54
|
+
...defaultConfig,
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
stateHandlerNames: {
|
|
58
|
+
checkedItems: "setCheckedItems",
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
treeInstance: {
|
|
62
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
63
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
itemInstance: {
|
|
68
|
+
getCheckboxProps: ({ item, itemId }) => {
|
|
69
|
+
const checkedState = item.getCheckedState();
|
|
70
|
+
// console.log("prop", itemId, checkedState);
|
|
71
|
+
return {
|
|
72
|
+
onChange: item.toggleCheckedState,
|
|
73
|
+
checked: checkedState === CheckedState.Checked,
|
|
74
|
+
ref: (r: any) => {
|
|
75
|
+
if (r) {
|
|
76
|
+
// console.log("ref", itemId, checkedState);
|
|
77
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
toggleCheckedState: async ({ item }) => {
|
|
84
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
85
|
+
await item.setUnchecked();
|
|
86
|
+
} else {
|
|
87
|
+
await item.setChecked();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
92
|
+
// TODO checkedcache
|
|
93
|
+
const { checkedItems } = tree.getState();
|
|
94
|
+
|
|
95
|
+
if (checkedItems.includes(itemId)) {
|
|
96
|
+
return CheckedState.Checked;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (item.isFolder()) {
|
|
100
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
101
|
+
console.log("descendants of ", itemId, descendants);
|
|
102
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
103
|
+
return CheckedState.Checked;
|
|
104
|
+
}
|
|
105
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
106
|
+
return CheckedState.Indeterminate;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
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
|
+
return CheckedState.Unchecked;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
setChecked: async ({ item, tree, itemId }) => {
|
|
128
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
129
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
130
|
+
} else {
|
|
131
|
+
const descendants = await fetchAllDescendants(tree, itemId);
|
|
132
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
133
|
+
...items,
|
|
134
|
+
...descendants,
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
setUnchecked: async ({ item, tree, itemId }) => {
|
|
140
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
141
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
142
|
+
items.filter((id) => id !== itemId),
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
await tree.loadChildrenIds(itemId);
|
|
146
|
+
item.getChildren().forEach((item) => item.setUnchecked());
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
|
|
3
|
+
export enum CheckedState {
|
|
4
|
+
Checked = "checked",
|
|
5
|
+
Unchecked = "unchecked",
|
|
6
|
+
Indeterminate = "indeterminate",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CheckboxesFeatureDef<T> = {
|
|
10
|
+
state: {
|
|
11
|
+
checkedItems: string[];
|
|
12
|
+
};
|
|
13
|
+
config: {
|
|
14
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
15
|
+
canCheckFolders?: boolean;
|
|
16
|
+
};
|
|
17
|
+
treeInstance: {
|
|
18
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
19
|
+
};
|
|
20
|
+
itemInstance: {
|
|
21
|
+
setChecked: () => Promise<void>;
|
|
22
|
+
setUnchecked: () => Promise<void>;
|
|
23
|
+
toggleCheckedState: () => Promise<void>;
|
|
24
|
+
getCheckedState: () => CheckedState;
|
|
25
|
+
getCheckboxProps: () => Record<string, any>;
|
|
26
|
+
};
|
|
27
|
+
hotkeys: never;
|
|
28
|
+
};
|
|
@@ -36,6 +36,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
36
36
|
stateName: K,
|
|
37
37
|
updater: Updater<TreeState<T>[K]>,
|
|
38
38
|
) => void;
|
|
39
|
+
/** @internal */
|
|
40
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
39
41
|
setState: SetStateFn<TreeState<T>>;
|
|
40
42
|
getState: () => TreeState<T>;
|
|
41
43
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -47,6 +47,9 @@ 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),
|
|
50
53
|
},
|
|
51
54
|
|
|
52
55
|
itemInstance: {
|
|
@@ -17,8 +17,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
17
17
|
dataLoader: TreeDataLoader<T>;
|
|
18
18
|
};
|
|
19
19
|
treeInstance: {
|
|
20
|
-
retrieveItemData: (itemId: string) => T;
|
|
21
|
-
retrieveChildrenIds: (itemId: string) => string[];
|
|
20
|
+
retrieveItemData: (itemId: string, skipFetch?: boolean) => T;
|
|
21
|
+
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
22
22
|
};
|
|
23
23
|
itemInstance: {
|
|
24
24
|
isLoading: () => boolean;
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
6
6
|
export * from "./features/drag-and-drop/types";
|
|
7
7
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
8
8
|
export * from "./features/selection/types";
|
|
9
|
+
export * from "./features/checkboxes/types";
|
|
9
10
|
export * from "./features/async-data-loader/types";
|
|
10
11
|
export * from "./features/sync-data-loader/types";
|
|
11
12
|
export * from "./features/hotkeys-core/types";
|
|
@@ -15,6 +16,7 @@ export * from "./features/expand-all/types";
|
|
|
15
16
|
export * from "./features/prop-memoization/types";
|
|
16
17
|
|
|
17
18
|
export * from "./features/selection/feature";
|
|
19
|
+
export * from "./features/checkboxes/feature";
|
|
18
20
|
export * from "./features/hotkeys-core/feature";
|
|
19
21
|
export * from "./features/async-data-loader/feature";
|
|
20
22
|
export * from "./features/sync-data-loader/feature";
|
package/src/types/core.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
13
13
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
14
14
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
15
15
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
16
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
16
17
|
|
|
17
18
|
export type Updater<T> = T | ((old: T) => T);
|
|
18
19
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
@@ -53,6 +54,7 @@ export type RegisteredFeatures<T> =
|
|
|
53
54
|
| MainFeatureDef<T>
|
|
54
55
|
| TreeFeatureDef<T>
|
|
55
56
|
| SelectionFeatureDef<T>
|
|
57
|
+
| CheckboxesFeatureDef<T>
|
|
56
58
|
| DragAndDropFeatureDef<T>
|
|
57
59
|
| KeyboardDragAndDropFeatureDef<T>
|
|
58
60
|
| HotkeysCoreFeatureDef<T>
|