@headless-tree/core 0.0.0-20250717001619 → 0.0.0-20250725212154
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -1
- package/lib/cjs/features/checkboxes/feature.js +26 -18
- package/lib/cjs/features/checkboxes/types.d.ts +1 -0
- package/lib/cjs/features/tree/feature.js +3 -2
- package/lib/esm/features/checkboxes/feature.js +26 -18
- package/lib/esm/features/checkboxes/types.d.ts +1 -0
- package/lib/esm/features/tree/feature.js +3 -2
- package/package.json +1 -1
- package/src/features/checkboxes/checkboxes.spec.ts +20 -5
- package/src/features/checkboxes/feature.ts +30 -15
- package/src/features/checkboxes/types.ts +1 -0
- package/src/features/tree/feature.ts +2 -1
- package/src/features/tree/tree.spec.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20250725212154
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
7
|
- b41e1d2: fixed a bug where ending drag without successful drop doesn't properly reset drag line (#132)
|
|
8
8
|
- b413f74: Fix `aria-posinset` and `aria-level` to be 1-based indexing
|
|
9
|
+
- a250b3b: Fix a bug where expand from the initial keyboard focus fails when rootItemId is an empty string
|
|
9
10
|
|
|
10
11
|
## 1.2.1
|
|
11
12
|
|
|
@@ -4,26 +4,30 @@ exports.checkboxesFeature = void 0;
|
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const errors_1 = require("../../utilities/errors");
|
|
7
|
-
const getAllLoadedDescendants = (tree, itemId) => {
|
|
7
|
+
const getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
8
8
|
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
9
9
|
return [itemId];
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
const descendants = tree
|
|
12
12
|
.retrieveChildrenIds(itemId)
|
|
13
|
-
.map((child) => getAllLoadedDescendants(tree, child))
|
|
13
|
+
.map((child) => getAllLoadedDescendants(tree, child, includeFolders))
|
|
14
14
|
.flat();
|
|
15
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
15
16
|
};
|
|
16
17
|
exports.checkboxesFeature = {
|
|
17
18
|
key: "checkboxes",
|
|
18
19
|
overwrites: ["selection"],
|
|
19
20
|
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
20
21
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
21
|
-
var _a;
|
|
22
|
+
var _a, _b, _c;
|
|
22
23
|
const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
|
|
23
|
-
if (hasAsyncLoader &&
|
|
24
|
-
(0, errors_1.throwError)(
|
|
24
|
+
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
25
|
+
(0, errors_1.throwError)(`propagateCheckedState not supported with async trees`);
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) !== null && _b !== void 0 ? _b : !hasAsyncLoader;
|
|
28
|
+
const canCheckFolders = (_c = defaultConfig.canCheckFolders) !== null && _c !== void 0 ? _c : !propagateCheckedState;
|
|
29
|
+
return Object.assign({ setCheckedItems: (0, utils_1.makeStateUpdater)("checkedItems", tree), propagateCheckedState,
|
|
30
|
+
canCheckFolders }, defaultConfig);
|
|
27
31
|
},
|
|
28
32
|
stateHandlerNames: {
|
|
29
33
|
checkedItems: "setCheckedItems",
|
|
@@ -54,12 +58,14 @@ exports.checkboxesFeature = {
|
|
|
54
58
|
item.setChecked();
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
|
-
getCheckedState: ({ item, tree
|
|
61
|
+
getCheckedState: ({ item, tree }) => {
|
|
58
62
|
const { checkedItems } = tree.getState();
|
|
63
|
+
const { propagateCheckedState } = tree.getConfig();
|
|
64
|
+
const itemId = item.getId();
|
|
59
65
|
if (checkedItems.includes(itemId)) {
|
|
60
66
|
return types_1.CheckedState.Checked;
|
|
61
67
|
}
|
|
62
|
-
if (item.isFolder() &&
|
|
68
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
63
69
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
64
70
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
65
71
|
return types_1.CheckedState.Checked;
|
|
@@ -71,23 +77,25 @@ exports.checkboxesFeature = {
|
|
|
71
77
|
return types_1.CheckedState.Unchecked;
|
|
72
78
|
},
|
|
73
79
|
setChecked: ({ item, tree, itemId }) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
80
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
81
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
78
82
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
79
83
|
...items,
|
|
80
|
-
...getAllLoadedDescendants(tree, itemId),
|
|
84
|
+
...getAllLoadedDescendants(tree, itemId, canCheckFolders),
|
|
81
85
|
]);
|
|
82
86
|
}
|
|
87
|
+
else if (!item.isFolder() || canCheckFolders) {
|
|
88
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
89
|
+
}
|
|
83
90
|
},
|
|
84
91
|
setUnchecked: ({ item, tree, itemId }) => {
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
93
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
94
|
+
const descendants = getAllLoadedDescendants(tree, itemId, canCheckFolders);
|
|
95
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id) && id !== itemId));
|
|
87
96
|
}
|
|
88
97
|
else {
|
|
89
|
-
|
|
90
|
-
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
98
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
91
99
|
}
|
|
92
100
|
},
|
|
93
101
|
},
|
|
@@ -56,8 +56,9 @@ exports.treeFeature = {
|
|
|
56
56
|
return flatItems;
|
|
57
57
|
},
|
|
58
58
|
getFocusedItem: ({ tree }) => {
|
|
59
|
-
var _a
|
|
60
|
-
|
|
59
|
+
var _a;
|
|
60
|
+
const focusedItemId = tree.getState().focusedItem;
|
|
61
|
+
return ((_a = (focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null)) !== null && _a !== void 0 ? _a : tree.getItems()[0]);
|
|
61
62
|
},
|
|
62
63
|
getRootItem: ({ tree }) => {
|
|
63
64
|
const { rootItemId } = tree.getConfig();
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
import { makeStateUpdater } from "../../utils";
|
|
2
2
|
import { CheckedState } from "./types";
|
|
3
3
|
import { throwError } from "../../utilities/errors";
|
|
4
|
-
const getAllLoadedDescendants = (tree, itemId) => {
|
|
4
|
+
const getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
5
5
|
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
6
6
|
return [itemId];
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
const descendants = tree
|
|
9
9
|
.retrieveChildrenIds(itemId)
|
|
10
|
-
.map((child) => getAllLoadedDescendants(tree, child))
|
|
10
|
+
.map((child) => getAllLoadedDescendants(tree, child, includeFolders))
|
|
11
11
|
.flat();
|
|
12
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
12
13
|
};
|
|
13
14
|
export const checkboxesFeature = {
|
|
14
15
|
key: "checkboxes",
|
|
15
16
|
overwrites: ["selection"],
|
|
16
17
|
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
17
18
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
18
|
-
var _a;
|
|
19
|
+
var _a, _b, _c;
|
|
19
20
|
const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
|
|
20
|
-
if (hasAsyncLoader &&
|
|
21
|
-
throwError(
|
|
21
|
+
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
22
|
+
throwError(`propagateCheckedState not supported with async trees`);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) !== null && _b !== void 0 ? _b : !hasAsyncLoader;
|
|
25
|
+
const canCheckFolders = (_c = defaultConfig.canCheckFolders) !== null && _c !== void 0 ? _c : !propagateCheckedState;
|
|
26
|
+
return Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree), propagateCheckedState,
|
|
27
|
+
canCheckFolders }, defaultConfig);
|
|
24
28
|
},
|
|
25
29
|
stateHandlerNames: {
|
|
26
30
|
checkedItems: "setCheckedItems",
|
|
@@ -51,12 +55,14 @@ export const checkboxesFeature = {
|
|
|
51
55
|
item.setChecked();
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
|
-
getCheckedState: ({ item, tree
|
|
58
|
+
getCheckedState: ({ item, tree }) => {
|
|
55
59
|
const { checkedItems } = tree.getState();
|
|
60
|
+
const { propagateCheckedState } = tree.getConfig();
|
|
61
|
+
const itemId = item.getId();
|
|
56
62
|
if (checkedItems.includes(itemId)) {
|
|
57
63
|
return CheckedState.Checked;
|
|
58
64
|
}
|
|
59
|
-
if (item.isFolder() &&
|
|
65
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
60
66
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
61
67
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
62
68
|
return CheckedState.Checked;
|
|
@@ -68,23 +74,25 @@ export const checkboxesFeature = {
|
|
|
68
74
|
return CheckedState.Unchecked;
|
|
69
75
|
},
|
|
70
76
|
setChecked: ({ item, tree, itemId }) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
77
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
78
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
75
79
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
76
80
|
...items,
|
|
77
|
-
...getAllLoadedDescendants(tree, itemId),
|
|
81
|
+
...getAllLoadedDescendants(tree, itemId, canCheckFolders),
|
|
78
82
|
]);
|
|
79
83
|
}
|
|
84
|
+
else if (!item.isFolder() || canCheckFolders) {
|
|
85
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
86
|
+
}
|
|
80
87
|
},
|
|
81
88
|
setUnchecked: ({ item, tree, itemId }) => {
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
90
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
91
|
+
const descendants = getAllLoadedDescendants(tree, itemId, canCheckFolders);
|
|
92
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id) && id !== itemId));
|
|
84
93
|
}
|
|
85
94
|
else {
|
|
86
|
-
|
|
87
|
-
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
|
|
95
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
88
96
|
}
|
|
89
97
|
},
|
|
90
98
|
},
|
|
@@ -53,8 +53,9 @@ export const treeFeature = {
|
|
|
53
53
|
return flatItems;
|
|
54
54
|
},
|
|
55
55
|
getFocusedItem: ({ tree }) => {
|
|
56
|
-
var _a
|
|
57
|
-
|
|
56
|
+
var _a;
|
|
57
|
+
const focusedItemId = tree.getState().focusedItem;
|
|
58
|
+
return ((_a = (focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null)) !== null && _a !== void 0 ? _a : tree.getItems()[0]);
|
|
58
59
|
},
|
|
59
60
|
getRootItem: ({ tree }) => {
|
|
60
61
|
const { rootItemId } = tree.getConfig();
|
package/package.json
CHANGED
|
@@ -74,17 +74,28 @@ describe("core-feature/checkboxes", () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
it("should handle folder checking
|
|
77
|
+
it("should handle folder checking", async () => {
|
|
78
78
|
const tree = await factory
|
|
79
|
-
.with({ canCheckFolders: true })
|
|
79
|
+
.with({ canCheckFolders: true, propagateCheckedState: false })
|
|
80
80
|
.createTestCaseTree();
|
|
81
81
|
|
|
82
82
|
tree.item("x11").setChecked();
|
|
83
83
|
expect(tree.instance.getState().checkedItems).toContain("x11");
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it("should
|
|
87
|
-
const tree = await factory
|
|
86
|
+
it("should not check folders if disabled", async () => {
|
|
87
|
+
const tree = await factory
|
|
88
|
+
.with({ canCheckFolders: false, propagateCheckedState: false })
|
|
89
|
+
.createTestCaseTree();
|
|
90
|
+
|
|
91
|
+
tree.item("x11").setChecked();
|
|
92
|
+
expect(tree.instance.getState().checkedItems.length).toBe(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should propagate checked state", async () => {
|
|
96
|
+
const tree = await factory
|
|
97
|
+
.with({ propagateCheckedState: true })
|
|
98
|
+
.createTestCaseTree();
|
|
88
99
|
|
|
89
100
|
tree.item("x11").setChecked();
|
|
90
101
|
expect(tree.instance.getState().checkedItems).toEqual(
|
|
@@ -93,7 +104,9 @@ describe("core-feature/checkboxes", () => {
|
|
|
93
104
|
});
|
|
94
105
|
|
|
95
106
|
it("should turn folder indeterminate", async () => {
|
|
96
|
-
const tree = await factory
|
|
107
|
+
const tree = await factory
|
|
108
|
+
.with({ propagateCheckedState: true })
|
|
109
|
+
.createTestCaseTree();
|
|
97
110
|
|
|
98
111
|
tree.item("x111").setChecked();
|
|
99
112
|
expect(tree.item("x11").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
@@ -103,6 +116,8 @@ describe("core-feature/checkboxes", () => {
|
|
|
103
116
|
const tree = await factory
|
|
104
117
|
.with({
|
|
105
118
|
isItemFolder: (item) => item.getItemData().length < 4,
|
|
119
|
+
propagateCheckedState: true,
|
|
120
|
+
canCheckFolders: false,
|
|
106
121
|
})
|
|
107
122
|
.createTestCaseTree();
|
|
108
123
|
|
|
@@ -6,14 +6,16 @@ import { throwError } from "../../utilities/errors";
|
|
|
6
6
|
const getAllLoadedDescendants = <T>(
|
|
7
7
|
tree: TreeInstance<T>,
|
|
8
8
|
itemId: string,
|
|
9
|
+
includeFolders = false,
|
|
9
10
|
): string[] => {
|
|
10
11
|
if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
|
|
11
12
|
return [itemId];
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
const descendants = tree
|
|
14
15
|
.retrieveChildrenIds(itemId)
|
|
15
|
-
.map((child) => getAllLoadedDescendants(tree, child))
|
|
16
|
+
.map((child) => getAllLoadedDescendants(tree, child, includeFolders))
|
|
16
17
|
.flat();
|
|
18
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export const checkboxesFeature: FeatureImplementation = {
|
|
@@ -30,12 +32,17 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
30
32
|
const hasAsyncLoader = defaultConfig.features?.some(
|
|
31
33
|
(f) => f.key === "async-data-loader",
|
|
32
34
|
);
|
|
33
|
-
if (hasAsyncLoader &&
|
|
34
|
-
throwError(
|
|
35
|
+
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
36
|
+
throwError(`propagateCheckedState not supported with async trees`);
|
|
35
37
|
}
|
|
38
|
+
const propagateCheckedState =
|
|
39
|
+
defaultConfig.propagateCheckedState ?? !hasAsyncLoader;
|
|
40
|
+
const canCheckFolders =
|
|
41
|
+
defaultConfig.canCheckFolders ?? !propagateCheckedState;
|
|
36
42
|
return {
|
|
37
43
|
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
38
|
-
|
|
44
|
+
propagateCheckedState,
|
|
45
|
+
canCheckFolders,
|
|
39
46
|
...defaultConfig,
|
|
40
47
|
};
|
|
41
48
|
},
|
|
@@ -72,14 +79,16 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
72
79
|
}
|
|
73
80
|
},
|
|
74
81
|
|
|
75
|
-
getCheckedState: ({ item, tree
|
|
82
|
+
getCheckedState: ({ item, tree }) => {
|
|
76
83
|
const { checkedItems } = tree.getState();
|
|
84
|
+
const { propagateCheckedState } = tree.getConfig();
|
|
85
|
+
const itemId = item.getId();
|
|
77
86
|
|
|
78
87
|
if (checkedItems.includes(itemId)) {
|
|
79
88
|
return CheckedState.Checked;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
if (item.isFolder() &&
|
|
91
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
83
92
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
84
93
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
85
94
|
return CheckedState.Checked;
|
|
@@ -93,25 +102,31 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
93
102
|
},
|
|
94
103
|
|
|
95
104
|
setChecked: ({ item, tree, itemId }) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else {
|
|
105
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
106
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
99
107
|
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
100
108
|
...items,
|
|
101
|
-
...getAllLoadedDescendants(tree, itemId),
|
|
109
|
+
...getAllLoadedDescendants(tree, itemId, canCheckFolders),
|
|
102
110
|
]);
|
|
111
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
112
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
103
113
|
}
|
|
104
114
|
},
|
|
105
115
|
|
|
106
116
|
setUnchecked: ({ item, tree, itemId }) => {
|
|
107
|
-
|
|
117
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
118
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
119
|
+
const descendants = getAllLoadedDescendants(
|
|
120
|
+
tree,
|
|
121
|
+
itemId,
|
|
122
|
+
canCheckFolders,
|
|
123
|
+
);
|
|
108
124
|
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
109
|
-
items.filter((id) => id !== itemId),
|
|
125
|
+
items.filter((id) => !descendants.includes(id) && id !== itemId),
|
|
110
126
|
);
|
|
111
127
|
} else {
|
|
112
|
-
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
113
128
|
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
114
|
-
items.filter((id) =>
|
|
129
|
+
items.filter((id) => id !== itemId),
|
|
115
130
|
);
|
|
116
131
|
}
|
|
117
132
|
},
|
|
@@ -76,8 +76,9 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
76
76
|
},
|
|
77
77
|
|
|
78
78
|
getFocusedItem: ({ tree }) => {
|
|
79
|
+
const focusedItemId = tree.getState().focusedItem;
|
|
79
80
|
return (
|
|
80
|
-
tree.getItemInstance(
|
|
81
|
+
(focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null) ??
|
|
81
82
|
tree.getItems()[0]
|
|
82
83
|
);
|
|
83
84
|
},
|
|
@@ -472,4 +472,14 @@ describe("core-feature/selections", () => {
|
|
|
472
472
|
});
|
|
473
473
|
});
|
|
474
474
|
});
|
|
475
|
+
|
|
476
|
+
describe("empty rootItemId", () => {
|
|
477
|
+
factory.with({ rootItemId: "" }).forSuits((tree) => {
|
|
478
|
+
describe("focused item", () => {
|
|
479
|
+
it("returns correct initial focused item", () => {
|
|
480
|
+
expect(tree.instance.getFocusedItem().getId()).toBe("1");
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
});
|
|
475
485
|
});
|