@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.
Files changed (40) hide show
  1. package/dist/DragManager.main.js +79 -12
  2. package/dist/DragManager.main.js.map +1 -1
  3. package/dist/DragManager.mjs +79 -12
  4. package/dist/DragManager.module.js +79 -12
  5. package/dist/DragManager.module.js.map +1 -1
  6. package/dist/DropTargetKeyboardNavigation.main.js +201 -0
  7. package/dist/DropTargetKeyboardNavigation.main.js.map +1 -0
  8. package/dist/DropTargetKeyboardNavigation.mjs +196 -0
  9. package/dist/DropTargetKeyboardNavigation.module.js +196 -0
  10. package/dist/DropTargetKeyboardNavigation.module.js.map +1 -0
  11. package/dist/ListDropTargetDelegate.main.js.map +1 -1
  12. package/dist/ListDropTargetDelegate.module.js.map +1 -1
  13. package/dist/types.d.ts +7 -1
  14. package/dist/types.d.ts.map +1 -1
  15. package/dist/useDrop.main.js.map +1 -1
  16. package/dist/useDrop.module.js.map +1 -1
  17. package/dist/useDropIndicator.main.js +15 -7
  18. package/dist/useDropIndicator.main.js.map +1 -1
  19. package/dist/useDropIndicator.mjs +15 -7
  20. package/dist/useDropIndicator.module.js +15 -7
  21. package/dist/useDropIndicator.module.js.map +1 -1
  22. package/dist/useDroppableCollection.main.js +23 -98
  23. package/dist/useDroppableCollection.main.js.map +1 -1
  24. package/dist/useDroppableCollection.mjs +23 -98
  25. package/dist/useDroppableCollection.module.js +23 -98
  26. package/dist/useDroppableCollection.module.js.map +1 -1
  27. package/dist/useDroppableItem.main.js +4 -2
  28. package/dist/useDroppableItem.main.js.map +1 -1
  29. package/dist/useDroppableItem.mjs +4 -2
  30. package/dist/useDroppableItem.module.js +4 -2
  31. package/dist/useDroppableItem.module.js.map +1 -1
  32. package/package.json +17 -12
  33. package/src/.DS_Store +0 -0
  34. package/src/DragManager.ts +66 -17
  35. package/src/DropTargetKeyboardNavigation.ts +273 -0
  36. package/src/ListDropTargetDelegate.ts +1 -1
  37. package/src/useDrop.ts +0 -1
  38. package/src/useDropIndicator.ts +17 -11
  39. package/src/useDroppableCollection.ts +23 -123
  40. package/src/useDroppableItem.ts +7 -4
@@ -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' && state.target?.dropPosition === 'on' && typeof props.onDropActivate === 'function') {
209
+ if (state.target?.type === 'item' && typeof props.onDropActivate === 'function') {
205
210
  props.onDropActivate({
206
211
  type: 'dropactivate',
207
212
  x: e.x, // todo
@@ -364,118 +369,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
364
369
  return;
365
370
  }
366
371
 
367
- let getNextTarget = (target: DropTarget | null | undefined, wrap = true, horizontal = false): DropTarget | null => {
368
- if (!target) {
369
- return {
370
- type: 'root'
371
- };
372
- }
373
-
374
- let {keyboardDelegate} = localState.props;
375
- let nextKey: Key | null | undefined;
376
- if (target?.type === 'item') {
377
- nextKey = horizontal ? keyboardDelegate.getKeyRightOf?.(target.key) : keyboardDelegate.getKeyBelow?.(target.key);
378
- } else {
379
- nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getLastKey?.() : keyboardDelegate.getFirstKey?.();
380
- }
381
- let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
382
- let dropPosition: DropPosition = dropPositions[0];
383
-
384
- if (target.type === 'item') {
385
- // If the the keyboard delegate returned the next key in the collection,
386
- // first try the other positions in the current key. Otherwise (e.g. in a grid layout),
387
- // jump to the same drop position in the new key.
388
- let nextCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyBefore(target.key) : localState.state.collection.getKeyAfter(target.key);
389
- if (nextKey == null || nextKey === nextCollectionKey) {
390
- let positionIndex = dropPositions.indexOf(target.dropPosition);
391
- let nextDropPosition = dropPositions[positionIndex + 1];
392
- if (positionIndex < dropPositions.length - 1 && !(nextDropPosition === dropPositions[2] && nextKey != null)) {
393
- return {
394
- type: 'item',
395
- key: target.key,
396
- dropPosition: nextDropPosition
397
- };
398
- }
399
-
400
- // If the last drop position was 'after', then 'before' on the next key is equivalent.
401
- // Switch to 'on' instead.
402
- if (target.dropPosition === dropPositions[2]) {
403
- dropPosition = 'on';
404
- }
405
- } else {
406
- dropPosition = target.dropPosition;
407
- }
408
- }
409
-
410
- if (nextKey == null) {
411
- if (wrap) {
412
- return {
413
- type: 'root'
414
- };
415
- }
416
-
417
- return null;
418
- }
419
-
420
- return {
421
- type: 'item',
422
- key: nextKey,
423
- dropPosition
424
- };
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);
425
374
  };
426
375
 
427
- let getPreviousTarget = (target: DropTarget | null | undefined, wrap = true, horizontal = false): DropTarget | null => {
428
- let {keyboardDelegate} = localState.props;
429
- let nextKey: Key | null | undefined;
430
- if (target?.type === 'item') {
431
- nextKey = horizontal ? keyboardDelegate.getKeyLeftOf?.(target.key) : keyboardDelegate.getKeyAbove?.(target.key);
432
- } else {
433
- nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getFirstKey?.() : keyboardDelegate.getLastKey?.();
434
- }
435
- let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
436
- let dropPosition: DropPosition = !target || target.type === 'root' ? dropPositions[2] : 'on';
437
-
438
- if (target?.type === 'item') {
439
- // If the the keyboard delegate returned the previous key in the collection,
440
- // first try the other positions in the current key. Otherwise (e.g. in a grid layout),
441
- // jump to the same drop position in the new key.
442
- let prevCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyAfter(target.key) : localState.state.collection.getKeyBefore(target.key);
443
- if (nextKey == null || nextKey === prevCollectionKey) {
444
- let positionIndex = dropPositions.indexOf(target.dropPosition);
445
- let nextDropPosition = dropPositions[positionIndex - 1];
446
- if (positionIndex > 0 && nextDropPosition !== dropPositions[2]) {
447
- return {
448
- type: 'item',
449
- key: target.key,
450
- dropPosition: nextDropPosition
451
- };
452
- }
453
-
454
- // If the last drop position was 'before', then 'after' on the previous key is equivalent.
455
- // Switch to 'on' instead.
456
- if (target.dropPosition === dropPositions[0]) {
457
- dropPosition = 'on';
458
- }
459
- } else {
460
- dropPosition = target.dropPosition;
461
- }
462
- }
463
-
464
- if (nextKey == null) {
465
- if (wrap) {
466
- return {
467
- type: 'root'
468
- };
469
- }
470
-
471
- return null;
472
- }
473
-
474
- return {
475
- type: 'item',
476
- key: nextKey,
477
- dropPosition
478
- };
376
+ let getPreviousTarget = (target: DropTarget | null | undefined, wrap = true) => {
377
+ return getNextTarget(target, wrap, 'up');
479
378
  };
480
379
 
481
380
  let nextValidTarget = (
@@ -588,17 +487,17 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
588
487
  onDropTargetEnter(target) {
589
488
  localState.state.setTarget(target);
590
489
  },
591
- onDropActivate(e) {
490
+ onDropActivate(e, target) {
592
491
  if (
593
- localState.state.target?.type === 'item' &&
594
- localState.state.target?.dropPosition === 'on' &&
492
+ target?.type === 'item' &&
493
+ target?.dropPosition === 'on' &&
595
494
  typeof localState.props.onDropActivate === 'function'
596
495
  ) {
597
496
  localState.props.onDropActivate({
598
497
  type: 'dropactivate',
599
498
  x: e.x, // todo
600
499
  y: e.y,
601
- target: localState.state.target
500
+ target
602
501
  });
603
502
  }
604
503
  },
@@ -614,28 +513,28 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
614
513
  switch (e.key) {
615
514
  case 'ArrowDown': {
616
515
  if (keyboardDelegate.getKeyBelow) {
617
- 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'));
618
517
  localState.state.setTarget(target);
619
518
  }
620
519
  break;
621
520
  }
622
521
  case 'ArrowUp': {
623
522
  if (keyboardDelegate.getKeyAbove) {
624
- let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, getPreviousTarget);
523
+ let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'up'));
625
524
  localState.state.setTarget(target);
626
525
  }
627
526
  break;
628
527
  }
629
528
  case 'ArrowLeft': {
630
529
  if (keyboardDelegate.getKeyLeftOf) {
631
- let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getPreviousTarget(target, wrap, true));
530
+ let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'left'));
632
531
  localState.state.setTarget(target);
633
532
  }
634
533
  break;
635
534
  }
636
535
  case 'ArrowRight': {
637
536
  if (keyboardDelegate.getKeyRightOf) {
638
- let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, true));
537
+ let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, 'right'));
639
538
  localState.state.setTarget(target);
640
539
  }
641
540
  break;
@@ -748,6 +647,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
748
647
  break;
749
648
  }
750
649
  }
650
+ localState.props.onKeyDown?.(e);
751
651
  }
752
652
  });
753
653
  }, [localState, ref, onDrop, direction]);
@@ -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;