@react-aria/dnd 3.9.2 → 3.10.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/dist/DragManager.main.js +79 -12
- package/dist/DragManager.main.js.map +1 -1
- package/dist/DragManager.mjs +79 -12
- package/dist/DragManager.module.js +79 -12
- package/dist/DragManager.module.js.map +1 -1
- package/dist/DropTargetKeyboardNavigation.main.js +201 -0
- package/dist/DropTargetKeyboardNavigation.main.js.map +1 -0
- package/dist/DropTargetKeyboardNavigation.mjs +196 -0
- package/dist/DropTargetKeyboardNavigation.module.js +196 -0
- package/dist/DropTargetKeyboardNavigation.module.js.map +1 -0
- package/dist/ListDropTargetDelegate.main.js.map +1 -1
- package/dist/ListDropTargetDelegate.module.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useDrop.main.js.map +1 -1
- package/dist/useDrop.module.js.map +1 -1
- package/dist/useDropIndicator.main.js +15 -7
- package/dist/useDropIndicator.main.js.map +1 -1
- package/dist/useDropIndicator.mjs +15 -7
- package/dist/useDropIndicator.module.js +15 -7
- package/dist/useDropIndicator.module.js.map +1 -1
- package/dist/useDroppableCollection.main.js +33 -103
- package/dist/useDroppableCollection.main.js.map +1 -1
- package/dist/useDroppableCollection.mjs +33 -103
- package/dist/useDroppableCollection.module.js +33 -103
- package/dist/useDroppableCollection.module.js.map +1 -1
- package/dist/useDroppableItem.main.js +4 -2
- package/dist/useDroppableItem.main.js.map +1 -1
- package/dist/useDroppableItem.mjs +4 -2
- package/dist/useDroppableItem.module.js +4 -2
- package/dist/useDroppableItem.module.js.map +1 -1
- package/package.json +17 -12
- package/src/.DS_Store +0 -0
- package/src/DragManager.ts +66 -17
- package/src/DropTargetKeyboardNavigation.ts +273 -0
- package/src/ListDropTargetDelegate.ts +1 -1
- package/src/useDrop.ts +0 -1
- package/src/useDropIndicator.ts +17 -11
- package/src/useDroppableCollection.ts +41 -134
- package/src/useDroppableItem.ts +7 -4
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {Collection, DropTarget, Key, KeyboardDelegate, Node} from '@react-types/shared';
|
|
2
|
+
import {getChildNodes} from '@react-stately/collections';
|
|
3
|
+
|
|
4
|
+
export function navigate(
|
|
5
|
+
keyboardDelegate: KeyboardDelegate,
|
|
6
|
+
collection: Collection<Node<unknown>>,
|
|
7
|
+
target: DropTarget | null | undefined,
|
|
8
|
+
direction: 'left' | 'right' | 'up' | 'down',
|
|
9
|
+
rtl = false,
|
|
10
|
+
wrap = false
|
|
11
|
+
): DropTarget | null {
|
|
12
|
+
switch (direction) {
|
|
13
|
+
case 'left':
|
|
14
|
+
return rtl
|
|
15
|
+
? nextDropTarget(keyboardDelegate, collection, target, wrap, 'left')
|
|
16
|
+
: previousDropTarget(keyboardDelegate, collection, target, wrap, 'left');
|
|
17
|
+
case 'right':
|
|
18
|
+
return rtl
|
|
19
|
+
? previousDropTarget(keyboardDelegate, collection, target, wrap, 'right')
|
|
20
|
+
: nextDropTarget(keyboardDelegate, collection, target, wrap, 'right');
|
|
21
|
+
case 'up':
|
|
22
|
+
return previousDropTarget(keyboardDelegate, collection, target, wrap);
|
|
23
|
+
case 'down':
|
|
24
|
+
return nextDropTarget(keyboardDelegate, collection, target, wrap);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nextDropTarget(
|
|
29
|
+
keyboardDelegate: KeyboardDelegate,
|
|
30
|
+
collection: Collection<Node<unknown>>,
|
|
31
|
+
target: DropTarget | null | undefined,
|
|
32
|
+
wrap = false,
|
|
33
|
+
horizontal: 'left' | 'right' | null = null
|
|
34
|
+
): DropTarget | null {
|
|
35
|
+
if (!target) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'root'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (target.type === 'root') {
|
|
42
|
+
let nextKey = keyboardDelegate.getFirstKey?.() ?? null;
|
|
43
|
+
if (nextKey != null) {
|
|
44
|
+
return {
|
|
45
|
+
type: 'item',
|
|
46
|
+
key: nextKey,
|
|
47
|
+
dropPosition: 'before'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (target.type === 'item') {
|
|
55
|
+
let nextKey: Key | null | undefined = null;
|
|
56
|
+
if (horizontal) {
|
|
57
|
+
nextKey = horizontal === 'right' ? keyboardDelegate.getKeyRightOf?.(target.key) : keyboardDelegate.getKeyLeftOf?.(target.key);
|
|
58
|
+
} else {
|
|
59
|
+
nextKey = keyboardDelegate.getKeyBelow?.(target.key);
|
|
60
|
+
}
|
|
61
|
+
let nextCollectionKey = collection.getKeyAfter(target.key);
|
|
62
|
+
|
|
63
|
+
// If the keyboard delegate did not move to the next key in the collection,
|
|
64
|
+
// jump to that key with the same drop position. Otherwise, try the other
|
|
65
|
+
// drop positions on the current key first.
|
|
66
|
+
if (nextKey != null && nextKey !== nextCollectionKey) {
|
|
67
|
+
return {
|
|
68
|
+
type: 'item',
|
|
69
|
+
key: nextKey,
|
|
70
|
+
dropPosition: target.dropPosition
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
switch (target.dropPosition) {
|
|
75
|
+
case 'before': {
|
|
76
|
+
return {
|
|
77
|
+
type: 'item',
|
|
78
|
+
key: target.key,
|
|
79
|
+
dropPosition: 'on'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
case 'on': {
|
|
83
|
+
// If there are nested items, traverse to them prior to the "after" position of this target.
|
|
84
|
+
// If the next key is on the same level, then its "before" position is equivalent to this item's "after" position.
|
|
85
|
+
let targetNode = collection.getItem(target.key);
|
|
86
|
+
let nextNode = nextKey != null ? collection.getItem(nextKey) : null;
|
|
87
|
+
if (targetNode && nextNode && nextNode.level >= targetNode.level) {
|
|
88
|
+
return {
|
|
89
|
+
type: 'item',
|
|
90
|
+
key: nextNode.key,
|
|
91
|
+
dropPosition: 'before'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
type: 'item',
|
|
97
|
+
key: target.key,
|
|
98
|
+
dropPosition: 'after'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
case 'after': {
|
|
102
|
+
// If this is the last sibling in a level, traverse to the parent.
|
|
103
|
+
let targetNode = collection.getItem(target.key);
|
|
104
|
+
if (targetNode && targetNode.nextKey == null && targetNode.parentKey != null) {
|
|
105
|
+
// If the parent item has an item after it, use the "before" position.
|
|
106
|
+
let parentNode = collection.getItem(targetNode.parentKey);
|
|
107
|
+
if (parentNode?.nextKey != null) {
|
|
108
|
+
return {
|
|
109
|
+
type: 'item',
|
|
110
|
+
key: parentNode.nextKey,
|
|
111
|
+
dropPosition: 'before'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (parentNode) {
|
|
116
|
+
return {
|
|
117
|
+
type: 'item',
|
|
118
|
+
key: parentNode.key,
|
|
119
|
+
dropPosition: 'after'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (targetNode?.nextKey != null) {
|
|
125
|
+
return {
|
|
126
|
+
type: 'item',
|
|
127
|
+
key: targetNode.nextKey,
|
|
128
|
+
dropPosition: 'on'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (wrap) {
|
|
136
|
+
return {
|
|
137
|
+
type: 'root'
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function previousDropTarget(
|
|
145
|
+
keyboardDelegate: KeyboardDelegate,
|
|
146
|
+
collection: Collection<Node<unknown>>,
|
|
147
|
+
target: DropTarget | null | undefined,
|
|
148
|
+
wrap = false,
|
|
149
|
+
horizontal: 'left' | 'right' | null = null
|
|
150
|
+
): DropTarget | null {
|
|
151
|
+
// Start after the last root-level item.
|
|
152
|
+
if (!target || (wrap && target.type === 'root')) {
|
|
153
|
+
// Keyboard delegate gets the deepest item but we want the shallowest.
|
|
154
|
+
let prevKey: Key | null = null;
|
|
155
|
+
let lastKey = keyboardDelegate.getLastKey?.();
|
|
156
|
+
while (lastKey != null) {
|
|
157
|
+
prevKey = lastKey;
|
|
158
|
+
let node = collection.getItem(lastKey);
|
|
159
|
+
lastKey = node?.parentKey;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (prevKey != null) {
|
|
163
|
+
return {
|
|
164
|
+
type: 'item',
|
|
165
|
+
key: prevKey,
|
|
166
|
+
dropPosition: 'after'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (target.type === 'item') {
|
|
174
|
+
let prevKey: Key | null | undefined = null;
|
|
175
|
+
if (horizontal) {
|
|
176
|
+
prevKey = horizontal === 'left' ? keyboardDelegate.getKeyLeftOf?.(target.key) : keyboardDelegate.getKeyRightOf?.(target.key);
|
|
177
|
+
} else {
|
|
178
|
+
prevKey = keyboardDelegate.getKeyAbove?.(target.key);
|
|
179
|
+
}
|
|
180
|
+
let prevCollectionKey = collection.getKeyBefore(target.key);
|
|
181
|
+
|
|
182
|
+
// If the keyboard delegate did not move to the next key in the collection,
|
|
183
|
+
// jump to that key with the same drop position. Otherwise, try the other
|
|
184
|
+
// drop positions on the current key first.
|
|
185
|
+
if (prevKey != null && prevKey !== prevCollectionKey) {
|
|
186
|
+
return {
|
|
187
|
+
type: 'item',
|
|
188
|
+
key: prevKey,
|
|
189
|
+
dropPosition: target.dropPosition
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
switch (target.dropPosition) {
|
|
194
|
+
case 'before': {
|
|
195
|
+
// Move after the last child of the previous item.
|
|
196
|
+
let targetNode = collection.getItem(target.key);
|
|
197
|
+
if (targetNode && targetNode.prevKey != null) {
|
|
198
|
+
let lastChild = getLastChild(collection, targetNode.prevKey);
|
|
199
|
+
if (lastChild) {
|
|
200
|
+
return lastChild;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (prevKey != null) {
|
|
205
|
+
return {
|
|
206
|
+
type: 'item',
|
|
207
|
+
key: prevKey,
|
|
208
|
+
dropPosition: 'on'
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
type: 'root'
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
case 'on': {
|
|
217
|
+
return {
|
|
218
|
+
type: 'item',
|
|
219
|
+
key: target.key,
|
|
220
|
+
dropPosition: 'before'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
case 'after': {
|
|
224
|
+
// Move after the last child of this item.
|
|
225
|
+
let lastChild = getLastChild(collection, target.key);
|
|
226
|
+
if (lastChild) {
|
|
227
|
+
return lastChild;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
type: 'item',
|
|
232
|
+
key: target.key,
|
|
233
|
+
dropPosition: 'on'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (target.type !== 'root') {
|
|
240
|
+
return {
|
|
241
|
+
type: 'root'
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getLastChild(collection: Collection<Node<unknown>>, key: Key): DropTarget | null {
|
|
249
|
+
// getChildNodes still returns child tree items even when the item is collapsed.
|
|
250
|
+
// Checking if the next item has a greater level is a silly way to determine if the item is expanded.
|
|
251
|
+
let targetNode = collection.getItem(key);
|
|
252
|
+
let nextKey = collection.getKeyAfter(key);
|
|
253
|
+
let nextNode = nextKey != null ? collection.getItem(nextKey) : null;
|
|
254
|
+
if (targetNode && nextNode && nextNode.level > targetNode.level) {
|
|
255
|
+
let children = getChildNodes(targetNode, collection);
|
|
256
|
+
let lastChild: Node<unknown> | null = null;
|
|
257
|
+
for (let child of children) {
|
|
258
|
+
if (child.type === 'item') {
|
|
259
|
+
lastChild = child;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (lastChild) {
|
|
264
|
+
return {
|
|
265
|
+
type: 'item',
|
|
266
|
+
key: lastChild.key,
|
|
267
|
+
dropPosition: 'after'
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
@@ -33,7 +33,7 @@ export class ListDropTargetDelegate implements DropTargetDelegate {
|
|
|
33
33
|
private ref: RefObject<HTMLElement | null>;
|
|
34
34
|
private layout: 'stack' | 'grid';
|
|
35
35
|
private orientation: Orientation;
|
|
36
|
-
|
|
36
|
+
protected direction: Direction;
|
|
37
37
|
|
|
38
38
|
constructor(collection: Iterable<Node<unknown>>, ref: RefObject<HTMLElement | null>, options?: ListDropTargetDelegateOptions) {
|
|
39
39
|
this.collection = collection;
|
package/src/useDrop.ts
CHANGED
|
@@ -36,7 +36,6 @@ export interface DropOptions {
|
|
|
36
36
|
/**
|
|
37
37
|
* Handler that is called after a valid drag is held over the drop target for a period of time.
|
|
38
38
|
* This typically opens the item so that the user can drop within it.
|
|
39
|
-
* @private
|
|
40
39
|
*/
|
|
41
40
|
onDropActivate?: (e: DropActivateEvent) => void,
|
|
42
41
|
/** Handler that is called when a valid drag exits the drop target. */
|
package/src/useDropIndicator.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import * as DragManager from './DragManager';
|
|
14
14
|
import {DroppableCollectionState} from '@react-stately/dnd';
|
|
15
|
-
import {DropTarget, Key, RefObject} from '@react-types/shared';
|
|
15
|
+
import {DropTarget, FocusableElement, Key, RefObject} from '@react-types/shared';
|
|
16
16
|
import {getDroppableCollectionId} from './utils';
|
|
17
17
|
import {HTMLAttributes} from 'react';
|
|
18
18
|
// @ts-ignore
|
|
@@ -23,7 +23,9 @@ import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
|
23
23
|
|
|
24
24
|
export interface DropIndicatorProps {
|
|
25
25
|
/** The drop target that the drop indicator represents. */
|
|
26
|
-
target: DropTarget
|
|
26
|
+
target: DropTarget,
|
|
27
|
+
/** The ref to the activate button. */
|
|
28
|
+
activateButtonRef?: RefObject<FocusableElement | null>
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface DropIndicatorAria {
|
|
@@ -69,28 +71,32 @@ export function useDropIndicator(props: DropIndicatorProps, state: DroppableColl
|
|
|
69
71
|
} else {
|
|
70
72
|
let before: Key | null | undefined;
|
|
71
73
|
let after: Key | null | undefined;
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
+
if (target.dropPosition === 'before') {
|
|
75
|
+
let prevKey = collection.getItem(target.key)?.prevKey;
|
|
76
|
+
let prevNode = prevKey != null ? collection.getItem(prevKey) : null;
|
|
77
|
+
before = prevNode?.type === 'item' ? prevNode.key : null;
|
|
74
78
|
} else {
|
|
75
|
-
before = target.
|
|
79
|
+
before = target.key;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
82
|
+
if (target.dropPosition === 'after') {
|
|
83
|
+
let nextKey = collection.getItem(target.key)?.nextKey;
|
|
84
|
+
let nextNode = nextKey != null ? collection.getItem(nextKey) : null;
|
|
85
|
+
after = nextNode?.type === 'item' ? nextNode.key : null;
|
|
80
86
|
} else {
|
|
81
|
-
after = target.
|
|
87
|
+
after = target.key;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
if (before && after) {
|
|
90
|
+
if (before != null && after != null) {
|
|
85
91
|
label = stringFormatter.format('insertBetween', {
|
|
86
92
|
beforeItemText: getText(before),
|
|
87
93
|
afterItemText: getText(after)
|
|
88
94
|
});
|
|
89
|
-
} else if (before) {
|
|
95
|
+
} else if (before != null) {
|
|
90
96
|
label = stringFormatter.format('insertAfter', {
|
|
91
97
|
itemText: getText(before)
|
|
92
98
|
});
|
|
93
|
-
} else if (after) {
|
|
99
|
+
} else if (after != null) {
|
|
94
100
|
label = stringFormatter.format('insertBefore', {
|
|
95
101
|
itemText: getText(after)
|
|
96
102
|
});
|
|
@@ -37,6 +37,7 @@ import * as DragManager from './DragManager';
|
|
|
37
37
|
import {DroppableCollectionState} from '@react-stately/dnd';
|
|
38
38
|
import {HTMLAttributes, useCallback, useEffect, useRef} from 'react';
|
|
39
39
|
import {mergeProps, useId, useLayoutEffect} from '@react-aria/utils';
|
|
40
|
+
import {navigate} from './DropTargetKeyboardNavigation';
|
|
40
41
|
import {setInteractionModality} from '@react-aria/interactions';
|
|
41
42
|
import {useAutoScroll} from './useAutoScroll';
|
|
42
43
|
import {useDrop} from './useDrop';
|
|
@@ -46,7 +47,9 @@ export interface DroppableCollectionOptions extends DroppableCollectionProps {
|
|
|
46
47
|
/** A delegate object that implements behavior for keyboard focus movement. */
|
|
47
48
|
keyboardDelegate: KeyboardDelegate,
|
|
48
49
|
/** A delegate object that provides drop targets for pointer coordinates within the collection. */
|
|
49
|
-
dropTargetDelegate: DropTargetDelegate
|
|
50
|
+
dropTargetDelegate: DropTargetDelegate,
|
|
51
|
+
/** A custom keyboard event handler for drop targets. */
|
|
52
|
+
onKeyDown?: (e: KeyboardEvent) => void
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export interface DroppableCollectionResult {
|
|
@@ -64,9 +67,6 @@ interface DroppingState {
|
|
|
64
67
|
timeout: ReturnType<typeof setTimeout> | undefined
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
const DROP_POSITIONS: DropPosition[] = ['before', 'on', 'after'];
|
|
68
|
-
const DROP_POSITIONS_RTL: DropPosition[] = ['after', 'on', 'before'];
|
|
69
|
-
|
|
70
70
|
/**
|
|
71
71
|
* Handles drop interactions for a collection component, with support for traditional mouse and touch
|
|
72
72
|
* based drag and drop, in addition to full parity for keyboard and screen reader users.
|
|
@@ -92,6 +92,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
92
92
|
onRootDrop,
|
|
93
93
|
onItemDrop,
|
|
94
94
|
onReorder,
|
|
95
|
+
onMove,
|
|
95
96
|
acceptedDragTypes = 'all',
|
|
96
97
|
shouldAcceptItemDrop
|
|
97
98
|
} = localState.props;
|
|
@@ -137,6 +138,10 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
137
138
|
await onItemDrop({items: filteredItems, dropOperation, isInternal, target});
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
if (onMove && isInternal) {
|
|
142
|
+
await onMove({keys: draggingKeys, dropOperation, target});
|
|
143
|
+
}
|
|
144
|
+
|
|
140
145
|
if (target.dropPosition !== 'on') {
|
|
141
146
|
if (!isInternal && onInsert) {
|
|
142
147
|
await onInsert({items: filteredItems, dropOperation, target});
|
|
@@ -201,7 +206,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
201
206
|
autoScroll.stop();
|
|
202
207
|
},
|
|
203
208
|
onDropActivate(e) {
|
|
204
|
-
if (state.target?.type === 'item' &&
|
|
209
|
+
if (state.target?.type === 'item' && typeof props.onDropActivate === 'function') {
|
|
205
210
|
props.onDropActivate({
|
|
206
211
|
type: 'dropactivate',
|
|
207
212
|
x: e.x, // todo
|
|
@@ -258,18 +263,25 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
258
263
|
// inserted item. If selection is disabled, then also show the focus ring so there
|
|
259
264
|
// is some indication that items were added.
|
|
260
265
|
if (state.selectionManager.focusedKey === prevFocusedKey) {
|
|
261
|
-
let first = newKeys.keys().next().value;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
let first: Key | null | undefined = newKeys.keys().next().value;
|
|
267
|
+
if (first != null) {
|
|
268
|
+
let item = state.collection.getItem(first);
|
|
269
|
+
|
|
270
|
+
// If this is a cell, focus the parent row.
|
|
271
|
+
// eslint-disable-next-line max-depth
|
|
272
|
+
if (item?.type === 'cell') {
|
|
273
|
+
first = item.parentKey;
|
|
274
|
+
}
|
|
268
275
|
|
|
269
|
-
|
|
276
|
+
// eslint-disable-next-line max-depth
|
|
277
|
+
if (first != null) {
|
|
278
|
+
state.selectionManager.setFocusedKey(first);
|
|
279
|
+
}
|
|
270
280
|
|
|
271
|
-
|
|
272
|
-
|
|
281
|
+
// eslint-disable-next-line max-depth
|
|
282
|
+
if (state.selectionManager.selectionMode === 'none') {
|
|
283
|
+
setInteractionModality('keyboard');
|
|
284
|
+
}
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
} else if (
|
|
@@ -335,7 +347,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
335
347
|
}, 50);
|
|
336
348
|
}, [localState, defaultOnDrop, ref, updateFocusAfterDrop]);
|
|
337
349
|
|
|
338
|
-
|
|
350
|
+
|
|
339
351
|
useEffect(() => {
|
|
340
352
|
return () => {
|
|
341
353
|
if (droppingState.current) {
|
|
@@ -357,118 +369,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
357
369
|
return;
|
|
358
370
|
}
|
|
359
371
|
|
|
360
|
-
let getNextTarget = (target: DropTarget | null | undefined, wrap = true,
|
|
361
|
-
|
|
362
|
-
return {
|
|
363
|
-
type: 'root'
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
let {keyboardDelegate} = localState.props;
|
|
368
|
-
let nextKey: Key | null | undefined;
|
|
369
|
-
if (target?.type === 'item') {
|
|
370
|
-
nextKey = horizontal ? keyboardDelegate.getKeyRightOf?.(target.key) : keyboardDelegate.getKeyBelow?.(target.key);
|
|
371
|
-
} else {
|
|
372
|
-
nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getLastKey?.() : keyboardDelegate.getFirstKey?.();
|
|
373
|
-
}
|
|
374
|
-
let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
|
|
375
|
-
let dropPosition: DropPosition = dropPositions[0];
|
|
376
|
-
|
|
377
|
-
if (target.type === 'item') {
|
|
378
|
-
// If the the keyboard delegate returned the next key in the collection,
|
|
379
|
-
// first try the other positions in the current key. Otherwise (e.g. in a grid layout),
|
|
380
|
-
// jump to the same drop position in the new key.
|
|
381
|
-
let nextCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyBefore(target.key) : localState.state.collection.getKeyAfter(target.key);
|
|
382
|
-
if (nextKey == null || nextKey === nextCollectionKey) {
|
|
383
|
-
let positionIndex = dropPositions.indexOf(target.dropPosition);
|
|
384
|
-
let nextDropPosition = dropPositions[positionIndex + 1];
|
|
385
|
-
if (positionIndex < dropPositions.length - 1 && !(nextDropPosition === dropPositions[2] && nextKey != null)) {
|
|
386
|
-
return {
|
|
387
|
-
type: 'item',
|
|
388
|
-
key: target.key,
|
|
389
|
-
dropPosition: nextDropPosition
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// If the last drop position was 'after', then 'before' on the next key is equivalent.
|
|
394
|
-
// Switch to 'on' instead.
|
|
395
|
-
if (target.dropPosition === dropPositions[2]) {
|
|
396
|
-
dropPosition = 'on';
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
dropPosition = target.dropPosition;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (nextKey == null) {
|
|
404
|
-
if (wrap) {
|
|
405
|
-
return {
|
|
406
|
-
type: 'root'
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
type: 'item',
|
|
415
|
-
key: nextKey,
|
|
416
|
-
dropPosition
|
|
417
|
-
};
|
|
372
|
+
let getNextTarget = (target: DropTarget | null | undefined, wrap = true, key: 'left' | 'right' | 'up' | 'down' = 'down') => {
|
|
373
|
+
return navigate(localState.props.keyboardDelegate, localState.state.collection, target, key, direction === 'rtl', wrap);
|
|
418
374
|
};
|
|
419
375
|
|
|
420
|
-
let getPreviousTarget = (target: DropTarget | null | undefined, wrap = true
|
|
421
|
-
|
|
422
|
-
let nextKey: Key | null | undefined;
|
|
423
|
-
if (target?.type === 'item') {
|
|
424
|
-
nextKey = horizontal ? keyboardDelegate.getKeyLeftOf?.(target.key) : keyboardDelegate.getKeyAbove?.(target.key);
|
|
425
|
-
} else {
|
|
426
|
-
nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getFirstKey?.() : keyboardDelegate.getLastKey?.();
|
|
427
|
-
}
|
|
428
|
-
let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
|
|
429
|
-
let dropPosition: DropPosition = !target || target.type === 'root' ? dropPositions[2] : 'on';
|
|
430
|
-
|
|
431
|
-
if (target?.type === 'item') {
|
|
432
|
-
// If the the keyboard delegate returned the previous key in the collection,
|
|
433
|
-
// first try the other positions in the current key. Otherwise (e.g. in a grid layout),
|
|
434
|
-
// jump to the same drop position in the new key.
|
|
435
|
-
let prevCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyAfter(target.key) : localState.state.collection.getKeyBefore(target.key);
|
|
436
|
-
if (nextKey == null || nextKey === prevCollectionKey) {
|
|
437
|
-
let positionIndex = dropPositions.indexOf(target.dropPosition);
|
|
438
|
-
let nextDropPosition = dropPositions[positionIndex - 1];
|
|
439
|
-
if (positionIndex > 0 && nextDropPosition !== dropPositions[2]) {
|
|
440
|
-
return {
|
|
441
|
-
type: 'item',
|
|
442
|
-
key: target.key,
|
|
443
|
-
dropPosition: nextDropPosition
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// If the last drop position was 'before', then 'after' on the previous key is equivalent.
|
|
448
|
-
// Switch to 'on' instead.
|
|
449
|
-
if (target.dropPosition === dropPositions[0]) {
|
|
450
|
-
dropPosition = 'on';
|
|
451
|
-
}
|
|
452
|
-
} else {
|
|
453
|
-
dropPosition = target.dropPosition;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (nextKey == null) {
|
|
458
|
-
if (wrap) {
|
|
459
|
-
return {
|
|
460
|
-
type: 'root'
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return {
|
|
468
|
-
type: 'item',
|
|
469
|
-
key: nextKey,
|
|
470
|
-
dropPosition
|
|
471
|
-
};
|
|
376
|
+
let getPreviousTarget = (target: DropTarget | null | undefined, wrap = true) => {
|
|
377
|
+
return getNextTarget(target, wrap, 'up');
|
|
472
378
|
};
|
|
473
379
|
|
|
474
380
|
let nextValidTarget = (
|
|
@@ -581,17 +487,17 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
581
487
|
onDropTargetEnter(target) {
|
|
582
488
|
localState.state.setTarget(target);
|
|
583
489
|
},
|
|
584
|
-
onDropActivate(e) {
|
|
490
|
+
onDropActivate(e, target) {
|
|
585
491
|
if (
|
|
586
|
-
|
|
587
|
-
|
|
492
|
+
target?.type === 'item' &&
|
|
493
|
+
target?.dropPosition === 'on' &&
|
|
588
494
|
typeof localState.props.onDropActivate === 'function'
|
|
589
495
|
) {
|
|
590
496
|
localState.props.onDropActivate({
|
|
591
497
|
type: 'dropactivate',
|
|
592
498
|
x: e.x, // todo
|
|
593
499
|
y: e.y,
|
|
594
|
-
target
|
|
500
|
+
target
|
|
595
501
|
});
|
|
596
502
|
}
|
|
597
503
|
},
|
|
@@ -607,28 +513,28 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
607
513
|
switch (e.key) {
|
|
608
514
|
case 'ArrowDown': {
|
|
609
515
|
if (keyboardDelegate.getKeyBelow) {
|
|
610
|
-
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, getNextTarget);
|
|
516
|
+
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'down'));
|
|
611
517
|
localState.state.setTarget(target);
|
|
612
518
|
}
|
|
613
519
|
break;
|
|
614
520
|
}
|
|
615
521
|
case 'ArrowUp': {
|
|
616
522
|
if (keyboardDelegate.getKeyAbove) {
|
|
617
|
-
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations,
|
|
523
|
+
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'up'));
|
|
618
524
|
localState.state.setTarget(target);
|
|
619
525
|
}
|
|
620
526
|
break;
|
|
621
527
|
}
|
|
622
528
|
case 'ArrowLeft': {
|
|
623
529
|
if (keyboardDelegate.getKeyLeftOf) {
|
|
624
|
-
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) =>
|
|
530
|
+
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'left'));
|
|
625
531
|
localState.state.setTarget(target);
|
|
626
532
|
}
|
|
627
533
|
break;
|
|
628
534
|
}
|
|
629
535
|
case 'ArrowRight': {
|
|
630
536
|
if (keyboardDelegate.getKeyRightOf) {
|
|
631
|
-
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap,
|
|
537
|
+
let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'right'));
|
|
632
538
|
localState.state.setTarget(target);
|
|
633
539
|
}
|
|
634
540
|
break;
|
|
@@ -741,6 +647,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
741
647
|
break;
|
|
742
648
|
}
|
|
743
649
|
}
|
|
650
|
+
localState.props.onKeyDown?.(e);
|
|
744
651
|
}
|
|
745
652
|
});
|
|
746
653
|
}, [localState, ref, onDrop, direction]);
|
package/src/useDroppableItem.ts
CHANGED
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
import * as DragManager from './DragManager';
|
|
14
14
|
import {DroppableCollectionState} from '@react-stately/dnd';
|
|
15
|
-
import {DropTarget, RefObject} from '@react-types/shared';
|
|
15
|
+
import {DropTarget, FocusableElement, RefObject} from '@react-types/shared';
|
|
16
16
|
import {getDroppableCollectionRef, getTypes, globalDndState, isInternalDropOperation} from './utils';
|
|
17
17
|
import {HTMLAttributes, useEffect} from 'react';
|
|
18
18
|
import {useVirtualDrop} from './useVirtualDrop';
|
|
19
19
|
|
|
20
20
|
export interface DroppableItemOptions {
|
|
21
21
|
/** The drop target represented by the item. */
|
|
22
|
-
target: DropTarget
|
|
22
|
+
target: DropTarget,
|
|
23
|
+
/** The ref to the activate button. */
|
|
24
|
+
activateButtonRef?: RefObject<FocusableElement | null>
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface DroppableItemResult {
|
|
@@ -50,10 +52,11 @@ export function useDroppableItem(options: DroppableItemOptions, state: Droppable
|
|
|
50
52
|
isInternal,
|
|
51
53
|
draggingKeys
|
|
52
54
|
});
|
|
53
|
-
}
|
|
55
|
+
},
|
|
56
|
+
activateButtonRef: options.activateButtonRef
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
|
-
}, [ref, options.target, state, droppableCollectionRef]);
|
|
59
|
+
}, [ref, options.target, state, droppableCollectionRef, options.activateButtonRef]);
|
|
57
60
|
|
|
58
61
|
let dragSession = DragManager.useDragSession();
|
|
59
62
|
let {draggingKeys} = globalDndState;
|