@headless-tree/core 0.0.4 → 0.0.6
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/create-tree.js +11 -10
- package/lib/cjs/features/async-data-loader/feature.js +30 -21
- package/lib/cjs/features/drag-and-drop/feature.js +34 -22
- package/lib/cjs/features/drag-and-drop/types.d.ts +14 -1
- package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/cjs/features/drag-and-drop/utils.js +34 -17
- package/lib/cjs/features/expand-all/feature.js +12 -9
- package/lib/cjs/features/expand-all/types.d.ts +2 -2
- package/lib/cjs/features/main/types.d.ts +4 -1
- package/lib/cjs/features/renaming/feature.js +10 -9
- package/lib/cjs/features/search/feature.js +21 -7
- package/lib/cjs/features/search/types.d.ts +1 -1
- package/lib/cjs/features/selection/feature.js +6 -4
- package/lib/cjs/features/sync-data-loader/feature.js +6 -0
- package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
- package/lib/cjs/features/tree/feature.js +41 -24
- package/lib/cjs/features/tree/types.d.ts +7 -2
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/types/core.d.ts +1 -3
- package/lib/cjs/utilities/create-on-drop-handler.d.ts +3 -0
- package/lib/cjs/utilities/create-on-drop-handler.js +11 -0
- package/lib/cjs/utilities/insert-items-at-target.d.ts +3 -0
- package/lib/cjs/utilities/insert-items-at-target.js +24 -0
- package/lib/cjs/utilities/remove-items-from-parents.d.ts +2 -0
- package/lib/cjs/utilities/remove-items-from-parents.js +17 -0
- package/lib/cjs/utils.d.ts +1 -4
- package/lib/cjs/utils.js +1 -53
- package/lib/esm/core/create-tree.js +11 -10
- package/lib/esm/features/async-data-loader/feature.js +30 -21
- package/lib/esm/features/drag-and-drop/feature.js +34 -22
- package/lib/esm/features/drag-and-drop/types.d.ts +14 -1
- package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/esm/features/drag-and-drop/utils.js +35 -18
- package/lib/esm/features/expand-all/feature.js +12 -9
- package/lib/esm/features/expand-all/types.d.ts +2 -2
- package/lib/esm/features/main/types.d.ts +4 -1
- package/lib/esm/features/renaming/feature.js +10 -9
- package/lib/esm/features/search/feature.js +21 -7
- package/lib/esm/features/search/types.d.ts +1 -1
- package/lib/esm/features/selection/feature.js +6 -4
- package/lib/esm/features/sync-data-loader/feature.js +6 -0
- package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
- package/lib/esm/features/tree/feature.js +41 -24
- package/lib/esm/features/tree/types.d.ts +7 -2
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/types/core.d.ts +1 -3
- package/lib/esm/utilities/create-on-drop-handler.d.ts +3 -0
- package/lib/esm/utilities/create-on-drop-handler.js +7 -0
- package/lib/esm/utilities/insert-items-at-target.d.ts +3 -0
- package/lib/esm/utilities/insert-items-at-target.js +20 -0
- package/lib/esm/utilities/remove-items-from-parents.d.ts +2 -0
- package/lib/esm/utilities/remove-items-from-parents.js +13 -0
- package/lib/esm/utils.d.ts +1 -4
- package/lib/esm/utils.js +0 -50
- package/package.json +3 -3
- package/src/core/create-tree.ts +12 -8
- package/src/features/async-data-loader/feature.ts +15 -5
- package/src/features/drag-and-drop/feature.ts +42 -20
- package/src/features/drag-and-drop/types.ts +23 -6
- package/src/features/drag-and-drop/utils.ts +53 -24
- package/src/features/expand-all/feature.ts +10 -8
- package/src/features/expand-all/types.ts +2 -2
- package/src/features/main/types.ts +7 -0
- package/src/features/renaming/feature.ts +10 -5
- package/src/features/search/feature.ts +22 -5
- package/src/features/search/types.ts +1 -0
- package/src/features/selection/feature.ts +8 -3
- package/src/features/sync-data-loader/feature.ts +17 -2
- package/src/features/sync-data-loader/types.ts +1 -1
- package/src/features/tree/feature.ts +43 -23
- package/src/features/tree/types.ts +10 -2
- package/src/index.ts +4 -1
- package/src/types/core.ts +4 -4
- package/src/utilities/create-on-drop-handler.ts +14 -0
- package/src/utilities/insert-items-at-target.ts +30 -0
- package/src/utilities/remove-items-from-parents.ts +21 -0
- package/src/utils.ts +1 -69
- package/tsconfig.json +1 -1
- package/lib/cjs/data-adapters/nested-data-adapter.d.ts +0 -9
- package/lib/cjs/data-adapters/nested-data-adapter.js +0 -32
- package/lib/cjs/data-adapters/types.d.ts +0 -7
- package/lib/cjs/data-adapters/types.js +0 -2
- package/lib/esm/data-adapters/nested-data-adapter.d.ts +0 -9
- package/lib/esm/data-adapters/nested-data-adapter.js +0 -28
- package/lib/esm/data-adapters/types.d.ts +0 -7
- package/lib/esm/data-adapters/types.js +0 -1
- package/src/data-adapters/nested-data-adapter.ts +0 -48
- package/src/data-adapters/types.ts +0 -9
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
SetStateFn,
|
|
6
6
|
TreeConfig,
|
|
7
7
|
TreeState,
|
|
8
|
+
Updater,
|
|
8
9
|
} from "../../types/core";
|
|
9
10
|
import { ItemMeta } from "../tree/types";
|
|
10
11
|
|
|
@@ -12,10 +13,16 @@ export type MainFeatureDef<T = any> = {
|
|
|
12
13
|
state: {};
|
|
13
14
|
config: {
|
|
14
15
|
features?: FeatureImplementation<any>[];
|
|
16
|
+
initialState?: Partial<TreeState<T>>;
|
|
15
17
|
state?: Partial<TreeState<T>>;
|
|
16
18
|
setState?: SetStateFn<TreeState<T>>;
|
|
17
19
|
};
|
|
18
20
|
treeInstance: {
|
|
21
|
+
/** @internal */
|
|
22
|
+
applySubStateUpdate: <K extends keyof TreeState<any>>(
|
|
23
|
+
stateName: K,
|
|
24
|
+
updater: Updater<TreeState<T>[K]>
|
|
25
|
+
) => void;
|
|
19
26
|
setState: SetStateFn<TreeState<T>>;
|
|
20
27
|
getState: () => TreeState<T>;
|
|
21
28
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -19,6 +19,11 @@ export const renamingFeature: FeatureImplementation<
|
|
|
19
19
|
...defaultConfig,
|
|
20
20
|
}),
|
|
21
21
|
|
|
22
|
+
stateHandlerNames: {
|
|
23
|
+
renamingItem: "setRenamingItem",
|
|
24
|
+
renamingValue: "setRenamingValue",
|
|
25
|
+
},
|
|
26
|
+
|
|
22
27
|
createTreeInstance: (prev, instance) => ({
|
|
23
28
|
...prev,
|
|
24
29
|
|
|
@@ -30,8 +35,8 @@ export const renamingFeature: FeatureImplementation<
|
|
|
30
35
|
return;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
instance.applySubStateUpdate("renamingItem", itemId);
|
|
39
|
+
instance.applySubStateUpdate("renamingValue", item.getItemName());
|
|
35
40
|
},
|
|
36
41
|
|
|
37
42
|
getRenamingItem: () => {
|
|
@@ -42,7 +47,7 @@ export const renamingFeature: FeatureImplementation<
|
|
|
42
47
|
getRenamingValue: () => instance.getState().renamingValue || "",
|
|
43
48
|
|
|
44
49
|
abortRenaming: () => {
|
|
45
|
-
instance.
|
|
50
|
+
instance.applySubStateUpdate("renamingItem", null);
|
|
46
51
|
},
|
|
47
52
|
|
|
48
53
|
completeRenaming: () => {
|
|
@@ -51,7 +56,7 @@ export const renamingFeature: FeatureImplementation<
|
|
|
51
56
|
if (item) {
|
|
52
57
|
config.onRename?.(item, instance.getState().renamingValue || "");
|
|
53
58
|
}
|
|
54
|
-
instance.
|
|
59
|
+
instance.applySubStateUpdate("renamingItem", null);
|
|
55
60
|
},
|
|
56
61
|
|
|
57
62
|
isRenamingItem: () => !!instance.getState().renamingItem,
|
|
@@ -63,7 +68,7 @@ export const renamingFeature: FeatureImplementation<
|
|
|
63
68
|
onBlur: () => tree.abortRenaming(),
|
|
64
69
|
value: tree.getRenamingValue(),
|
|
65
70
|
onChange: (e) => {
|
|
66
|
-
tree.
|
|
71
|
+
tree.applySubStateUpdate("renamingValue", e.target.value);
|
|
67
72
|
},
|
|
68
73
|
}),
|
|
69
74
|
|
|
@@ -25,11 +25,15 @@ export const searchFeature: FeatureImplementation<
|
|
|
25
25
|
...defaultConfig,
|
|
26
26
|
}),
|
|
27
27
|
|
|
28
|
+
stateHandlerNames: {
|
|
29
|
+
search: "setSearch",
|
|
30
|
+
},
|
|
31
|
+
|
|
28
32
|
createTreeInstance: (prev, instance) => ({
|
|
29
33
|
...prev,
|
|
30
34
|
|
|
31
35
|
setSearch: (search) => {
|
|
32
|
-
instance.
|
|
36
|
+
instance.applySubStateUpdate("search", search);
|
|
33
37
|
instance
|
|
34
38
|
.getItems()
|
|
35
39
|
.find((item) =>
|
|
@@ -71,8 +75,9 @@ export const searchFeature: FeatureImplementation<
|
|
|
71
75
|
|
|
72
76
|
getSearchMatchingItems: memo(
|
|
73
77
|
(search, items) =>
|
|
74
|
-
items.filter(
|
|
75
|
-
|
|
78
|
+
items.filter(
|
|
79
|
+
(item) =>
|
|
80
|
+
search && instance.getConfig().isSearchMatchingItem?.(search, item)
|
|
76
81
|
),
|
|
77
82
|
() => [instance.getSearchValue(), instance.getItems()]
|
|
78
83
|
),
|
|
@@ -105,12 +110,22 @@ export const searchFeature: FeatureImplementation<
|
|
|
105
110
|
},
|
|
106
111
|
},
|
|
107
112
|
|
|
113
|
+
submitSearch: {
|
|
114
|
+
hotkey: "Enter",
|
|
115
|
+
allowWhenInputFocused: true,
|
|
116
|
+
isEnabled: (tree) => tree.isSearchOpen(),
|
|
117
|
+
handler: (e, tree) => {
|
|
118
|
+
tree.closeSearch();
|
|
119
|
+
tree.setSelectedItems([tree.getFocusedItem().getId()]);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
|
|
108
123
|
nextSearchItem: {
|
|
109
124
|
hotkey: "ArrowDown",
|
|
110
125
|
allowWhenInputFocused: true,
|
|
126
|
+
canRepeat: true,
|
|
111
127
|
isEnabled: (tree) => tree.isSearchOpen(),
|
|
112
128
|
handler: (e, tree) => {
|
|
113
|
-
// TODO scroll into view
|
|
114
129
|
const focusItem = tree
|
|
115
130
|
.getSearchMatchingItems()
|
|
116
131
|
.find(
|
|
@@ -119,15 +134,16 @@ export const searchFeature: FeatureImplementation<
|
|
|
119
134
|
tree.getFocusedItem().getItemMeta().index
|
|
120
135
|
);
|
|
121
136
|
focusItem?.setFocused();
|
|
137
|
+
focusItem?.scrollTo({ block: "nearest", inline: "nearest" });
|
|
122
138
|
},
|
|
123
139
|
},
|
|
124
140
|
|
|
125
141
|
previousSearchItem: {
|
|
126
142
|
hotkey: "ArrowUp",
|
|
127
143
|
allowWhenInputFocused: true,
|
|
144
|
+
canRepeat: true,
|
|
128
145
|
isEnabled: (tree) => tree.isSearchOpen(),
|
|
129
146
|
handler: (e, tree) => {
|
|
130
|
-
// TODO scroll into view
|
|
131
147
|
const focusItem = [...tree.getSearchMatchingItems()]
|
|
132
148
|
.reverse()
|
|
133
149
|
.find(
|
|
@@ -136,6 +152,7 @@ export const searchFeature: FeatureImplementation<
|
|
|
136
152
|
tree.getFocusedItem().getItemMeta().index
|
|
137
153
|
);
|
|
138
154
|
focusItem?.setFocused();
|
|
155
|
+
focusItem?.scrollTo({ block: "nearest", inline: "nearest" });
|
|
139
156
|
},
|
|
140
157
|
},
|
|
141
158
|
},
|
|
@@ -22,11 +22,15 @@ export const selectionFeature: FeatureImplementation<
|
|
|
22
22
|
...defaultConfig,
|
|
23
23
|
}),
|
|
24
24
|
|
|
25
|
+
stateHandlerNames: {
|
|
26
|
+
selectedItems: "setSelectedItems",
|
|
27
|
+
},
|
|
28
|
+
|
|
25
29
|
createTreeInstance: (prev, instance) => ({
|
|
26
30
|
...prev,
|
|
27
31
|
|
|
28
32
|
setSelectedItems: (selectedItems) => {
|
|
29
|
-
instance.
|
|
33
|
+
instance.applySubStateUpdate("selectedItems", selectedItems);
|
|
30
34
|
},
|
|
31
35
|
|
|
32
36
|
// TODO memo
|
|
@@ -91,7 +95,8 @@ export const selectionFeature: FeatureImplementation<
|
|
|
91
95
|
|
|
92
96
|
getProps: () => ({
|
|
93
97
|
...prev.getProps(),
|
|
94
|
-
|
|
98
|
+
"aria-selected": item.isSelected() ? "true" : "false",
|
|
99
|
+
onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
|
|
95
100
|
if (e.shiftKey) {
|
|
96
101
|
item.selectUpTo(e.ctrlKey || e.metaKey);
|
|
97
102
|
} else if (e.ctrlKey || e.metaKey) {
|
|
@@ -101,7 +106,7 @@ export const selectionFeature: FeatureImplementation<
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
prev.getProps().onClick?.(e);
|
|
104
|
-
},
|
|
109
|
+
}),
|
|
105
110
|
}),
|
|
106
111
|
}),
|
|
107
112
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
2
|
import { SyncDataLoaderFeatureDef } from "./types";
|
|
3
3
|
import { MainFeatureDef } from "../main/types";
|
|
4
|
+
import { makeStateUpdater } from "../../utils";
|
|
4
5
|
|
|
5
6
|
export const syncDataLoaderFeature: FeatureImplementation<
|
|
6
7
|
any,
|
|
@@ -10,14 +11,28 @@ export const syncDataLoaderFeature: FeatureImplementation<
|
|
|
10
11
|
key: "sync-data-loader",
|
|
11
12
|
dependingFeatures: ["main"],
|
|
12
13
|
|
|
14
|
+
getInitialState: (initialState) => ({
|
|
15
|
+
loadingItems: [],
|
|
16
|
+
...initialState,
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
getDefaultConfig: (defaultConfig, tree) => ({
|
|
20
|
+
setLoadingItems: makeStateUpdater("loadingItems", tree),
|
|
21
|
+
...defaultConfig,
|
|
22
|
+
}),
|
|
23
|
+
|
|
24
|
+
stateHandlerNames: {
|
|
25
|
+
loadingItems: "setLoadingItems",
|
|
26
|
+
},
|
|
27
|
+
|
|
13
28
|
createTreeInstance: (prev, instance) => ({
|
|
14
29
|
...prev,
|
|
15
30
|
|
|
16
31
|
retrieveItemData: (itemId) =>
|
|
17
|
-
instance.getConfig().dataLoader
|
|
32
|
+
instance.getConfig().dataLoader!.getItem(itemId),
|
|
18
33
|
|
|
19
34
|
retrieveChildrenIds: (itemId) =>
|
|
20
|
-
instance.getConfig().dataLoader
|
|
35
|
+
instance.getConfig().dataLoader!.getChildren(itemId),
|
|
21
36
|
}),
|
|
22
37
|
|
|
23
38
|
createItemInstance: (prev) => ({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FeatureImplementation, ItemInstance } from "../../types/core";
|
|
2
|
-
import { ItemMeta, TreeFeatureDef } from "./types";
|
|
2
|
+
import { ItemMeta, TreeFeatureDef, TreeItemDataRef } from "./types";
|
|
3
3
|
import { makeStateUpdater, memo, poll } from "../../utils";
|
|
4
4
|
import { MainFeatureDef } from "../main/types";
|
|
5
5
|
import { HotkeysCoreFeatureDef } from "../hotkeys-core/types";
|
|
@@ -28,6 +28,11 @@ export const treeFeature: FeatureImplementation<
|
|
|
28
28
|
...defaultConfig,
|
|
29
29
|
}),
|
|
30
30
|
|
|
31
|
+
stateHandlerNames: {
|
|
32
|
+
expandedItems: "setExpandedItems",
|
|
33
|
+
focusedItem: "setFocusedItem",
|
|
34
|
+
},
|
|
35
|
+
|
|
31
36
|
createTreeInstance: (prev, instance) => ({
|
|
32
37
|
...prev,
|
|
33
38
|
|
|
@@ -45,7 +50,6 @@ export const treeFeature: FeatureImplementation<
|
|
|
45
50
|
getItemsMeta: () => {
|
|
46
51
|
const { rootItemId } = instance.getConfig();
|
|
47
52
|
const { expandedItems } = instance.getState();
|
|
48
|
-
// console.log("!", instance.getConfig());
|
|
49
53
|
const flatItems: ItemMeta[] = [];
|
|
50
54
|
|
|
51
55
|
const recursiveAdd = (
|
|
@@ -91,9 +95,10 @@ export const treeFeature: FeatureImplementation<
|
|
|
91
95
|
return;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
instance
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
instance.applySubStateUpdate("expandedItems", (expandedItems) => [
|
|
99
|
+
...expandedItems,
|
|
100
|
+
itemId,
|
|
101
|
+
]);
|
|
97
102
|
instance.rebuildTree();
|
|
98
103
|
},
|
|
99
104
|
|
|
@@ -102,11 +107,9 @@ export const treeFeature: FeatureImplementation<
|
|
|
102
107
|
return;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
instance
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
expandedItems.filter((id) => id !== itemId)
|
|
109
|
-
);
|
|
110
|
+
instance.applySubStateUpdate("expandedItems", (expandedItems) =>
|
|
111
|
+
expandedItems.filter((id) => id !== itemId)
|
|
112
|
+
);
|
|
110
113
|
instance.rebuildTree();
|
|
111
114
|
},
|
|
112
115
|
|
|
@@ -119,7 +122,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
119
122
|
},
|
|
120
123
|
|
|
121
124
|
focusItem: (itemId) => {
|
|
122
|
-
instance.
|
|
125
|
+
instance.applySubStateUpdate("focusedItem", itemId);
|
|
123
126
|
},
|
|
124
127
|
|
|
125
128
|
focusNextItem: () => {
|
|
@@ -134,7 +137,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
134
137
|
instance.focusItem(instance.getItems()[nextIndex].getId());
|
|
135
138
|
},
|
|
136
139
|
|
|
137
|
-
updateDomFocus: (
|
|
140
|
+
updateDomFocus: () => {
|
|
138
141
|
// Required because if the state is managed outside in react, the state only updated during next render
|
|
139
142
|
setTimeout(async () => {
|
|
140
143
|
const focusedItem = instance.getFocusedItem();
|
|
@@ -143,9 +146,6 @@ export const treeFeature: FeatureImplementation<
|
|
|
143
146
|
const focusedElement = focusedItem.getElement();
|
|
144
147
|
if (!focusedElement) return;
|
|
145
148
|
focusedElement.focus();
|
|
146
|
-
// if (scrollIntoView) {
|
|
147
|
-
// focusedElement.scrollIntoView();
|
|
148
|
-
// }
|
|
149
149
|
});
|
|
150
150
|
},
|
|
151
151
|
|
|
@@ -162,6 +162,11 @@ export const treeFeature: FeatureImplementation<
|
|
|
162
162
|
isLoading: () => {
|
|
163
163
|
throw new Error("No data-loader registered");
|
|
164
164
|
},
|
|
165
|
+
scrollTo: async (scrollIntoViewArg) => {
|
|
166
|
+
tree.getConfig().scrollToItem?.(item as any);
|
|
167
|
+
await poll(() => item.getElement() !== null, 20);
|
|
168
|
+
item.getElement()!.scrollIntoView(scrollIntoViewArg);
|
|
169
|
+
},
|
|
165
170
|
getId: () => item.getItemMeta().itemId,
|
|
166
171
|
getProps: () => {
|
|
167
172
|
const itemMeta = item.getItemMeta();
|
|
@@ -170,11 +175,11 @@ export const treeFeature: FeatureImplementation<
|
|
|
170
175
|
role: "treeitem",
|
|
171
176
|
"aria-setsize": itemMeta.setSize,
|
|
172
177
|
"aria-posinset": itemMeta.posInSet,
|
|
173
|
-
"aria-selected": false,
|
|
178
|
+
"aria-selected": "false",
|
|
174
179
|
"aria-label": item.getItemName(),
|
|
175
180
|
"aria-level": itemMeta.level,
|
|
176
181
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
177
|
-
onClick: (e) => {
|
|
182
|
+
onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
|
|
178
183
|
item.setFocused();
|
|
179
184
|
item.primaryAction();
|
|
180
185
|
|
|
@@ -191,7 +196,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
191
196
|
} else {
|
|
192
197
|
item.expand();
|
|
193
198
|
}
|
|
194
|
-
},
|
|
199
|
+
}),
|
|
195
200
|
};
|
|
196
201
|
},
|
|
197
202
|
expand: () => tree.expandItem(item.getItemMeta().itemId),
|
|
@@ -224,10 +229,8 @@ export const treeFeature: FeatureImplementation<
|
|
|
224
229
|
},
|
|
225
230
|
() => [item.getItemMeta()]
|
|
226
231
|
),
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
(item.getParent()?.getItemMeta().index ?? 0) -
|
|
230
|
-
1,
|
|
232
|
+
// TODO remove
|
|
233
|
+
getIndexInParent: () => item.getItemMeta().posInSet,
|
|
231
234
|
getChildren: () =>
|
|
232
235
|
tree
|
|
233
236
|
.retrieveChildrenIds(item.getItemMeta().itemId)
|
|
@@ -235,6 +238,23 @@ export const treeFeature: FeatureImplementation<
|
|
|
235
238
|
getTree: () => tree as any,
|
|
236
239
|
getItemAbove: () => tree.getItems()[item.getItemMeta().index - 1],
|
|
237
240
|
getItemBelow: () => tree.getItems()[item.getItemMeta().index + 1],
|
|
241
|
+
getMemoizedProp: (name, create, deps) => {
|
|
242
|
+
const data = item.getDataRef<TreeItemDataRef>();
|
|
243
|
+
const memoizedValue = data.current.memoizedValues?.[name];
|
|
244
|
+
if (
|
|
245
|
+
memoizedValue &&
|
|
246
|
+
(!deps ||
|
|
247
|
+
data.current.memoizedDeps?.[name]?.every((d, i) => d === deps![i]))
|
|
248
|
+
) {
|
|
249
|
+
return memoizedValue;
|
|
250
|
+
}
|
|
251
|
+
data.current.memoizedDeps ??= {};
|
|
252
|
+
data.current.memoizedValues ??= {};
|
|
253
|
+
const value = create();
|
|
254
|
+
data.current.memoizedDeps[name] = deps;
|
|
255
|
+
data.current.memoizedValues[name] = value;
|
|
256
|
+
return value;
|
|
257
|
+
},
|
|
238
258
|
}),
|
|
239
259
|
|
|
240
260
|
hotkeys: {
|
|
@@ -283,7 +303,7 @@ export const treeFeature: FeatureImplementation<
|
|
|
283
303
|
item.getItemMeta().level !== 0
|
|
284
304
|
) {
|
|
285
305
|
item.getParent()?.setFocused();
|
|
286
|
-
tree.updateDomFocus(
|
|
306
|
+
tree.updateDomFocus();
|
|
287
307
|
} else {
|
|
288
308
|
item.collapse();
|
|
289
309
|
}
|
|
@@ -9,6 +9,11 @@ export type ItemMeta = {
|
|
|
9
9
|
posInSet: number;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
export type TreeItemDataRef = {
|
|
13
|
+
memoizedValues: Record<string, any>;
|
|
14
|
+
memoizedDeps: Record<string, any[] | undefined>;
|
|
15
|
+
};
|
|
16
|
+
|
|
12
17
|
export type TreeFeatureDef<T> = {
|
|
13
18
|
state: {
|
|
14
19
|
expandedItems: string[];
|
|
@@ -36,8 +41,7 @@ export type TreeFeatureDef<T> = {
|
|
|
36
41
|
getFocusedItem: () => ItemInstance<any>;
|
|
37
42
|
focusNextItem: () => void;
|
|
38
43
|
focusPreviousItem: () => void;
|
|
39
|
-
|
|
40
|
-
updateDomFocus: (scrollIntoView?: boolean) => void;
|
|
44
|
+
updateDomFocus: () => void;
|
|
41
45
|
|
|
42
46
|
getContainerProps: () => Record<string, any>;
|
|
43
47
|
};
|
|
@@ -59,6 +63,10 @@ export type TreeFeatureDef<T> = {
|
|
|
59
63
|
getTree: () => TreeInstance<T>;
|
|
60
64
|
getItemAbove: () => ItemInstance<T> | null;
|
|
61
65
|
getItemBelow: () => ItemInstance<T> | null;
|
|
66
|
+
getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
|
|
67
|
+
scrollTo: (
|
|
68
|
+
scrollIntoViewArg?: boolean | ScrollIntoViewOptions
|
|
69
|
+
) => Promise<void>;
|
|
62
70
|
};
|
|
63
71
|
hotkeys:
|
|
64
72
|
| "focusNextItem"
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./types/core";
|
|
2
2
|
export * from "./core/create-tree";
|
|
3
|
+
|
|
3
4
|
export * from "./features/tree/types";
|
|
4
5
|
export * from "./features/main/types";
|
|
5
6
|
export * from "./features/drag-and-drop/types";
|
|
@@ -20,4 +21,6 @@ export * from "./features/search/feature";
|
|
|
20
21
|
export * from "./features/renaming/feature";
|
|
21
22
|
export * from "./features/expand-all/feature";
|
|
22
23
|
|
|
23
|
-
export * from "./
|
|
24
|
+
export * from "./utilities/create-on-drop-handler";
|
|
25
|
+
export * from "./utilities/insert-items-at-target";
|
|
26
|
+
export * from "./utilities/remove-items-from-parents";
|
package/src/types/core.ts
CHANGED
|
@@ -130,6 +130,10 @@ export type FeatureImplementation<
|
|
|
130
130
|
key?: string;
|
|
131
131
|
dependingFeatures?: string[];
|
|
132
132
|
|
|
133
|
+
stateHandlerNames?: Partial<
|
|
134
|
+
Record<keyof MergedFeatures<F>["state"], keyof MergedFeatures<F>["config"]>
|
|
135
|
+
>;
|
|
136
|
+
|
|
133
137
|
getInitialState?: (
|
|
134
138
|
initialState: Partial<MergedFeatures<F>["state"]>,
|
|
135
139
|
tree: MergedFeatures<F>["treeInstance"]
|
|
@@ -174,9 +178,5 @@ export type FeatureImplementation<
|
|
|
174
178
|
tree: MergedFeatures<F>["treeInstance"]
|
|
175
179
|
) => void;
|
|
176
180
|
|
|
177
|
-
setState?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
178
|
-
onConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
179
|
-
onStateOrConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
180
|
-
|
|
181
181
|
hotkeys?: HotkeysConfig<T, D>;
|
|
182
182
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ItemInstance } from "../types/core";
|
|
2
|
+
import { DropTarget } from "../features/drag-and-drop/types";
|
|
3
|
+
import { removeItemsFromParents } from "./remove-items-from-parents";
|
|
4
|
+
import { insertItemsAtTarget } from "./insert-items-at-target";
|
|
5
|
+
|
|
6
|
+
export const createOnDropHandler =
|
|
7
|
+
<T>(
|
|
8
|
+
onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void
|
|
9
|
+
) =>
|
|
10
|
+
(items: ItemInstance<T>[], target: DropTarget<T>) => {
|
|
11
|
+
const itemIds = items.map((item) => item.getId());
|
|
12
|
+
removeItemsFromParents(items, onChangeChildren);
|
|
13
|
+
insertItemsAtTarget(itemIds, target, onChangeChildren);
|
|
14
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ItemInstance } from "../types/core";
|
|
2
|
+
import { DropTarget } from "../features/drag-and-drop/types";
|
|
3
|
+
|
|
4
|
+
export const insertItemsAtTarget = <T>(
|
|
5
|
+
itemIds: string[],
|
|
6
|
+
target: DropTarget<T>,
|
|
7
|
+
onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void
|
|
8
|
+
) => {
|
|
9
|
+
// add moved items to new common parent, if dropped onto parent
|
|
10
|
+
if (target.childIndex === null) {
|
|
11
|
+
onChangeChildren(target.item, [
|
|
12
|
+
...target.item.getChildren().map((item) => item.getId()),
|
|
13
|
+
...itemIds,
|
|
14
|
+
]);
|
|
15
|
+
// TODO items[0].getTree().rebuildTree();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// add moved items to new common parent, if dropped between siblings
|
|
20
|
+
const oldChildren = target.item.getChildren();
|
|
21
|
+
const newChildren = [
|
|
22
|
+
...oldChildren.slice(0, target.insertionIndex).map((item) => item.getId()),
|
|
23
|
+
...itemIds,
|
|
24
|
+
...oldChildren.slice(target.insertionIndex).map((item) => item.getId()),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
onChangeChildren(target.item, newChildren);
|
|
28
|
+
|
|
29
|
+
target.item.getTree().rebuildTree();
|
|
30
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ItemInstance } from "../types/core";
|
|
2
|
+
|
|
3
|
+
export const removeItemsFromParents = <T>(
|
|
4
|
+
movedItems: ItemInstance<T>[],
|
|
5
|
+
onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void
|
|
6
|
+
) => {
|
|
7
|
+
// TODO bulk sibling changes together
|
|
8
|
+
for (const item of movedItems) {
|
|
9
|
+
const siblings = item.getParent()?.getChildren();
|
|
10
|
+
if (siblings) {
|
|
11
|
+
onChangeChildren(
|
|
12
|
+
item.getParent(),
|
|
13
|
+
siblings
|
|
14
|
+
.filter((sibling) => sibling.getId() !== item.getId())
|
|
15
|
+
.map((i) => i.getId())
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
movedItems[0].getTree().rebuildTree();
|
|
21
|
+
};
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DropTarget } from "./features/drag-and-drop/types";
|
|
1
|
+
import { TreeState, Updater } from "./types/core";
|
|
3
2
|
|
|
4
3
|
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
5
4
|
|
|
@@ -53,73 +52,6 @@ export function makeStateUpdater<K extends keyof TreeState<any>>(
|
|
|
53
52
|
};
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
export const scrollIntoView = (element: Element | undefined | null) => {
|
|
57
|
-
if (!element) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if ((element as any).scrollIntoViewIfNeeded) {
|
|
62
|
-
(element as any).scrollIntoViewIfNeeded();
|
|
63
|
-
} else {
|
|
64
|
-
const boundingBox = element.getBoundingClientRect();
|
|
65
|
-
const isElementInViewport =
|
|
66
|
-
boundingBox.top >= 0 &&
|
|
67
|
-
boundingBox.left >= 0 &&
|
|
68
|
-
boundingBox.bottom <=
|
|
69
|
-
(window.innerHeight || document.documentElement.clientHeight) &&
|
|
70
|
-
boundingBox.right <=
|
|
71
|
-
(window.innerWidth || document.documentElement.clientWidth);
|
|
72
|
-
if (!isElementInViewport) {
|
|
73
|
-
element.scrollIntoView();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const performItemsMove = <T>(
|
|
79
|
-
items: ItemInstance<T>[],
|
|
80
|
-
target: DropTarget<T>,
|
|
81
|
-
onChangeChildren: (
|
|
82
|
-
item: ItemInstance<T>,
|
|
83
|
-
newChildren: ItemInstance<T>[]
|
|
84
|
-
) => void
|
|
85
|
-
) => {
|
|
86
|
-
const numberOfDragItemsBeforeTarget = !target.childIndex
|
|
87
|
-
? 0
|
|
88
|
-
: target.item
|
|
89
|
-
.getChildren()
|
|
90
|
-
.slice(0, target.childIndex)
|
|
91
|
-
.filter((child) => items.some((item) => item.getId() === child.getId()))
|
|
92
|
-
.length;
|
|
93
|
-
|
|
94
|
-
// TODO bulk sibling changes together
|
|
95
|
-
for (const item of items) {
|
|
96
|
-
const siblings = item.getParent()?.getChildren();
|
|
97
|
-
if (siblings) {
|
|
98
|
-
onChangeChildren(
|
|
99
|
-
item.getParent(),
|
|
100
|
-
siblings.filter((sibling) => sibling.getId() !== item.getId())
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (target.childIndex === null) {
|
|
106
|
-
onChangeChildren(target.item, [...target.item.getChildren(), ...items]);
|
|
107
|
-
items[0].getTree().rebuildTree();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const oldChildren = target.item.getChildren();
|
|
112
|
-
const newChildren = [
|
|
113
|
-
...oldChildren.slice(0, target.childIndex - numberOfDragItemsBeforeTarget),
|
|
114
|
-
...items,
|
|
115
|
-
...oldChildren.slice(target.childIndex - numberOfDragItemsBeforeTarget),
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
onChangeChildren(target.item, newChildren);
|
|
119
|
-
|
|
120
|
-
items[0].getTree().rebuildTree();
|
|
121
|
-
};
|
|
122
|
-
|
|
123
55
|
export const poll = (fn: () => boolean, interval = 100, timeout = 1000) =>
|
|
124
56
|
new Promise<void>((resolve) => {
|
|
125
57
|
let clear: ReturnType<typeof setTimeout>;
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { DataAdapterConfig } from "./types";
|
|
2
|
-
interface NestedDataAdapterProps<T> {
|
|
3
|
-
rootItem: T;
|
|
4
|
-
getItemId: (item: T) => string;
|
|
5
|
-
getChildren: (item: T) => T[] | undefined;
|
|
6
|
-
changeChildren?: (item: T, children: T[]) => void;
|
|
7
|
-
}
|
|
8
|
-
export declare const nestedDataAdapter: <T = any>(props: NestedDataAdapterProps<T>) => DataAdapterConfig<T>;
|
|
9
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.nestedDataAdapter = void 0;
|
|
4
|
-
const utils_1 = require("../utils");
|
|
5
|
-
const createItemMap = (props, item, map = {}) => {
|
|
6
|
-
var _a;
|
|
7
|
-
map[props.getItemId(item)] = item;
|
|
8
|
-
(_a = props.getChildren(item)) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
|
|
9
|
-
createItemMap(props, child, map);
|
|
10
|
-
});
|
|
11
|
-
return map;
|
|
12
|
-
};
|
|
13
|
-
const nestedDataAdapter = (props) => {
|
|
14
|
-
const itemMap = createItemMap(props, props.rootItem);
|
|
15
|
-
return {
|
|
16
|
-
rootItemId: props.getItemId(props.rootItem),
|
|
17
|
-
dataLoader: {
|
|
18
|
-
getItem: (itemId) => itemMap[itemId],
|
|
19
|
-
getChildren: (itemId) => { var _a, _b; return (_b = (_a = props.getChildren(itemMap[itemId])) === null || _a === void 0 ? void 0 : _a.map(props.getItemId)) !== null && _b !== void 0 ? _b : []; },
|
|
20
|
-
},
|
|
21
|
-
onDrop: (items, target) => {
|
|
22
|
-
if (!props.changeChildren) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
(0, utils_1.performItemsMove)(items, target, (item, newChildren) => {
|
|
26
|
-
var _a;
|
|
27
|
-
(_a = props.changeChildren) === null || _a === void 0 ? void 0 : _a.call(props, item.getItemData(), newChildren.map((child) => child.getItemData()));
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
exports.nestedDataAdapter = nestedDataAdapter;
|