@headless-tree/core 0.0.0-20250509160455 → 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/core/create-tree.js +9 -0
- package/lib/cjs/features/async-data-loader/feature.js +4 -4
- package/lib/cjs/features/checkboxes/feature.d.ts +2 -0
- package/lib/cjs/features/checkboxes/feature.js +94 -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/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 +4 -4
- package/lib/esm/features/checkboxes/feature.d.ts +2 -0
- package/lib/esm/features/checkboxes/feature.js +91 -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/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/feature.ts +4 -4
- package/src/features/checkboxes/checkboxes.spec.ts +134 -0
- package/src/features/checkboxes/feature.ts +119 -0
- package/src/features/checkboxes/types.ts +28 -0
- package/src/features/main/types.ts +2 -0
- package/src/features/tree/types.ts +1 -1
- package/src/index.ts +2 -0
- package/src/types/core.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -100,6 +100,15 @@ const createTree = (initialConfig) => {
|
|
|
100
100
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
101
101
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
102
102
|
},
|
|
103
|
+
buildItemInstance: ({}, itemId) => {
|
|
104
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
105
|
+
item: instance,
|
|
106
|
+
tree: treeInstance,
|
|
107
|
+
itemId,
|
|
108
|
+
}));
|
|
109
|
+
finalizeInstance();
|
|
110
|
+
return instance;
|
|
111
|
+
},
|
|
103
112
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
104
113
|
rebuildTree: () => {
|
|
105
114
|
var _a;
|
|
@@ -96,14 +96,14 @@ exports.asyncDataLoaderFeature = {
|
|
|
96
96
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
97
97
|
});
|
|
98
98
|
}),
|
|
99
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
99
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
100
100
|
var _a, _b;
|
|
101
101
|
const config = tree.getConfig();
|
|
102
102
|
const dataRef = getDataRef(tree);
|
|
103
103
|
if (dataRef.current.itemData[itemId]) {
|
|
104
104
|
return dataRef.current.itemData[itemId];
|
|
105
105
|
}
|
|
106
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
106
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
107
107
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
108
108
|
...loadingItemData,
|
|
109
109
|
itemId,
|
|
@@ -112,12 +112,12 @@ exports.asyncDataLoaderFeature = {
|
|
|
112
112
|
}
|
|
113
113
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
114
114
|
},
|
|
115
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
115
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
116
116
|
const dataRef = getDataRef(tree);
|
|
117
117
|
if (dataRef.current.childrenIds[itemId]) {
|
|
118
118
|
return dataRef.current.childrenIds[itemId];
|
|
119
119
|
}
|
|
120
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
120
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
121
121
|
return [];
|
|
122
122
|
}
|
|
123
123
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkboxesFeature = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const errors_1 = require("../../utilities/errors");
|
|
7
|
+
const getAllLoadedDescendants = (tree, itemId) => {
|
|
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();
|
|
15
|
+
};
|
|
16
|
+
exports.checkboxesFeature = {
|
|
17
|
+
key: "checkboxes",
|
|
18
|
+
overwrites: ["selection"],
|
|
19
|
+
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
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
|
+
},
|
|
28
|
+
stateHandlerNames: {
|
|
29
|
+
checkedItems: "setCheckedItems",
|
|
30
|
+
},
|
|
31
|
+
treeInstance: {
|
|
32
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
33
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
itemInstance: {
|
|
37
|
+
getCheckboxProps: ({ item }) => {
|
|
38
|
+
const checkedState = item.getCheckedState();
|
|
39
|
+
return {
|
|
40
|
+
onChange: item.toggleCheckedState,
|
|
41
|
+
checked: checkedState === types_1.CheckedState.Checked,
|
|
42
|
+
ref: (r) => {
|
|
43
|
+
if (r) {
|
|
44
|
+
r.indeterminate = checkedState === types_1.CheckedState.Indeterminate;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
toggleCheckedState: ({ item }) => {
|
|
50
|
+
if (item.getCheckedState() === types_1.CheckedState.Checked) {
|
|
51
|
+
item.setUnchecked();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
item.setChecked();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
58
|
+
const { checkedItems } = tree.getState();
|
|
59
|
+
if (checkedItems.includes(itemId)) {
|
|
60
|
+
return types_1.CheckedState.Checked;
|
|
61
|
+
}
|
|
62
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
63
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
64
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
65
|
+
return types_1.CheckedState.Checked;
|
|
66
|
+
}
|
|
67
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
68
|
+
return types_1.CheckedState.Indeterminate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return types_1.CheckedState.Unchecked;
|
|
72
|
+
},
|
|
73
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
74
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
75
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
79
|
+
...items,
|
|
80
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
85
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
86
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
90
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
export declare enum CheckedState {
|
|
3
|
+
Checked = "checked",
|
|
4
|
+
Unchecked = "unchecked",
|
|
5
|
+
Indeterminate = "indeterminate"
|
|
6
|
+
}
|
|
7
|
+
export type CheckboxesFeatureDef<T> = {
|
|
8
|
+
state: {
|
|
9
|
+
checkedItems: string[];
|
|
10
|
+
};
|
|
11
|
+
config: {
|
|
12
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
13
|
+
canCheckFolders?: boolean;
|
|
14
|
+
};
|
|
15
|
+
treeInstance: {
|
|
16
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
|
+
};
|
|
18
|
+
itemInstance: {
|
|
19
|
+
setChecked: () => void;
|
|
20
|
+
setUnchecked: () => void;
|
|
21
|
+
toggleCheckedState: () => void;
|
|
22
|
+
getCheckedState: () => CheckedState;
|
|
23
|
+
getCheckboxProps: () => Record<string, any>;
|
|
24
|
+
};
|
|
25
|
+
hotkeys: never;
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckedState = void 0;
|
|
4
|
+
var CheckedState;
|
|
5
|
+
(function (CheckedState) {
|
|
6
|
+
CheckedState["Checked"] = "checked";
|
|
7
|
+
CheckedState["Unchecked"] = "unchecked";
|
|
8
|
+
CheckedState["Indeterminate"] = "indeterminate";
|
|
9
|
+
})(CheckedState || (exports.CheckedState = CheckedState = {}));
|
|
@@ -17,6 +17,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
17
17
|
treeInstance: {
|
|
18
18
|
/** @internal */
|
|
19
19
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
20
|
+
/** @internal */
|
|
21
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
20
22
|
setState: SetStateFn<TreeState<T>>;
|
|
21
23
|
getState: () => TreeState<T>;
|
|
22
24
|
setConfig: SetStateFn<TreeConfig<T>>;
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
5
5
|
export * from "./features/drag-and-drop/types";
|
|
6
6
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
7
7
|
export * from "./features/selection/types";
|
|
8
|
+
export * from "./features/checkboxes/types";
|
|
8
9
|
export * from "./features/async-data-loader/types";
|
|
9
10
|
export * from "./features/sync-data-loader/types";
|
|
10
11
|
export * from "./features/hotkeys-core/types";
|
|
@@ -13,6 +14,7 @@ export * from "./features/renaming/types";
|
|
|
13
14
|
export * from "./features/expand-all/types";
|
|
14
15
|
export * from "./features/prop-memoization/types";
|
|
15
16
|
export * from "./features/selection/feature";
|
|
17
|
+
export * from "./features/checkboxes/feature";
|
|
16
18
|
export * from "./features/hotkeys-core/feature";
|
|
17
19
|
export * from "./features/async-data-loader/feature";
|
|
18
20
|
export * from "./features/sync-data-loader/feature";
|
package/lib/cjs/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __exportStar(require("./features/tree/types"), exports);
|
|
|
20
20
|
__exportStar(require("./features/drag-and-drop/types"), exports);
|
|
21
21
|
__exportStar(require("./features/keyboard-drag-and-drop/types"), exports);
|
|
22
22
|
__exportStar(require("./features/selection/types"), exports);
|
|
23
|
+
__exportStar(require("./features/checkboxes/types"), exports);
|
|
23
24
|
__exportStar(require("./features/async-data-loader/types"), exports);
|
|
24
25
|
__exportStar(require("./features/sync-data-loader/types"), exports);
|
|
25
26
|
__exportStar(require("./features/hotkeys-core/types"), exports);
|
|
@@ -28,6 +29,7 @@ __exportStar(require("./features/renaming/types"), exports);
|
|
|
28
29
|
__exportStar(require("./features/expand-all/types"), exports);
|
|
29
30
|
__exportStar(require("./features/prop-memoization/types"), exports);
|
|
30
31
|
__exportStar(require("./features/selection/feature"), exports);
|
|
32
|
+
__exportStar(require("./features/checkboxes/feature"), exports);
|
|
31
33
|
__exportStar(require("./features/hotkeys-core/feature"), exports);
|
|
32
34
|
__exportStar(require("./features/async-data-loader/feature"), exports);
|
|
33
35
|
__exportStar(require("./features/sync-data-loader/feature"), exports);
|
package/lib/cjs/types/core.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
10
10
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
11
11
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
12
12
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
13
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
13
14
|
export type Updater<T> = T | ((old: T) => T);
|
|
14
15
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
15
16
|
export type FeatureDef = {
|
|
@@ -34,7 +35,7 @@ type MergedFeatures<F extends FeatureDef> = {
|
|
|
34
35
|
itemInstance: UnionToIntersection<F["itemInstance"]>;
|
|
35
36
|
hotkeys: F["hotkeys"];
|
|
36
37
|
};
|
|
37
|
-
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
|
+
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
39
|
type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
|
|
39
40
|
export interface TreeState<T> extends TreeStateType<T> {
|
|
40
41
|
}
|
|
@@ -97,6 +97,15 @@ export const createTree = (initialConfig) => {
|
|
|
97
97
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
98
98
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
99
99
|
},
|
|
100
|
+
buildItemInstance: ({}, itemId) => {
|
|
101
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
102
|
+
item: instance,
|
|
103
|
+
tree: treeInstance,
|
|
104
|
+
itemId,
|
|
105
|
+
}));
|
|
106
|
+
finalizeInstance();
|
|
107
|
+
return instance;
|
|
108
|
+
},
|
|
100
109
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
101
110
|
rebuildTree: () => {
|
|
102
111
|
var _a;
|
|
@@ -93,14 +93,14 @@ export const asyncDataLoaderFeature = {
|
|
|
93
93
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
94
94
|
});
|
|
95
95
|
}),
|
|
96
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
96
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
97
97
|
var _a, _b;
|
|
98
98
|
const config = tree.getConfig();
|
|
99
99
|
const dataRef = getDataRef(tree);
|
|
100
100
|
if (dataRef.current.itemData[itemId]) {
|
|
101
101
|
return dataRef.current.itemData[itemId];
|
|
102
102
|
}
|
|
103
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
103
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
104
104
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
105
105
|
...loadingItemData,
|
|
106
106
|
itemId,
|
|
@@ -109,12 +109,12 @@ export const asyncDataLoaderFeature = {
|
|
|
109
109
|
}
|
|
110
110
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
111
111
|
},
|
|
112
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
112
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
113
113
|
const dataRef = getDataRef(tree);
|
|
114
114
|
if (dataRef.current.childrenIds[itemId]) {
|
|
115
115
|
return dataRef.current.childrenIds[itemId];
|
|
116
116
|
}
|
|
117
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
117
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
118
118
|
return [];
|
|
119
119
|
}
|
|
120
120
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { makeStateUpdater } from "../../utils";
|
|
2
|
+
import { CheckedState } from "./types";
|
|
3
|
+
import { throwError } from "../../utilities/errors";
|
|
4
|
+
const getAllLoadedDescendants = (tree, itemId) => {
|
|
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();
|
|
12
|
+
};
|
|
13
|
+
export const checkboxesFeature = {
|
|
14
|
+
key: "checkboxes",
|
|
15
|
+
overwrites: ["selection"],
|
|
16
|
+
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
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
|
+
},
|
|
25
|
+
stateHandlerNames: {
|
|
26
|
+
checkedItems: "setCheckedItems",
|
|
27
|
+
},
|
|
28
|
+
treeInstance: {
|
|
29
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
30
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
itemInstance: {
|
|
34
|
+
getCheckboxProps: ({ item }) => {
|
|
35
|
+
const checkedState = item.getCheckedState();
|
|
36
|
+
return {
|
|
37
|
+
onChange: item.toggleCheckedState,
|
|
38
|
+
checked: checkedState === CheckedState.Checked,
|
|
39
|
+
ref: (r) => {
|
|
40
|
+
if (r) {
|
|
41
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
toggleCheckedState: ({ item }) => {
|
|
47
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
48
|
+
item.setUnchecked();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
item.setChecked();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
55
|
+
const { checkedItems } = tree.getState();
|
|
56
|
+
if (checkedItems.includes(itemId)) {
|
|
57
|
+
return CheckedState.Checked;
|
|
58
|
+
}
|
|
59
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
60
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
61
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
62
|
+
return CheckedState.Checked;
|
|
63
|
+
}
|
|
64
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
65
|
+
return CheckedState.Indeterminate;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return CheckedState.Unchecked;
|
|
69
|
+
},
|
|
70
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
71
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
72
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
76
|
+
...items,
|
|
77
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
82
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
83
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
87
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
export declare enum CheckedState {
|
|
3
|
+
Checked = "checked",
|
|
4
|
+
Unchecked = "unchecked",
|
|
5
|
+
Indeterminate = "indeterminate"
|
|
6
|
+
}
|
|
7
|
+
export type CheckboxesFeatureDef<T> = {
|
|
8
|
+
state: {
|
|
9
|
+
checkedItems: string[];
|
|
10
|
+
};
|
|
11
|
+
config: {
|
|
12
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
13
|
+
canCheckFolders?: boolean;
|
|
14
|
+
};
|
|
15
|
+
treeInstance: {
|
|
16
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
|
+
};
|
|
18
|
+
itemInstance: {
|
|
19
|
+
setChecked: () => void;
|
|
20
|
+
setUnchecked: () => void;
|
|
21
|
+
toggleCheckedState: () => void;
|
|
22
|
+
getCheckedState: () => CheckedState;
|
|
23
|
+
getCheckboxProps: () => Record<string, any>;
|
|
24
|
+
};
|
|
25
|
+
hotkeys: never;
|
|
26
|
+
};
|
|
@@ -17,6 +17,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
17
17
|
treeInstance: {
|
|
18
18
|
/** @internal */
|
|
19
19
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
20
|
+
/** @internal */
|
|
21
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
20
22
|
setState: SetStateFn<TreeState<T>>;
|
|
21
23
|
getState: () => TreeState<T>;
|
|
22
24
|
setConfig: SetStateFn<TreeConfig<T>>;
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
5
5
|
export * from "./features/drag-and-drop/types";
|
|
6
6
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
7
7
|
export * from "./features/selection/types";
|
|
8
|
+
export * from "./features/checkboxes/types";
|
|
8
9
|
export * from "./features/async-data-loader/types";
|
|
9
10
|
export * from "./features/sync-data-loader/types";
|
|
10
11
|
export * from "./features/hotkeys-core/types";
|
|
@@ -13,6 +14,7 @@ export * from "./features/renaming/types";
|
|
|
13
14
|
export * from "./features/expand-all/types";
|
|
14
15
|
export * from "./features/prop-memoization/types";
|
|
15
16
|
export * from "./features/selection/feature";
|
|
17
|
+
export * from "./features/checkboxes/feature";
|
|
16
18
|
export * from "./features/hotkeys-core/feature";
|
|
17
19
|
export * from "./features/async-data-loader/feature";
|
|
18
20
|
export * from "./features/sync-data-loader/feature";
|
package/lib/esm/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./features/tree/types";
|
|
|
4
4
|
export * from "./features/drag-and-drop/types";
|
|
5
5
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
6
6
|
export * from "./features/selection/types";
|
|
7
|
+
export * from "./features/checkboxes/types";
|
|
7
8
|
export * from "./features/async-data-loader/types";
|
|
8
9
|
export * from "./features/sync-data-loader/types";
|
|
9
10
|
export * from "./features/hotkeys-core/types";
|
|
@@ -12,6 +13,7 @@ export * from "./features/renaming/types";
|
|
|
12
13
|
export * from "./features/expand-all/types";
|
|
13
14
|
export * from "./features/prop-memoization/types";
|
|
14
15
|
export * from "./features/selection/feature";
|
|
16
|
+
export * from "./features/checkboxes/feature";
|
|
15
17
|
export * from "./features/hotkeys-core/feature";
|
|
16
18
|
export * from "./features/async-data-loader/feature";
|
|
17
19
|
export * from "./features/sync-data-loader/feature";
|
package/lib/esm/types/core.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
10
10
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
11
11
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
12
12
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
13
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
13
14
|
export type Updater<T> = T | ((old: T) => T);
|
|
14
15
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
15
16
|
export type FeatureDef = {
|
|
@@ -34,7 +35,7 @@ type MergedFeatures<F extends FeatureDef> = {
|
|
|
34
35
|
itemInstance: UnionToIntersection<F["itemInstance"]>;
|
|
35
36
|
hotkeys: F["hotkeys"];
|
|
36
37
|
};
|
|
37
|
-
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
|
+
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
39
|
type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
|
|
39
40
|
export interface TreeState<T> extends TreeStateType<T> {
|
|
40
41
|
}
|
package/package.json
CHANGED
package/src/core/create-tree.ts
CHANGED
|
@@ -151,6 +151,19 @@ export const createTree = <T>(
|
|
|
151
151
|
] as Function;
|
|
152
152
|
externalStateSetter?.(state[stateName]);
|
|
153
153
|
},
|
|
154
|
+
buildItemInstance: ({}, itemId) => {
|
|
155
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
156
|
+
features,
|
|
157
|
+
"itemInstance",
|
|
158
|
+
(instance) => ({
|
|
159
|
+
item: instance,
|
|
160
|
+
tree: treeInstance,
|
|
161
|
+
itemId,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
finalizeInstance();
|
|
165
|
+
return instance;
|
|
166
|
+
},
|
|
154
167
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
155
168
|
rebuildTree: () => {
|
|
156
169
|
rebuildItemMeta();
|
|
@@ -106,7 +106,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
106
106
|
});
|
|
107
107
|
},
|
|
108
108
|
|
|
109
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
109
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
110
110
|
const config = tree.getConfig();
|
|
111
111
|
const dataRef = getDataRef(tree);
|
|
112
112
|
|
|
@@ -114,7 +114,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
114
114
|
return dataRef.current.itemData[itemId];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
117
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
118
118
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
119
119
|
...loadingItemData,
|
|
120
120
|
itemId,
|
|
@@ -126,13 +126,13 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
126
126
|
return config.createLoadingItemData?.() ?? null;
|
|
127
127
|
},
|
|
128
128
|
|
|
129
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
129
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
130
130
|
const dataRef = getDataRef(tree);
|
|
131
131
|
if (dataRef.current.childrenIds[itemId]) {
|
|
132
132
|
return dataRef.current.childrenIds[itemId];
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
135
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
136
136
|
return [];
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
|
+
import { makeStateUpdater } from "../../utils";
|
|
3
|
+
import { CheckedState } from "./types";
|
|
4
|
+
import { throwError } from "../../utilities/errors";
|
|
5
|
+
|
|
6
|
+
const getAllLoadedDescendants = <T>(
|
|
7
|
+
tree: TreeInstance<T>,
|
|
8
|
+
itemId: string,
|
|
9
|
+
): string[] => {
|
|
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();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const checkboxesFeature: FeatureImplementation = {
|
|
20
|
+
key: "checkboxes",
|
|
21
|
+
|
|
22
|
+
overwrites: ["selection"],
|
|
23
|
+
|
|
24
|
+
getInitialState: (initialState) => ({
|
|
25
|
+
checkedItems: [],
|
|
26
|
+
...initialState,
|
|
27
|
+
}),
|
|
28
|
+
|
|
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
|
+
},
|
|
42
|
+
|
|
43
|
+
stateHandlerNames: {
|
|
44
|
+
checkedItems: "setCheckedItems",
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
treeInstance: {
|
|
48
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
49
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
itemInstance: {
|
|
54
|
+
getCheckboxProps: ({ item }) => {
|
|
55
|
+
const checkedState = item.getCheckedState();
|
|
56
|
+
return {
|
|
57
|
+
onChange: item.toggleCheckedState,
|
|
58
|
+
checked: checkedState === CheckedState.Checked,
|
|
59
|
+
ref: (r: any) => {
|
|
60
|
+
if (r) {
|
|
61
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
toggleCheckedState: ({ item }) => {
|
|
68
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
69
|
+
item.setUnchecked();
|
|
70
|
+
} else {
|
|
71
|
+
item.setChecked();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
76
|
+
const { checkedItems } = tree.getState();
|
|
77
|
+
|
|
78
|
+
if (checkedItems.includes(itemId)) {
|
|
79
|
+
return CheckedState.Checked;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (item.isFolder() && !tree.getConfig().canCheckFolders) {
|
|
83
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
84
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
85
|
+
return CheckedState.Checked;
|
|
86
|
+
}
|
|
87
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
88
|
+
return CheckedState.Indeterminate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return CheckedState.Unchecked;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
96
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
97
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
98
|
+
} else {
|
|
99
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
100
|
+
...items,
|
|
101
|
+
...getAllLoadedDescendants(tree, itemId),
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
107
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
108
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
109
|
+
items.filter((id) => id !== itemId),
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
113
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
114
|
+
items.filter((id) => !descendants.includes(id)),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
@@ -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: () => void;
|
|
22
|
+
setUnchecked: () => void;
|
|
23
|
+
toggleCheckedState: () => 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>>;
|
|
@@ -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;
|
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>
|