@headless-tree/core 0.0.13 → 0.0.15
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 +13 -0
- package/lib/cjs/core/create-tree.js +13 -4
- package/lib/cjs/features/async-data-loader/feature.js +73 -48
- package/lib/cjs/features/async-data-loader/types.d.ts +17 -14
- package/lib/cjs/features/drag-and-drop/feature.js +98 -93
- package/lib/cjs/features/drag-and-drop/types.d.ts +17 -29
- package/lib/cjs/features/drag-and-drop/types.js +7 -7
- package/lib/cjs/features/drag-and-drop/utils.d.ts +18 -3
- package/lib/cjs/features/drag-and-drop/utils.js +49 -51
- package/lib/cjs/features/expand-all/feature.js +26 -3
- package/lib/cjs/features/expand-all/types.d.ts +3 -1
- package/lib/cjs/features/hotkeys-core/feature.js +7 -3
- package/lib/cjs/features/hotkeys-core/types.d.ts +4 -5
- package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +2 -0
- package/lib/cjs/features/keyboard-drag-and-drop/feature.js +207 -0
- package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +27 -0
- package/lib/cjs/features/keyboard-drag-and-drop/types.js +11 -0
- package/lib/cjs/features/prop-memoization/feature.js +2 -2
- package/lib/cjs/features/prop-memoization/types.d.ts +2 -2
- package/lib/cjs/features/renaming/feature.js +1 -1
- package/lib/cjs/features/search/feature.js +2 -0
- package/lib/cjs/features/search/types.d.ts +2 -2
- package/lib/cjs/features/selection/feature.js +4 -4
- package/lib/cjs/features/selection/types.d.ts +1 -1
- package/lib/cjs/features/sync-data-loader/feature.js +31 -5
- package/lib/cjs/features/sync-data-loader/types.d.ts +5 -5
- package/lib/cjs/features/tree/feature.js +4 -7
- package/lib/cjs/features/tree/types.d.ts +7 -5
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/mddocs-entry.d.ts +10 -0
- package/lib/cjs/test-utils/test-tree-do.d.ts +2 -2
- package/lib/cjs/test-utils/test-tree-do.js +19 -6
- package/lib/cjs/test-utils/test-tree-expect.d.ts +5 -3
- package/lib/cjs/test-utils/test-tree-expect.js +3 -0
- package/lib/cjs/test-utils/test-tree.d.ts +2 -1
- package/lib/cjs/test-utils/test-tree.js +24 -21
- package/lib/cjs/types/core.d.ts +2 -1
- package/lib/cjs/utilities/create-on-drop-handler.d.ts +2 -2
- package/lib/cjs/utilities/create-on-drop-handler.js +13 -4
- package/lib/cjs/utilities/insert-items-at-target.d.ts +2 -2
- package/lib/cjs/utilities/insert-items-at-target.js +21 -12
- package/lib/cjs/utilities/remove-items-from-parents.d.ts +1 -1
- package/lib/cjs/utilities/remove-items-from-parents.js +12 -3
- package/lib/esm/core/create-tree.js +13 -4
- package/lib/esm/features/async-data-loader/feature.js +73 -48
- package/lib/esm/features/async-data-loader/types.d.ts +17 -14
- package/lib/esm/features/drag-and-drop/feature.js +99 -94
- package/lib/esm/features/drag-and-drop/types.d.ts +17 -29
- package/lib/esm/features/drag-and-drop/types.js +6 -6
- package/lib/esm/features/drag-and-drop/utils.d.ts +18 -3
- package/lib/esm/features/drag-and-drop/utils.js +44 -49
- package/lib/esm/features/expand-all/feature.js +26 -3
- package/lib/esm/features/expand-all/types.d.ts +3 -1
- package/lib/esm/features/hotkeys-core/feature.js +7 -3
- package/lib/esm/features/hotkeys-core/types.d.ts +4 -5
- package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +2 -0
- package/lib/esm/features/keyboard-drag-and-drop/feature.js +204 -0
- package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +27 -0
- package/lib/esm/features/keyboard-drag-and-drop/types.js +8 -0
- package/lib/esm/features/prop-memoization/feature.js +2 -2
- package/lib/esm/features/prop-memoization/types.d.ts +2 -2
- package/lib/esm/features/renaming/feature.js +1 -1
- package/lib/esm/features/search/feature.js +2 -0
- package/lib/esm/features/search/types.d.ts +2 -2
- package/lib/esm/features/selection/feature.js +4 -4
- package/lib/esm/features/selection/types.d.ts +1 -1
- package/lib/esm/features/sync-data-loader/feature.js +31 -5
- package/lib/esm/features/sync-data-loader/types.d.ts +5 -5
- package/lib/esm/features/tree/feature.js +4 -7
- package/lib/esm/features/tree/types.d.ts +7 -5
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/mddocs-entry.d.ts +10 -0
- package/lib/esm/test-utils/test-tree-do.d.ts +2 -2
- package/lib/esm/test-utils/test-tree-do.js +19 -6
- package/lib/esm/test-utils/test-tree-expect.d.ts +5 -3
- package/lib/esm/test-utils/test-tree-expect.js +3 -0
- package/lib/esm/test-utils/test-tree.d.ts +2 -1
- package/lib/esm/test-utils/test-tree.js +24 -21
- package/lib/esm/types/core.d.ts +2 -1
- package/lib/esm/utilities/create-on-drop-handler.d.ts +2 -2
- package/lib/esm/utilities/create-on-drop-handler.js +13 -4
- package/lib/esm/utilities/insert-items-at-target.d.ts +2 -2
- package/lib/esm/utilities/insert-items-at-target.js +21 -12
- package/lib/esm/utilities/remove-items-from-parents.d.ts +1 -1
- package/lib/esm/utilities/remove-items-from-parents.js +12 -3
- package/package.json +2 -2
- package/src/core/core.spec.ts +31 -0
- package/src/core/create-tree.ts +15 -5
- package/src/features/async-data-loader/async-data-loader.spec.ts +10 -6
- package/src/features/async-data-loader/feature.ts +76 -48
- package/src/features/async-data-loader/types.ts +18 -11
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +75 -89
- package/src/features/drag-and-drop/feature.ts +21 -22
- package/src/features/drag-and-drop/types.ts +23 -35
- package/src/features/drag-and-drop/utils.ts +67 -57
- package/src/features/expand-all/feature.ts +29 -5
- package/src/features/expand-all/types.ts +3 -1
- package/src/features/hotkeys-core/feature.ts +4 -0
- package/src/features/hotkeys-core/types.ts +4 -13
- package/src/features/keyboard-drag-and-drop/feature.ts +255 -0
- package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +401 -0
- package/src/features/keyboard-drag-and-drop/types.ts +30 -0
- package/src/features/prop-memoization/feature.ts +2 -2
- package/src/features/prop-memoization/prop-memoization.spec.ts +2 -2
- package/src/features/prop-memoization/types.ts +2 -2
- package/src/features/renaming/feature.ts +8 -2
- package/src/features/search/feature.ts +2 -0
- package/src/features/search/types.ts +2 -2
- package/src/features/selection/feature.ts +4 -4
- package/src/features/selection/types.ts +1 -1
- package/src/features/sync-data-loader/feature.ts +26 -7
- package/src/features/sync-data-loader/types.ts +5 -5
- package/src/features/tree/feature.ts +8 -11
- package/src/features/tree/types.ts +7 -5
- package/src/index.ts +2 -0
- package/src/mddocs-entry.ts +16 -0
- package/src/test-utils/test-tree-do.ts +3 -3
- package/src/test-utils/test-tree-expect.ts +7 -2
- package/src/test-utils/test-tree.ts +26 -22
- package/src/types/core.ts +2 -0
- package/src/utilities/create-on-drop-handler.ts +4 -4
- package/src/utilities/insert-items-at-target.ts +18 -14
- package/src/utilities/remove-items-from-parents.ts +6 -3
|
@@ -17,8 +17,8 @@ const customFeature: FeatureImplementation = {
|
|
|
17
17
|
}),
|
|
18
18
|
},
|
|
19
19
|
treeInstance: {
|
|
20
|
-
getContainerProps: ({ prev }) => ({
|
|
21
|
-
...prev?.(),
|
|
20
|
+
getContainerProps: ({ prev }, treeLabel) => ({
|
|
21
|
+
...prev?.(treeLabel),
|
|
22
22
|
customValue: createTreeValue(),
|
|
23
23
|
onCustomEvent: () => treeHandler(),
|
|
24
24
|
}),
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { FeatureImplementation, ItemInstance } from "../../types/core";
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
3
|
|
|
4
|
+
type InputEvent = {
|
|
5
|
+
target?: {
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
4
10
|
export const renamingFeature: FeatureImplementation = {
|
|
5
11
|
key: "renaming",
|
|
6
12
|
|
|
@@ -53,10 +59,10 @@ export const renamingFeature: FeatureImplementation = {
|
|
|
53
59
|
},
|
|
54
60
|
|
|
55
61
|
getRenameInputProps: ({ tree }) => ({
|
|
62
|
+
ref: (r: HTMLInputElement) => r?.focus(),
|
|
56
63
|
onBlur: () => tree.abortRenaming(),
|
|
57
64
|
value: tree.getRenamingValue(),
|
|
58
|
-
onChange: (e:
|
|
59
|
-
// TODO custom type with e.target.value
|
|
65
|
+
onChange: (e: InputEvent) => {
|
|
60
66
|
tree.applySubStateUpdate("renamingValue", e.target?.value);
|
|
61
67
|
},
|
|
62
68
|
}),
|
|
@@ -56,10 +56,12 @@ export const searchFeature: FeatureImplementation = {
|
|
|
56
56
|
getSearchInputElement: ({ tree }) =>
|
|
57
57
|
tree.getDataRef<SearchFeatureDataRef>().current.searchInput ?? null,
|
|
58
58
|
|
|
59
|
+
// TODO memoize with propMemoizationFeature
|
|
59
60
|
getSearchInputElementProps: ({ tree }) => ({
|
|
60
61
|
value: tree.getSearchValue(),
|
|
61
62
|
onChange: (e: any) => tree.setSearch(e.target.value),
|
|
62
63
|
onBlur: () => tree.closeSearch(),
|
|
64
|
+
ref: tree.registerSearchInputElement,
|
|
63
65
|
}),
|
|
64
66
|
|
|
65
67
|
getSearchMatchingItems: memo(
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ItemInstance, SetStateFn } from "../../types/core";
|
|
2
2
|
import { HotkeysCoreDataRef } from "../hotkeys-core/types";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface SearchFeatureDataRef<T = any> extends HotkeysCoreDataRef {
|
|
5
5
|
matchingItems: ItemInstance<T>[];
|
|
6
6
|
searchInput: HTMLInputElement | null;
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
8
|
|
|
9
9
|
export type SearchFeatureDef<T> = {
|
|
10
10
|
state: {
|
|
@@ -23,7 +23,6 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
23
23
|
tree.applySubStateUpdate("selectedItems", selectedItems);
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
-
// TODO memo
|
|
27
26
|
getSelectedItems: ({ tree }) => {
|
|
28
27
|
return tree.getState().selectedItems.map(tree.getItemInstance);
|
|
29
28
|
},
|
|
@@ -103,9 +102,10 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
103
102
|
// tree.setSelectedItems([tree.getFocusedItem().getId()]);
|
|
104
103
|
// },
|
|
105
104
|
// },
|
|
106
|
-
|
|
107
|
-
hotkey: "
|
|
108
|
-
|
|
105
|
+
toggleSelectedItem: {
|
|
106
|
+
hotkey: "Control+Space",
|
|
107
|
+
preventDefault: true,
|
|
108
|
+
handler: (_, tree) => {
|
|
109
109
|
tree.getFocusedItem().toggleSelect();
|
|
110
110
|
},
|
|
111
111
|
},
|
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
|
+
import { throwError } from "../../utilities/errors";
|
|
4
|
+
|
|
5
|
+
const promiseErrorMessage = "sync dataLoader returned promise";
|
|
3
6
|
|
|
4
7
|
export const syncDataLoaderFeature: FeatureImplementation = {
|
|
5
8
|
key: "sync-data-loader",
|
|
6
9
|
|
|
7
10
|
getInitialState: (initialState) => ({
|
|
8
|
-
|
|
11
|
+
loadingItemData: [],
|
|
12
|
+
loadingItemChildrens: [],
|
|
9
13
|
...initialState,
|
|
10
14
|
}),
|
|
11
15
|
|
|
12
16
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
13
|
-
|
|
17
|
+
setLoadingItemData: makeStateUpdater("loadingItemData", tree),
|
|
18
|
+
setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree),
|
|
14
19
|
...defaultConfig,
|
|
15
20
|
}),
|
|
16
21
|
|
|
17
22
|
stateHandlerNames: {
|
|
18
|
-
|
|
23
|
+
loadingItemData: "setLoadingItemData",
|
|
24
|
+
loadingItemChildrens: "setLoadingItemChildrens",
|
|
19
25
|
},
|
|
20
26
|
|
|
21
27
|
treeInstance: {
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
waitForItemDataLoaded: async () => {},
|
|
29
|
+
waitForItemChildrenLoaded: async () => {},
|
|
30
|
+
|
|
31
|
+
retrieveItemData: ({ tree }, itemId) => {
|
|
32
|
+
const data = tree.getConfig().dataLoader.getItem(itemId);
|
|
33
|
+
if (typeof data === "object" && "then" in data) {
|
|
34
|
+
throw throwError(promiseErrorMessage);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
},
|
|
24
38
|
|
|
25
|
-
retrieveChildrenIds: ({ tree }, itemId) =>
|
|
26
|
-
tree.getConfig().dataLoader
|
|
39
|
+
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
40
|
+
const data = tree.getConfig().dataLoader.getChildren(itemId);
|
|
41
|
+
if (typeof data === "object" && "then" in data) {
|
|
42
|
+
throw throwError(promiseErrorMessage);
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
},
|
|
27
46
|
},
|
|
28
47
|
|
|
29
48
|
itemInstance: {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export
|
|
2
|
-
getItem: (itemId: string) => T
|
|
3
|
-
getChildren: (itemId: string) => string[]
|
|
4
|
-
}
|
|
1
|
+
export interface TreeDataLoader<T> {
|
|
2
|
+
getItem: (itemId: string) => T | Promise<T>;
|
|
3
|
+
getChildren: (itemId: string) => string[] | Promise<string[]>;
|
|
4
|
+
}
|
|
5
5
|
|
|
6
6
|
export type SyncDataLoaderFeatureDef<T> = {
|
|
7
7
|
state: {};
|
|
8
8
|
config: {
|
|
9
9
|
rootItemId: string;
|
|
10
|
-
dataLoader
|
|
10
|
+
dataLoader: TreeDataLoader<T>;
|
|
11
11
|
};
|
|
12
12
|
treeInstance: {
|
|
13
13
|
retrieveItemData: (itemId: string) => T;
|
|
@@ -27,7 +27,7 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
27
27
|
const { rootItemId } = tree.getConfig();
|
|
28
28
|
const { expandedItems } = tree.getState();
|
|
29
29
|
const flatItems: ItemMeta[] = [];
|
|
30
|
-
const expandedItemsSet = new Set(expandedItems);
|
|
30
|
+
const expandedItemsSet = new Set(expandedItems); // TODO support setting state expandedItems as set instead of array
|
|
31
31
|
|
|
32
32
|
const recursiveAdd = (
|
|
33
33
|
itemId: string,
|
|
@@ -46,7 +46,6 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
if (expandedItemsSet.has(itemId)) {
|
|
49
|
-
// TODO THIS MADE A HUGE DIFFERENCE!
|
|
50
49
|
const children = tree.retrieveChildrenIds(itemId) ?? [];
|
|
51
50
|
let i = 0;
|
|
52
51
|
for (const childId of children) {
|
|
@@ -97,11 +96,11 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
97
96
|
},
|
|
98
97
|
|
|
99
98
|
// TODO add label parameter
|
|
100
|
-
getContainerProps: ({ prev, tree }) => ({
|
|
99
|
+
getContainerProps: ({ prev, tree }, treeLabel) => ({
|
|
101
100
|
...prev?.(),
|
|
102
101
|
role: "tree",
|
|
103
|
-
"aria-label": "",
|
|
104
|
-
ref: tree.registerElement,
|
|
102
|
+
"aria-label": treeLabel ?? "",
|
|
103
|
+
ref: tree.registerElement,
|
|
105
104
|
}),
|
|
106
105
|
|
|
107
106
|
// relevant for hotkeys of this feature
|
|
@@ -119,7 +118,7 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
119
118
|
const itemMeta = item.getItemMeta();
|
|
120
119
|
return {
|
|
121
120
|
...prev?.(),
|
|
122
|
-
ref: item.registerElement,
|
|
121
|
+
ref: item.registerElement,
|
|
123
122
|
role: "treeitem",
|
|
124
123
|
"aria-setsize": itemMeta.setSize,
|
|
125
124
|
"aria-posinset": itemMeta.posInSet,
|
|
@@ -152,7 +151,7 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
152
151
|
return;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
if (tree.getState().
|
|
154
|
+
if (tree.getState().loadingItemChildrens?.includes(itemId)) {
|
|
156
155
|
return;
|
|
157
156
|
}
|
|
158
157
|
|
|
@@ -202,10 +201,8 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
202
201
|
? tree.getItemInstance(item.getItemMeta().parentId)
|
|
203
202
|
: undefined,
|
|
204
203
|
getIndexInParent: ({ item }) => item.getItemMeta().posInSet,
|
|
205
|
-
getChildren: ({ tree,
|
|
206
|
-
tree
|
|
207
|
-
.retrieveChildrenIds(item.getItemMeta().itemId)
|
|
208
|
-
.map((id) => tree.getItemInstance(id)),
|
|
204
|
+
getChildren: ({ tree, itemId }) =>
|
|
205
|
+
tree.retrieveChildrenIds(itemId).map((id) => tree.getItemInstance(id)),
|
|
209
206
|
getTree: ({ tree }) => tree as any,
|
|
210
207
|
getItemAbove: ({ tree, item }) =>
|
|
211
208
|
tree.getItems()[item.getItemMeta().index - 1],
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { ItemInstance, SetStateFn, TreeInstance } from "../../types/core";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export interface ItemMeta {
|
|
4
4
|
itemId: string;
|
|
5
5
|
parentId: string;
|
|
6
6
|
level: number;
|
|
7
7
|
index: number;
|
|
8
8
|
setSize: number;
|
|
9
9
|
posInSet: number;
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export interface TreeItemDataRef {
|
|
13
13
|
memoizedValues: Record<string, any>;
|
|
14
14
|
memoizedDeps: Record<string, any[] | undefined>;
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
16
|
|
|
17
17
|
export type TreeFeatureDef<T> = {
|
|
18
18
|
state: {
|
|
@@ -38,7 +38,9 @@ export type TreeFeatureDef<T> = {
|
|
|
38
38
|
focusPreviousItem: () => void;
|
|
39
39
|
updateDomFocus: () => void;
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/** Pass to the container rendering the tree children. The `treeLabel` parameter
|
|
42
|
+
* will be passed as `aria-label` parameter, and is recommended to be set. */
|
|
43
|
+
getContainerProps: (treeLabel?: string) => Record<string, any>;
|
|
42
44
|
};
|
|
43
45
|
itemInstance: {
|
|
44
46
|
getId: () => string;
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./core/create-tree";
|
|
|
4
4
|
export * from "./features/tree/types";
|
|
5
5
|
export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
6
6
|
export * from "./features/drag-and-drop/types";
|
|
7
|
+
export * from "./features/keyboard-drag-and-drop/types";
|
|
7
8
|
export * from "./features/selection/types";
|
|
8
9
|
export * from "./features/async-data-loader/types";
|
|
9
10
|
export * from "./features/sync-data-loader/types";
|
|
@@ -18,6 +19,7 @@ export * from "./features/hotkeys-core/feature";
|
|
|
18
19
|
export * from "./features/async-data-loader/feature";
|
|
19
20
|
export * from "./features/sync-data-loader/feature";
|
|
20
21
|
export * from "./features/drag-and-drop/feature";
|
|
22
|
+
export * from "./features/keyboard-drag-and-drop/feature";
|
|
21
23
|
export * from "./features/search/feature";
|
|
22
24
|
export * from "./features/renaming/feature";
|
|
23
25
|
export * from "./features/expand-all/feature";
|
package/src/mddocs-entry.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { SelectionFeatureDef } from "./features/selection/types";
|
|
|
9
9
|
import { SyncDataLoaderFeatureDef } from "./features/sync-data-loader/types";
|
|
10
10
|
import { TreeFeatureDef } from "./features/tree/types";
|
|
11
11
|
import { PropMemoizationFeatureDef } from "./features/prop-memoization/types";
|
|
12
|
+
import { KeyboardDragAndDropFeatureDef } from "./features/keyboard-drag-and-drop/types";
|
|
12
13
|
|
|
13
14
|
export * from ".";
|
|
14
15
|
|
|
@@ -49,6 +50,21 @@ export type DragAndDropFeatureItemInstance<T> =
|
|
|
49
50
|
DragAndDropFeatureDef<T>["itemInstance"];
|
|
50
51
|
export type DragAndDropFeatureHotkeys<T> = DragAndDropFeatureDef<T>["hotkeys"];
|
|
51
52
|
|
|
53
|
+
/** @interface */
|
|
54
|
+
export type KeyboardDragAndDropFeatureConfig<T> =
|
|
55
|
+
KeyboardDragAndDropFeatureDef<T>["config"];
|
|
56
|
+
/** @interface */
|
|
57
|
+
export type KeyboardDragAndDropFeatureState<T> =
|
|
58
|
+
KeyboardDragAndDropFeatureDef<T>["state"];
|
|
59
|
+
/** @interface */
|
|
60
|
+
export type KeyboardDragAndDropFeatureTreeInstance<T> =
|
|
61
|
+
KeyboardDragAndDropFeatureDef<T>["treeInstance"];
|
|
62
|
+
/** @interface */
|
|
63
|
+
export type KeyboardDragAndDropFeatureItemInstance<T> =
|
|
64
|
+
KeyboardDragAndDropFeatureDef<T>["itemInstance"];
|
|
65
|
+
export type KeyboardDragAndDropFeatureHotkeys<T> =
|
|
66
|
+
KeyboardDragAndDropFeatureDef<T>["hotkeys"];
|
|
67
|
+
|
|
52
68
|
/** @interface */
|
|
53
69
|
export type ExpandAllFeatureConfig = ExpandAllFeatureDef["config"];
|
|
54
70
|
/** @interface */
|
|
@@ -108,13 +108,13 @@ export class TestTreeDo<T> {
|
|
|
108
108
|
return e;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
drop(itemId: string, event?: DragEvent) {
|
|
111
|
+
async drop(itemId: string, event?: DragEvent) {
|
|
112
112
|
const e = event ?? TestTree.dragEvent();
|
|
113
|
-
this.itemProps(itemId).onDrop(e);
|
|
113
|
+
await this.itemProps(itemId).onDrop(e);
|
|
114
114
|
return e;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
dragOverAndDrop(itemId: string, event?: DragEvent) {
|
|
117
|
+
async dragOverAndDrop(itemId: string, event?: DragEvent) {
|
|
118
118
|
const e = event ?? TestTree.dragEvent();
|
|
119
119
|
this.dragOver(itemId, e);
|
|
120
120
|
return this.drop(itemId, e);
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { Mock, expect } from "vitest";
|
|
3
3
|
import { DragEvent } from "react";
|
|
4
4
|
import { TestTree } from "./test-tree";
|
|
5
|
-
import {
|
|
5
|
+
import { DragTarget } from "../features/drag-and-drop/types";
|
|
6
|
+
import { TreeState } from "../types/core";
|
|
6
7
|
|
|
7
8
|
export class TestTreeExpect<T> {
|
|
8
9
|
protected itemInstance(itemId: string) {
|
|
@@ -39,7 +40,11 @@ export class TestTreeExpect<T> {
|
|
|
39
40
|
expect(itemChildren).toEqual(children);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
substate<K extends keyof TreeState<T>>(key: K, value: TreeState<T>[K]) {
|
|
44
|
+
expect(this.tree.instance.getState()[key]).toEqual(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dropped(draggedItems: string[], target: DragTarget<any>) {
|
|
43
48
|
expect(this.tree.instance.getConfig().onDrop).toBeCalledWith(
|
|
44
49
|
draggedItems.map((id) => this.tree.item(id)),
|
|
45
50
|
target,
|
|
@@ -8,6 +8,7 @@ import { TestTreeExpect } from "./test-tree-expect";
|
|
|
8
8
|
import { syncDataLoaderFeature } from "../features/sync-data-loader/feature";
|
|
9
9
|
import { asyncDataLoaderFeature } from "../features/async-data-loader/feature";
|
|
10
10
|
import { buildProxiedInstance } from "../core/build-proxified-instance";
|
|
11
|
+
import { TreeDataLoader } from "../features/sync-data-loader/types";
|
|
11
12
|
|
|
12
13
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
13
14
|
|
|
@@ -20,13 +21,32 @@ export class TestTree<T = string> {
|
|
|
20
21
|
|
|
21
22
|
private static asyncLoaderResolvers: (() => void)[] = [];
|
|
22
23
|
|
|
24
|
+
private asyncDataLoaderImp: TreeDataLoader<T> = {
|
|
25
|
+
getItem: async (id: string) => {
|
|
26
|
+
await new Promise<void>((r) => {
|
|
27
|
+
(r as any).debugName = `Loading getItem ${id}`;
|
|
28
|
+
TestTree.asyncLoaderResolvers.push(r);
|
|
29
|
+
});
|
|
30
|
+
return id as T;
|
|
31
|
+
},
|
|
32
|
+
getChildren: async (id: string) => {
|
|
33
|
+
await new Promise<void>((r) => {
|
|
34
|
+
(r as any).debugName = `Loading getChildren ${id}`;
|
|
35
|
+
TestTree.asyncLoaderResolvers.push(r);
|
|
36
|
+
});
|
|
37
|
+
return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
23
41
|
suits = {
|
|
24
42
|
sync: () => ({
|
|
25
43
|
tree: this.withFeatures(syncDataLoaderFeature),
|
|
26
44
|
title: "Synchronous Data Loader",
|
|
27
45
|
}),
|
|
28
46
|
async: () => ({
|
|
29
|
-
tree: this.withFeatures(asyncDataLoaderFeature)
|
|
47
|
+
tree: this.withFeatures(asyncDataLoaderFeature).with({
|
|
48
|
+
dataLoader: this.asyncDataLoaderImp,
|
|
49
|
+
}),
|
|
30
50
|
title: "Asynchronous Data Loader",
|
|
31
51
|
}),
|
|
32
52
|
proxifiedSync: () => ({
|
|
@@ -65,12 +85,12 @@ export class TestTree<T = string> {
|
|
|
65
85
|
private constructor(private config: TreeConfig<T>) {}
|
|
66
86
|
|
|
67
87
|
static async resolveAsyncLoaders() {
|
|
68
|
-
|
|
88
|
+
do {
|
|
69
89
|
TestTree.asyncLoaderResolvers.shift()?.();
|
|
70
90
|
await new Promise<void>((r) => {
|
|
71
91
|
setTimeout(r);
|
|
72
92
|
});
|
|
73
|
-
}
|
|
93
|
+
} while (TestTree.asyncLoaderResolvers.length);
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
async resolveAsyncVisibleItems() {
|
|
@@ -88,22 +108,6 @@ export class TestTree<T = string> {
|
|
|
88
108
|
getItem: (id) => id,
|
|
89
109
|
getChildren: (id) => [`${id}1`, `${id}2`, `${id}3`, `${id}4`],
|
|
90
110
|
},
|
|
91
|
-
asyncDataLoader: {
|
|
92
|
-
getItem: async (id) => {
|
|
93
|
-
await new Promise<void>((r) => {
|
|
94
|
-
(r as any).debugName = `Loading getItem ${id}`;
|
|
95
|
-
TestTree.asyncLoaderResolvers.push(r);
|
|
96
|
-
});
|
|
97
|
-
return id;
|
|
98
|
-
},
|
|
99
|
-
getChildren: async (id) => {
|
|
100
|
-
await new Promise<void>((r) => {
|
|
101
|
-
(r as any).debugName = `Loading getChildren ${id}`;
|
|
102
|
-
TestTree.asyncLoaderResolvers.push(r);
|
|
103
|
-
});
|
|
104
|
-
return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
111
|
getItemName: (item) => item.getItemData(),
|
|
108
112
|
indent: 20,
|
|
109
113
|
isItemFolder: (item) => item.getItemMeta().level < 2,
|
|
@@ -203,7 +207,7 @@ export class TestTree<T = string> {
|
|
|
203
207
|
} as HTMLElement);
|
|
204
208
|
}
|
|
205
209
|
|
|
206
|
-
static dragEvent(
|
|
210
|
+
static dragEvent(clientX = 1000, clientY = 0) {
|
|
207
211
|
return {
|
|
208
212
|
preventDefault: vi.fn(),
|
|
209
213
|
stopPropagation: vi.fn(),
|
|
@@ -212,8 +216,8 @@ export class TestTree<T = string> {
|
|
|
212
216
|
getData: vi.fn(),
|
|
213
217
|
dropEffect: "unchaged-from-test",
|
|
214
218
|
},
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
clientX,
|
|
220
|
+
clientY,
|
|
217
221
|
} as unknown as DragEvent;
|
|
218
222
|
}
|
|
219
223
|
|
package/src/types/core.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { SearchFeatureDef } from "../features/search/types";
|
|
|
12
12
|
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
|
+
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
15
16
|
|
|
16
17
|
export type Updater<T> = T | ((old: T) => T);
|
|
17
18
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
@@ -53,6 +54,7 @@ export type RegisteredFeatures<T> =
|
|
|
53
54
|
| TreeFeatureDef<T>
|
|
54
55
|
| SelectionFeatureDef<T>
|
|
55
56
|
| DragAndDropFeatureDef<T>
|
|
57
|
+
| KeyboardDragAndDropFeatureDef<T>
|
|
56
58
|
| HotkeysCoreFeatureDef<T>
|
|
57
59
|
| SyncDataLoaderFeatureDef<T>
|
|
58
60
|
| AsyncDataLoaderFeatureDef<T>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ItemInstance } from "../types/core";
|
|
2
|
-
import {
|
|
2
|
+
import { DragTarget } from "../features/drag-and-drop/types";
|
|
3
3
|
import { removeItemsFromParents } from "./remove-items-from-parents";
|
|
4
4
|
import { insertItemsAtTarget } from "./insert-items-at-target";
|
|
5
5
|
|
|
@@ -7,8 +7,8 @@ export const createOnDropHandler =
|
|
|
7
7
|
<T>(
|
|
8
8
|
onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void,
|
|
9
9
|
) =>
|
|
10
|
-
(items: ItemInstance<T>[], target:
|
|
10
|
+
async (items: ItemInstance<T>[], target: DragTarget<T>) => {
|
|
11
11
|
const itemIds = items.map((item) => item.getId());
|
|
12
|
-
removeItemsFromParents(items, onChangeChildren);
|
|
13
|
-
insertItemsAtTarget(itemIds, target, onChangeChildren);
|
|
12
|
+
await removeItemsFromParents(items, onChangeChildren);
|
|
13
|
+
await insertItemsAtTarget(itemIds, target, onChangeChildren);
|
|
14
14
|
};
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { ItemInstance } from "../types/core";
|
|
2
|
-
import {
|
|
2
|
+
import { DragTarget } from "../features/drag-and-drop/types";
|
|
3
3
|
|
|
4
|
-
export const insertItemsAtTarget = <T>(
|
|
4
|
+
export const insertItemsAtTarget = async <T>(
|
|
5
5
|
itemIds: string[],
|
|
6
|
-
target:
|
|
7
|
-
onChangeChildren: (
|
|
6
|
+
target: DragTarget<T>,
|
|
7
|
+
onChangeChildren: (
|
|
8
|
+
item: ItemInstance<T>,
|
|
9
|
+
newChildrenIds: string[],
|
|
10
|
+
) => Promise<void> | void,
|
|
8
11
|
) => {
|
|
12
|
+
await target.item.getTree().waitForItemChildrenLoaded(target.item.getId());
|
|
13
|
+
const oldChildrenIds = target.item
|
|
14
|
+
.getTree()
|
|
15
|
+
.retrieveChildrenIds(target.item.getId());
|
|
16
|
+
|
|
9
17
|
// add moved items to new common parent, if dropped onto parent
|
|
10
|
-
if (
|
|
11
|
-
const newChildren = [
|
|
12
|
-
|
|
13
|
-
...itemIds,
|
|
14
|
-
];
|
|
15
|
-
onChangeChildren(target.item, newChildren);
|
|
18
|
+
if (!("childIndex" in target)) {
|
|
19
|
+
const newChildren = [...oldChildrenIds, ...itemIds];
|
|
20
|
+
await onChangeChildren(target.item, newChildren);
|
|
16
21
|
if (target.item && "updateCachedChildrenIds" in target.item) {
|
|
17
22
|
target.item.updateCachedChildrenIds(newChildren);
|
|
18
23
|
}
|
|
@@ -21,14 +26,13 @@ export const insertItemsAtTarget = <T>(
|
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
// add moved items to new common parent, if dropped between siblings
|
|
24
|
-
const oldChildren = target.item.getChildren();
|
|
25
29
|
const newChildren = [
|
|
26
|
-
...
|
|
30
|
+
...oldChildrenIds.slice(0, target.insertionIndex),
|
|
27
31
|
...itemIds,
|
|
28
|
-
...
|
|
32
|
+
...oldChildrenIds.slice(target.insertionIndex),
|
|
29
33
|
];
|
|
30
34
|
|
|
31
|
-
onChangeChildren(target.item, newChildren);
|
|
35
|
+
await onChangeChildren(target.item, newChildren);
|
|
32
36
|
|
|
33
37
|
if (target.item && "updateCachedChildrenIds" in target.item) {
|
|
34
38
|
target.item.updateCachedChildrenIds(newChildren);
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { ItemInstance } from "../types/core";
|
|
2
2
|
|
|
3
|
-
export const removeItemsFromParents = <T>(
|
|
3
|
+
export const removeItemsFromParents = async <T>(
|
|
4
4
|
movedItems: ItemInstance<T>[],
|
|
5
|
-
onChangeChildren: (
|
|
5
|
+
onChangeChildren: (
|
|
6
|
+
item: ItemInstance<T>,
|
|
7
|
+
newChildrenIds: string[],
|
|
8
|
+
) => void | Promise<void>,
|
|
6
9
|
) => {
|
|
7
10
|
const movedItemsIds = movedItems.map((item) => item.getId());
|
|
8
11
|
const uniqueParents = [
|
|
@@ -15,7 +18,7 @@ export const removeItemsFromParents = <T>(
|
|
|
15
18
|
const newChildren = siblings
|
|
16
19
|
.filter((sibling) => !movedItemsIds.includes(sibling.getId()))
|
|
17
20
|
.map((i) => i.getId());
|
|
18
|
-
onChangeChildren(parent, newChildren);
|
|
21
|
+
await onChangeChildren(parent, newChildren);
|
|
19
22
|
if (parent && "updateCachedChildrenIds" in parent) {
|
|
20
23
|
parent?.updateCachedChildrenIds(newChildren);
|
|
21
24
|
}
|