@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/lib/cjs/core/create-tree.js +9 -0
  3. package/lib/cjs/features/async-data-loader/feature.js +90 -64
  4. package/lib/cjs/features/async-data-loader/types.d.ts +12 -3
  5. package/lib/cjs/features/checkboxes/feature.d.ts +2 -0
  6. package/lib/cjs/features/checkboxes/feature.js +128 -0
  7. package/lib/cjs/features/checkboxes/types.d.ts +26 -0
  8. package/lib/cjs/features/checkboxes/types.js +9 -0
  9. package/lib/cjs/features/main/types.d.ts +2 -0
  10. package/lib/cjs/features/sync-data-loader/feature.js +2 -0
  11. package/lib/cjs/features/sync-data-loader/types.d.ts +2 -2
  12. package/lib/cjs/index.d.ts +2 -0
  13. package/lib/cjs/index.js +2 -0
  14. package/lib/cjs/types/core.d.ts +2 -1
  15. package/lib/esm/core/create-tree.js +9 -0
  16. package/lib/esm/features/async-data-loader/feature.js +90 -64
  17. package/lib/esm/features/async-data-loader/types.d.ts +12 -3
  18. package/lib/esm/features/checkboxes/feature.d.ts +2 -0
  19. package/lib/esm/features/checkboxes/feature.js +125 -0
  20. package/lib/esm/features/checkboxes/types.d.ts +26 -0
  21. package/lib/esm/features/checkboxes/types.js +6 -0
  22. package/lib/esm/features/main/types.d.ts +2 -0
  23. package/lib/esm/features/sync-data-loader/feature.js +2 -0
  24. package/lib/esm/features/sync-data-loader/types.d.ts +2 -2
  25. package/lib/esm/index.d.ts +2 -0
  26. package/lib/esm/index.js +2 -0
  27. package/lib/esm/types/core.d.ts +2 -1
  28. package/package.json +1 -1
  29. package/src/core/create-tree.ts +13 -0
  30. package/src/features/async-data-loader/async-data-loader.spec.ts +36 -0
  31. package/src/features/async-data-loader/feature.ts +103 -68
  32. package/src/features/async-data-loader/types.ts +14 -3
  33. package/src/features/checkboxes/feature.ts +150 -0
  34. package/src/features/checkboxes/types.ts +28 -0
  35. package/src/features/main/types.ts +2 -0
  36. package/src/features/sync-data-loader/feature.ts +3 -0
  37. package/src/features/sync-data-loader/types.ts +2 -2
  38. package/src/index.ts +2 -0
  39. 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
- retrieveItemData: ({ tree }, itemId) => {
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 = tree.getDataRef<AsyncDataLoaderDataRef>();
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
- (async () => {
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 config = tree.getConfig();
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
- (async () => {
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
- const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
150
- delete dataRef.current.itemData?.[itemId];
151
- tree.retrieveItemData(itemId);
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
- const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
155
- delete dataRef.current.childrenIds?.[itemId];
156
- tree.retrieveChildrenIds(itemId);
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
- invalidateItemData: () => void;
41
- invalidateChildrenIds: () => void;
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>