@headless-tree/core 1.2.0 → 1.3.0
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 +27 -0
- package/dist/index.d.mts +577 -0
- package/dist/index.d.ts +577 -0
- package/dist/index.js +2321 -0
- package/dist/index.mjs +2276 -0
- package/package.json +18 -10
- package/src/core/create-tree.ts +26 -2
- package/src/features/async-data-loader/feature.ts +9 -4
- package/src/features/async-data-loader/types.ts +2 -0
- package/src/features/checkboxes/checkboxes.spec.ts +149 -0
- package/src/features/checkboxes/feature.ts +134 -0
- package/src/features/checkboxes/types.ts +29 -0
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +11 -2
- package/src/features/drag-and-drop/feature.ts +88 -17
- package/src/features/drag-and-drop/types.ts +22 -0
- package/src/features/keyboard-drag-and-drop/feature.ts +8 -1
- package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +34 -3
- package/src/features/sync-data-loader/feature.ts +5 -1
- package/src/features/tree/feature.ts +9 -3
- package/src/features/tree/tree.spec.ts +14 -4
- package/src/features/tree/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/test-utils/test-tree-do.ts +2 -0
- package/src/test-utils/test-tree.ts +1 -0
- package/src/types/core.ts +2 -0
- package/tsconfig.json +1 -4
- package/vitest.config.ts +3 -1
- package/lib/cjs/core/build-proxified-instance.d.ts +0 -2
- package/lib/cjs/core/build-proxified-instance.js +0 -58
- package/lib/cjs/core/build-static-instance.d.ts +0 -2
- package/lib/cjs/core/build-static-instance.js +0 -26
- package/lib/cjs/core/create-tree.d.ts +0 -2
- package/lib/cjs/core/create-tree.js +0 -182
- package/lib/cjs/features/async-data-loader/feature.d.ts +0 -2
- package/lib/cjs/features/async-data-loader/feature.js +0 -135
- package/lib/cjs/features/async-data-loader/types.d.ts +0 -47
- package/lib/cjs/features/async-data-loader/types.js +0 -2
- package/lib/cjs/features/drag-and-drop/feature.d.ts +0 -2
- package/lib/cjs/features/drag-and-drop/feature.js +0 -179
- package/lib/cjs/features/drag-and-drop/types.d.ts +0 -66
- package/lib/cjs/features/drag-and-drop/types.js +0 -9
- package/lib/cjs/features/drag-and-drop/utils.d.ts +0 -27
- package/lib/cjs/features/drag-and-drop/utils.js +0 -182
- package/lib/cjs/features/expand-all/feature.d.ts +0 -2
- package/lib/cjs/features/expand-all/feature.js +0 -70
- package/lib/cjs/features/expand-all/types.d.ts +0 -19
- package/lib/cjs/features/expand-all/types.js +0 -2
- package/lib/cjs/features/hotkeys-core/feature.d.ts +0 -2
- package/lib/cjs/features/hotkeys-core/feature.js +0 -107
- package/lib/cjs/features/hotkeys-core/types.d.ts +0 -27
- package/lib/cjs/features/hotkeys-core/types.js +0 -2
- package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +0 -2
- package/lib/cjs/features/keyboard-drag-and-drop/feature.js +0 -206
- package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +0 -27
- package/lib/cjs/features/keyboard-drag-and-drop/types.js +0 -11
- package/lib/cjs/features/main/types.d.ts +0 -45
- package/lib/cjs/features/main/types.js +0 -2
- package/lib/cjs/features/prop-memoization/feature.d.ts +0 -2
- package/lib/cjs/features/prop-memoization/feature.js +0 -70
- package/lib/cjs/features/prop-memoization/types.d.ts +0 -15
- package/lib/cjs/features/prop-memoization/types.js +0 -2
- package/lib/cjs/features/renaming/feature.d.ts +0 -2
- package/lib/cjs/features/renaming/feature.js +0 -86
- package/lib/cjs/features/renaming/types.d.ts +0 -27
- package/lib/cjs/features/renaming/types.js +0 -2
- package/lib/cjs/features/search/feature.d.ts +0 -2
- package/lib/cjs/features/search/feature.js +0 -119
- package/lib/cjs/features/search/types.d.ts +0 -32
- package/lib/cjs/features/search/types.js +0 -2
- package/lib/cjs/features/selection/feature.d.ts +0 -2
- package/lib/cjs/features/selection/feature.js +0 -132
- package/lib/cjs/features/selection/types.d.ts +0 -21
- package/lib/cjs/features/selection/types.js +0 -2
- package/lib/cjs/features/sync-data-loader/feature.d.ts +0 -2
- package/lib/cjs/features/sync-data-loader/feature.js +0 -49
- package/lib/cjs/features/sync-data-loader/types.d.ts +0 -28
- package/lib/cjs/features/sync-data-loader/types.js +0 -2
- package/lib/cjs/features/tree/feature.d.ts +0 -2
- package/lib/cjs/features/tree/feature.js +0 -240
- package/lib/cjs/features/tree/types.d.ts +0 -62
- package/lib/cjs/features/tree/types.js +0 -2
- package/lib/cjs/index.d.ts +0 -31
- package/lib/cjs/index.js +0 -49
- package/lib/cjs/mddocs-entry.d.ts +0 -121
- package/lib/cjs/mddocs-entry.js +0 -17
- package/lib/cjs/test-utils/test-tree-do.d.ts +0 -23
- package/lib/cjs/test-utils/test-tree-do.js +0 -112
- package/lib/cjs/test-utils/test-tree-expect.d.ts +0 -17
- package/lib/cjs/test-utils/test-tree-expect.js +0 -66
- package/lib/cjs/test-utils/test-tree.d.ts +0 -48
- package/lib/cjs/test-utils/test-tree.js +0 -207
- package/lib/cjs/types/core.d.ts +0 -83
- package/lib/cjs/types/core.js +0 -2
- package/lib/cjs/types/deep-merge.d.ts +0 -13
- package/lib/cjs/types/deep-merge.js +0 -2
- package/lib/cjs/utilities/create-on-drop-handler.d.ts +0 -3
- package/lib/cjs/utilities/create-on-drop-handler.js +0 -20
- package/lib/cjs/utilities/errors.d.ts +0 -2
- package/lib/cjs/utilities/errors.js +0 -9
- package/lib/cjs/utilities/insert-items-at-target.d.ts +0 -3
- package/lib/cjs/utilities/insert-items-at-target.js +0 -40
- package/lib/cjs/utilities/remove-items-from-parents.d.ts +0 -2
- package/lib/cjs/utilities/remove-items-from-parents.js +0 -32
- package/lib/cjs/utils.d.ts +0 -6
- package/lib/cjs/utils.js +0 -53
- package/lib/esm/core/build-proxified-instance.d.ts +0 -2
- package/lib/esm/core/build-proxified-instance.js +0 -54
- package/lib/esm/core/build-static-instance.d.ts +0 -2
- package/lib/esm/core/build-static-instance.js +0 -22
- package/lib/esm/core/create-tree.d.ts +0 -2
- package/lib/esm/core/create-tree.js +0 -178
- package/lib/esm/features/async-data-loader/feature.d.ts +0 -2
- package/lib/esm/features/async-data-loader/feature.js +0 -132
- package/lib/esm/features/async-data-loader/types.d.ts +0 -47
- package/lib/esm/features/async-data-loader/types.js +0 -1
- package/lib/esm/features/drag-and-drop/feature.d.ts +0 -2
- package/lib/esm/features/drag-and-drop/feature.js +0 -176
- package/lib/esm/features/drag-and-drop/types.d.ts +0 -66
- package/lib/esm/features/drag-and-drop/types.js +0 -6
- package/lib/esm/features/drag-and-drop/utils.d.ts +0 -27
- package/lib/esm/features/drag-and-drop/utils.js +0 -172
- package/lib/esm/features/expand-all/feature.d.ts +0 -2
- package/lib/esm/features/expand-all/feature.js +0 -67
- package/lib/esm/features/expand-all/types.d.ts +0 -19
- package/lib/esm/features/expand-all/types.js +0 -1
- package/lib/esm/features/hotkeys-core/feature.d.ts +0 -2
- package/lib/esm/features/hotkeys-core/feature.js +0 -104
- package/lib/esm/features/hotkeys-core/types.d.ts +0 -27
- package/lib/esm/features/hotkeys-core/types.js +0 -1
- package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +0 -2
- package/lib/esm/features/keyboard-drag-and-drop/feature.js +0 -203
- package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +0 -27
- package/lib/esm/features/keyboard-drag-and-drop/types.js +0 -8
- package/lib/esm/features/main/types.d.ts +0 -45
- package/lib/esm/features/main/types.js +0 -1
- package/lib/esm/features/prop-memoization/feature.d.ts +0 -2
- package/lib/esm/features/prop-memoization/feature.js +0 -67
- package/lib/esm/features/prop-memoization/types.d.ts +0 -15
- package/lib/esm/features/prop-memoization/types.js +0 -1
- package/lib/esm/features/renaming/feature.d.ts +0 -2
- package/lib/esm/features/renaming/feature.js +0 -83
- package/lib/esm/features/renaming/types.d.ts +0 -27
- package/lib/esm/features/renaming/types.js +0 -1
- package/lib/esm/features/search/feature.d.ts +0 -2
- package/lib/esm/features/search/feature.js +0 -116
- package/lib/esm/features/search/types.d.ts +0 -32
- package/lib/esm/features/search/types.js +0 -1
- package/lib/esm/features/selection/feature.d.ts +0 -2
- package/lib/esm/features/selection/feature.js +0 -129
- package/lib/esm/features/selection/types.d.ts +0 -21
- package/lib/esm/features/selection/types.js +0 -1
- package/lib/esm/features/sync-data-loader/feature.d.ts +0 -2
- package/lib/esm/features/sync-data-loader/feature.js +0 -46
- package/lib/esm/features/sync-data-loader/types.d.ts +0 -28
- package/lib/esm/features/sync-data-loader/types.js +0 -1
- package/lib/esm/features/tree/feature.d.ts +0 -2
- package/lib/esm/features/tree/feature.js +0 -237
- package/lib/esm/features/tree/types.d.ts +0 -62
- package/lib/esm/features/tree/types.js +0 -1
- package/lib/esm/index.d.ts +0 -31
- package/lib/esm/index.js +0 -30
- package/lib/esm/mddocs-entry.d.ts +0 -121
- package/lib/esm/mddocs-entry.js +0 -1
- package/lib/esm/test-utils/test-tree-do.d.ts +0 -23
- package/lib/esm/test-utils/test-tree-do.js +0 -108
- package/lib/esm/test-utils/test-tree-expect.d.ts +0 -17
- package/lib/esm/test-utils/test-tree-expect.js +0 -62
- package/lib/esm/test-utils/test-tree.d.ts +0 -48
- package/lib/esm/test-utils/test-tree.js +0 -203
- package/lib/esm/types/core.d.ts +0 -83
- package/lib/esm/types/core.js +0 -1
- package/lib/esm/types/deep-merge.d.ts +0 -13
- package/lib/esm/types/deep-merge.js +0 -1
- package/lib/esm/utilities/create-on-drop-handler.d.ts +0 -3
- package/lib/esm/utilities/create-on-drop-handler.js +0 -16
- package/lib/esm/utilities/errors.d.ts +0 -2
- package/lib/esm/utilities/errors.js +0 -4
- package/lib/esm/utilities/insert-items-at-target.d.ts +0 -3
- package/lib/esm/utilities/insert-items-at-target.js +0 -36
- package/lib/esm/utilities/remove-items-from-parents.d.ts +0 -2
- package/lib/esm/utilities/remove-items-from-parents.js +0 -28
- package/lib/esm/utils.d.ts +0 -6
- package/lib/esm/utils.js +0 -46
package/package.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-tree/core",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"main": "
|
|
5
|
-
"module": "
|
|
6
|
-
"types": "
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"main": "dist/index.d.ts",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.mts",
|
|
7
7
|
"exports": {
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
11
19
|
},
|
|
12
20
|
"sideEffects": false,
|
|
13
21
|
"scripts": {
|
|
14
|
-
"build
|
|
15
|
-
"
|
|
16
|
-
"start": "tsc -w",
|
|
22
|
+
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
23
|
+
"start": "tsup ./src/index.ts --format esm,cjs --dts --watch",
|
|
17
24
|
"test": "vitest run"
|
|
18
25
|
},
|
|
19
26
|
"repository": {
|
|
@@ -26,6 +33,7 @@
|
|
|
26
33
|
"license": "MIT",
|
|
27
34
|
"devDependencies": {
|
|
28
35
|
"jsdom": "^26.0.0",
|
|
36
|
+
"tsup": "^8.5.0",
|
|
29
37
|
"typescript": "^5.7.2",
|
|
30
38
|
"vitest": "^3.0.3"
|
|
31
39
|
}
|
package/src/core/create-tree.ts
CHANGED
|
@@ -193,7 +193,23 @@ export const createTree = <T>(
|
|
|
193
193
|
config.setState?.(state);
|
|
194
194
|
}
|
|
195
195
|
},
|
|
196
|
-
getItemInstance: ({}, itemId) =>
|
|
196
|
+
getItemInstance: ({}, itemId) => {
|
|
197
|
+
const existingInstance = itemInstancesMap[itemId];
|
|
198
|
+
if (!existingInstance) {
|
|
199
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
200
|
+
features,
|
|
201
|
+
"itemInstance",
|
|
202
|
+
(instance) => ({
|
|
203
|
+
item: instance,
|
|
204
|
+
tree: treeInstance,
|
|
205
|
+
itemId,
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
finalizeInstance();
|
|
209
|
+
return instance;
|
|
210
|
+
}
|
|
211
|
+
return existingInstance;
|
|
212
|
+
},
|
|
197
213
|
getItems: () => itemInstances,
|
|
198
214
|
registerElement: ({}, element) => {
|
|
199
215
|
if (treeElement === element) {
|
|
@@ -236,7 +252,15 @@ export const createTree = <T>(
|
|
|
236
252
|
getElement: ({ itemId }) => itemElementsMap[itemId],
|
|
237
253
|
// eslint-disable-next-line no-return-assign
|
|
238
254
|
getDataRef: ({ itemId }) => (itemDataRefs[itemId] ??= { current: {} }),
|
|
239
|
-
getItemMeta: ({ itemId }) =>
|
|
255
|
+
getItemMeta: ({ itemId }) =>
|
|
256
|
+
itemMetaMap[itemId] ?? {
|
|
257
|
+
itemId,
|
|
258
|
+
parentId: null,
|
|
259
|
+
level: -1,
|
|
260
|
+
index: -1,
|
|
261
|
+
posInSet: 0,
|
|
262
|
+
setSize: 1,
|
|
263
|
+
},
|
|
240
264
|
},
|
|
241
265
|
};
|
|
242
266
|
|
|
@@ -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
|
|
|
@@ -165,5 +165,10 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
165
165
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
166
166
|
tree.rebuildTree();
|
|
167
167
|
},
|
|
168
|
+
updateCachedData: ({ tree, itemId }, data) => {
|
|
169
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
170
|
+
dataRef.current.itemData[itemId] = data;
|
|
171
|
+
tree.rebuildTree();
|
|
172
|
+
},
|
|
168
173
|
},
|
|
169
174
|
};
|
|
@@ -34,6 +34,7 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
34
34
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
35
35
|
loadItemData: (itemId: string) => Promise<T>;
|
|
36
36
|
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
37
|
+
/* idea: recursiveLoadItems: (itemId: string, cancelToken?: { current: boolean }, onLoad: (itemIds: string[]) => void) => Promise<T[]> */
|
|
37
38
|
};
|
|
38
39
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
39
40
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -46,6 +47,7 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
46
47
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
47
48
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
48
49
|
|
|
50
|
+
updateCachedData: (data: T) => void;
|
|
49
51
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
50
52
|
isLoading: () => boolean;
|
|
51
53
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
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", async () => {
|
|
78
|
+
const tree = await factory
|
|
79
|
+
.with({ canCheckFolders: true, propagateCheckedState: false })
|
|
80
|
+
.createTestCaseTree();
|
|
81
|
+
|
|
82
|
+
tree.item("x11").setChecked();
|
|
83
|
+
expect(tree.instance.getState().checkedItems).toContain("x11");
|
|
84
|
+
});
|
|
85
|
+
|
|
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();
|
|
99
|
+
|
|
100
|
+
tree.item("x11").setChecked();
|
|
101
|
+
expect(tree.instance.getState().checkedItems).toEqual(
|
|
102
|
+
expect.arrayContaining(["x111", "x112", "x113", "x114"]),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should turn folder indeterminate", async () => {
|
|
107
|
+
const tree = await factory
|
|
108
|
+
.with({ propagateCheckedState: true })
|
|
109
|
+
.createTestCaseTree();
|
|
110
|
+
|
|
111
|
+
tree.item("x111").setChecked();
|
|
112
|
+
expect(tree.item("x11").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should turn folder checked if all children are checked", async () => {
|
|
116
|
+
const tree = await factory
|
|
117
|
+
.with({
|
|
118
|
+
isItemFolder: (item) => item.getItemData().length < 4,
|
|
119
|
+
propagateCheckedState: true,
|
|
120
|
+
canCheckFolders: false,
|
|
121
|
+
})
|
|
122
|
+
.createTestCaseTree();
|
|
123
|
+
|
|
124
|
+
tree.item("x11").setChecked();
|
|
125
|
+
tree.item("x12").setChecked();
|
|
126
|
+
tree.item("x13").setChecked();
|
|
127
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
128
|
+
tree.do.selectItem("x14");
|
|
129
|
+
tree.item("x141").setChecked();
|
|
130
|
+
tree.item("x142").setChecked();
|
|
131
|
+
tree.item("x143").setChecked();
|
|
132
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
133
|
+
tree.item("x144").setChecked();
|
|
134
|
+
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Checked);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should return correct checked state for items", async () => {
|
|
138
|
+
const tree = await factory.createTestCaseTree();
|
|
139
|
+
const item = tree.instance.getItemInstance("x111");
|
|
140
|
+
|
|
141
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
142
|
+
|
|
143
|
+
item.setChecked();
|
|
144
|
+
expect(item.getCheckedState()).toBe(CheckedState.Checked);
|
|
145
|
+
|
|
146
|
+
item.setUnchecked();
|
|
147
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
includeFolders = false,
|
|
10
|
+
): string[] => {
|
|
11
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
12
|
+
return [itemId];
|
|
13
|
+
}
|
|
14
|
+
const descendants = tree
|
|
15
|
+
.retrieveChildrenIds(itemId)
|
|
16
|
+
.map((child) => getAllLoadedDescendants(tree, child, includeFolders))
|
|
17
|
+
.flat();
|
|
18
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const checkboxesFeature: FeatureImplementation = {
|
|
22
|
+
key: "checkboxes",
|
|
23
|
+
|
|
24
|
+
overwrites: ["selection"],
|
|
25
|
+
|
|
26
|
+
getInitialState: (initialState) => ({
|
|
27
|
+
checkedItems: [],
|
|
28
|
+
...initialState,
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
getDefaultConfig: (defaultConfig, tree) => {
|
|
32
|
+
const hasAsyncLoader = defaultConfig.features?.some(
|
|
33
|
+
(f) => f.key === "async-data-loader",
|
|
34
|
+
);
|
|
35
|
+
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
36
|
+
throwError(`propagateCheckedState not supported with async trees`);
|
|
37
|
+
}
|
|
38
|
+
const propagateCheckedState =
|
|
39
|
+
defaultConfig.propagateCheckedState ?? !hasAsyncLoader;
|
|
40
|
+
const canCheckFolders =
|
|
41
|
+
defaultConfig.canCheckFolders ?? !propagateCheckedState;
|
|
42
|
+
return {
|
|
43
|
+
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
44
|
+
propagateCheckedState,
|
|
45
|
+
canCheckFolders,
|
|
46
|
+
...defaultConfig,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
stateHandlerNames: {
|
|
51
|
+
checkedItems: "setCheckedItems",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
treeInstance: {
|
|
55
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
56
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
itemInstance: {
|
|
61
|
+
getCheckboxProps: ({ item }) => {
|
|
62
|
+
const checkedState = item.getCheckedState();
|
|
63
|
+
return {
|
|
64
|
+
onChange: item.toggleCheckedState,
|
|
65
|
+
checked: checkedState === CheckedState.Checked,
|
|
66
|
+
ref: (r: any) => {
|
|
67
|
+
if (r) {
|
|
68
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
toggleCheckedState: ({ item }) => {
|
|
75
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
76
|
+
item.setUnchecked();
|
|
77
|
+
} else {
|
|
78
|
+
item.setChecked();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
getCheckedState: ({ item, tree }) => {
|
|
83
|
+
const { checkedItems } = tree.getState();
|
|
84
|
+
const { propagateCheckedState } = tree.getConfig();
|
|
85
|
+
const itemId = item.getId();
|
|
86
|
+
|
|
87
|
+
if (checkedItems.includes(itemId)) {
|
|
88
|
+
return CheckedState.Checked;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
92
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
93
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
94
|
+
return CheckedState.Checked;
|
|
95
|
+
}
|
|
96
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
97
|
+
return CheckedState.Indeterminate;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return CheckedState.Unchecked;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
setChecked: ({ item, tree, itemId }) => {
|
|
105
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
106
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
107
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
108
|
+
...items,
|
|
109
|
+
...getAllLoadedDescendants(tree, itemId, canCheckFolders),
|
|
110
|
+
]);
|
|
111
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
112
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
setUnchecked: ({ item, tree, itemId }) => {
|
|
117
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
118
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
119
|
+
const descendants = getAllLoadedDescendants(
|
|
120
|
+
tree,
|
|
121
|
+
itemId,
|
|
122
|
+
canCheckFolders,
|
|
123
|
+
);
|
|
124
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
125
|
+
items.filter((id) => !descendants.includes(id) && id !== itemId),
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
129
|
+
items.filter((id) => id !== itemId),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
propagateCheckedState?: boolean;
|
|
17
|
+
};
|
|
18
|
+
treeInstance: {
|
|
19
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
20
|
+
};
|
|
21
|
+
itemInstance: {
|
|
22
|
+
setChecked: () => void;
|
|
23
|
+
setUnchecked: () => void;
|
|
24
|
+
toggleCheckedState: () => void;
|
|
25
|
+
getCheckedState: () => CheckedState;
|
|
26
|
+
getCheckboxProps: () => Record<string, any>;
|
|
27
|
+
};
|
|
28
|
+
hotkeys: never;
|
|
29
|
+
};
|
|
@@ -182,7 +182,7 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
182
182
|
});
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
-
it("updates dnd state", () => {
|
|
185
|
+
it("updates dnd state", async () => {
|
|
186
186
|
const setDndState = tree.mockedHandler("setDndState");
|
|
187
187
|
tree.do.startDrag("x111");
|
|
188
188
|
expect(setDndState).toBeCalledWith({
|
|
@@ -198,7 +198,7 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
198
198
|
},
|
|
199
199
|
});
|
|
200
200
|
tree.do.drop("x22");
|
|
201
|
-
expect(setDndState).toBeCalledWith(null);
|
|
201
|
+
await vi.waitFor(() => expect(setDndState).toBeCalledWith(null));
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
204
|
|
|
@@ -300,6 +300,9 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
300
300
|
|
|
301
301
|
it("drags foreign object inside tree, on folder", () => {
|
|
302
302
|
tree.mockedHandler("canDropForeignDragObject").mockReturnValue(true);
|
|
303
|
+
tree
|
|
304
|
+
.mockedHandler("canDragForeignDragObjectOver")
|
|
305
|
+
.mockReturnValue(true);
|
|
303
306
|
const onDropForeignDragObject = tree.mockedHandler(
|
|
304
307
|
"onDropForeignDragObject",
|
|
305
308
|
);
|
|
@@ -318,6 +321,9 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
318
321
|
tree
|
|
319
322
|
.mockedHandler("canDropForeignDragObject")
|
|
320
323
|
.mockImplementation((_, target) => target.item.isFolder());
|
|
324
|
+
tree
|
|
325
|
+
.mockedHandler("canDragForeignDragObjectOver")
|
|
326
|
+
.mockImplementation((_, target) => target.item.isFolder());
|
|
321
327
|
const onDropForeignDragObject = tree.mockedHandler(
|
|
322
328
|
"onDropForeignDragObject",
|
|
323
329
|
);
|
|
@@ -340,6 +346,9 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
340
346
|
|
|
341
347
|
it("doesnt drag foreign object inside tree if not allowed", () => {
|
|
342
348
|
tree.mockedHandler("canDropForeignDragObject").mockReturnValue(false);
|
|
349
|
+
tree
|
|
350
|
+
.mockedHandler("canDragForeignDragObjectOver")
|
|
351
|
+
.mockReturnValue(false);
|
|
343
352
|
const onDropForeignDragObject = tree.mockedHandler(
|
|
344
353
|
"onDropForeignDragObject",
|
|
345
354
|
);
|