@headless-tree/core 0.0.0-20250611234714 → 0.0.0-20250615192258
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 +11 -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/drag-and-drop/feature.js +1 -1
- 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/drag-and-drop/feature.js +1 -1
- 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/drag-and-drop/feature.ts +2 -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
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20250615192258
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8dd6231: Introduced new Checkboxes feature (#88)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 309feba: fixed an issue where the drag-forbidden cursor is shown briefly between changing drag targets (#114)
|
|
12
|
+
|
|
13
|
+
## 1.2.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
|
6
16
|
|
|
@@ -112,6 +112,15 @@ const createTree = (initialConfig) => {
|
|
|
112
112
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
113
113
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
114
114
|
},
|
|
115
|
+
buildItemInstance: ({}, itemId) => {
|
|
116
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
117
|
+
item: instance,
|
|
118
|
+
tree: treeInstance,
|
|
119
|
+
itemId,
|
|
120
|
+
}));
|
|
121
|
+
finalizeInstance();
|
|
122
|
+
return instance;
|
|
123
|
+
},
|
|
115
124
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
116
125
|
rebuildTree: () => {
|
|
117
126
|
var _a;
|
|
@@ -75,14 +75,14 @@ exports.asyncDataLoaderFeature = {
|
|
|
75
75
|
var _b;
|
|
76
76
|
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
77
77
|
}),
|
|
78
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
78
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
79
79
|
var _a, _b;
|
|
80
80
|
const config = tree.getConfig();
|
|
81
81
|
const dataRef = getDataRef(tree);
|
|
82
82
|
if (dataRef.current.itemData[itemId]) {
|
|
83
83
|
return dataRef.current.itemData[itemId];
|
|
84
84
|
}
|
|
85
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
85
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
86
86
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
87
87
|
...loadingItemData,
|
|
88
88
|
itemId,
|
|
@@ -91,12 +91,12 @@ exports.asyncDataLoaderFeature = {
|
|
|
91
91
|
}
|
|
92
92
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
93
93
|
},
|
|
94
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
94
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
95
95
|
const dataRef = getDataRef(tree);
|
|
96
96
|
if (dataRef.current.childrenIds[itemId]) {
|
|
97
97
|
return dataRef.current.childrenIds[itemId];
|
|
98
98
|
}
|
|
99
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
99
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
100
100
|
return [];
|
|
101
101
|
}
|
|
102
102
|
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 = {}));
|
|
@@ -74,7 +74,7 @@ exports.dragAndDropFeature = {
|
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
itemInstance: {
|
|
77
|
-
getProps: ({ tree, item, prev }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { draggable: true, onDragStart: (e) => {
|
|
77
|
+
getProps: ({ tree, item, prev }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { draggable: true, onDragEnter: (e) => e.preventDefault(), onDragStart: (e) => {
|
|
78
78
|
var _a, _b, _c;
|
|
79
79
|
const selectedItems = tree.getSelectedItems();
|
|
80
80
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
@@ -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
|
@@ -21,6 +21,7 @@ __exportStar(require("./features/tree/types"), exports);
|
|
|
21
21
|
__exportStar(require("./features/drag-and-drop/types"), exports);
|
|
22
22
|
__exportStar(require("./features/keyboard-drag-and-drop/types"), exports);
|
|
23
23
|
__exportStar(require("./features/selection/types"), exports);
|
|
24
|
+
__exportStar(require("./features/checkboxes/types"), exports);
|
|
24
25
|
__exportStar(require("./features/async-data-loader/types"), exports);
|
|
25
26
|
__exportStar(require("./features/sync-data-loader/types"), exports);
|
|
26
27
|
__exportStar(require("./features/hotkeys-core/types"), exports);
|
|
@@ -29,6 +30,7 @@ __exportStar(require("./features/renaming/types"), exports);
|
|
|
29
30
|
__exportStar(require("./features/expand-all/types"), exports);
|
|
30
31
|
__exportStar(require("./features/prop-memoization/types"), exports);
|
|
31
32
|
__exportStar(require("./features/selection/feature"), exports);
|
|
33
|
+
__exportStar(require("./features/checkboxes/feature"), exports);
|
|
32
34
|
__exportStar(require("./features/hotkeys-core/feature"), exports);
|
|
33
35
|
__exportStar(require("./features/async-data-loader/feature"), exports);
|
|
34
36
|
__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
|
}
|
|
@@ -109,6 +109,15 @@ export const createTree = (initialConfig) => {
|
|
|
109
109
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
110
110
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
111
111
|
},
|
|
112
|
+
buildItemInstance: ({}, itemId) => {
|
|
113
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
114
|
+
item: instance,
|
|
115
|
+
tree: treeInstance,
|
|
116
|
+
itemId,
|
|
117
|
+
}));
|
|
118
|
+
finalizeInstance();
|
|
119
|
+
return instance;
|
|
120
|
+
},
|
|
112
121
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
113
122
|
rebuildTree: () => {
|
|
114
123
|
var _a;
|
|
@@ -72,14 +72,14 @@ export const asyncDataLoaderFeature = {
|
|
|
72
72
|
var _b;
|
|
73
73
|
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
74
74
|
}),
|
|
75
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
75
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
76
76
|
var _a, _b;
|
|
77
77
|
const config = tree.getConfig();
|
|
78
78
|
const dataRef = getDataRef(tree);
|
|
79
79
|
if (dataRef.current.itemData[itemId]) {
|
|
80
80
|
return dataRef.current.itemData[itemId];
|
|
81
81
|
}
|
|
82
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
82
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
83
83
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
84
84
|
...loadingItemData,
|
|
85
85
|
itemId,
|
|
@@ -88,12 +88,12 @@ export const asyncDataLoaderFeature = {
|
|
|
88
88
|
}
|
|
89
89
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
90
90
|
},
|
|
91
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
91
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
92
92
|
const dataRef = getDataRef(tree);
|
|
93
93
|
if (dataRef.current.childrenIds[itemId]) {
|
|
94
94
|
return dataRef.current.childrenIds[itemId];
|
|
95
95
|
}
|
|
96
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
96
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
97
97
|
return [];
|
|
98
98
|
}
|
|
99
99
|
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
|
+
};
|
|
@@ -71,7 +71,7 @@ export const dragAndDropFeature = {
|
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
itemInstance: {
|
|
74
|
-
getProps: ({ tree, item, prev }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { draggable: true, onDragStart: (e) => {
|
|
74
|
+
getProps: ({ tree, item, prev }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { draggable: true, onDragEnter: (e) => e.preventDefault(), onDragStart: (e) => {
|
|
75
75
|
var _a, _b, _c;
|
|
76
76
|
const selectedItems = tree.getSelectedItems();
|
|
77
77
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
@@ -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
|
@@ -170,6 +170,19 @@ export const createTree = <T>(
|
|
|
170
170
|
] as Function;
|
|
171
171
|
externalStateSetter?.(state[stateName]);
|
|
172
172
|
},
|
|
173
|
+
buildItemInstance: ({}, itemId) => {
|
|
174
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
175
|
+
features,
|
|
176
|
+
"itemInstance",
|
|
177
|
+
(instance) => ({
|
|
178
|
+
item: instance,
|
|
179
|
+
tree: treeInstance,
|
|
180
|
+
itemId,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
finalizeInstance();
|
|
184
|
+
return instance;
|
|
185
|
+
},
|
|
173
186
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
174
187
|
rebuildTree: () => {
|
|
175
188
|
rebuildItemMeta();
|
|
@@ -95,7 +95,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
95
95
|
);
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
-
retrieveItemData: ({ tree }, itemId) => {
|
|
98
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
99
99
|
const config = tree.getConfig();
|
|
100
100
|
const dataRef = getDataRef(tree);
|
|
101
101
|
|
|
@@ -103,7 +103,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
103
103
|
return dataRef.current.itemData[itemId];
|
|
104
104
|
}
|
|
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,
|
|
@@ -115,13 +115,13 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
115
115
|
return config.createLoadingItemData?.() ?? null;
|
|
116
116
|
},
|
|
117
117
|
|
|
118
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
118
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
119
119
|
const dataRef = getDataRef(tree);
|
|
120
120
|
if (dataRef.current.childrenIds[itemId]) {
|
|
121
121
|
return dataRef.current.childrenIds[itemId];
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
124
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
125
125
|
return [];
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -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
|
+
};
|
|
@@ -101,6 +101,8 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
101
101
|
|
|
102
102
|
draggable: true,
|
|
103
103
|
|
|
104
|
+
onDragEnter: (e: DragEvent) => e.preventDefault(),
|
|
105
|
+
|
|
104
106
|
onDragStart: (e: DragEvent) => {
|
|
105
107
|
const selectedItems = tree.getSelectedItems();
|
|
106
108
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
@@ -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>
|