@headless-tree/core 1.1.0 → 1.2.1
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 +28 -0
- package/lib/cjs/core/create-tree.js +22 -2
- 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 +31 -5
- package/lib/cjs/features/drag-and-drop/types.d.ts +5 -0
- package/lib/cjs/features/main/types.d.ts +2 -0
- package/lib/cjs/features/renaming/feature.js +8 -0
- package/lib/cjs/features/tree/feature.js +5 -0
- package/lib/cjs/features/tree/types.d.ts +3 -1
- package/lib/cjs/index.d.ts +4 -0
- package/lib/cjs/index.js +7 -0
- package/lib/cjs/test-utils/test-tree.js +1 -0
- package/lib/cjs/types/core.d.ts +2 -1
- package/lib/esm/core/create-tree.js +22 -2
- 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 +31 -5
- package/lib/esm/features/drag-and-drop/types.d.ts +5 -0
- package/lib/esm/features/main/types.d.ts +2 -0
- package/lib/esm/features/renaming/feature.js +8 -0
- package/lib/esm/features/tree/feature.js +5 -0
- package/lib/esm/features/tree/types.d.ts +3 -1
- package/lib/esm/index.d.ts +4 -0
- package/lib/esm/index.js +4 -0
- package/lib/esm/test-utils/test-tree.js +1 -0
- package/lib/esm/types/core.d.ts +2 -1
- package/package.json +7 -2
- package/readme.md +157 -0
- package/src/core/create-tree.ts +33 -2
- 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 +38 -2
- package/src/features/drag-and-drop/types.ts +5 -0
- package/src/features/main/types.ts +2 -0
- package/src/features/renaming/feature.ts +13 -0
- package/src/features/renaming/renaming.spec.ts +31 -0
- package/src/features/tree/feature.ts +6 -0
- package/src/features/tree/types.ts +4 -2
- package/src/index.ts +5 -0
- package/src/test-utils/test-tree.ts +1 -0
- package/src/types/core.ts +2 -0
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
|
-
import { DndDataRef, DragLineData } from "./types";
|
|
2
|
+
import { DndDataRef, DragLineData, DragTarget } from "./types";
|
|
3
3
|
import {
|
|
4
4
|
canDrop,
|
|
5
5
|
getDragCode,
|
|
@@ -83,10 +83,38 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
83
83
|
: { display: "none" };
|
|
84
84
|
},
|
|
85
85
|
|
|
86
|
-
getContainerProps: ({ prev }, treeLabel) => {
|
|
86
|
+
getContainerProps: ({ prev, tree }, treeLabel) => {
|
|
87
87
|
const prevProps = prev?.(treeLabel);
|
|
88
88
|
return {
|
|
89
89
|
...prevProps,
|
|
90
|
+
|
|
91
|
+
onDragOver: (e: DragEvent) => {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
onDrop: async (e: DragEvent) => {
|
|
96
|
+
// TODO merge implementation with itemInstance.onDrop
|
|
97
|
+
const dataRef = tree.getDataRef<DndDataRef>();
|
|
98
|
+
const target: DragTarget<any> = { item: tree.getRootItem() };
|
|
99
|
+
|
|
100
|
+
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const config = tree.getConfig();
|
|
106
|
+
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
107
|
+
|
|
108
|
+
dataRef.current.lastDragCode = undefined;
|
|
109
|
+
tree.applySubStateUpdate("dnd", null);
|
|
110
|
+
|
|
111
|
+
if (draggedItems) {
|
|
112
|
+
await config.onDrop?.(draggedItems, target);
|
|
113
|
+
} else if (e.dataTransfer) {
|
|
114
|
+
await config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
90
118
|
style: {
|
|
91
119
|
...prevProps?.style,
|
|
92
120
|
position: "relative",
|
|
@@ -101,6 +129,8 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
101
129
|
|
|
102
130
|
draggable: true,
|
|
103
131
|
|
|
132
|
+
onDragEnter: (e: DragEvent) => e.preventDefault(),
|
|
133
|
+
|
|
104
134
|
onDragStart: (e: DragEvent) => {
|
|
105
135
|
const selectedItems = tree.getSelectedItems();
|
|
106
136
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
@@ -115,6 +145,11 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
115
145
|
return;
|
|
116
146
|
}
|
|
117
147
|
|
|
148
|
+
if (config.setDragImage) {
|
|
149
|
+
const { imgElement, xOffset, yOffset } = config.setDragImage(items);
|
|
150
|
+
e.dataTransfer?.setDragImage(imgElement, xOffset ?? 0, yOffset ?? 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
118
153
|
if (config.createForeignDragObject) {
|
|
119
154
|
const { format, data } = config.createForeignDragObject(items);
|
|
120
155
|
e.dataTransfer?.setData(format, data);
|
|
@@ -186,6 +221,7 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
186
221
|
},
|
|
187
222
|
|
|
188
223
|
onDrop: async (e: DragEvent) => {
|
|
224
|
+
e.stopPropagation();
|
|
189
225
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
190
226
|
const target = getDragTarget(e, item, tree);
|
|
191
227
|
|
|
@@ -58,6 +58,11 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
58
58
|
format: string;
|
|
59
59
|
data: any;
|
|
60
60
|
};
|
|
61
|
+
setDragImage?: (items: ItemInstance<T>[]) => {
|
|
62
|
+
imgElement: Element;
|
|
63
|
+
xOffset?: number;
|
|
64
|
+
yOffset?: number;
|
|
65
|
+
};
|
|
61
66
|
canDropForeignDragObject?: (
|
|
62
67
|
dataTransfer: DataTransfer,
|
|
63
68
|
target: DragTarget<T>,
|
|
@@ -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>>;
|
|
@@ -9,6 +9,7 @@ type InputEvent = {
|
|
|
9
9
|
|
|
10
10
|
export const renamingFeature: FeatureImplementation = {
|
|
11
11
|
key: "renaming",
|
|
12
|
+
overwrites: ["drag-and-drop"],
|
|
12
13
|
|
|
13
14
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
14
15
|
setRenamingItem: makeStateUpdater("renamingItem", tree),
|
|
@@ -72,6 +73,18 @@ export const renamingFeature: FeatureImplementation = {
|
|
|
72
73
|
|
|
73
74
|
isRenaming: ({ tree, item }) =>
|
|
74
75
|
item.getId() === tree.getState().renamingItem,
|
|
76
|
+
|
|
77
|
+
getProps: ({ prev, item }) => {
|
|
78
|
+
const isRenaming = item.isRenaming();
|
|
79
|
+
const prevProps = prev?.() ?? {};
|
|
80
|
+
return isRenaming
|
|
81
|
+
? {
|
|
82
|
+
...prevProps,
|
|
83
|
+
draggable: false,
|
|
84
|
+
onDragStart: () => {},
|
|
85
|
+
}
|
|
86
|
+
: prevProps;
|
|
87
|
+
},
|
|
75
88
|
},
|
|
76
89
|
|
|
77
90
|
hotkeys: {
|
|
@@ -93,6 +93,37 @@ describe("core-feature/renaming", () => {
|
|
|
93
93
|
expect(setRenamingItem).toHaveBeenCalledWith(null);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
describe("dragging", async () => {
|
|
97
|
+
const suiteTree = await tree
|
|
98
|
+
.withFeatures({
|
|
99
|
+
key: "drag-and-drop",
|
|
100
|
+
itemInstance: {
|
|
101
|
+
getProps: ({ prev }: any) => ({
|
|
102
|
+
...prev?.(),
|
|
103
|
+
draggable: true,
|
|
104
|
+
onDragStart: "initialOnDragStart",
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
.createTestCaseTree();
|
|
109
|
+
suiteTree.resetBeforeEach();
|
|
110
|
+
|
|
111
|
+
it("sets draggable to undefined for items being renamed", () => {
|
|
112
|
+
const item = suiteTree.item("x1");
|
|
113
|
+
item.startRenaming();
|
|
114
|
+
const props = item.getProps();
|
|
115
|
+
expect(props.draggable).toBe(false);
|
|
116
|
+
expect(props.onDragStart).toStrictEqual(expect.any(Function));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("retains draggable for items not being renamed", () => {
|
|
120
|
+
const item = suiteTree.item("x1");
|
|
121
|
+
const props = item.getProps();
|
|
122
|
+
expect(props.draggable).toBe(true);
|
|
123
|
+
expect(props.onDragStart).toBe("initialOnDragStart");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
96
127
|
describe("hotkeys", () => {
|
|
97
128
|
it("starts renaming", () => {
|
|
98
129
|
const setRenamingItem = tree.mockedHandler("setRenamingItem");
|
|
@@ -82,6 +82,11 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
82
82
|
);
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
+
getRootItem: ({ tree }) => {
|
|
86
|
+
const { rootItemId } = tree.getConfig();
|
|
87
|
+
return tree.getItemInstance(rootItemId);
|
|
88
|
+
},
|
|
89
|
+
|
|
85
90
|
focusNextItem: ({ tree }) => {
|
|
86
91
|
const focused = tree.getFocusedItem().getItemMeta();
|
|
87
92
|
if (!focused) return;
|
|
@@ -126,6 +131,7 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
126
131
|
item.getElement()?.scrollIntoView(scrollIntoViewArg);
|
|
127
132
|
},
|
|
128
133
|
getId: ({ itemId }) => itemId,
|
|
134
|
+
getKey: ({ itemId }) => itemId, // TODO apply to all stories to use
|
|
129
135
|
getProps: ({ item, prev }) => {
|
|
130
136
|
const itemMeta = item.getItemMeta();
|
|
131
137
|
return {
|
|
@@ -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;
|
|
@@ -33,7 +33,8 @@ export type TreeFeatureDef<T> = {
|
|
|
33
33
|
/** @internal */
|
|
34
34
|
getItemsMeta: () => ItemMeta[];
|
|
35
35
|
|
|
36
|
-
getFocusedItem: () => ItemInstance<
|
|
36
|
+
getFocusedItem: () => ItemInstance<T>;
|
|
37
|
+
getRootItem: () => ItemInstance<T>;
|
|
37
38
|
focusNextItem: () => void;
|
|
38
39
|
focusPreviousItem: () => void;
|
|
39
40
|
updateDomFocus: () => void;
|
|
@@ -44,6 +45,7 @@ export type TreeFeatureDef<T> = {
|
|
|
44
45
|
};
|
|
45
46
|
itemInstance: {
|
|
46
47
|
getId: () => string;
|
|
48
|
+
getKey: () => string;
|
|
47
49
|
getProps: () => Record<string, any>;
|
|
48
50
|
getItemName: () => string;
|
|
49
51
|
getItemData: () => T;
|
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";
|
|
@@ -31,3 +33,6 @@ export * from "./utilities/remove-items-from-parents";
|
|
|
31
33
|
|
|
32
34
|
export * from "./core/build-proxified-instance";
|
|
33
35
|
export * from "./core/build-static-instance";
|
|
36
|
+
|
|
37
|
+
export { makeStateUpdater } from "./utils";
|
|
38
|
+
export { isOrderedDragTarget } from "./features/drag-and-drop/utils";
|
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>
|