@react-aria/dnd 3.9.3 → 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 +23 -98
- package/dist/useDroppableCollection.main.js.map +1 -1
- package/dist/useDroppableCollection.mjs +23 -98
- package/dist/useDroppableCollection.module.js +23 -98
- 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 +23 -123
- package/src/useDroppableItem.ts +7 -4
package/src/DragManager.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {DragEndEvent, DragItem, DropActivateEvent, DropEnterEvent, DropEvent, Dr
|
|
|
16
16
|
import {getDragModality, getTypes} from './utils';
|
|
17
17
|
import {isVirtualClick, isVirtualPointerEvent} from '@react-aria/utils';
|
|
18
18
|
import type {LocalizedStringFormatter} from '@internationalized/string';
|
|
19
|
-
import {useEffect, useState} from 'react';
|
|
19
|
+
import {RefObject, useEffect, useState} from 'react';
|
|
20
20
|
|
|
21
21
|
let dropTargets = new Map<Element, DropTarget>();
|
|
22
22
|
let dropItems = new Map<Element, DroppableItem>();
|
|
@@ -32,7 +32,8 @@ interface DropTarget {
|
|
|
32
32
|
onDropTargetEnter?: (target: DroppableCollectionTarget | null) => void,
|
|
33
33
|
onDropActivate?: (e: DropActivateEvent, target: DroppableCollectionTarget | null) => void,
|
|
34
34
|
onDrop?: (e: DropEvent, target: DroppableCollectionTarget | null) => void,
|
|
35
|
-
onKeyDown?: (e: KeyboardEvent, dragTarget: DragTarget) => void
|
|
35
|
+
onKeyDown?: (e: KeyboardEvent, dragTarget: DragTarget) => void,
|
|
36
|
+
activateButtonRef?: RefObject<FocusableElement | null>
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function registerDropTarget(target: DropTarget) {
|
|
@@ -47,7 +48,8 @@ export function registerDropTarget(target: DropTarget) {
|
|
|
47
48
|
interface DroppableItem {
|
|
48
49
|
element: FocusableElement,
|
|
49
50
|
target: DroppableCollectionTarget,
|
|
50
|
-
getDropOperation?: (types: Set<string>, allowedOperations: DropOperation[]) => DropOperation
|
|
51
|
+
getDropOperation?: (types: Set<string>, allowedOperations: DropOperation[]) => DropOperation,
|
|
52
|
+
activateButtonRef?: RefObject<FocusableElement | null>
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function registerDropItem(item: DroppableItem) {
|
|
@@ -241,15 +243,26 @@ class DragSession {
|
|
|
241
243
|
this.cancelEvent(e);
|
|
242
244
|
|
|
243
245
|
if (e.key === 'Enter') {
|
|
244
|
-
if (e.altKey) {
|
|
245
|
-
this.activate();
|
|
246
|
+
if (e.altKey || this.getCurrentActivateButton()?.contains(e.target as Node)) {
|
|
247
|
+
this.activate(this.currentDropTarget, this.currentDropItem);
|
|
246
248
|
} else {
|
|
247
249
|
this.drop();
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
getCurrentActivateButton(): FocusableElement | null {
|
|
255
|
+
return this.currentDropItem?.activateButtonRef?.current ?? this.currentDropTarget?.activateButtonRef?.current ?? null;
|
|
256
|
+
}
|
|
257
|
+
|
|
252
258
|
onFocus(e: FocusEvent) {
|
|
259
|
+
let activateButton = this.getCurrentActivateButton();
|
|
260
|
+
if (e.target === activateButton) {
|
|
261
|
+
// TODO: canceling this breaks the focus ring. Revisit when we support tabbing.
|
|
262
|
+
this.cancelEvent(e);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
253
266
|
// Prevent focus events, except to the original drag target.
|
|
254
267
|
if (e.target !== this.dragTarget.element) {
|
|
255
268
|
this.cancelEvent(e);
|
|
@@ -265,6 +278,9 @@ class DragSession {
|
|
|
265
278
|
this.validDropTargets.find(target => target.element.contains(e.target as HTMLElement));
|
|
266
279
|
|
|
267
280
|
if (!dropTarget) {
|
|
281
|
+
// if (e.target === activateButton) {
|
|
282
|
+
// activateButton.focus();
|
|
283
|
+
// }
|
|
268
284
|
if (this.currentDropTarget) {
|
|
269
285
|
this.currentDropTarget.element.focus();
|
|
270
286
|
} else {
|
|
@@ -274,10 +290,18 @@ class DragSession {
|
|
|
274
290
|
}
|
|
275
291
|
|
|
276
292
|
let item = dropItems.get(e.target as HTMLElement);
|
|
277
|
-
|
|
293
|
+
if (dropTarget) {
|
|
294
|
+
this.setCurrentDropTarget(dropTarget, item);
|
|
295
|
+
}
|
|
278
296
|
}
|
|
279
297
|
|
|
280
298
|
onBlur(e: FocusEvent) {
|
|
299
|
+
let activateButton = this.getCurrentActivateButton();
|
|
300
|
+
if (e.relatedTarget === activateButton) {
|
|
301
|
+
this.cancelEvent(e);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
281
305
|
if (e.target !== this.dragTarget.element) {
|
|
282
306
|
this.cancelEvent(e);
|
|
283
307
|
}
|
|
@@ -296,14 +320,21 @@ class DragSession {
|
|
|
296
320
|
onClick(e: MouseEvent) {
|
|
297
321
|
this.cancelEvent(e);
|
|
298
322
|
if (isVirtualClick(e) || this.isVirtualClick) {
|
|
323
|
+
let dropElements = dropItems.values();
|
|
324
|
+
let item = [...dropElements].find(item => item.element === e.target as HTMLElement || item.activateButtonRef?.current?.contains(e.target as HTMLElement));
|
|
325
|
+
let dropTarget = this.validDropTargets.find(target => target.element.contains(e.target as HTMLElement));
|
|
326
|
+
let activateButton = item?.activateButtonRef?.current ?? dropTarget?.activateButtonRef?.current;
|
|
327
|
+
if (activateButton?.contains(e.target as HTMLElement) && dropTarget) {
|
|
328
|
+
this.activate(dropTarget, item);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
299
332
|
if (e.target === this.dragTarget.element) {
|
|
300
333
|
this.cancel();
|
|
301
334
|
return;
|
|
302
335
|
}
|
|
303
336
|
|
|
304
|
-
let dropTarget = this.validDropTargets.find(target => target.element.contains(e.target as HTMLElement));
|
|
305
337
|
if (dropTarget) {
|
|
306
|
-
let item = dropItems.get(e.target as HTMLElement);
|
|
307
338
|
this.setCurrentDropTarget(dropTarget, item);
|
|
308
339
|
this.drop(item);
|
|
309
340
|
}
|
|
@@ -319,7 +350,7 @@ class DragSession {
|
|
|
319
350
|
|
|
320
351
|
cancelEvent(e: Event) {
|
|
321
352
|
// Allow focusin and focusout on the drag target so focus ring works properly.
|
|
322
|
-
if ((e.type === 'focusin' || e.type === 'focusout') && e.target === this.dragTarget?.element) {
|
|
353
|
+
if ((e.type === 'focusin' || e.type === 'focusout') && (e.target === this.dragTarget?.element || e.target === this.getCurrentActivateButton())) {
|
|
323
354
|
return;
|
|
324
355
|
}
|
|
325
356
|
|
|
@@ -375,14 +406,23 @@ class DragSession {
|
|
|
375
406
|
|
|
376
407
|
this.restoreAriaHidden = ariaHideOutside([
|
|
377
408
|
this.dragTarget.element,
|
|
378
|
-
...validDropItems.
|
|
379
|
-
...visibleDropTargets.
|
|
409
|
+
...validDropItems.flatMap(item => item.activateButtonRef?.current ? [item.element, item.activateButtonRef?.current] : [item.element]),
|
|
410
|
+
...visibleDropTargets.flatMap(target => target.activateButtonRef?.current ? [target.element, target.activateButtonRef?.current] : [target.element])
|
|
380
411
|
]);
|
|
381
412
|
|
|
382
413
|
this.mutationObserver.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['aria-hidden']});
|
|
383
414
|
}
|
|
384
415
|
|
|
385
416
|
next() {
|
|
417
|
+
// TODO: Allow tabbing to the activate button. Revisit once we fix the focus ring.
|
|
418
|
+
// For now, the activate button is reachable by screen readers and ArrowLeft/ArrowRight
|
|
419
|
+
// is usable specifically by Tree. Will need tabbing for other components.
|
|
420
|
+
// let activateButton = this.getCurrentActivateButton();
|
|
421
|
+
// if (activateButton && document.activeElement !== activateButton) {
|
|
422
|
+
// activateButton.focus();
|
|
423
|
+
// return;
|
|
424
|
+
// }
|
|
425
|
+
|
|
386
426
|
if (!this.currentDropTarget) {
|
|
387
427
|
this.setCurrentDropTarget(this.validDropTargets[0]);
|
|
388
428
|
return;
|
|
@@ -409,6 +449,15 @@ class DragSession {
|
|
|
409
449
|
}
|
|
410
450
|
|
|
411
451
|
previous() {
|
|
452
|
+
// let activateButton = this.getCurrentActivateButton();
|
|
453
|
+
// if (activateButton && document.activeElement === activateButton) {
|
|
454
|
+
// let target = this.currentDropItem ?? this.currentDropTarget;
|
|
455
|
+
// if (target) {
|
|
456
|
+
// target.element.focus();
|
|
457
|
+
// return;
|
|
458
|
+
// }
|
|
459
|
+
// }
|
|
460
|
+
|
|
412
461
|
if (!this.currentDropTarget) {
|
|
413
462
|
this.setCurrentDropTarget(this.validDropTargets[this.validDropTargets.length - 1]);
|
|
414
463
|
return;
|
|
@@ -487,7 +536,6 @@ class DragSession {
|
|
|
487
536
|
if (this.currentDropTarget && typeof this.currentDropTarget.onDropTargetEnter === 'function') {
|
|
488
537
|
this.currentDropTarget.onDropTargetEnter(item.target);
|
|
489
538
|
}
|
|
490
|
-
|
|
491
539
|
item.element.focus();
|
|
492
540
|
this.currentDropItem = item;
|
|
493
541
|
|
|
@@ -576,14 +624,15 @@ class DragSession {
|
|
|
576
624
|
announce(this.stringFormatter.format('dropComplete'));
|
|
577
625
|
}
|
|
578
626
|
|
|
579
|
-
activate() {
|
|
580
|
-
if (
|
|
581
|
-
let
|
|
582
|
-
|
|
627
|
+
activate(dropTarget: DropTarget | null, dropItem: DroppableItem | null | undefined) {
|
|
628
|
+
if (dropTarget && typeof dropTarget.onDropActivate === 'function') {
|
|
629
|
+
let target = dropItem?.target ?? null;
|
|
630
|
+
let rect = dropTarget.element.getBoundingClientRect();
|
|
631
|
+
dropTarget.onDropActivate({
|
|
583
632
|
type: 'dropactivate',
|
|
584
633
|
x: rect.left + (rect.width / 2),
|
|
585
634
|
y: rect.top + (rect.height / 2)
|
|
586
|
-
},
|
|
635
|
+
}, target);
|
|
587
636
|
}
|
|
588
637
|
}
|
|
589
638
|
}
|
|
@@ -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
|
});
|