@react-aria/dnd 3.0.0-alpha.8 → 3.0.0-nightly-641446f65-240905

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 +38 -1855
  123. package/dist/main.js.map +1 -1
  124. package/dist/module.js +24 -1838
  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 +184 -32
  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 +54 -0
  261. package/src/ListDropTargetDelegate.ts +173 -0
  262. package/src/constants.ts +2 -0
  263. package/src/index.ts +56 -7
  264. package/src/useAutoScroll.ts +26 -6
  265. package/src/useClipboard.ts +65 -59
  266. package/src/useDrag.ts +195 -61
  267. package/src/useDraggableCollection.ts +29 -0
  268. package/src/useDraggableItem.ts +106 -15
  269. package/src/useDrop.ts +273 -93
  270. package/src/useDropIndicator.ts +49 -23
  271. package/src/useDroppableCollection.ts +317 -122
  272. package/src/useDroppableItem.ts +35 -19
  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
- interface DroppableCollectionOptions extends DroppableCollectionProps {
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
- interface DroppableCollectionResult {
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,46 +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
- if (state.isDropTarget(target)) {
72
- localState.nextTarget = target;
73
- return localState.dropOperation;
74
- }
75
-
76
- localState.dropOperation = state.getDropOperation(target, types, allowedOperations);
169
+ localState.dropOperation = state.getDropOperation({target, types, allowedOperations, isInternal, draggingKeys});
77
170
 
78
171
  // If the target doesn't accept the drop, see if the root accepts it instead.
79
172
  if (localState.dropOperation === 'cancel') {
80
173
  let rootTarget: DropTarget = {type: 'root'};
81
- let dropOperation = state.getDropOperation(rootTarget, types, allowedOperations);
174
+ let dropOperation = state.getDropOperation({target: rootTarget, types, allowedOperations, isInternal, draggingKeys});
82
175
  if (dropOperation !== 'cancel') {
83
176
  target = rootTarget;
84
177
  localState.dropOperation = dropOperation;
85
178
  }
86
179
  }
87
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
+ }
88
186
  localState.nextTarget = localState.dropOperation === 'cancel' ? null : target;
89
187
  return localState.dropOperation;
90
188
  },
91
189
  onDropExit() {
190
+ setDropCollectionRef(undefined);
92
191
  state.setTarget(null);
93
192
  autoScroll.stop();
94
193
  },
@@ -103,29 +202,112 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
103
202
  }
104
203
  },
105
204
  onDrop(e) {
106
- if (state.target && typeof props.onDrop === 'function') {
205
+ setDropCollectionRef(ref);
206
+ if (state.target) {
107
207
  onDrop(e, state.target);
108
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
+ }
109
216
  }
110
217
  });
111
218
 
112
219
  let droppingState = useRef<DroppingState>(null);
113
- let onDrop = useCallback((e: DropEvent, target: DropTarget) => {
220
+ let updateFocusAfterDrop = useCallback(() => {
114
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);
115
247
 
116
- // Focus the collection.
117
- state.selectionManager.setFocused(true);
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);
254
+
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
+ }
290
+
291
+ state.selectionManager.setFocused(true);
292
+ }
293
+ }, [localState]);
118
294
 
295
+ let onDrop = useCallback((e: DropEvent, target: DropTarget) => {
296
+ let {state} = localState;
297
+
119
298
  // Save some state of the collection/selection before the drop occurs so we can compare later.
120
- let focusedKey = state.selectionManager.focusedKey;
121
299
  droppingState.current = {
122
300
  timeout: null,
123
- focusedKey,
301
+ focusedKey: state.selectionManager.focusedKey,
124
302
  collection: state.collection,
125
- selectedKeys: state.selectionManager.selectedKeys
303
+ selectedKeys: state.selectionManager.selectedKeys,
304
+ draggingKeys: globalDndState.draggingKeys,
305
+ isInternal: isInternalDropOperation(ref),
306
+ target
126
307
  };
127
308
 
128
- localState.props.onDrop({
309
+ let onDropFn = localState.props.onDrop || defaultOnDrop;
310
+ onDropFn({
129
311
  type: 'drop',
130
312
  x: e.x, // todo
131
313
  y: e.y,
@@ -135,27 +317,13 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
135
317
  });
136
318
 
137
319
  // Wait for a short time period after the onDrop is called to allow the data to be read asynchronously
138
- // and for React to re-render. If an insert occurs during this time, it will be selected/focused below.
139
- // If items are not "immediately" inserted by the onDrop handler, the application will need to handle
140
- // 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.
141
322
  droppingState.current.timeout = setTimeout(() => {
142
- // If focus didn't move already (e.g. due to an insert), and the user dropped on an item,
143
- // focus that item and show the focus ring to give the user feedback that the drop occurred.
144
- // Also show the focus ring if the focused key is not selected, e.g. in case of a reorder.
145
- let {state} = localState;
146
- if (state.selectionManager.focusedKey === focusedKey) {
147
- if (target.type === 'item' && target.dropPosition === 'on' && state.collection.getItem(target.key) != null) {
148
- state.selectionManager.setFocusedKey(target.key);
149
- state.selectionManager.setFocused(true);
150
- setInteractionModality('keyboard');
151
- } else if (!state.selectionManager.isSelected(focusedKey)) {
152
- setInteractionModality('keyboard');
153
- }
154
- }
155
-
323
+ updateFocusAfterDrop();
156
324
  droppingState.current = null;
157
325
  }, 50);
158
- }, [localState]);
326
+ }, [localState, defaultOnDrop, ref, updateFocusAfterDrop]);
159
327
 
160
328
  // eslint-disable-next-line arrow-body-style
161
329
  useEffect(() => {
@@ -167,42 +335,15 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
167
335
  }, []);
168
336
 
169
337
  useLayoutEffect(() => {
170
- // If an insert occurs during a drop, we want to immediately select these items to give
171
- // feedback to the user that a drop occurred. Only do this if the selection didn't change
172
- // since the drop started so we don't override if the user or application did something.
173
- if (
174
- droppingState.current &&
175
- state.selectionManager.isFocused &&
176
- state.collection.size > droppingState.current.collection.size &&
177
- state.selectionManager.isSelectionEqual(droppingState.current.selectedKeys)
178
- ) {
179
- let newKeys = new Set<Key>();
180
- for (let key of state.collection.getKeys()) {
181
- if (!droppingState.current.collection.getItem(key)) {
182
- newKeys.add(key);
183
- }
184
- }
185
-
186
- state.selectionManager.setSelectedKeys(newKeys);
187
-
188
- // If the focused item didn't change since the drop occurred, also focus the first
189
- // inserted item. If selection is disabled, then also show the focus ring so there
190
- // is some indication that items were added.
191
- if (state.selectionManager.focusedKey === droppingState.current.focusedKey) {
192
- let first = newKeys.keys().next().value;
193
- state.selectionManager.setFocusedKey(first);
194
-
195
- if (state.selectionManager.selectionMode === 'none') {
196
- setInteractionModality('keyboard');
197
- }
198
- }
199
-
200
- 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();
201
341
  }
202
342
  });
203
343
 
344
+ let {direction} = useLocale();
204
345
  useEffect(() => {
205
- let getNextTarget = (target: DropTarget, wrap = true): DropTarget => {
346
+ let getNextTarget = (target: DropTarget, wrap = true, horizontal = false): DropTarget => {
206
347
  if (!target) {
207
348
  return {
208
349
  type: 'root'
@@ -210,26 +351,38 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
210
351
  }
211
352
 
212
353
  let {keyboardDelegate} = localState.props;
213
- let nextKey = target.type === 'item'
214
- ? keyboardDelegate.getKeyBelow(target.key)
215
- : keyboardDelegate.getFirstKey();
216
- 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];
217
362
 
218
363
  if (target.type === 'item') {
219
- let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
220
- let nextDropPosition = DROP_POSITIONS[positionIndex + 1];
221
- if (positionIndex < DROP_POSITIONS.length - 1 && !(nextDropPosition === 'after' && nextKey != null)) {
222
- return {
223
- type: 'item',
224
- key: target.key,
225
- dropPosition: nextDropPosition
226
- };
227
- }
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
+ }
228
378
 
229
- // If the last drop position was 'after', then 'before' on the next key is equivalent.
230
- // Switch to 'on' instead.
231
- if (target.dropPosition === 'after') {
232
- 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;
233
386
  }
234
387
  }
235
388
 
@@ -250,28 +403,40 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
250
403
  };
251
404
  };
252
405
 
253
- let getPreviousTarget = (target: DropTarget, wrap = true): DropTarget => {
406
+ let getPreviousTarget = (target: DropTarget, wrap = true, horizontal = false): DropTarget => {
254
407
  let {keyboardDelegate} = localState.props;
255
- let nextKey = target?.type === 'item'
256
- ? keyboardDelegate.getKeyAbove(target.key)
257
- : keyboardDelegate.getLastKey();
258
- 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';
259
416
 
260
417
  if (target?.type === 'item') {
261
- let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
262
- let nextDropPosition = DROP_POSITIONS[positionIndex - 1];
263
- if (positionIndex > 0 && nextDropPosition !== 'after') {
264
- return {
265
- type: 'item',
266
- key: target.key,
267
- dropPosition: nextDropPosition
268
- };
269
- }
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
+ }
270
432
 
271
- // If the last drop position was 'before', then 'after' on the previous key is equivalent.
272
- // Switch to 'on' instead.
273
- if (target.dropPosition === 'before') {
274
- 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;
275
440
  }
276
441
  }
277
442
 
@@ -301,13 +466,15 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
301
466
  ): DropTarget => {
302
467
  let seenRoot = 0;
303
468
  let operation: DropOperation;
469
+ let {draggingKeys} = globalDndState;
470
+ let isInternal = isInternalDropOperation(ref);
304
471
  do {
305
472
  let nextTarget = getNextTarget(target, wrap);
306
473
  if (!nextTarget) {
307
474
  return null;
308
475
  }
309
476
  target = nextTarget;
310
- operation = localState.state.getDropOperation(nextTarget, types, allowedDropOperations);
477
+ operation = localState.state.getDropOperation({target: nextTarget, types, allowedOperations: allowedDropOperations, isInternal, draggingKeys});
311
478
  if (target.type === 'root') {
312
479
  seenRoot++;
313
480
  }
@@ -326,9 +493,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
326
493
 
327
494
  return DragManager.registerDropTarget({
328
495
  element: ref.current,
496
+ preventFocusOnDrop: true,
329
497
  getDropOperation(types, allowedOperations) {
330
498
  if (localState.state.target) {
331
- 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});
332
502
  }
333
503
 
334
504
  // Check if any of the targets accept the drop.
@@ -340,6 +510,8 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
340
510
  let types = getTypes(drag.items);
341
511
  let selectionManager = localState.state.selectionManager;
342
512
  let target: DropTarget;
513
+ // Update the drop collection ref tracker for useDroppableItem's getDropOperation isInternal check
514
+ setDropCollectionRef(ref);
343
515
 
344
516
  // When entering the droppable collection for the first time, the default drop target
345
517
  // is after the focused key.
@@ -372,8 +544,10 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
372
544
  dropPosition
373
545
  };
374
546
 
547
+ let {draggingKeys} = globalDndState;
548
+ let isInternal = isInternalDropOperation(ref);
375
549
  // If the default target is not valid, find the next one that is.
376
- if (localState.state.getDropOperation(target, types, drag.allowedDropOperations) === 'cancel') {
550
+ if (localState.state.getDropOperation({target, types, allowedOperations: drag.allowedDropOperations, isInternal, draggingKeys}) === 'cancel') {
377
551
  target = nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false)
378
552
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false);
379
553
  }
@@ -387,6 +561,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
387
561
  localState.state.setTarget(target);
388
562
  },
389
563
  onDropExit() {
564
+ setDropCollectionRef(undefined);
390
565
  localState.state.setTarget(null);
391
566
  },
392
567
  onDropTargetEnter(target) {
@@ -407,7 +582,8 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
407
582
  }
408
583
  },
409
584
  onDrop(e, target) {
410
- if (localState.state.target && typeof localState.props.onDrop === 'function') {
585
+ setDropCollectionRef(ref);
586
+ if (localState.state.target) {
411
587
  onDrop(e, target || localState.state.target);
412
588
  }
413
589
  },
@@ -429,6 +605,20 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
429
605
  }
430
606
  break;
431
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
+ }
432
622
  case 'Home': {
433
623
  if (keyboardDelegate.getFirstKey) {
434
624
  let target = nextValidTarget(null, types, drag.allowedDropOperations, getNextTarget);
@@ -471,7 +661,9 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
471
661
 
472
662
  // If the target does not accept the drop, find the next valid target.
473
663
  // If no next valid target, find the previous valid target.
474
- 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});
475
667
  if (operation === 'cancel') {
476
668
  target = nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false)
477
669
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false);
@@ -513,7 +705,9 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
513
705
 
514
706
  // If the target does not accept the drop, find the previous valid target.
515
707
  // If no next valid target, find the next valid target.
516
- 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});
517
711
  if (operation === 'cancel') {
518
712
  target = nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false)
519
713
  ?? nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false);
@@ -526,9 +720,10 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
526
720
  }
527
721
  }
528
722
  });
529
- }, [localState, ref, onDrop]);
723
+ }, [localState, ref, onDrop, direction]);
530
724
 
531
- let id = useDroppableCollectionId(state);
725
+ let id = useId();
726
+ droppableCollectionMap.set(state, {id, ref});
532
727
  return {
533
728
  collectionProps: mergeProps(dropProps, {
534
729
  id,