@@ -57,6 +57,9 @@ interface DroppingState {
5757 collection : Collection < Node < unknown > > ,
5858 focusedKey : Key ,
5959 selectedKeys : Set < Key > ,
60+ target : DropTarget ,
61+ draggingKeys : Set < Key > ,
62+ isInternal : boolean ,
6063 timeout : ReturnType < typeof setTimeout >
6164}
6265
@@ -213,26 +216,93 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
213216 } ) ;
214217
215218 let droppingState = useRef < DroppingState > ( null ) ;
216- let onDrop = useCallback ( ( e : DropEvent , target : DropTarget ) => {
219+ let updateFocusAfterDrop = useCallback ( ( ) => {
217220 let { state} = localState ;
221+ if ( droppingState . current ) {
222+ let {
223+ target,
224+ collection : prevCollection ,
225+ selectedKeys : prevSelectedKeys ,
226+ focusedKey : prevFocusedKey ,
227+ isInternal,
228+ draggingKeys
229+ } = droppingState . current ;
230+
231+ // If an insert occurs during a drop, we want to immediately select these items to give
232+ // feedback to the user that a drop occurred. Only do this if the selection didn't change
233+ // since the drop started so we don't override if the user or application did something.
234+ if (
235+ state . collection . size > prevCollection . size &&
236+ state . selectionManager . isSelectionEqual ( prevSelectedKeys )
237+ ) {
238+ let newKeys = new Set < Key > ( ) ;
239+ for ( let key of state . collection . getKeys ( ) ) {
240+ if ( ! prevCollection . getItem ( key ) ) {
241+ newKeys . add ( key ) ;
242+ }
243+ }
218244
219- // Focus the collection.
220- state . selectionManager . setFocused ( true ) ;
245+ state . selectionManager . setSelectedKeys ( newKeys ) ;
221246
222- // Save some state of the collection/selection before the drop occurs so we can compare later.
223- let focusedKey = state . selectionManager . focusedKey ;
247+ // If the focused item didn't change since the drop occurred, also focus the first
248+ // inserted item. If selection is disabled, then also show the focus ring so there
249+ // is some indication that items were added.
250+ if ( state . selectionManager . focusedKey === prevFocusedKey ) {
251+ let first = newKeys . keys ( ) . next ( ) . value ;
252+ let item = state . collection . getItem ( first ) ;
253+
254+ // If this is a cell, focus the parent row.
255+ if ( item ?. type === 'cell' ) {
256+ first = item . parentKey ;
257+ }
258+
259+ state . selectionManager . setFocusedKey ( first ) ;
224260
225- // If parent key was dragged, we want to use it instead (i.e. focus row instead of cell after dropping)
226- if ( globalDndState . draggingKeys . has ( state . collection . getItem ( focusedKey ) ?. parentKey ) ) {
227- focusedKey = state . collection . getItem ( focusedKey ) . parentKey ;
228- state . selectionManager . setFocusedKey ( focusedKey ) ;
261+ if ( state . selectionManager . selectionMode === 'none' ) {
262+ setInteractionModality ( 'keyboard' ) ;
263+ }
264+ }
265+ } else if (
266+ state . selectionManager . focusedKey === prevFocusedKey &&
267+ isInternal &&
268+ target . type === 'item' &&
269+ target . dropPosition !== 'on' &&
270+ draggingKeys . has ( state . collection . getItem ( prevFocusedKey ) ?. parentKey )
271+ ) {
272+ // Focus row instead of cell when reordering.
273+ state . selectionManager . setFocusedKey ( state . collection . getItem ( prevFocusedKey ) . parentKey ) ;
274+ setInteractionModality ( 'keyboard' ) ;
275+ } else if (
276+ state . selectionManager . focusedKey === prevFocusedKey &&
277+ target . type === 'item' &&
278+ target . dropPosition === 'on' &&
279+ state . collection . getItem ( target . key ) != null
280+ ) {
281+ // If focus didn't move already (e.g. due to an insert), and the user dropped on an item,
282+ // focus that item and show the focus ring to give the user feedback that the drop occurred.
283+ // Also show the focus ring if the focused key is not selected, e.g. in case of a reorder.
284+ state . selectionManager . setFocusedKey ( target . key ) ;
285+ setInteractionModality ( 'keyboard' ) ;
286+ } else if ( ! state . selectionManager . isSelected ( state . selectionManager . focusedKey ) ) {
287+ setInteractionModality ( 'keyboard' ) ;
288+ }
289+
290+ state . selectionManager . setFocused ( true ) ;
229291 }
292+ } , [ localState ] ) ;
230293
294+ let onDrop = useCallback ( ( e : DropEvent , target : DropTarget ) => {
295+ let { state} = localState ;
296+
297+ // Save some state of the collection/selection before the drop occurs so we can compare later.
231298 droppingState . current = {
232299 timeout : null ,
233- focusedKey,
300+ focusedKey : state . selectionManager . focusedKey ,
234301 collection : state . collection ,
235- selectedKeys : state . selectionManager . selectedKeys
302+ selectedKeys : state . selectionManager . selectedKeys ,
303+ draggingKeys : globalDndState . draggingKeys ,
304+ isInternal : isInternalDropOperation ( ref ) ,
305+ target
236306 } ;
237307
238308 let onDropFn = localState . props . onDrop || defaultOnDrop ;
@@ -246,26 +316,13 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
246316 } ) ;
247317
248318 // Wait for a short time period after the onDrop is called to allow the data to be read asynchronously
249- // and for React to re-render. If an insert occurs during this time, it will be selected/focused below.
250- // If items are not "immediately" inserted by the onDrop handler, the application will need to handle
251- // selecting and focusing those items themselves.
319+ // and for React to re-render. If the collection didn't already change during this time (handled below),
320+ // update the focused key here.
252321 droppingState . current . timeout = setTimeout ( ( ) => {
253- // If focus didn't move already (e.g. due to an insert), and the user dropped on an item,
254- // focus that item and show the focus ring to give the user feedback that the drop occurred.
255- // Also show the focus ring if the focused key is not selected, e.g. in case of a reorder.
256- let { state} = localState ;
257-
258- if ( target . type === 'item' && target . dropPosition === 'on' && state . collection . getItem ( target . key ) != null ) {
259- state . selectionManager . setFocusedKey ( target . key ) ;
260- state . selectionManager . setFocused ( true ) ;
261- setInteractionModality ( 'keyboard' ) ;
262- } else if ( ! state . selectionManager . isSelected ( focusedKey ) ) {
263- setInteractionModality ( 'keyboard' ) ;
264- }
265-
322+ updateFocusAfterDrop ( ) ;
266323 droppingState . current = null ;
267324 } , 50 ) ;
268- } , [ localState , defaultOnDrop ] ) ;
325+ } , [ localState , defaultOnDrop , ref , updateFocusAfterDrop ] ) ;
269326
270327 // eslint-disable-next-line arrow-body-style
271328 useEffect ( ( ) => {
@@ -277,44 +334,9 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
277334 } , [ ] ) ;
278335
279336 useLayoutEffect ( ( ) => {
280- // If an insert occurs during a drop, we want to immediately select these items to give
281- // feedback to the user that a drop occurred. Only do this if the selection didn't change
282- // since the drop started so we don't override if the user or application did something.
283- if (
284- droppingState . current &&
285- state . selectionManager . isFocused &&
286- state . collection . size > droppingState . current . collection . size &&
287- state . selectionManager . isSelectionEqual ( droppingState . current . selectedKeys )
288- ) {
289- let newKeys = new Set < Key > ( ) ;
290- for ( let key of state . collection . getKeys ( ) ) {
291- if ( ! droppingState . current . collection . getItem ( key ) ) {
292- newKeys . add ( key ) ;
293- }
294- }
295-
296- state . selectionManager . setSelectedKeys ( newKeys ) ;
297-
298- // If the focused item didn't change since the drop occurred, also focus the first
299- // inserted item. If selection is disabled, then also show the focus ring so there
300- // is some indication that items were added.
301- if ( state . selectionManager . focusedKey === droppingState . current . focusedKey ) {
302- let first = newKeys . keys ( ) . next ( ) . value ;
303- let item = state . collection . getItem ( first ) ;
304-
305- // If this is a cell, focus the parent row.
306- if ( item ?. type === 'cell' ) {
307- first = item . parentKey ;
308- }
309-
310- state . selectionManager . setFocusedKey ( first ) ;
311-
312- if ( state . selectionManager . selectionMode === 'none' ) {
313- setInteractionModality ( 'keyboard' ) ;
314- }
315- }
316-
317- droppingState . current = null ;
337+ // If the collection changed after a drop, update the focused key.
338+ if ( droppingState . current && state . collection !== droppingState . current . collection ) {
339+ updateFocusAfterDrop ( ) ;
318340 }
319341 } ) ;
320342
@@ -470,6 +492,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
470492
471493 return DragManager . registerDropTarget ( {
472494 element : ref . current ,
495+ preventFocusOnDrop : true ,
473496 getDropOperation ( types , allowedOperations ) {
474497 if ( localState . state . target ) {
475498 let { draggingKeys} = globalDndState ;
0 commit comments