@headless-tree/core 0.0.9 → 0.0.11
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 +12 -0
- package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
- package/lib/cjs/core/build-proxified-instance.js +58 -0
- package/lib/cjs/core/build-static-instance.d.ts +2 -0
- package/lib/cjs/core/build-static-instance.js +27 -0
- package/lib/cjs/core/create-tree.js +55 -36
- package/lib/cjs/features/async-data-loader/feature.js +37 -23
- package/lib/cjs/features/async-data-loader/types.d.ts +2 -1
- package/lib/cjs/features/drag-and-drop/feature.js +64 -32
- package/lib/cjs/features/drag-and-drop/types.d.ts +13 -4
- package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -2
- package/lib/cjs/features/drag-and-drop/utils.js +140 -37
- package/lib/cjs/features/expand-all/feature.js +12 -6
- package/lib/cjs/features/main/types.d.ts +8 -2
- package/lib/cjs/features/renaming/feature.js +33 -18
- package/lib/cjs/features/renaming/types.d.ts +1 -1
- package/lib/cjs/features/search/feature.js +38 -24
- package/lib/cjs/features/search/types.d.ts +0 -1
- package/lib/cjs/features/selection/feature.js +23 -14
- package/lib/cjs/features/sync-data-loader/feature.js +7 -2
- package/lib/cjs/features/tree/feature.d.ts +2 -1
- package/lib/cjs/features/tree/feature.js +85 -63
- package/lib/cjs/features/tree/types.d.ts +5 -3
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +2 -1
- package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
- package/lib/cjs/test-utils/test-tree-do.js +99 -0
- package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/cjs/test-utils/test-tree-expect.js +62 -0
- package/lib/cjs/test-utils/test-tree.d.ts +47 -0
- package/lib/cjs/test-utils/test-tree.js +195 -0
- package/lib/cjs/types/core.d.ts +31 -15
- package/lib/cjs/utilities/errors.d.ts +1 -0
- package/lib/cjs/utilities/errors.js +5 -0
- package/lib/cjs/utilities/insert-items-at-target.js +10 -3
- package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
- package/lib/cjs/utils.d.ts +3 -3
- package/lib/cjs/utils.js +6 -6
- package/lib/esm/core/build-proxified-instance.d.ts +2 -0
- package/lib/esm/core/build-proxified-instance.js +54 -0
- package/lib/esm/core/build-static-instance.d.ts +2 -0
- package/lib/esm/core/build-static-instance.js +23 -0
- package/lib/esm/core/create-tree.js +55 -36
- package/lib/esm/features/async-data-loader/feature.js +37 -23
- package/lib/esm/features/async-data-loader/types.d.ts +2 -1
- package/lib/esm/features/drag-and-drop/feature.js +64 -32
- package/lib/esm/features/drag-and-drop/types.d.ts +13 -4
- package/lib/esm/features/drag-and-drop/utils.d.ts +1 -2
- package/lib/esm/features/drag-and-drop/utils.js +138 -34
- package/lib/esm/features/expand-all/feature.js +12 -6
- package/lib/esm/features/main/types.d.ts +8 -2
- package/lib/esm/features/renaming/feature.js +33 -18
- package/lib/esm/features/renaming/types.d.ts +1 -1
- package/lib/esm/features/search/feature.js +38 -24
- package/lib/esm/features/search/types.d.ts +0 -1
- package/lib/esm/features/selection/feature.js +23 -14
- package/lib/esm/features/sync-data-loader/feature.js +7 -2
- package/lib/esm/features/tree/feature.d.ts +2 -1
- package/lib/esm/features/tree/feature.js +86 -64
- package/lib/esm/features/tree/types.d.ts +5 -3
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +2 -1
- package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
- package/lib/esm/test-utils/test-tree-do.js +95 -0
- package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/esm/test-utils/test-tree-expect.js +58 -0
- package/lib/esm/test-utils/test-tree.d.ts +47 -0
- package/lib/esm/test-utils/test-tree.js +191 -0
- package/lib/esm/types/core.d.ts +31 -15
- package/lib/esm/utilities/errors.d.ts +1 -0
- package/lib/esm/utilities/errors.js +1 -0
- package/lib/esm/utilities/insert-items-at-target.js +10 -3
- package/lib/esm/utilities/remove-items-from-parents.js +14 -8
- package/lib/esm/utils.d.ts +3 -3
- package/lib/esm/utils.js +3 -3
- package/package.json +7 -3
- package/src/core/build-proxified-instance.ts +115 -0
- package/src/core/build-static-instance.ts +28 -0
- package/src/core/create-tree.ts +60 -62
- package/src/features/async-data-loader/async-data-loader.spec.ts +143 -0
- package/src/features/async-data-loader/feature.ts +33 -31
- package/src/features/async-data-loader/types.ts +3 -1
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +716 -0
- package/src/features/drag-and-drop/feature.ts +109 -85
- package/src/features/drag-and-drop/types.ts +21 -7
- package/src/features/drag-and-drop/utils.ts +196 -55
- package/src/features/expand-all/expand-all.spec.ts +52 -0
- package/src/features/expand-all/feature.ts +8 -12
- package/src/features/hotkeys-core/feature.ts +1 -1
- package/src/features/main/types.ts +14 -1
- package/src/features/renaming/feature.ts +30 -29
- package/src/features/renaming/renaming.spec.ts +125 -0
- package/src/features/renaming/types.ts +1 -1
- package/src/features/search/feature.ts +34 -38
- package/src/features/search/search.spec.ts +115 -0
- package/src/features/search/types.ts +0 -1
- package/src/features/selection/feature.ts +29 -30
- package/src/features/selection/selection.spec.ts +220 -0
- package/src/features/sync-data-loader/feature.ts +8 -11
- package/src/features/tree/feature.ts +82 -87
- package/src/features/tree/tree.spec.ts +515 -0
- package/src/features/tree/types.ts +5 -3
- package/src/index.ts +4 -1
- package/src/test-utils/test-tree-do.ts +136 -0
- package/src/test-utils/test-tree-expect.ts +86 -0
- package/src/test-utils/test-tree.ts +217 -0
- package/src/types/core.ts +92 -33
- package/src/utilities/errors.ts +2 -0
- package/src/utilities/insert-items-at-target.ts +10 -3
- package/src/utilities/remove-items-from-parents.ts +15 -10
- package/src/utils.spec.ts +89 -0
- package/src/utils.ts +6 -6
- package/tsconfig.json +1 -0
- package/vitest.config.ts +6 -0
|
@@ -25,23 +25,19 @@ export const selectionFeature: FeatureImplementation<
|
|
|
25
25
|
selectedItems: "setSelectedItems",
|
|
26
26
|
},
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
setSelectedItems: (selectedItems) => {
|
|
32
|
-
instance.applySubStateUpdate("selectedItems", selectedItems);
|
|
28
|
+
treeInstance: {
|
|
29
|
+
setSelectedItems: ({ tree }, selectedItems) => {
|
|
30
|
+
tree.applySubStateUpdate("selectedItems", selectedItems);
|
|
33
31
|
},
|
|
34
32
|
|
|
35
33
|
// TODO memo
|
|
36
|
-
getSelectedItems: () => {
|
|
37
|
-
return
|
|
34
|
+
getSelectedItems: ({ tree }) => {
|
|
35
|
+
return tree.getState().selectedItems.map(tree.getItemInstance);
|
|
38
36
|
},
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
createItemInstance: (prev, item, tree) => ({
|
|
42
|
-
...prev,
|
|
37
|
+
},
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
itemInstance: {
|
|
40
|
+
select: ({ tree, item }) => {
|
|
45
41
|
const { selectedItems } = tree.getState();
|
|
46
42
|
tree.setSelectedItems(
|
|
47
43
|
selectedItems.includes(item.getItemMeta().itemId)
|
|
@@ -50,19 +46,19 @@ export const selectionFeature: FeatureImplementation<
|
|
|
50
46
|
);
|
|
51
47
|
},
|
|
52
48
|
|
|
53
|
-
deselect: () => {
|
|
49
|
+
deselect: ({ tree, item }) => {
|
|
54
50
|
const { selectedItems } = tree.getState();
|
|
55
51
|
tree.setSelectedItems(
|
|
56
52
|
selectedItems.filter((id) => id !== item.getItemMeta().itemId),
|
|
57
53
|
);
|
|
58
54
|
},
|
|
59
55
|
|
|
60
|
-
isSelected: () => {
|
|
56
|
+
isSelected: ({ tree, item }) => {
|
|
61
57
|
const { selectedItems } = tree.getState();
|
|
62
58
|
return selectedItems.includes(item.getItemMeta().itemId);
|
|
63
59
|
},
|
|
64
60
|
|
|
65
|
-
selectUpTo: (ctrl: boolean) => {
|
|
61
|
+
selectUpTo: ({ tree, item }, ctrl: boolean) => {
|
|
66
62
|
const indexA = item.getItemMeta().index;
|
|
67
63
|
// TODO dont use focused item as anchor, but last primary-clicked item
|
|
68
64
|
const indexB = tree.getFocusedItem().getItemMeta().index;
|
|
@@ -84,7 +80,7 @@ export const selectionFeature: FeatureImplementation<
|
|
|
84
80
|
tree.setSelectedItems(uniqueSelectedItems);
|
|
85
81
|
},
|
|
86
82
|
|
|
87
|
-
toggleSelect: () => {
|
|
83
|
+
toggleSelect: ({ item }) => {
|
|
88
84
|
if (item.isSelected()) {
|
|
89
85
|
item.deselect();
|
|
90
86
|
} else {
|
|
@@ -92,22 +88,25 @@ export const selectionFeature: FeatureImplementation<
|
|
|
92
88
|
}
|
|
93
89
|
},
|
|
94
90
|
|
|
95
|
-
getProps: () => ({
|
|
96
|
-
...prev
|
|
91
|
+
getProps: ({ tree, item, prev }) => ({
|
|
92
|
+
...prev?.(),
|
|
97
93
|
"aria-selected": item.isSelected() ? "true" : "false",
|
|
98
|
-
onClick: item.getMemoizedProp(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
onClick: item.getMemoizedProp(
|
|
95
|
+
"selection/onClick",
|
|
96
|
+
() => (e: MouseEvent) => {
|
|
97
|
+
if (e.shiftKey) {
|
|
98
|
+
item.selectUpTo(e.ctrlKey || e.metaKey);
|
|
99
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
100
|
+
item.toggleSelect();
|
|
101
|
+
} else {
|
|
102
|
+
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
prev?.()?.onClick?.(e);
|
|
106
|
+
},
|
|
107
|
+
),
|
|
109
108
|
}),
|
|
110
|
-
}
|
|
109
|
+
},
|
|
111
110
|
|
|
112
111
|
hotkeys: {
|
|
113
112
|
// setSelectedItem: {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { TestTree } from "../../test-utils/test-tree";
|
|
3
|
+
import { selectionFeature } from "./feature";
|
|
4
|
+
|
|
5
|
+
const factory = TestTree.default({}).withFeatures(selectionFeature);
|
|
6
|
+
|
|
7
|
+
// it("test", async () => {
|
|
8
|
+
// const tree = await factory.suits.async().tree.createDebugTree();
|
|
9
|
+
// const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
10
|
+
// const setFocusedItem = tree.mockedHandler("setFocusedItem");
|
|
11
|
+
//
|
|
12
|
+
// tree.do.selectItem("x111");
|
|
13
|
+
// tree.do.ctrlSelectItem("x112");
|
|
14
|
+
// tree.do.ctrlSelectItem("x113");
|
|
15
|
+
//
|
|
16
|
+
// expect(setSelectedItems).toHaveBeenCalledWith(["x111", "x112", "x113"]);
|
|
17
|
+
// expect(setFocusedItem).toHaveBeenCalledWith("x113");
|
|
18
|
+
// });
|
|
19
|
+
|
|
20
|
+
describe("core-feature/selections", () => {
|
|
21
|
+
factory.forSuits((tree) => {
|
|
22
|
+
it("sets aria-selected to false", () => {
|
|
23
|
+
expect(
|
|
24
|
+
tree.instance.getItemInstance("x111").getProps()["aria-selected"],
|
|
25
|
+
).toBe("false");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("sets aria-selected to true", () => {
|
|
29
|
+
tree.do.selectItem("x111");
|
|
30
|
+
expect(
|
|
31
|
+
tree.instance.getItemInstance("x111").getProps()["aria-selected"],
|
|
32
|
+
).toBe("true");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("resets old selection after new select", () => {
|
|
36
|
+
tree.do.selectItem("x111");
|
|
37
|
+
tree.do.selectItem("x112");
|
|
38
|
+
expect(
|
|
39
|
+
tree.instance.getItemInstance("x111").getProps()["aria-selected"],
|
|
40
|
+
).toBe("false");
|
|
41
|
+
expect(
|
|
42
|
+
tree.instance.getItemInstance("x112").getProps()["aria-selected"],
|
|
43
|
+
).toBe("true");
|
|
44
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(false);
|
|
45
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("select and control select", () => {
|
|
49
|
+
it("should make isSelected true", () => {
|
|
50
|
+
tree.do.selectItem("x111");
|
|
51
|
+
tree.do.ctrlSelectItem("x112");
|
|
52
|
+
tree.do.ctrlSelectItem("x113");
|
|
53
|
+
|
|
54
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
55
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
56
|
+
expect(tree.instance.getItemInstance("x113").isSelected()).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should call individual state setters", () => {
|
|
60
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
61
|
+
const setFocusedItem = tree.mockedHandler("setFocusedItem");
|
|
62
|
+
|
|
63
|
+
tree.do.selectItem("x111");
|
|
64
|
+
tree.do.ctrlSelectItem("x112");
|
|
65
|
+
tree.do.ctrlSelectItem("x113");
|
|
66
|
+
|
|
67
|
+
expect(setSelectedItems).toHaveBeenCalledWith(["x111", "x112", "x113"]);
|
|
68
|
+
expect(setFocusedItem).toHaveBeenCalledWith("x113");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should call joint state setter", () => {
|
|
72
|
+
const setState = tree.mockedHandler("setState");
|
|
73
|
+
|
|
74
|
+
tree.do.selectItem("x111");
|
|
75
|
+
tree.do.ctrlSelectItem("x112");
|
|
76
|
+
tree.do.ctrlSelectItem("x113");
|
|
77
|
+
|
|
78
|
+
expect(setState).toHaveBeenCalledWith(
|
|
79
|
+
expect.objectContaining({
|
|
80
|
+
selectedItems: ["x111", "x112", "x113"],
|
|
81
|
+
focusedItem: "x113",
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("shift select", () => {
|
|
88
|
+
it("should make isSelected true", () => {
|
|
89
|
+
tree.do.selectItem("x111");
|
|
90
|
+
tree.do.shiftSelectItem("x113");
|
|
91
|
+
tree.do.ctrlSelectItem("x2");
|
|
92
|
+
|
|
93
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
94
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
95
|
+
expect(tree.instance.getItemInstance("x113").isSelected()).toBe(true);
|
|
96
|
+
expect(tree.instance.getItemInstance("x2").isSelected()).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should call individual state setters", () => {
|
|
100
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
101
|
+
const setFocusedItem = tree.mockedHandler("setFocusedItem");
|
|
102
|
+
|
|
103
|
+
tree.do.selectItem("x111");
|
|
104
|
+
tree.do.shiftSelectItem("x113");
|
|
105
|
+
expect(setFocusedItem).toHaveBeenCalledWith("x113");
|
|
106
|
+
|
|
107
|
+
tree.do.ctrlSelectItem("x2");
|
|
108
|
+
|
|
109
|
+
expect(setSelectedItems).toHaveBeenCalledWith([
|
|
110
|
+
"x111",
|
|
111
|
+
"x112",
|
|
112
|
+
"x113",
|
|
113
|
+
"x2",
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
expect(setFocusedItem).toHaveBeenCalledWith("x2");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should call joint state setter", () => {
|
|
120
|
+
const setState = tree.mockedHandler("setState");
|
|
121
|
+
|
|
122
|
+
tree.do.selectItem("x111");
|
|
123
|
+
tree.do.shiftSelectItem("x113");
|
|
124
|
+
expect(setState).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
selectedItems: ["x111", "x112", "x113"],
|
|
127
|
+
focusedItem: "x113",
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
tree.do.ctrlSelectItem("x2");
|
|
132
|
+
|
|
133
|
+
expect(setState).toHaveBeenCalledWith(
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
selectedItems: ["x111", "x112", "x113", "x2"],
|
|
136
|
+
focusedItem: "x2",
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("programmatic select", () => {
|
|
143
|
+
describe("item instance actions", () => {
|
|
144
|
+
it("should handle select", () => {
|
|
145
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
146
|
+
tree.instance.getItemInstance("x111").select();
|
|
147
|
+
expect(setSelectedItems).toHaveBeenCalledWith(["x111"]);
|
|
148
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should handle deselect", () => {
|
|
152
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
153
|
+
tree.instance.getItemInstance("x111").select();
|
|
154
|
+
tree.instance.getItemInstance("x111").deselect();
|
|
155
|
+
expect(setSelectedItems).toHaveBeenCalledWith([]);
|
|
156
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should handle toggle select on", () => {
|
|
162
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
163
|
+
tree.instance.getItemInstance("x111").toggleSelect();
|
|
164
|
+
expect(setSelectedItems).toHaveBeenCalledWith(["x111"]);
|
|
165
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should handle toggle select off", () => {
|
|
169
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
170
|
+
tree.instance.getItemInstance("x111").select();
|
|
171
|
+
tree.instance.getItemInstance("x111").toggleSelect();
|
|
172
|
+
expect(setSelectedItems).toHaveBeenCalledWith([]);
|
|
173
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(
|
|
174
|
+
false,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should handle selectUpTo without ctrl", () => {
|
|
179
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
180
|
+
tree.instance.getItemInstance("x111").toggleSelect();
|
|
181
|
+
tree.instance.getItemInstance("x112").toggleSelect();
|
|
182
|
+
tree.instance.getItemInstance("x112").setFocused();
|
|
183
|
+
tree.instance.getItemInstance("x114").selectUpTo(false);
|
|
184
|
+
expect(setSelectedItems).toHaveBeenCalledWith([
|
|
185
|
+
"x112",
|
|
186
|
+
"x113",
|
|
187
|
+
"x114",
|
|
188
|
+
]);
|
|
189
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should handle selectUpTo with ctrl", () => {
|
|
193
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
194
|
+
tree.instance.getItemInstance("x111").toggleSelect();
|
|
195
|
+
tree.instance.getItemInstance("x112").toggleSelect();
|
|
196
|
+
tree.instance.getItemInstance("x112").setFocused();
|
|
197
|
+
tree.instance.getItemInstance("x114").selectUpTo(true);
|
|
198
|
+
expect(setSelectedItems).toHaveBeenCalledWith([
|
|
199
|
+
"x111",
|
|
200
|
+
"x112",
|
|
201
|
+
"x113",
|
|
202
|
+
"x114",
|
|
203
|
+
]);
|
|
204
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should handle getters", () => {
|
|
209
|
+
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
210
|
+
tree.instance.setSelectedItems(["x111", "x112"]);
|
|
211
|
+
expect(setSelectedItems).toHaveBeenCalledWith(["x111", "x112"]);
|
|
212
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
213
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
214
|
+
expect(tree.instance.getSelectedItems()[0].getId()).toEqual("x111");
|
|
215
|
+
expect(tree.instance.getSelectedItems()[1].getId()).toEqual("x112");
|
|
216
|
+
expect(tree.instance.getSelectedItems().length).toBe(2);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -24,18 +24,15 @@ export const syncDataLoaderFeature: FeatureImplementation<
|
|
|
24
24
|
loadingItems: "setLoadingItems",
|
|
25
25
|
},
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
treeInstance: {
|
|
28
|
+
retrieveItemData: ({ tree }, itemId) =>
|
|
29
|
+
tree.getConfig().dataLoader!.getItem(itemId),
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
retrieveChildrenIds: (itemId) =>
|
|
34
|
-
instance.getConfig().dataLoader!.getChildren(itemId),
|
|
35
|
-
}),
|
|
31
|
+
retrieveChildrenIds: ({ tree }, itemId) =>
|
|
32
|
+
tree.getConfig().dataLoader!.getChildren(itemId),
|
|
33
|
+
},
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
...prev,
|
|
35
|
+
itemInstance: {
|
|
39
36
|
isLoading: () => false,
|
|
40
|
-
}
|
|
37
|
+
},
|
|
41
38
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FeatureImplementation, ItemInstance } from "../../types/core";
|
|
2
2
|
import { ItemMeta, TreeFeatureDef, TreeItemDataRef } from "./types";
|
|
3
|
-
import { makeStateUpdater,
|
|
3
|
+
import { makeStateUpdater, poll } from "../../utils";
|
|
4
4
|
import { MainFeatureDef } from "../main/types";
|
|
5
5
|
import { HotkeysCoreFeatureDef } from "../hotkeys-core/types";
|
|
6
6
|
import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
|
|
7
|
+
import { SearchFeatureDef } from "../search/types";
|
|
7
8
|
|
|
8
9
|
export const treeFeature: FeatureImplementation<
|
|
9
10
|
any,
|
|
@@ -12,6 +13,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
12
13
|
| TreeFeatureDef<any>
|
|
13
14
|
| HotkeysCoreFeatureDef<any>
|
|
14
15
|
| SyncDataLoaderFeatureDef<any>
|
|
16
|
+
| SearchFeatureDef<any>
|
|
15
17
|
> = {
|
|
16
18
|
key: "tree",
|
|
17
19
|
|
|
@@ -32,24 +34,15 @@ export const treeFeature: FeatureImplementation<
|
|
|
32
34
|
focusedItem: "setFocusedItem",
|
|
33
35
|
},
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
treeInstance: {
|
|
38
|
+
isItemExpanded: ({ tree }, itemId) =>
|
|
39
|
+
tree.getState().expandedItems.includes(itemId),
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
retrieveChildrenIds: () => {
|
|
43
|
-
throw new Error("No data-loader registered");
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
isItemExpanded: (itemId) =>
|
|
47
|
-
instance.getState().expandedItems.includes(itemId),
|
|
48
|
-
|
|
49
|
-
getItemsMeta: () => {
|
|
50
|
-
const { rootItemId } = instance.getConfig();
|
|
51
|
-
const { expandedItems } = instance.getState();
|
|
41
|
+
getItemsMeta: ({ tree }) => {
|
|
42
|
+
const { rootItemId } = tree.getConfig();
|
|
43
|
+
const { expandedItems } = tree.getState();
|
|
52
44
|
const flatItems: ItemMeta[] = [];
|
|
45
|
+
const expandedItemsSet = new Set(expandedItems);
|
|
53
46
|
|
|
54
47
|
const recursiveAdd = (
|
|
55
48
|
itemId: string,
|
|
@@ -67,8 +60,9 @@ export const treeFeature: FeatureImplementation<
|
|
|
67
60
|
posInSet,
|
|
68
61
|
});
|
|
69
62
|
|
|
70
|
-
if (
|
|
71
|
-
|
|
63
|
+
if (expandedItemsSet.has(itemId)) {
|
|
64
|
+
// TODO THIS MADE A HUGE DIFFERENCE!
|
|
65
|
+
const children = tree.retrieveChildrenIds(itemId) ?? [];
|
|
72
66
|
let i = 0;
|
|
73
67
|
for (const childId of children) {
|
|
74
68
|
recursiveAdd(childId, itemId, level + 1, children.length, i++);
|
|
@@ -76,7 +70,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
76
70
|
}
|
|
77
71
|
};
|
|
78
72
|
|
|
79
|
-
const children =
|
|
73
|
+
const children = tree.retrieveChildrenIds(rootItemId);
|
|
80
74
|
let i = 0;
|
|
81
75
|
for (const itemId of children) {
|
|
82
76
|
recursiveAdd(itemId, rootItemId, 0, children.length, i++);
|
|
@@ -85,62 +79,62 @@ export const treeFeature: FeatureImplementation<
|
|
|
85
79
|
return flatItems;
|
|
86
80
|
},
|
|
87
81
|
|
|
88
|
-
expandItem: (itemId) => {
|
|
89
|
-
if (!
|
|
82
|
+
expandItem: ({ tree }, itemId) => {
|
|
83
|
+
if (!tree.getItemInstance(itemId).isFolder()) {
|
|
90
84
|
return;
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
if (
|
|
87
|
+
if (tree.getState().loadingItems?.includes(itemId)) {
|
|
94
88
|
return;
|
|
95
89
|
}
|
|
96
90
|
|
|
97
|
-
|
|
91
|
+
tree.applySubStateUpdate("expandedItems", (expandedItems) => [
|
|
98
92
|
...expandedItems,
|
|
99
93
|
itemId,
|
|
100
94
|
]);
|
|
101
|
-
|
|
95
|
+
tree.rebuildTree();
|
|
102
96
|
},
|
|
103
97
|
|
|
104
|
-
collapseItem: (itemId) => {
|
|
105
|
-
if (!
|
|
98
|
+
collapseItem: ({ tree }, itemId) => {
|
|
99
|
+
if (!tree.getItemInstance(itemId).isFolder()) {
|
|
106
100
|
return;
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
|
|
103
|
+
tree.applySubStateUpdate("expandedItems", (expandedItems) =>
|
|
110
104
|
expandedItems.filter((id) => id !== itemId),
|
|
111
105
|
);
|
|
112
|
-
|
|
106
|
+
tree.rebuildTree();
|
|
113
107
|
},
|
|
114
108
|
|
|
115
109
|
// TODO memo
|
|
116
|
-
getFocusedItem: () => {
|
|
110
|
+
getFocusedItem: ({ tree }) => {
|
|
117
111
|
return (
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
tree.getItemInstance(tree.getState().focusedItem ?? "") ??
|
|
113
|
+
tree.getItems()[0]
|
|
120
114
|
);
|
|
121
115
|
},
|
|
122
116
|
|
|
123
|
-
focusItem: (itemId) => {
|
|
124
|
-
|
|
117
|
+
focusItem: ({ tree }, itemId) => {
|
|
118
|
+
tree.applySubStateUpdate("focusedItem", itemId);
|
|
125
119
|
},
|
|
126
120
|
|
|
127
|
-
focusNextItem: () => {
|
|
128
|
-
const { index } =
|
|
129
|
-
const nextIndex = Math.min(index + 1,
|
|
130
|
-
|
|
121
|
+
focusNextItem: ({ tree }) => {
|
|
122
|
+
const { index } = tree.getFocusedItem().getItemMeta();
|
|
123
|
+
const nextIndex = Math.min(index + 1, tree.getItems().length - 1);
|
|
124
|
+
tree.focusItem(tree.getItems()[nextIndex].getId());
|
|
131
125
|
},
|
|
132
126
|
|
|
133
|
-
focusPreviousItem: () => {
|
|
134
|
-
const { index } =
|
|
127
|
+
focusPreviousItem: ({ tree }) => {
|
|
128
|
+
const { index } = tree.getFocusedItem().getItemMeta();
|
|
135
129
|
const nextIndex = Math.max(index - 1, 0);
|
|
136
|
-
|
|
130
|
+
tree.focusItem(tree.getItems()[nextIndex].getId());
|
|
137
131
|
},
|
|
138
132
|
|
|
139
|
-
updateDomFocus: () => {
|
|
133
|
+
updateDomFocus: ({ tree }) => {
|
|
140
134
|
// Required because if the state is managed outside in react, the state only updated during next render
|
|
141
135
|
setTimeout(async () => {
|
|
142
|
-
const focusedItem =
|
|
143
|
-
|
|
136
|
+
const focusedItem = tree.getFocusedItem();
|
|
137
|
+
tree.getConfig().scrollToItem?.(focusedItem);
|
|
144
138
|
await poll(() => focusedItem.getElement() !== null, 20);
|
|
145
139
|
const focusedElement = focusedItem.getElement();
|
|
146
140
|
if (!focusedElement) return;
|
|
@@ -148,29 +142,28 @@ export const treeFeature: FeatureImplementation<
|
|
|
148
142
|
});
|
|
149
143
|
},
|
|
150
144
|
|
|
151
|
-
getContainerProps: () => ({
|
|
152
|
-
...prev
|
|
145
|
+
getContainerProps: ({ prev }) => ({
|
|
146
|
+
...prev?.(),
|
|
153
147
|
role: "tree",
|
|
154
148
|
ariaLabel: "",
|
|
155
149
|
ariaActivedescendant: "",
|
|
156
150
|
}),
|
|
157
|
-
}),
|
|
158
151
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
scrollTo: async (scrollIntoViewArg) => {
|
|
152
|
+
// relevant for hotkeys of this feature
|
|
153
|
+
isSearchOpen: () => false,
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
itemInstance: {
|
|
157
|
+
scrollTo: async ({ tree, item }, scrollIntoViewArg) => {
|
|
165
158
|
tree.getConfig().scrollToItem?.(item as any);
|
|
166
159
|
await poll(() => item.getElement() !== null, 20);
|
|
167
|
-
item.getElement()
|
|
160
|
+
item.getElement()?.scrollIntoView(scrollIntoViewArg);
|
|
168
161
|
},
|
|
169
|
-
getId: () => item.getItemMeta().itemId,
|
|
170
|
-
getProps: () => {
|
|
162
|
+
getId: ({ item }) => item.getItemMeta().itemId,
|
|
163
|
+
getProps: ({ item, prev }) => {
|
|
171
164
|
const itemMeta = item.getItemMeta();
|
|
172
165
|
return {
|
|
173
|
-
...prev
|
|
166
|
+
...prev?.(),
|
|
174
167
|
role: "treeitem",
|
|
175
168
|
"aria-setsize": itemMeta.setSize,
|
|
176
169
|
"aria-posinset": itemMeta.posInSet,
|
|
@@ -178,7 +171,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
178
171
|
"aria-label": item.getItemName(),
|
|
179
172
|
"aria-level": itemMeta.level,
|
|
180
173
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
181
|
-
onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
|
|
174
|
+
onClick: item.getMemoizedProp("tree/onClick", () => (e: MouseEvent) => {
|
|
182
175
|
item.setFocused();
|
|
183
176
|
item.primaryAction();
|
|
184
177
|
|
|
@@ -198,46 +191,48 @@ export const treeFeature: FeatureImplementation<
|
|
|
198
191
|
}),
|
|
199
192
|
};
|
|
200
193
|
},
|
|
201
|
-
expand: () => tree.expandItem(item.getItemMeta().itemId),
|
|
202
|
-
collapse: () => tree.collapseItem(item.getItemMeta().itemId),
|
|
203
|
-
getItemData: (
|
|
204
|
-
|
|
194
|
+
expand: ({ tree, item }) => tree.expandItem(item.getItemMeta().itemId),
|
|
195
|
+
collapse: ({ tree, item }) => tree.collapseItem(item.getItemMeta().itemId),
|
|
196
|
+
getItemData: ({ tree, item }) =>
|
|
197
|
+
tree.retrieveItemData(item.getItemMeta().itemId),
|
|
198
|
+
equals: ({ item }, other) => item.getId() === other?.getId(),
|
|
199
|
+
isExpanded: ({ tree, item }) =>
|
|
205
200
|
tree.getState().expandedItems.includes(item.getItemMeta().itemId),
|
|
206
|
-
|
|
201
|
+
isDescendentOf: ({ item }, parentId) => {
|
|
202
|
+
const parent = item.getParent();
|
|
203
|
+
return Boolean(
|
|
204
|
+
parent?.getId() === parentId || parent?.isDescendentOf(parentId),
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
isFocused: ({ tree, item }) =>
|
|
207
208
|
tree.getState().focusedItem === item.getItemMeta().itemId ||
|
|
208
209
|
(tree.getState().focusedItem === null && item.getItemMeta().index === 0),
|
|
209
|
-
isFolder: () =>
|
|
210
|
+
isFolder: ({ tree, item }) =>
|
|
210
211
|
item.getItemMeta().level === -1 ||
|
|
211
212
|
tree.getConfig().isItemFolder(item as ItemInstance<any>),
|
|
212
|
-
getItemName: () => {
|
|
213
|
+
getItemName: ({ tree, item }) => {
|
|
213
214
|
const config = tree.getConfig();
|
|
214
215
|
return config.getItemName(item as ItemInstance<any>);
|
|
215
216
|
},
|
|
216
|
-
setFocused: () => tree.focusItem(item.getItemMeta().itemId),
|
|
217
|
-
primaryAction: () =>
|
|
217
|
+
setFocused: ({ tree, item }) => tree.focusItem(item.getItemMeta().itemId),
|
|
218
|
+
primaryAction: ({ tree, item }) =>
|
|
218
219
|
tree.getConfig().onPrimaryAction?.(item as ItemInstance<any>),
|
|
219
|
-
getParent:
|
|
220
|
-
(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (potentialParent.getItemMeta().level < itemMeta.level) {
|
|
224
|
-
return potentialParent;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return tree.getItemInstance(tree.getConfig().rootItemId);
|
|
228
|
-
},
|
|
229
|
-
() => [item.getItemMeta()],
|
|
230
|
-
),
|
|
220
|
+
getParent: ({ tree, item }) =>
|
|
221
|
+
item.getItemMeta().parentId
|
|
222
|
+
? tree.getItemInstance(item.getItemMeta().parentId)
|
|
223
|
+
: undefined,
|
|
231
224
|
// TODO remove
|
|
232
|
-
getIndexInParent: () => item.getItemMeta().posInSet,
|
|
233
|
-
getChildren: () =>
|
|
225
|
+
getIndexInParent: ({ item }) => item.getItemMeta().posInSet,
|
|
226
|
+
getChildren: ({ tree, item }) =>
|
|
234
227
|
tree
|
|
235
228
|
.retrieveChildrenIds(item.getItemMeta().itemId)
|
|
236
229
|
.map((id) => tree.getItemInstance(id)),
|
|
237
|
-
getTree: () => tree as any,
|
|
238
|
-
getItemAbove: (
|
|
239
|
-
|
|
240
|
-
|
|
230
|
+
getTree: ({ tree }) => tree as any,
|
|
231
|
+
getItemAbove: ({ tree, item }) =>
|
|
232
|
+
tree.getItems()[item.getItemMeta().index - 1],
|
|
233
|
+
getItemBelow: ({ tree, item }) =>
|
|
234
|
+
tree.getItems()[item.getItemMeta().index + 1],
|
|
235
|
+
getMemoizedProp: ({ item }, name, create, deps) => {
|
|
241
236
|
const data = item.getDataRef<TreeItemDataRef>();
|
|
242
237
|
const memoizedValue = data.current.memoizedValues?.[name];
|
|
243
238
|
if (
|
|
@@ -254,7 +249,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
254
249
|
data.current.memoizedValues[name] = value;
|
|
255
250
|
return value;
|
|
256
251
|
},
|
|
257
|
-
}
|
|
252
|
+
},
|
|
258
253
|
|
|
259
254
|
hotkeys: {
|
|
260
255
|
focusNextItem: {
|
|
@@ -262,7 +257,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
262
257
|
canRepeat: true,
|
|
263
258
|
preventDefault: true,
|
|
264
259
|
isEnabled: (tree) =>
|
|
265
|
-
!(tree.isSearchOpen?.() ?? false) && !tree.getState().dnd,
|
|
260
|
+
!(tree.isSearchOpen?.() ?? false) && !tree.getState().dnd, // TODO what happens when the feature doesnt exist? proxy method still claims to exist
|
|
266
261
|
handler: (e, tree) => {
|
|
267
262
|
tree.focusNextItem();
|
|
268
263
|
tree.updateDomFocus();
|