@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
|
@@ -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,
|
|
@@ -8,13 +8,18 @@ import {
|
|
|
8
8
|
} from "./utils";
|
|
9
9
|
import { makeStateUpdater } from "../../utils";
|
|
10
10
|
|
|
11
|
+
const defaultCanDropForeignDragObject = () => false;
|
|
11
12
|
export const dragAndDropFeature: FeatureImplementation = {
|
|
12
13
|
key: "drag-and-drop",
|
|
13
14
|
deps: ["selection"],
|
|
14
15
|
|
|
15
16
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
16
17
|
canDrop: (_, target) => target.item.isFolder(),
|
|
17
|
-
canDropForeignDragObject:
|
|
18
|
+
canDropForeignDragObject: defaultCanDropForeignDragObject,
|
|
19
|
+
canDragForeignDragObjectOver:
|
|
20
|
+
defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject
|
|
21
|
+
? (dataTransfer) => dataTransfer.effectAllowed !== "none"
|
|
22
|
+
: () => false,
|
|
18
23
|
setDndState: makeStateUpdater("dnd", tree),
|
|
19
24
|
canReorder: true,
|
|
20
25
|
...defaultConfig,
|
|
@@ -24,6 +29,19 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
24
29
|
dnd: "setDndState",
|
|
25
30
|
},
|
|
26
31
|
|
|
32
|
+
onTreeMount: (tree) => {
|
|
33
|
+
const listener = () => {
|
|
34
|
+
tree.applySubStateUpdate("dnd", null);
|
|
35
|
+
};
|
|
36
|
+
tree.getDataRef<DndDataRef>().current.windowDragEndListener = listener;
|
|
37
|
+
window.addEventListener("dragend", listener);
|
|
38
|
+
},
|
|
39
|
+
onTreeUnmount: (tree) => {
|
|
40
|
+
const { windowDragEndListener } = tree.getDataRef<DndDataRef>().current;
|
|
41
|
+
if (!windowDragEndListener) return;
|
|
42
|
+
window.removeEventListener("dragend", windowDragEndListener);
|
|
43
|
+
},
|
|
44
|
+
|
|
27
45
|
treeInstance: {
|
|
28
46
|
getDragTarget: ({ tree }) => {
|
|
29
47
|
return tree.getState().dnd?.dragTarget ?? null;
|
|
@@ -83,10 +101,37 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
83
101
|
: { display: "none" };
|
|
84
102
|
},
|
|
85
103
|
|
|
86
|
-
getContainerProps: ({ prev }, treeLabel) => {
|
|
104
|
+
getContainerProps: ({ prev, tree }, treeLabel) => {
|
|
87
105
|
const prevProps = prev?.(treeLabel);
|
|
88
106
|
return {
|
|
89
107
|
...prevProps,
|
|
108
|
+
|
|
109
|
+
onDragOver: (e: DragEvent) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
onDrop: async (e: DragEvent) => {
|
|
114
|
+
// TODO merge implementation with itemInstance.onDrop
|
|
115
|
+
const dataRef = tree.getDataRef<DndDataRef>();
|
|
116
|
+
const target: DragTarget<any> = { item: tree.getRootItem() };
|
|
117
|
+
|
|
118
|
+
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
const config = tree.getConfig();
|
|
124
|
+
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
125
|
+
|
|
126
|
+
dataRef.current.lastDragCode = undefined;
|
|
127
|
+
|
|
128
|
+
if (draggedItems) {
|
|
129
|
+
await config.onDrop?.(draggedItems, target);
|
|
130
|
+
} else if (e.dataTransfer) {
|
|
131
|
+
await config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
90
135
|
style: {
|
|
91
136
|
...prevProps?.style,
|
|
92
137
|
position: "relative",
|
|
@@ -101,6 +146,8 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
101
146
|
|
|
102
147
|
draggable: true,
|
|
103
148
|
|
|
149
|
+
onDragEnter: (e: DragEvent) => e.preventDefault(),
|
|
150
|
+
|
|
104
151
|
onDragStart: (e: DragEvent) => {
|
|
105
152
|
const selectedItems = tree.getSelectedItems();
|
|
106
153
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
@@ -115,9 +162,18 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
115
162
|
return;
|
|
116
163
|
}
|
|
117
164
|
|
|
118
|
-
if (config.
|
|
119
|
-
const {
|
|
120
|
-
e.dataTransfer?.
|
|
165
|
+
if (config.setDragImage) {
|
|
166
|
+
const { imgElement, xOffset, yOffset } = config.setDragImage(items);
|
|
167
|
+
e.dataTransfer?.setDragImage(imgElement, xOffset ?? 0, yOffset ?? 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (config.createForeignDragObject && e.dataTransfer) {
|
|
171
|
+
const { format, data, dropEffect, effectAllowed } =
|
|
172
|
+
config.createForeignDragObject(items);
|
|
173
|
+
e.dataTransfer.setData(format, data);
|
|
174
|
+
|
|
175
|
+
if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
|
|
176
|
+
if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
|
|
121
177
|
}
|
|
122
178
|
|
|
123
179
|
tree.applySubStateUpdate("dnd", {
|
|
@@ -127,6 +183,7 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
127
183
|
},
|
|
128
184
|
|
|
129
185
|
onDragOver: (e: DragEvent) => {
|
|
186
|
+
e.stopPropagation(); // don't bubble up to container dragover
|
|
130
187
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
131
188
|
const nextDragCode = getDragCode(e, item, tree);
|
|
132
189
|
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
@@ -136,6 +193,7 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
136
193
|
return;
|
|
137
194
|
}
|
|
138
195
|
dataRef.current.lastDragCode = nextDragCode;
|
|
196
|
+
dataRef.current.lastDragEnter = Date.now();
|
|
139
197
|
|
|
140
198
|
const target = getDragTarget(e, item, tree);
|
|
141
199
|
|
|
@@ -144,7 +202,7 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
144
202
|
(!e.dataTransfer ||
|
|
145
203
|
!tree
|
|
146
204
|
.getConfig()
|
|
147
|
-
.
|
|
205
|
+
.canDragForeignDragObjectOver?.(e.dataTransfer, target))
|
|
148
206
|
) {
|
|
149
207
|
dataRef.current.lastAllowDrop = false;
|
|
150
208
|
return;
|
|
@@ -165,27 +223,41 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
165
223
|
},
|
|
166
224
|
|
|
167
225
|
onDragLeave: () => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
const dataRef = tree.getDataRef<DndDataRef>();
|
|
228
|
+
if ((dataRef.current.lastDragEnter ?? 0) + 100 >= Date.now()) return;
|
|
229
|
+
dataRef.current.lastDragCode = "no-drag";
|
|
230
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
231
|
+
...state,
|
|
232
|
+
draggingOverItem: undefined,
|
|
233
|
+
dragTarget: undefined,
|
|
234
|
+
}));
|
|
235
|
+
}, 100);
|
|
175
236
|
},
|
|
176
237
|
|
|
177
238
|
onDragEnd: (e: DragEvent) => {
|
|
239
|
+
const { onCompleteForeignDrop, canDragForeignDragObjectOver } =
|
|
240
|
+
tree.getConfig();
|
|
178
241
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
179
|
-
tree.applySubStateUpdate("dnd", null);
|
|
180
242
|
|
|
181
243
|
if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
|
|
182
244
|
return;
|
|
183
245
|
}
|
|
184
246
|
|
|
185
|
-
tree
|
|
247
|
+
const target = getDragTarget(e, item, tree);
|
|
248
|
+
if (
|
|
249
|
+
canDragForeignDragObjectOver &&
|
|
250
|
+
e.dataTransfer &&
|
|
251
|
+
!canDragForeignDragObjectOver(e.dataTransfer, target)
|
|
252
|
+
) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
onCompleteForeignDrop?.(draggedItems);
|
|
186
257
|
},
|
|
187
258
|
|
|
188
259
|
onDrop: async (e: DragEvent) => {
|
|
260
|
+
e.stopPropagation();
|
|
189
261
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
190
262
|
const target = getDragTarget(e, item, tree);
|
|
191
263
|
|
|
@@ -198,7 +270,6 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
198
270
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
199
271
|
|
|
200
272
|
dataRef.current.lastDragCode = undefined;
|
|
201
|
-
tree.applySubStateUpdate("dnd", null);
|
|
202
273
|
|
|
203
274
|
if (draggedItems) {
|
|
204
275
|
await config.onDrop?.(draggedItems, target);
|
|
@@ -3,6 +3,8 @@ import { ItemInstance, SetStateFn } from "../../types/core";
|
|
|
3
3
|
export interface DndDataRef {
|
|
4
4
|
lastDragCode?: string;
|
|
5
5
|
lastAllowDrop?: boolean;
|
|
6
|
+
lastDragEnter?: number;
|
|
7
|
+
windowDragEndListener?: () => void;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export interface DndState<T> {
|
|
@@ -57,11 +59,31 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
57
59
|
createForeignDragObject?: (items: ItemInstance<T>[]) => {
|
|
58
60
|
format: string;
|
|
59
61
|
data: any;
|
|
62
|
+
dropEffect?: DataTransfer["dropEffect"];
|
|
63
|
+
effectAllowed?: DataTransfer["effectAllowed"];
|
|
60
64
|
};
|
|
65
|
+
setDragImage?: (items: ItemInstance<T>[]) => {
|
|
66
|
+
imgElement: Element;
|
|
67
|
+
xOffset?: number;
|
|
68
|
+
yOffset?: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Checks if a foreign drag object can be dropped on a target, validating that an actual drop can commence based on
|
|
72
|
+
* the data in the DataTransfer object. */
|
|
61
73
|
canDropForeignDragObject?: (
|
|
62
74
|
dataTransfer: DataTransfer,
|
|
63
75
|
target: DragTarget<T>,
|
|
64
76
|
) => boolean;
|
|
77
|
+
|
|
78
|
+
/** Checks if a droppable visualization should be displayed when dragging a foreign object over a target. Since this
|
|
79
|
+
* is executed on a dragover event, `dataTransfer.getData()` is not available, so `dataTransfer.effectAllowed` or
|
|
80
|
+
* `dataTransfer.types` should be used instead. Before actually completing the drag, @{link canDropForeignDragObject}
|
|
81
|
+
* will be called by HT before applying the drop. */
|
|
82
|
+
canDragForeignDragObjectOver?: (
|
|
83
|
+
dataTransfer: DataTransfer,
|
|
84
|
+
target: DragTarget<T>,
|
|
85
|
+
) => boolean;
|
|
86
|
+
|
|
65
87
|
onDrop?: (
|
|
66
88
|
items: ItemInstance<T>[],
|
|
67
89
|
target: DragTarget<T>,
|
|
@@ -191,7 +191,14 @@ export const keyboardDragAndDropFeature: FeatureImplementation = {
|
|
|
191
191
|
preventDefault: true,
|
|
192
192
|
isEnabled: (tree) => !tree.getState().dnd,
|
|
193
193
|
handler: (_, tree) => {
|
|
194
|
-
tree.
|
|
194
|
+
const selectedItems = tree.getSelectedItems();
|
|
195
|
+
const focusedItem = tree.getFocusedItem();
|
|
196
|
+
|
|
197
|
+
tree.startKeyboardDrag(
|
|
198
|
+
selectedItems.includes(focusedItem)
|
|
199
|
+
? selectedItems
|
|
200
|
+
: selectedItems.concat(focusedItem),
|
|
201
|
+
);
|
|
195
202
|
},
|
|
196
203
|
},
|
|
197
204
|
dragUp: {
|
|
@@ -49,6 +49,37 @@ describe("core-feature/keyboard-drag-and-drop", () => {
|
|
|
49
49
|
tree.expect.substate("assistiveDndState", AssistiveDndState.Started);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
it("starts dragging only focused item", () => {
|
|
53
|
+
tree.item("x3").setFocused();
|
|
54
|
+
tree.do.hotkey("startDrag");
|
|
55
|
+
tree.expect.substate("dnd", {
|
|
56
|
+
draggedItems: [tree.item("x3")],
|
|
57
|
+
dragTarget: {
|
|
58
|
+
childIndex: 3,
|
|
59
|
+
dragLineIndex: 19,
|
|
60
|
+
dragLineLevel: 0,
|
|
61
|
+
insertionIndex: 2,
|
|
62
|
+
item: tree.item("x"),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("starts dragging both selected and focused item", () => {
|
|
68
|
+
tree.do.selectMultiple("x111", "x112");
|
|
69
|
+
tree.item("x3").setFocused();
|
|
70
|
+
tree.do.hotkey("startDrag");
|
|
71
|
+
tree.expect.substate("dnd", {
|
|
72
|
+
draggedItems: [tree.item("x111"), tree.item("x112"), tree.item("x3")],
|
|
73
|
+
dragTarget: {
|
|
74
|
+
childIndex: 3,
|
|
75
|
+
dragLineIndex: 19,
|
|
76
|
+
dragLineLevel: 0,
|
|
77
|
+
insertionIndex: 2,
|
|
78
|
+
item: tree.item("x"),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
52
83
|
it("moves down 1", () => {
|
|
53
84
|
tree.do.selectMultiple("x111", "x112");
|
|
54
85
|
tree.do.hotkey("startDrag");
|
|
@@ -355,13 +386,13 @@ describe("core-feature/keyboard-drag-and-drop", () => {
|
|
|
355
386
|
|
|
356
387
|
it("doesnt go below end of tree", () => {
|
|
357
388
|
const lastState = {
|
|
358
|
-
draggedItems: [tree.item("x111")],
|
|
389
|
+
draggedItems: [tree.item("x111"), tree.item("x3")],
|
|
359
390
|
dragTarget: {
|
|
360
391
|
item: tree.item("x"),
|
|
361
392
|
childIndex: 4,
|
|
362
393
|
dragLineIndex: 20,
|
|
363
394
|
dragLineLevel: 0,
|
|
364
|
-
insertionIndex:
|
|
395
|
+
insertionIndex: 3,
|
|
365
396
|
},
|
|
366
397
|
};
|
|
367
398
|
|
|
@@ -378,7 +409,7 @@ describe("core-feature/keyboard-drag-and-drop", () => {
|
|
|
378
409
|
|
|
379
410
|
it("doesnt go above top of tree", () => {
|
|
380
411
|
const firstState = {
|
|
381
|
-
draggedItems: [tree.item("x111")],
|
|
412
|
+
draggedItems: [tree.item("x111"), tree.item("x1")],
|
|
382
413
|
dragTarget: {
|
|
383
414
|
item: tree.item("x"),
|
|
384
415
|
childIndex: 0,
|
|
@@ -2,9 +2,13 @@ import { FeatureImplementation } from "../../types/core";
|
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
3
|
import { throwError } from "../../utilities/errors";
|
|
4
4
|
|
|
5
|
+
const undefErrorMessage = "sync dataLoader returned undefined";
|
|
5
6
|
const promiseErrorMessage = "sync dataLoader returned promise";
|
|
6
7
|
const unpromise = <T>(data: T | Promise<T>): T => {
|
|
7
|
-
if (!data
|
|
8
|
+
if (!data) {
|
|
9
|
+
throw throwError(undefErrorMessage);
|
|
10
|
+
}
|
|
11
|
+
if (typeof data === "object" && "then" in data) {
|
|
8
12
|
throw throwError(promiseErrorMessage);
|
|
9
13
|
}
|
|
10
14
|
return data;
|
|
@@ -76,12 +76,18 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
76
76
|
},
|
|
77
77
|
|
|
78
78
|
getFocusedItem: ({ tree }) => {
|
|
79
|
+
const focusedItemId = tree.getState().focusedItem;
|
|
79
80
|
return (
|
|
80
|
-
tree.getItemInstance(
|
|
81
|
+
(focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null) ??
|
|
81
82
|
tree.getItems()[0]
|
|
82
83
|
);
|
|
83
84
|
},
|
|
84
85
|
|
|
86
|
+
getRootItem: ({ tree }) => {
|
|
87
|
+
const { rootItemId } = tree.getConfig();
|
|
88
|
+
return tree.getItemInstance(rootItemId);
|
|
89
|
+
},
|
|
90
|
+
|
|
85
91
|
focusNextItem: ({ tree }) => {
|
|
86
92
|
const focused = tree.getFocusedItem().getItemMeta();
|
|
87
93
|
if (!focused) return;
|
|
@@ -134,10 +140,10 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
134
140
|
ref: item.registerElement,
|
|
135
141
|
role: "treeitem",
|
|
136
142
|
"aria-setsize": itemMeta.setSize,
|
|
137
|
-
"aria-posinset": itemMeta.posInSet,
|
|
143
|
+
"aria-posinset": itemMeta.posInSet + 1,
|
|
138
144
|
"aria-selected": "false",
|
|
139
145
|
"aria-label": item.getItemName(),
|
|
140
|
-
"aria-level": itemMeta.level,
|
|
146
|
+
"aria-level": itemMeta.level + 1,
|
|
141
147
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
142
148
|
onClick: (e: MouseEvent) => {
|
|
143
149
|
item.setFocused();
|
|
@@ -235,8 +235,8 @@ describe("core-feature/selections", () => {
|
|
|
235
235
|
it("generates item props for random item", () => {
|
|
236
236
|
expect(tree.instance.getItemInstance("x2").getProps()).toEqual({
|
|
237
237
|
"aria-label": "x2",
|
|
238
|
-
"aria-level":
|
|
239
|
-
"aria-posinset":
|
|
238
|
+
"aria-level": 1,
|
|
239
|
+
"aria-posinset": 2,
|
|
240
240
|
"aria-selected": "false",
|
|
241
241
|
"aria-setsize": 4,
|
|
242
242
|
onClick: expect.any(Function),
|
|
@@ -249,8 +249,8 @@ describe("core-feature/selections", () => {
|
|
|
249
249
|
it("generates item props for focused", () => {
|
|
250
250
|
expect(tree.instance.getItemInstance("x1").getProps()).toEqual({
|
|
251
251
|
"aria-label": "x1",
|
|
252
|
-
"aria-level":
|
|
253
|
-
"aria-posinset":
|
|
252
|
+
"aria-level": 1,
|
|
253
|
+
"aria-posinset": 1,
|
|
254
254
|
"aria-selected": "false",
|
|
255
255
|
"aria-setsize": 4,
|
|
256
256
|
onClick: expect.any(Function),
|
|
@@ -472,4 +472,14 @@ describe("core-feature/selections", () => {
|
|
|
472
472
|
});
|
|
473
473
|
});
|
|
474
474
|
});
|
|
475
|
+
|
|
476
|
+
describe("empty rootItemId", () => {
|
|
477
|
+
factory.with({ rootItemId: "" }).forSuits((tree) => {
|
|
478
|
+
describe("focused item", () => {
|
|
479
|
+
it("returns correct initial focused item", () => {
|
|
480
|
+
expect(tree.instance.getFocusedItem().getId()).toBe("1");
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
});
|
|
475
485
|
});
|
|
@@ -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;
|
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";
|
|
@@ -105,12 +105,14 @@ export class TestTreeDo<T> {
|
|
|
105
105
|
dragEnd(itemId: string, event?: DragEvent) {
|
|
106
106
|
const e = event ?? TestTree.dragEvent();
|
|
107
107
|
this.itemProps(itemId).onDragEnd(e);
|
|
108
|
+
window.dispatchEvent(new CustomEvent("dragend"));
|
|
108
109
|
return e;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
async drop(itemId: string, event?: DragEvent) {
|
|
112
113
|
const e = event ?? TestTree.dragEvent();
|
|
113
114
|
await this.itemProps(itemId).onDrop(e);
|
|
115
|
+
window.dispatchEvent(new CustomEvent("dragend"));
|
|
114
116
|
return e;
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -137,6 +137,7 @@ export class TestTree<T = string> {
|
|
|
137
137
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
138
138
|
this.instance;
|
|
139
139
|
await this.resolveAsyncVisibleItems();
|
|
140
|
+
this.instance.registerElement({ getBoundingClientRect: () => null } as any);
|
|
140
141
|
return this;
|
|
141
142
|
}
|
|
142
143
|
|
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>
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "../../tsconfig.json",
|
|
3
3
|
"include": ["./src/**/*"],
|
|
4
|
-
"exclude": ["./src/**/*.spec.tsx", "./src/**/*.spec.ts", "./src/**/*.stories.tsx"]
|
|
5
|
-
"compilerOptions": {
|
|
6
|
-
"outDir": "lib/esm"
|
|
7
|
-
}
|
|
4
|
+
"exclude": ["./src/**/*.spec.tsx", "./src/**/*.spec.ts", "./src/**/*.stories.tsx"]
|
|
8
5
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildProxiedInstance = void 0;
|
|
4
|
-
const errors_1 = require("../utilities/errors");
|
|
5
|
-
const noop = () => { };
|
|
6
|
-
const findPrevInstanceMethod = (features, instanceType, methodKey, featureSearchIndex) => {
|
|
7
|
-
var _a;
|
|
8
|
-
for (let i = featureSearchIndex; i >= 0; i--) {
|
|
9
|
-
const feature = features[i];
|
|
10
|
-
const itemInstanceMethod = (_a = feature[instanceType]) === null || _a === void 0 ? void 0 : _a[methodKey];
|
|
11
|
-
if (itemInstanceMethod) {
|
|
12
|
-
return i;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return null;
|
|
16
|
-
};
|
|
17
|
-
const invokeInstanceMethod = (features, instanceType, opts, methodKey, featureIndex, args) => {
|
|
18
|
-
var _a;
|
|
19
|
-
const prevIndex = findPrevInstanceMethod(features, instanceType, methodKey, featureIndex - 1);
|
|
20
|
-
const itemInstanceMethod = (_a = features[featureIndex][instanceType]) === null || _a === void 0 ? void 0 : _a[methodKey];
|
|
21
|
-
return itemInstanceMethod(Object.assign(Object.assign({}, opts), { prev: prevIndex !== null
|
|
22
|
-
? (...newArgs) => invokeInstanceMethod(features, instanceType, opts, methodKey, prevIndex, newArgs)
|
|
23
|
-
: null }), ...args);
|
|
24
|
-
};
|
|
25
|
-
const buildProxiedInstance = (features, instanceType, buildOpts) => {
|
|
26
|
-
// demo with prototypes: https://jsfiddle.net/bgenc58r/
|
|
27
|
-
const opts = {};
|
|
28
|
-
const item = new Proxy({}, {
|
|
29
|
-
has(target, key) {
|
|
30
|
-
if (typeof key === "symbol") {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
if (key === "toJSON") {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const hasInstanceMethod = findPrevInstanceMethod(features, instanceType, key, features.length - 1);
|
|
37
|
-
return Boolean(hasInstanceMethod);
|
|
38
|
-
},
|
|
39
|
-
get(target, key) {
|
|
40
|
-
if (typeof key === "symbol") {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
if (key === "toJSON") {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
return (...args) => {
|
|
47
|
-
const featureIndex = findPrevInstanceMethod(features, instanceType, key, features.length - 1);
|
|
48
|
-
if (featureIndex === null) {
|
|
49
|
-
throw (0, errors_1.throwError)(`feature missing for method ${key}`);
|
|
50
|
-
}
|
|
51
|
-
return invokeInstanceMethod(features, instanceType, opts, key, featureIndex, args);
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
Object.assign(opts, buildOpts(item));
|
|
56
|
-
return [item, noop];
|
|
57
|
-
};
|
|
58
|
-
exports.buildProxiedInstance = buildProxiedInstance;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-continue,no-labels,no-extra-label */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.buildStaticInstance = void 0;
|
|
5
|
-
const buildStaticInstance = (features, instanceType, buildOpts) => {
|
|
6
|
-
const instance = {};
|
|
7
|
-
const finalize = () => {
|
|
8
|
-
const opts = buildOpts(instance);
|
|
9
|
-
featureLoop: for (let i = 0; i < features.length; i++) {
|
|
10
|
-
// Loop goes in forward order, each features overwrite previous ones and wraps those in a prev() fn
|
|
11
|
-
const definition = features[i][instanceType];
|
|
12
|
-
if (!definition)
|
|
13
|
-
continue featureLoop;
|
|
14
|
-
methodLoop: for (const [key, method] of Object.entries(definition)) {
|
|
15
|
-
if (!method)
|
|
16
|
-
continue methodLoop;
|
|
17
|
-
const prev = instance[key];
|
|
18
|
-
instance[key] = (...args) => {
|
|
19
|
-
return method(Object.assign(Object.assign({}, opts), { prev }), ...args);
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
return [instance, finalize];
|
|
25
|
-
};
|
|
26
|
-
exports.buildStaticInstance = buildStaticInstance;
|