@react-aria/dnd 3.0.0-alpha.9 → 3.0.0-nightly-4980928d3-240906

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 (275) hide show
  1. package/dist/DragManager.main.js +432 -0
  2. package/dist/DragManager.main.js.map +1 -0
  3. package/dist/DragManager.mjs +423 -0
  4. package/dist/DragManager.module.js +423 -0
  5. package/dist/DragManager.module.js.map +1 -0
  6. package/dist/DragPreview.main.js +59 -0
  7. package/dist/DragPreview.main.js.map +1 -0
  8. package/dist/DragPreview.mjs +50 -0
  9. package/dist/DragPreview.module.js +50 -0
  10. package/dist/DragPreview.module.js.map +1 -0
  11. package/dist/ListDropTargetDelegate.main.js +123 -0
  12. package/dist/ListDropTargetDelegate.main.js.map +1 -0
  13. package/dist/ListDropTargetDelegate.mjs +118 -0
  14. package/dist/ListDropTargetDelegate.module.js +118 -0
  15. package/dist/ListDropTargetDelegate.module.js.map +1 -0
  16. package/dist/ar-AE.main.js +44 -0
  17. package/dist/ar-AE.main.js.map +1 -0
  18. package/dist/ar-AE.mjs +46 -0
  19. package/dist/ar-AE.module.js +46 -0
  20. package/dist/ar-AE.module.js.map +1 -0
  21. package/dist/bg-BG.main.js +44 -0
  22. package/dist/bg-BG.main.js.map +1 -0
  23. package/dist/bg-BG.mjs +46 -0
  24. package/dist/bg-BG.module.js +46 -0
  25. package/dist/bg-BG.module.js.map +1 -0
  26. package/dist/constants.main.js +65 -0
  27. package/dist/constants.main.js.map +1 -0
  28. package/dist/constants.mjs +53 -0
  29. package/dist/constants.module.js +53 -0
  30. package/dist/constants.module.js.map +1 -0
  31. package/dist/cs-CZ.main.js +45 -0
  32. package/dist/cs-CZ.main.js.map +1 -0
  33. package/dist/cs-CZ.mjs +47 -0
  34. package/dist/cs-CZ.module.js +47 -0
  35. package/dist/cs-CZ.module.js.map +1 -0
  36. package/dist/da-DK.main.js +44 -0
  37. package/dist/da-DK.main.js.map +1 -0
  38. package/dist/da-DK.mjs +46 -0
  39. package/dist/da-DK.module.js +46 -0
  40. package/dist/da-DK.module.js.map +1 -0
  41. package/dist/de-DE.main.js +44 -0
  42. package/dist/de-DE.main.js.map +1 -0
  43. package/dist/de-DE.mjs +46 -0
  44. package/dist/de-DE.module.js +46 -0
  45. package/dist/de-DE.module.js.map +1 -0
  46. package/dist/el-GR.main.js +44 -0
  47. package/dist/el-GR.main.js.map +1 -0
  48. package/dist/el-GR.mjs +46 -0
  49. package/dist/el-GR.module.js +46 -0
  50. package/dist/el-GR.module.js.map +1 -0
  51. package/dist/en-US.main.js +44 -0
  52. package/dist/en-US.main.js.map +1 -0
  53. package/dist/en-US.mjs +46 -0
  54. package/dist/en-US.module.js +46 -0
  55. package/dist/en-US.module.js.map +1 -0
  56. package/dist/es-ES.main.js +44 -0
  57. package/dist/es-ES.main.js.map +1 -0
  58. package/dist/es-ES.mjs +46 -0
  59. package/dist/es-ES.module.js +46 -0
  60. package/dist/es-ES.module.js.map +1 -0
  61. package/dist/et-EE.main.js +44 -0
  62. package/dist/et-EE.main.js.map +1 -0
  63. package/dist/et-EE.mjs +46 -0
  64. package/dist/et-EE.module.js +46 -0
  65. package/dist/et-EE.module.js.map +1 -0
  66. package/dist/fi-FI.main.js +44 -0
  67. package/dist/fi-FI.main.js.map +1 -0
  68. package/dist/fi-FI.mjs +46 -0
  69. package/dist/fi-FI.module.js +46 -0
  70. package/dist/fi-FI.module.js.map +1 -0
  71. package/dist/fr-FR.main.js +44 -0
  72. package/dist/fr-FR.main.js.map +1 -0
  73. package/dist/fr-FR.mjs +46 -0
  74. package/dist/fr-FR.module.js +46 -0
  75. package/dist/fr-FR.module.js.map +1 -0
  76. package/dist/he-IL.main.js +44 -0
  77. package/dist/he-IL.main.js.map +1 -0
  78. package/dist/he-IL.mjs +46 -0
  79. package/dist/he-IL.module.js +46 -0
  80. package/dist/he-IL.module.js.map +1 -0
  81. package/dist/hr-HR.main.js +44 -0
  82. package/dist/hr-HR.main.js.map +1 -0
  83. package/dist/hr-HR.mjs +46 -0
  84. package/dist/hr-HR.module.js +46 -0
  85. package/dist/hr-HR.module.js.map +1 -0
  86. package/dist/hu-HU.main.js +44 -0
  87. package/dist/hu-HU.main.js.map +1 -0
  88. package/dist/hu-HU.mjs +46 -0
  89. package/dist/hu-HU.module.js +46 -0
  90. package/dist/hu-HU.module.js.map +1 -0
  91. package/dist/import.mjs +39 -0
  92. package/dist/intlStrings.main.js +108 -0
  93. package/dist/intlStrings.main.js.map +1 -0
  94. package/dist/intlStrings.mjs +110 -0
  95. package/dist/intlStrings.module.js +110 -0
  96. package/dist/intlStrings.module.js.map +1 -0
  97. package/dist/it-IT.main.js +44 -0
  98. package/dist/it-IT.main.js.map +1 -0
  99. package/dist/it-IT.mjs +46 -0
  100. package/dist/it-IT.module.js +46 -0
  101. package/dist/it-IT.module.js.map +1 -0
  102. package/dist/ja-JP.main.js +44 -0
  103. package/dist/ja-JP.main.js.map +1 -0
  104. package/dist/ja-JP.mjs +46 -0
  105. package/dist/ja-JP.module.js +46 -0
  106. package/dist/ja-JP.module.js.map +1 -0
  107. package/dist/ko-KR.main.js +44 -0
  108. package/dist/ko-KR.main.js.map +1 -0
  109. package/dist/ko-KR.mjs +46 -0
  110. package/dist/ko-KR.module.js +46 -0
  111. package/dist/ko-KR.module.js.map +1 -0
  112. package/dist/lt-LT.main.js +44 -0
  113. package/dist/lt-LT.main.js.map +1 -0
  114. package/dist/lt-LT.mjs +46 -0
  115. package/dist/lt-LT.module.js +46 -0
  116. package/dist/lt-LT.module.js.map +1 -0
  117. package/dist/lv-LV.main.js +44 -0
  118. package/dist/lv-LV.main.js.map +1 -0
  119. package/dist/lv-LV.mjs +46 -0
  120. package/dist/lv-LV.module.js +46 -0
  121. package/dist/lv-LV.module.js.map +1 -0
  122. package/dist/main.js +39 -1873
  123. package/dist/main.js.map +1 -1
  124. package/dist/module.js +24 -1853
  125. package/dist/module.js.map +1 -1
  126. package/dist/nb-NO.main.js +44 -0
  127. package/dist/nb-NO.main.js.map +1 -0
  128. package/dist/nb-NO.mjs +46 -0
  129. package/dist/nb-NO.module.js +46 -0
  130. package/dist/nb-NO.module.js.map +1 -0
  131. package/dist/nl-NL.main.js +44 -0
  132. package/dist/nl-NL.main.js.map +1 -0
  133. package/dist/nl-NL.mjs +46 -0
  134. package/dist/nl-NL.module.js +46 -0
  135. package/dist/nl-NL.module.js.map +1 -0
  136. package/dist/pl-PL.main.js +44 -0
  137. package/dist/pl-PL.main.js.map +1 -0
  138. package/dist/pl-PL.mjs +46 -0
  139. package/dist/pl-PL.module.js +46 -0
  140. package/dist/pl-PL.module.js.map +1 -0
  141. package/dist/pt-BR.main.js +44 -0
  142. package/dist/pt-BR.main.js.map +1 -0
  143. package/dist/pt-BR.mjs +46 -0
  144. package/dist/pt-BR.module.js +46 -0
  145. package/dist/pt-BR.module.js.map +1 -0
  146. package/dist/pt-PT.main.js +44 -0
  147. package/dist/pt-PT.main.js.map +1 -0
  148. package/dist/pt-PT.mjs +46 -0
  149. package/dist/pt-PT.module.js +46 -0
  150. package/dist/pt-PT.module.js.map +1 -0
  151. package/dist/ro-RO.main.js +44 -0
  152. package/dist/ro-RO.main.js.map +1 -0
  153. package/dist/ro-RO.mjs +46 -0
  154. package/dist/ro-RO.module.js +46 -0
  155. package/dist/ro-RO.module.js.map +1 -0
  156. package/dist/ru-RU.main.js +44 -0
  157. package/dist/ru-RU.main.js.map +1 -0
  158. package/dist/ru-RU.mjs +46 -0
  159. package/dist/ru-RU.module.js +46 -0
  160. package/dist/ru-RU.module.js.map +1 -0
  161. package/dist/sk-SK.main.js +44 -0
  162. package/dist/sk-SK.main.js.map +1 -0
  163. package/dist/sk-SK.mjs +46 -0
  164. package/dist/sk-SK.module.js +46 -0
  165. package/dist/sk-SK.module.js.map +1 -0
  166. package/dist/sl-SI.main.js +44 -0
  167. package/dist/sl-SI.main.js.map +1 -0
  168. package/dist/sl-SI.mjs +46 -0
  169. package/dist/sl-SI.module.js +46 -0
  170. package/dist/sl-SI.module.js.map +1 -0
  171. package/dist/sr-SP.main.js +44 -0
  172. package/dist/sr-SP.main.js.map +1 -0
  173. package/dist/sr-SP.mjs +46 -0
  174. package/dist/sr-SP.module.js +46 -0
  175. package/dist/sr-SP.module.js.map +1 -0
  176. package/dist/sv-SE.main.js +44 -0
  177. package/dist/sv-SE.main.js.map +1 -0
  178. package/dist/sv-SE.mjs +46 -0
  179. package/dist/sv-SE.module.js +46 -0
  180. package/dist/sv-SE.module.js.map +1 -0
  181. package/dist/tr-TR.main.js +44 -0
  182. package/dist/tr-TR.main.js.map +1 -0
  183. package/dist/tr-TR.mjs +46 -0
  184. package/dist/tr-TR.module.js +46 -0
  185. package/dist/tr-TR.module.js.map +1 -0
  186. package/dist/types.d.ts +177 -29
  187. package/dist/types.d.ts.map +1 -1
  188. package/dist/uk-UA.main.js +44 -0
  189. package/dist/uk-UA.main.js.map +1 -0
  190. package/dist/uk-UA.mjs +46 -0
  191. package/dist/uk-UA.module.js +46 -0
  192. package/dist/uk-UA.module.js.map +1 -0
  193. package/dist/useAutoScroll.main.js +89 -0
  194. package/dist/useAutoScroll.main.js.map +1 -0
  195. package/dist/useAutoScroll.mjs +84 -0
  196. package/dist/useAutoScroll.module.js +84 -0
  197. package/dist/useAutoScroll.module.js.map +1 -0
  198. package/dist/useClipboard.main.js +107 -0
  199. package/dist/useClipboard.main.js.map +1 -0
  200. package/dist/useClipboard.mjs +102 -0
  201. package/dist/useClipboard.module.js +102 -0
  202. package/dist/useClipboard.module.js.map +1 -0
  203. package/dist/useDrag.main.js +286 -0
  204. package/dist/useDrag.main.js.map +1 -0
  205. package/dist/useDrag.mjs +281 -0
  206. package/dist/useDrag.module.js +281 -0
  207. package/dist/useDrag.module.js.map +1 -0
  208. package/dist/useDraggableCollection.main.js +27 -0
  209. package/dist/useDraggableCollection.main.js.map +1 -0
  210. package/dist/useDraggableCollection.mjs +22 -0
  211. package/dist/useDraggableCollection.module.js +22 -0
  212. package/dist/useDraggableCollection.module.js.map +1 -0
  213. package/dist/useDraggableItem.main.js +128 -0
  214. package/dist/useDraggableItem.main.js.map +1 -0
  215. package/dist/useDraggableItem.mjs +123 -0
  216. package/dist/useDraggableItem.module.js +123 -0
  217. package/dist/useDraggableItem.module.js.map +1 -0
  218. package/dist/useDrop.main.js +299 -0
  219. package/dist/useDrop.main.js.map +1 -0
  220. package/dist/useDrop.mjs +294 -0
  221. package/dist/useDrop.module.js +294 -0
  222. package/dist/useDrop.module.js.map +1 -0
  223. package/dist/useDropIndicator.main.js +93 -0
  224. package/dist/useDropIndicator.main.js.map +1 -0
  225. package/dist/useDropIndicator.mjs +88 -0
  226. package/dist/useDropIndicator.module.js +88 -0
  227. package/dist/useDropIndicator.module.js.map +1 -0
  228. package/dist/useDroppableCollection.main.js +592 -0
  229. package/dist/useDroppableCollection.main.js.map +1 -0
  230. package/dist/useDroppableCollection.mjs +587 -0
  231. package/dist/useDroppableCollection.module.js +587 -0
  232. package/dist/useDroppableCollection.module.js.map +1 -0
  233. package/dist/useDroppableItem.main.js +79 -0
  234. package/dist/useDroppableItem.main.js.map +1 -0
  235. package/dist/useDroppableItem.mjs +74 -0
  236. package/dist/useDroppableItem.module.js +74 -0
  237. package/dist/useDroppableItem.module.js.map +1 -0
  238. package/dist/useVirtualDrop.main.js +56 -0
  239. package/dist/useVirtualDrop.main.js.map +1 -0
  240. package/dist/useVirtualDrop.mjs +51 -0
  241. package/dist/useVirtualDrop.module.js +51 -0
  242. package/dist/useVirtualDrop.module.js.map +1 -0
  243. package/dist/utils.main.js +283 -0
  244. package/dist/utils.main.js.map +1 -0
  245. package/dist/utils.mjs +255 -0
  246. package/dist/utils.module.js +255 -0
  247. package/dist/utils.module.js.map +1 -0
  248. package/dist/zh-CN.main.js +44 -0
  249. package/dist/zh-CN.main.js.map +1 -0
  250. package/dist/zh-CN.mjs +46 -0
  251. package/dist/zh-CN.module.js +46 -0
  252. package/dist/zh-CN.module.js.map +1 -0
  253. package/dist/zh-TW.main.js +44 -0
  254. package/dist/zh-TW.main.js.map +1 -0
  255. package/dist/zh-TW.mjs +46 -0
  256. package/dist/zh-TW.module.js +46 -0
  257. package/dist/zh-TW.module.js.map +1 -0
  258. package/package.json +20 -16
  259. package/src/DragManager.ts +87 -52
  260. package/src/DragPreview.tsx +3 -3
  261. package/src/ListDropTargetDelegate.ts +173 -0
  262. package/src/constants.ts +2 -0
  263. package/src/index.ts +55 -9
  264. package/src/useAutoScroll.ts +26 -6
  265. package/src/useClipboard.ts +65 -59
  266. package/src/useDrag.ts +179 -31
  267. package/src/useDraggableCollection.ts +29 -0
  268. package/src/useDraggableItem.ts +105 -12
  269. package/src/useDrop.ts +273 -93
  270. package/src/useDropIndicator.ts +47 -21
  271. package/src/useDroppableCollection.ts +315 -115
  272. package/src/useDroppableItem.ts +33 -17
  273. package/src/useVirtualDrop.ts +5 -5
  274. package/src/utils.ts +92 -17
  275. package/LICENSE +0 -201
@@ -10,23 +10,47 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {Collection, DropEvent, DropOperation, DroppableCollectionProps, DropPosition, DropTarget, KeyboardDelegate, Node} from '@react-types/shared';
13
+ import {
14
+ clearGlobalDnDState,
15
+ DIRECTORY_DRAG_TYPE,
16
+ droppableCollectionMap,
17
+ getTypes,
18
+ globalDndState,
19
+ isInternalDropOperation,
20
+ setDropCollectionRef
21
+ } from './utils';
22
+ import {
23
+ Collection,
24
+ DropEvent,
25
+ DropOperation,
26
+ DroppableCollectionDropEvent,
27
+ DroppableCollectionProps,
28
+ DropPosition,
29
+ DropTarget,
30
+ DropTargetDelegate,
31
+ Key,
32
+ KeyboardDelegate,
33
+ Node,
34
+ RefObject
35
+ } from '@react-types/shared';
14
36
  import * as DragManager from './DragManager';
15
37
  import {DroppableCollectionState} from '@react-stately/dnd';
16
- import {getTypes} from './utils';
17
- import {HTMLAttributes, Key, RefObject, useCallback, useEffect, useRef} from 'react';
18
- import {mergeProps, useLayoutEffect} from '@react-aria/utils';
38
+ import {HTMLAttributes, useCallback, useEffect, useRef} from 'react';
39
+ import {mergeProps, useId, useLayoutEffect} from '@react-aria/utils';
19
40
  import {setInteractionModality} from '@react-aria/interactions';
20
41
  import {useAutoScroll} from './useAutoScroll';
21
42
  import {useDrop} from './useDrop';
22
- import {useDroppableCollectionId} from './utils';
43
+ import {useLocale} from '@react-aria/i18n';
23
44
 
24
45
  export interface DroppableCollectionOptions extends DroppableCollectionProps {
46
+ /** A delegate object that implements behavior for keyboard focus movement. */
25
47
  keyboardDelegate: KeyboardDelegate,
26
- getDropTargetFromPoint: (x: number, y: number) => DropTarget | null
48
+ /** A delegate object that provides drop targets for pointer coordinates within the collection. */
49
+ dropTargetDelegate: DropTargetDelegate
27
50
  }
28
51
 
29
52
  export interface DroppableCollectionResult {
53
+ /** Props for the collection element. */
30
54
  collectionProps: HTMLAttributes<HTMLElement>
31
55
  }
32
56
 
@@ -34,12 +58,20 @@ interface DroppingState {
34
58
  collection: Collection<Node<unknown>>,
35
59
  focusedKey: Key,
36
60
  selectedKeys: Set<Key>,
37
- timeout: NodeJS.Timeout
61
+ target: DropTarget,
62
+ draggingKeys: Set<Key>,
63
+ isInternal: boolean,
64
+ timeout: ReturnType<typeof setTimeout>
38
65
  }
39
66
 
40
67
  const DROP_POSITIONS: DropPosition[] = ['before', 'on', 'after'];
68
+ const DROP_POSITIONS_RTL: DropPosition[] = ['after', 'on', 'before'];
41
69
 
42
- export function useDroppableCollection(props: DroppableCollectionOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableCollectionResult {
70
+ /**
71
+ * Handles drop interactions for a collection component, with support for traditional mouse and touch
72
+ * based drag and drop, in addition to full parity for keyboard and screen reader users.
73
+ */
74
+ export function useDroppableCollection(props: DroppableCollectionOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement | null>): DroppableCollectionResult {
43
75
  let localState = useRef({
44
76
  props,
45
77
  state,
@@ -49,41 +81,113 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
49
81
  localState.props = props;
50
82
  localState.state = state;
51
83
 
84
+ let defaultOnDrop = useCallback(async (e: DroppableCollectionDropEvent) => {
85
+ let {
86
+ onInsert,
87
+ onRootDrop,
88
+ onItemDrop,
89
+ onReorder,
90
+ acceptedDragTypes = 'all',
91
+ shouldAcceptItemDrop
92
+ } = localState.props;
93
+
94
+ let {draggingKeys} = globalDndState;
95
+ let isInternal = isInternalDropOperation(ref);
96
+ let {
97
+ target,
98
+ dropOperation,
99
+ items
100
+ } = e;
101
+
102
+ let filteredItems = items;
103
+ if (acceptedDragTypes !== 'all' || shouldAcceptItemDrop) {
104
+ filteredItems = items.filter(item => {
105
+ let itemTypes: Set<string | symbol>;
106
+ if (item.kind === 'directory') {
107
+ itemTypes = new Set([DIRECTORY_DRAG_TYPE]);
108
+ } else {
109
+ itemTypes = item.kind === 'file' ? new Set([item.type]) : item.types;
110
+ }
111
+
112
+ if (acceptedDragTypes === 'all' || acceptedDragTypes.some(type => itemTypes.has(type))) {
113
+ // If we are performing a on item drop, check if the item in question accepts the dropped item since the item may have heavier restrictions
114
+ // than the droppable collection itself
115
+ if (target.type === 'item' && target.dropPosition === 'on' && shouldAcceptItemDrop) {
116
+ return shouldAcceptItemDrop(target, itemTypes);
117
+ }
118
+ return true;
119
+ }
120
+
121
+ return false;
122
+ });
123
+ }
124
+
125
+ if (filteredItems.length > 0) {
126
+ if (target.type === 'root' && onRootDrop) {
127
+ await onRootDrop({items: filteredItems, dropOperation});
128
+ }
129
+
130
+ if (target.type === 'item') {
131
+ if (target.dropPosition === 'on' && onItemDrop) {
132
+ await onItemDrop({items: filteredItems, dropOperation, isInternal, target});
133
+ }
134
+
135
+ if (target.dropPosition !== 'on') {
136
+ if (!isInternal && onInsert) {
137
+ await onInsert({items: filteredItems, dropOperation, target});
138
+ }
139
+
140
+ if (isInternal && onReorder) {
141
+ await onReorder({keys: draggingKeys, dropOperation, target});
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }, [localState, ref]);
147
+
52
148
  let autoScroll = useAutoScroll(ref);
53
149
  let {dropProps} = useDrop({
54
150
  ref,
55
- onDropEnter(e) {
56
- let target = props.getDropTargetFromPoint(e.x, e.y);
57
- state.setTarget(target);
151
+ onDropEnter() {
152
+ state.setTarget(localState.nextTarget);
58
153
  },
59
154
  onDropMove(e) {
60
155
  state.setTarget(localState.nextTarget);
61
156
  autoScroll.move(e.x, e.y);
62
157
  },
63
158
  getDropOperationForPoint(types, allowedOperations, x, y) {
64
- let target = props.getDropTargetFromPoint(x, y);
159
+ let {draggingKeys, dropCollectionRef} = globalDndState;
160
+ let isInternal = isInternalDropOperation(ref);
161
+ let isValidDropTarget = (target) => state.getDropOperation({target, types, allowedOperations, isInternal, draggingKeys}) !== 'cancel';
162
+ let target = props.dropTargetDelegate.getDropTargetFromPoint(x, y, isValidDropTarget);
65
163
  if (!target) {
66
164
  localState.dropOperation = 'cancel';
67
165
  localState.nextTarget = null;
68
166
  return 'cancel';
69
167
  }
70
168
 
71
- localState.dropOperation = state.getDropOperation(target, types, allowedOperations);
169
+ localState.dropOperation = state.getDropOperation({target, types, allowedOperations, isInternal, draggingKeys});
72
170
 
73
171
  // If the target doesn't accept the drop, see if the root accepts it instead.
74
172
  if (localState.dropOperation === 'cancel') {
75
173
  let rootTarget: DropTarget = {type: 'root'};
76
- let dropOperation = state.getDropOperation(rootTarget, types, allowedOperations);
174
+ let dropOperation = state.getDropOperation({target: rootTarget, types, allowedOperations, isInternal, draggingKeys});
77
175
  if (dropOperation !== 'cancel') {
78
176
  target = rootTarget;
79
177
  localState.dropOperation = dropOperation;
80
178
  }
81
179
  }
82
180
 
181
+ // Only set dropCollectionRef if there is a valid drop target since we cleanup dropCollectionRef in onDropExit
182
+ // which only runs when leaving a valid drop target or if the dropEffect become none (mouse dnd only).
183
+ if (target && localState.dropOperation !== 'cancel' && ref?.current !== dropCollectionRef?.current) {
184
+ setDropCollectionRef(ref);
185
+ }
83
186
  localState.nextTarget = localState.dropOperation === 'cancel' ? null : target;
84
187
  return localState.dropOperation;
85
188
  },
86
189
  onDropExit() {
190
+ setDropCollectionRef(undefined);
87
191
  state.setTarget(null);
88
192
  autoScroll.stop();
89
193
  },
@@ -98,29 +202,112 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
98
202
  }
99
203
  },
100
204
  onDrop(e) {
101
- if (state.target && typeof props.onDrop === 'function') {
205
+ setDropCollectionRef(ref);
206
+ if (state.target) {
102
207
  onDrop(e, state.target);
103
208
  }
209
+
210
+ // If there wasn't a collection being tracked as a dragged collection, then we are in a case where a non RSP drag is dropped on a
211
+ // RSP collection and thus we don't need to preserve the global DnD state for onDragEnd
212
+ let {draggingCollectionRef} = globalDndState;
213
+ if (draggingCollectionRef == null) {
214
+ clearGlobalDnDState();
215
+ }
104
216
  }
105
217
  });
106
218
 
107
219
  let droppingState = useRef<DroppingState>(null);
108
- let onDrop = useCallback((e: DropEvent, target: DropTarget) => {
220
+ let updateFocusAfterDrop = useCallback(() => {
109
221
  let {state} = localState;
222
+ if (droppingState.current) {
223
+ let {
224
+ target,
225
+ collection: prevCollection,
226
+ selectedKeys: prevSelectedKeys,
227
+ focusedKey: prevFocusedKey,
228
+ isInternal,
229
+ draggingKeys
230
+ } = droppingState.current;
231
+
232
+ // If an insert occurs during a drop, we want to immediately select these items to give
233
+ // feedback to the user that a drop occurred. Only do this if the selection didn't change
234
+ // since the drop started so we don't override if the user or application did something.
235
+ if (
236
+ state.collection.size > prevCollection.size &&
237
+ state.selectionManager.isSelectionEqual(prevSelectedKeys)
238
+ ) {
239
+ let newKeys = new Set<Key>();
240
+ for (let key of state.collection.getKeys()) {
241
+ if (!prevCollection.getItem(key)) {
242
+ newKeys.add(key);
243
+ }
244
+ }
245
+
246
+ state.selectionManager.setSelectedKeys(newKeys);
247
+
248
+ // If the focused item didn't change since the drop occurred, also focus the first
249
+ // inserted item. If selection is disabled, then also show the focus ring so there
250
+ // is some indication that items were added.
251
+ if (state.selectionManager.focusedKey === prevFocusedKey) {
252
+ let first = newKeys.keys().next().value;
253
+ let item = state.collection.getItem(first);
110
254
 
111
- // Focus the collection.
112
- state.selectionManager.setFocused(true);
255
+ // If this is a cell, focus the parent row.
256
+ if (item?.type === 'cell') {
257
+ first = item.parentKey;
258
+ }
259
+
260
+ state.selectionManager.setFocusedKey(first);
261
+
262
+ if (state.selectionManager.selectionMode === 'none') {
263
+ setInteractionModality('keyboard');
264
+ }
265
+ }
266
+ } else if (
267
+ state.selectionManager.focusedKey === prevFocusedKey &&
268
+ isInternal &&
269
+ target.type === 'item' &&
270
+ target.dropPosition !== 'on' &&
271
+ draggingKeys.has(state.collection.getItem(prevFocusedKey)?.parentKey)
272
+ ) {
273
+ // Focus row instead of cell when reordering.
274
+ state.selectionManager.setFocusedKey(state.collection.getItem(prevFocusedKey).parentKey);
275
+ setInteractionModality('keyboard');
276
+ } else if (
277
+ state.selectionManager.focusedKey === prevFocusedKey &&
278
+ target.type === 'item' &&
279
+ target.dropPosition === 'on' &&
280
+ state.collection.getItem(target.key) != null
281
+ ) {
282
+ // If focus didn't move already (e.g. due to an insert), and the user dropped on an item,
283
+ // focus that item and show the focus ring to give the user feedback that the drop occurred.
284
+ // Also show the focus ring if the focused key is not selected, e.g. in case of a reorder.
285
+ state.selectionManager.setFocusedKey(target.key);
286
+ setInteractionModality('keyboard');
287
+ } else if (!state.selectionManager.isSelected(state.selectionManager.focusedKey)) {
288
+ setInteractionModality('keyboard');
289
+ }
113
290
 
291
+ state.selectionManager.setFocused(true);
292
+ }
293
+ }, [localState]);
294
+
295
+ let onDrop = useCallback((e: DropEvent, target: DropTarget) => {
296
+ let {state} = localState;
297
+
114
298
  // Save some state of the collection/selection before the drop occurs so we can compare later.
115
- let focusedKey = state.selectionManager.focusedKey;
116
299
  droppingState.current = {
117
300
  timeout: null,
118
- focusedKey,
301
+ focusedKey: state.selectionManager.focusedKey,
119
302
  collection: state.collection,
120
- selectedKeys: state.selectionManager.selectedKeys
303
+ selectedKeys: state.selectionManager.selectedKeys,
304
+ draggingKeys: globalDndState.draggingKeys,
305
+ isInternal: isInternalDropOperation(ref),
306
+ target
121
307
  };
122
308
 
123
- localState.props.onDrop({
309
+ let onDropFn = localState.props.onDrop || defaultOnDrop;
310
+ onDropFn({
124
311
  type: 'drop',
125
312
  x: e.x, // todo
126
313
  y: e.y,
@@ -130,27 +317,13 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
130
317
  });
131
318
 
132
319
  // Wait for a short time period after the onDrop is called to allow the data to be read asynchronously
133
- // and for React to re-render. If an insert occurs during this time, it will be selected/focused below.
134
- // If items are not "immediately" inserted by the onDrop handler, the application will need to handle
135
- // selecting and focusing those items themselves.
320
+ // and for React to re-render. If the collection didn't already change during this time (handled below),
321
+ // update the focused key here.
136
322
  droppingState.current.timeout = setTimeout(() => {
137
- // If focus didn't move already (e.g. due to an insert), and the user dropped on an item,
138
- // focus that item and show the focus ring to give the user feedback that the drop occurred.
139
- // Also show the focus ring if the focused key is not selected, e.g. in case of a reorder.
140
- let {state} = localState;
141
- if (state.selectionManager.focusedKey === focusedKey) {
142
- if (target.type === 'item' && target.dropPosition === 'on' && state.collection.getItem(target.key) != null) {
143
- state.selectionManager.setFocusedKey(target.key);
144
- state.selectionManager.setFocused(true);
145
- setInteractionModality('keyboard');
146
- } else if (!state.selectionManager.isSelected(focusedKey)) {
147
- setInteractionModality('keyboard');
148
- }
149
- }
150
-
323
+ updateFocusAfterDrop();
151
324
  droppingState.current = null;
152
325
  }, 50);
153
- }, [localState]);
326
+ }, [localState, defaultOnDrop, ref, updateFocusAfterDrop]);
154
327
 
155
328
  // eslint-disable-next-line arrow-body-style
156
329
  useEffect(() => {
@@ -162,42 +335,15 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
162
335
  }, []);
163
336
 
164
337
  useLayoutEffect(() => {
165
- // If an insert occurs during a drop, we want to immediately select these items to give
166
- // feedback to the user that a drop occurred. Only do this if the selection didn't change
167
- // since the drop started so we don't override if the user or application did something.
168
- if (
169
- droppingState.current &&
170
- state.selectionManager.isFocused &&
171
- state.collection.size > droppingState.current.collection.size &&
172
- state.selectionManager.isSelectionEqual(droppingState.current.selectedKeys)
173
- ) {
174
- let newKeys = new Set<Key>();
175
- for (let key of state.collection.getKeys()) {
176
- if (!droppingState.current.collection.getItem(key)) {
177
- newKeys.add(key);
178
- }
179
- }
180
-
181
- state.selectionManager.setSelectedKeys(newKeys);
182
-
183
- // If the focused item didn't change since the drop occurred, also focus the first
184
- // inserted item. If selection is disabled, then also show the focus ring so there
185
- // is some indication that items were added.
186
- if (state.selectionManager.focusedKey === droppingState.current.focusedKey) {
187
- let first = newKeys.keys().next().value;
188
- state.selectionManager.setFocusedKey(first);
189
-
190
- if (state.selectionManager.selectionMode === 'none') {
191
- setInteractionModality('keyboard');
192
- }
193
- }
194
-
195
- droppingState.current = null;
338
+ // If the collection changed after a drop, update the focused key.
339
+ if (droppingState.current && state.collection !== droppingState.current.collection) {
340
+ updateFocusAfterDrop();
196
341
  }
197
342
  });
198
343
 
344
+ let {direction} = useLocale();
199
345
  useEffect(() => {
200
- let getNextTarget = (target: DropTarget, wrap = true): DropTarget => {
346
+ let getNextTarget = (target: DropTarget, wrap = true, horizontal = false): DropTarget => {
201
347
  if (!target) {
202
348
  return {
203
349
  type: 'root'
@@ -205,26 +351,38 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
205
351
  }
206
352
 
207
353
  let {keyboardDelegate} = localState.props;
208
- let nextKey = target.type === 'item'
209
- ? keyboardDelegate.getKeyBelow(target.key)
210
- : keyboardDelegate.getFirstKey();
211
- let dropPosition: DropPosition = 'before';
354
+ let nextKey: Key;
355
+ if (target?.type === 'item') {
356
+ nextKey = horizontal ? keyboardDelegate.getKeyRightOf(target.key) : keyboardDelegate.getKeyBelow(target.key);
357
+ } else {
358
+ nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getLastKey() : keyboardDelegate.getFirstKey();
359
+ }
360
+ let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
361
+ let dropPosition: DropPosition = dropPositions[0];
212
362
 
213
363
  if (target.type === 'item') {
214
- let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
215
- let nextDropPosition = DROP_POSITIONS[positionIndex + 1];
216
- if (positionIndex < DROP_POSITIONS.length - 1 && !(nextDropPosition === 'after' && nextKey != null)) {
217
- return {
218
- type: 'item',
219
- key: target.key,
220
- dropPosition: nextDropPosition
221
- };
222
- }
364
+ // If the the keyboard delegate returned the next key in the collection,
365
+ // first try the other positions in the current key. Otherwise (e.g. in a grid layout),
366
+ // jump to the same drop position in the new key.
367
+ let nextCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyBefore(target.key) : localState.state.collection.getKeyAfter(target.key);
368
+ if (nextKey == null || nextKey === nextCollectionKey) {
369
+ let positionIndex = dropPositions.indexOf(target.dropPosition);
370
+ let nextDropPosition = dropPositions[positionIndex + 1];
371
+ if (positionIndex < dropPositions.length - 1 && !(nextDropPosition === dropPositions[2] && nextKey != null)) {
372
+ return {
373
+ type: 'item',
374
+ key: target.key,
375
+ dropPosition: nextDropPosition
376
+ };
377
+ }
223
378
 
224
- // If the last drop position was 'after', then 'before' on the next key is equivalent.
225
- // Switch to 'on' instead.
226
- if (target.dropPosition === 'after') {
227
- dropPosition = 'on';
379
+ // If the last drop position was 'after', then 'before' on the next key is equivalent.
380
+ // Switch to 'on' instead.
381
+ if (target.dropPosition === dropPositions[2]) {
382
+ dropPosition = 'on';
383
+ }
384
+ } else {
385
+ dropPosition = target.dropPosition;
228
386
  }
229
387
  }
230
388
 
@@ -245,28 +403,40 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
245
403
  };
246
404
  };
247
405
 
248
- let getPreviousTarget = (target: DropTarget, wrap = true): DropTarget => {
406
+ let getPreviousTarget = (target: DropTarget, wrap = true, horizontal = false): DropTarget => {
249
407
  let {keyboardDelegate} = localState.props;
250
- let nextKey = target?.type === 'item'
251
- ? keyboardDelegate.getKeyAbove(target.key)
252
- : keyboardDelegate.getLastKey();
253
- let dropPosition: DropPosition = !target || target.type === 'root' ? 'after' : 'on';
408
+ let nextKey: Key;
409
+ if (target?.type === 'item') {
410
+ nextKey = horizontal ? keyboardDelegate.getKeyLeftOf(target.key) : keyboardDelegate.getKeyAbove(target.key);
411
+ } else {
412
+ nextKey = horizontal && direction === 'rtl' ? keyboardDelegate.getFirstKey() : keyboardDelegate.getLastKey();
413
+ }
414
+ let dropPositions = horizontal && direction === 'rtl' ? DROP_POSITIONS_RTL : DROP_POSITIONS;
415
+ let dropPosition: DropPosition = !target || target.type === 'root' ? dropPositions[2] : 'on';
254
416
 
255
417
  if (target?.type === 'item') {
256
- let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
257
- let nextDropPosition = DROP_POSITIONS[positionIndex - 1];
258
- if (positionIndex > 0 && nextDropPosition !== 'after') {
259
- return {
260
- type: 'item',
261
- key: target.key,
262
- dropPosition: nextDropPosition
263
- };
264
- }
418
+ // If the the keyboard delegate returned the previous key in the collection,
419
+ // first try the other positions in the current key. Otherwise (e.g. in a grid layout),
420
+ // jump to the same drop position in the new key.
421
+ let prevCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyAfter(target.key) : localState.state.collection.getKeyBefore(target.key);
422
+ if (nextKey == null || nextKey === prevCollectionKey) {
423
+ let positionIndex = dropPositions.indexOf(target.dropPosition);
424
+ let nextDropPosition = dropPositions[positionIndex - 1];
425
+ if (positionIndex > 0 && nextDropPosition !== dropPositions[2]) {
426
+ return {
427
+ type: 'item',
428
+ key: target.key,
429
+ dropPosition: nextDropPosition
430
+ };
431
+ }
265
432
 
266
- // If the last drop position was 'before', then 'after' on the previous key is equivalent.
267
- // Switch to 'on' instead.
268
- if (target.dropPosition === 'before') {
269
- dropPosition = 'on';
433
+ // If the last drop position was 'before', then 'after' on the previous key is equivalent.
434
+ // Switch to 'on' instead.
435
+ if (target.dropPosition === dropPositions[0]) {
436
+ dropPosition = 'on';
437
+ }
438
+ } else {
439
+ dropPosition = target.dropPosition;
270
440
  }
271
441
  }
272
442
 
@@ -296,13 +466,15 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
296
466
  ): DropTarget => {
297
467
  let seenRoot = 0;
298
468
  let operation: DropOperation;
469
+ let {draggingKeys} = globalDndState;
470
+ let isInternal = isInternalDropOperation(ref);
299
471
  do {
300
472
  let nextTarget = getNextTarget(target, wrap);
301
473
  if (!nextTarget) {
302
474
  return null;
303
475
  }
304
476
  target = nextTarget;
305
- operation = localState.state.getDropOperation(nextTarget, types, allowedDropOperations);
477
+ operation = localState.state.getDropOperation({target: nextTarget, types, allowedOperations: allowedDropOperations, isInternal, draggingKeys});
306
478
  if (target.type === 'root') {
307
479
  seenRoot++;
308
480
  }
@@ -321,9 +493,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
321
493
 
322
494
  return DragManager.registerDropTarget({
323
495
  element: ref.current,
496
+ preventFocusOnDrop: true,
324
497
  getDropOperation(types, allowedOperations) {
325
498
  if (localState.state.target) {
326
- return localState.state.getDropOperation(localState.state.target, types, allowedOperations);
499
+ let {draggingKeys} = globalDndState;
500
+ let isInternal = isInternalDropOperation(ref);
501
+ return localState.state.getDropOperation({target: localState.state.target, types, allowedOperations, isInternal, draggingKeys});
327
502
  }
328
503
 
329
504
  // Check if any of the targets accept the drop.
@@ -335,6 +510,8 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
335
510
  let types = getTypes(drag.items);
336
511
  let selectionManager = localState.state.selectionManager;
337
512
  let target: DropTarget;
513
+ // Update the drop collection ref tracker for useDroppableItem's getDropOperation isInternal check
514
+ setDropCollectionRef(ref);
338
515
 
339
516
  // When entering the droppable collection for the first time, the default drop target
340
517
  // is after the focused key.
@@ -367,8 +544,10 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
367
544
  dropPosition
368
545
  };
369
546
 
547
+ let {draggingKeys} = globalDndState;
548
+ let isInternal = isInternalDropOperation(ref);
370
549
  // If the default target is not valid, find the next one that is.
371
- if (localState.state.getDropOperation(target, types, drag.allowedDropOperations) === 'cancel') {
550
+ if (localState.state.getDropOperation({target, types, allowedOperations: drag.allowedDropOperations, isInternal, draggingKeys}) === 'cancel') {
372
551
  target = nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false)
373
552
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false);
374
553
  }
@@ -382,6 +561,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
382
561
  localState.state.setTarget(target);
383
562
  },
384
563
  onDropExit() {
564
+ setDropCollectionRef(undefined);
385
565
  localState.state.setTarget(null);
386
566
  },
387
567
  onDropTargetEnter(target) {
@@ -402,7 +582,8 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
402
582
  }
403
583
  },
404
584
  onDrop(e, target) {
405
- if (localState.state.target && typeof localState.props.onDrop === 'function') {
585
+ setDropCollectionRef(ref);
586
+ if (localState.state.target) {
406
587
  onDrop(e, target || localState.state.target);
407
588
  }
408
589
  },
@@ -424,6 +605,20 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
424
605
  }
425
606
  break;
426
607
  }
608
+ case 'ArrowLeft': {
609
+ if (keyboardDelegate.getKeyLeftOf) {
610
+ let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getPreviousTarget(target, wrap, true));
611
+ localState.state.setTarget(target);
612
+ }
613
+ break;
614
+ }
615
+ case 'ArrowRight': {
616
+ if (keyboardDelegate.getKeyRightOf) {
617
+ let target = nextValidTarget(localState.state.target, types, drag.allowedDropOperations, (target, wrap) => getNextTarget(target, wrap, true));
618
+ localState.state.setTarget(target);
619
+ }
620
+ break;
621
+ }
427
622
  case 'Home': {
428
623
  if (keyboardDelegate.getFirstKey) {
429
624
  let target = nextValidTarget(null, types, drag.allowedDropOperations, getNextTarget);
@@ -466,7 +661,9 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
466
661
 
467
662
  // If the target does not accept the drop, find the next valid target.
468
663
  // If no next valid target, find the previous valid target.
469
- let operation = localState.state.getDropOperation(target, types, drag.allowedDropOperations);
664
+ let {draggingCollectionRef, draggingKeys} = globalDndState;
665
+ let isInternal = draggingCollectionRef?.current === ref?.current;
666
+ let operation = localState.state.getDropOperation({target, types, allowedOperations: drag.allowedDropOperations, isInternal, draggingKeys});
470
667
  if (operation === 'cancel') {
471
668
  target = nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false)
472
669
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false);
@@ -508,7 +705,9 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
508
705
 
509
706
  // If the target does not accept the drop, find the previous valid target.
510
707
  // If no next valid target, find the next valid target.
511
- let operation = localState.state.getDropOperation(target, types, drag.allowedDropOperations);
708
+ let {draggingKeys} = globalDndState;
709
+ let isInternal = isInternalDropOperation(ref);
710
+ let operation = localState.state.getDropOperation({target, types, allowedOperations: drag.allowedDropOperations, isInternal, draggingKeys});
512
711
  if (operation === 'cancel') {
513
712
  target = nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false)
514
713
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false);
@@ -521,9 +720,10 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
521
720
  }
522
721
  }
523
722
  });
524
- }, [localState, ref, onDrop]);
723
+ }, [localState, ref, onDrop, direction]);
525
724
 
526
- let id = useDroppableCollectionId(state);
725
+ let id = useId();
726
+ droppableCollectionMap.set(state, {id, ref});
527
727
  return {
528
728
  collectionProps: mergeProps(dropProps, {
529
729
  id,