@react-aria/dnd 3.0.0-alpha.0 → 3.0.0-alpha.4

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.
@@ -1 +1 @@
1
- {"mappings":"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","sources":["./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/constants.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/utils.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/DragManager.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDrag.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useVirtualDrop.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDrop.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useAutoScroll.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDroppableCollection.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDroppableItem.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDropIndicator.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDraggableItem.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useClipboard.ts","./packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null,null],"names":[],"version":3,"file":"types.d.ts.map"}
1
+ {"mappings":";;;;AGyBA;IACE,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,MAAM,QAAQ,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,OAAO,CAAC;IACnD,wBAAwB,CAAC,EAAE,MAAM,aAAa,EAAE,CAAA;CACjD;AAED;IACE,SAAS,EAAE,eAAe,WAAW,CAAC,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,UAAU,EAAE,OAAO,CAAA;CACpB;AAiBD,wBAAwB,OAAO,EAAE,WAAW,GAAG,UAAU,CA8JxD;AEjMD;IACE,GAAG,EAAE,UAAU,WAAW,CAAC,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,SAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,KAAK,aAAa,CAAC;IAC5F,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,SAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC;IAC1H,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAGxC,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;CAChC;AAED;IACE,SAAS,EAAE,eAAe,WAAW,CAAC,CAAC;IACvC,YAAY,EAAE,OAAO,CAAA;CACtB;AAID,wBAAwB,OAAO,EAAE,WAAW,GAAG,UAAU,CAsMxD;AEvND,oCAAqC,SAAQ,wBAAwB;IACnE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,sBAAsB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAA;CACpE;AAED;IACE,eAAe,EAAE,eAAe,WAAW,CAAC,CAAA;CAC7C;AAWD,uCAAuC,KAAK,EAAE,0BAA0B,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,yBAAyB,CAkfjK;ACxgBD;IACE,MAAM,EAAE,UAAU,CAAA;CACnB;AAED;IACE,SAAS,EAAE,eAAe,WAAW,CAAC,CAAA;CACvC;AAED,iCAAiC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,mBAAmB,CAuCjJ;AC3CD;IACE,MAAM,EAAE,UAAU,CAAA;CACnB;AAED;IACE,kBAAkB,EAAE,eAAe,WAAW,CAAC,CAAA;CAChD;AAED,iCAAiC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,UAAU,WAAW,CAAC,GAAG,iBAAiB,CAsD3I;ACjED;IACE,GAAG,EAAE,GAAG,CAAA;CACT;AAED;IACE,SAAS,EAAE,eAAe,WAAW,CAAC,CAAC;IACvC,eAAe,EAAE,eAAe,CAAA;CACjC;AAED,iCAAiC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,wBAAwB,GAAG,mBAAmB,CAqChH;AChDD;IACE,QAAQ,CAAC,EAAE,MAAM,QAAQ,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;CACtC;AAED;IACE,cAAc,EAAE,eAAe,WAAW,CAAC,CAAA;CAC5C;AA6BD,6BAA6B,OAAO,EAAE,cAAc,GAAG,eAAe,CAkFrE","sources":["packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/constants.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/utils.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/DragManager.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDrag.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useVirtualDrop.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDrop.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useAutoScroll.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDroppableCollection.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDroppableItem.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDropIndicator.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDraggableItem.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useClipboard.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/index.ts","packages/@react-aria/dnd/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport * from './useDrag';\nexport * from './useDrop';\nexport * from './useDroppableCollection';\nexport * from './useDroppableItem';\nexport * from './useDropIndicator';\nexport * from './useDraggableItem';\nexport * from './useClipboard';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/dnd",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-alpha.4",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -18,16 +18,16 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@react-aria/i18n": "^3.3.0",
22
- "@react-aria/interactions": "^3.3.4",
23
- "@react-aria/live-announcer": "3.0.0-rc.0",
24
- "@react-aria/overlays": "^3.6.2",
25
- "@react-aria/utils": "^3.7.0",
26
- "@react-aria/visually-hidden": "^3.2.1",
27
- "@react-stately/dnd": "3.0.0-alpha.0",
28
- "@react-stately/selection": "^3.4.0",
29
- "@react-types/button": "^3.2.1",
30
- "@react-types/shared": "^3.5.0"
21
+ "@react-aria/i18n": "^3.3.5",
22
+ "@react-aria/interactions": "^3.8.0",
23
+ "@react-aria/live-announcer": "^3.0.2",
24
+ "@react-aria/overlays": "^3.7.4",
25
+ "@react-aria/utils": "^3.11.1",
26
+ "@react-aria/visually-hidden": "^3.2.4",
27
+ "@react-stately/dnd": "3.0.0-alpha.3",
28
+ "@react-stately/selection": "^3.9.1",
29
+ "@react-types/button": "^3.4.2",
30
+ "@react-types/shared": "^3.11.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": "^16.8.0 || ^17.0.0-rc.1",
@@ -36,5 +36,5 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "gitHead": "9920ffaa2596a03c4498a15cb940bd2f4ba5cd6a"
39
+ "gitHead": "54c2366c4f31bd4bf619126131cd583c12972acc"
40
40
  }
@@ -119,7 +119,9 @@ const CANCELED_EVENTS = [
119
119
  'touchstart',
120
120
  'touchmove',
121
121
  'touchend',
122
- 'keyup'
122
+ 'keyup',
123
+ 'focusin',
124
+ 'focusout'
123
125
  ];
124
126
 
125
127
  const CLICK_EVENTS = [
@@ -166,17 +168,9 @@ class DragSession {
166
168
  document.addEventListener(event, this.cancelEvent, true);
167
169
  }
168
170
 
169
- this.mutationObserver = new MutationObserver(() => {
170
- // JSDOM has a bug where MutationObserver enters an infinite loop if mutations
171
- // occur inside a MutationObserver callback. If running in Node, wait until
172
- // the next tick to update valid drop targets.
173
- // See https://github.com/jsdom/jsdom/issues/3096
174
- if (typeof setImmediate === 'function') {
175
- this.mutationImmediate = setImmediate(() => this.updateValidDropTargets());
176
- } else {
177
- this.updateValidDropTargets();
178
- }
179
- });
171
+ this.mutationObserver = new MutationObserver(() =>
172
+ this.updateValidDropTargets()
173
+ );
180
174
  this.updateValidDropTargets();
181
175
 
182
176
  announce(this.formatMessage(MESSAGES[getDragModality()]));
package/src/constants.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {DropOperation} from '@react-types/shared';
14
+
13
15
  export enum DROP_OPERATION {
14
16
  none = 0,
15
17
  cancel = 0,
@@ -31,7 +33,7 @@ export const DROP_OPERATION_ALLOWED = {
31
33
 
32
34
  export const EFFECT_ALLOWED = invert(DROP_OPERATION_ALLOWED);
33
35
  export const DROP_EFFECT = invert(DROP_OPERATION);
34
- export const DROP_EFFECT_TO_DROP_OPERATION = {
36
+ export const DROP_EFFECT_TO_DROP_OPERATION: {[name: string]: DropOperation} = {
35
37
  none: 'cancel',
36
38
  link: 'link',
37
39
  copy: 'copy',
package/src/useDrop.ts CHANGED
@@ -10,11 +10,12 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {DragEvent, HTMLAttributes, RefObject, useLayoutEffect, useRef, useState} from 'react';
13
+ import {DragEvent, HTMLAttributes, RefObject, useRef, useState} from 'react';
14
14
  import * as DragManager from './DragManager';
15
15
  import {DragTypes, readFromDataTransfer} from './utils';
16
16
  import {DROP_EFFECT_TO_DROP_OPERATION, DROP_OPERATION, DROP_OPERATION_ALLOWED, DROP_OPERATION_TO_DROP_EFFECT} from './constants';
17
17
  import {DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropMoveEvent, DropOperation, DragTypes as IDragTypes} from '@react-types/shared';
18
+ import {useLayoutEffect} from '@react-aria/utils';
18
19
  import {useVirtualDrop} from './useVirtualDrop';
19
20
 
20
21
  interface DropOptions {
@@ -43,12 +44,13 @@ export function useDrop(options: DropOptions): DropResult {
43
44
  x: 0,
44
45
  y: 0,
45
46
  dragEnterCount: 0,
46
- dropEffect: 'none',
47
+ dropEffect: 'none' as DataTransfer['dropEffect'],
47
48
  dropActivateTimer: null
48
49
  }).current;
49
50
 
50
51
  let onDragOver = (e: DragEvent) => {
51
52
  e.preventDefault();
53
+ e.stopPropagation();
52
54
 
53
55
  if (e.clientX === state.x && e.clientY === state.y) {
54
56
  e.dataTransfer.dropEffect = state.dropEffect;
@@ -92,6 +94,7 @@ export function useDrop(options: DropOptions): DropResult {
92
94
  };
93
95
 
94
96
  let onDragEnter = (e: DragEvent) => {
97
+ e.stopPropagation();
95
98
  state.dragEnterCount++;
96
99
  if (state.dragEnterCount > 1) {
97
100
  return;
@@ -132,6 +135,7 @@ export function useDrop(options: DropOptions): DropResult {
132
135
  };
133
136
 
134
137
  let onDragLeave = (e: DragEvent) => {
138
+ e.stopPropagation();
135
139
  state.dragEnterCount--;
136
140
  if (state.dragEnterCount > 0) {
137
141
  return;
@@ -152,6 +156,7 @@ export function useDrop(options: DropOptions): DropResult {
152
156
 
153
157
  let onDrop = (e: DragEvent) => {
154
158
  e.preventDefault();
159
+ e.stopPropagation();
155
160
 
156
161
  if (typeof options.onDrop === 'function') {
157
162
  let dropOperation = DROP_EFFECT_TO_DROP_OPERATION[state.dropEffect];
@@ -10,12 +10,13 @@
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
14
  import * as DragManager from './DragManager';
14
- import {DropOperation, DroppableCollectionProps, DropPosition, DropTarget, KeyboardDelegate} from '@react-types/shared';
15
15
  import {DroppableCollectionState} from '@react-stately/dnd';
16
16
  import {getTypes} from './utils';
17
- import {HTMLAttributes, RefObject, useEffect, useRef} from 'react';
18
- import {mergeProps} from '@react-aria/utils';
17
+ import {HTMLAttributes, Key, RefObject, useCallback, useEffect, useRef} from 'react';
18
+ import {mergeProps, useLayoutEffect} from '@react-aria/utils';
19
+ import {setInteractionModality} from '@react-aria/interactions';
19
20
  import {useAutoScroll} from './useAutoScroll';
20
21
  import {useDrop} from './useDrop';
21
22
  import {useDroppableCollectionId} from './utils';
@@ -29,6 +30,13 @@ interface DroppableCollectionResult {
29
30
  collectionProps: HTMLAttributes<HTMLElement>
30
31
  }
31
32
 
33
+ interface DroppingState {
34
+ collection: Collection<Node<unknown>>,
35
+ focusedKey: Key,
36
+ selectedKeys: Set<Key>,
37
+ timeout: NodeJS.Timeout
38
+ }
39
+
32
40
  const DROP_POSITIONS: DropPosition[] = ['before', 'on', 'after'];
33
41
 
34
42
  export function useDroppableCollection(props: DroppableCollectionOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableCollectionResult {
@@ -96,18 +104,103 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
96
104
  },
97
105
  onDrop(e) {
98
106
  if (state.target && typeof props.onDrop === 'function') {
99
- props.onDrop({
100
- type: 'drop',
101
- x: e.x, // todo
102
- y: e.y,
103
- target: state.target,
104
- items: e.items,
105
- dropOperation: e.dropOperation
106
- });
107
+ onDrop(e, state.target);
107
108
  }
108
109
  }
109
110
  });
110
111
 
112
+ let droppingState = useRef<DroppingState>(null);
113
+ let onDrop = useCallback((e: DropEvent, target: DropTarget) => {
114
+ let {state} = localState;
115
+
116
+ // Focus the collection.
117
+ state.selectionManager.setFocused(true);
118
+
119
+ // Save some state of the collection/selection before the drop occurs so we can compare later.
120
+ let focusedKey = state.selectionManager.focusedKey;
121
+ droppingState.current = {
122
+ timeout: null,
123
+ focusedKey,
124
+ collection: state.collection,
125
+ selectedKeys: state.selectionManager.selectedKeys
126
+ };
127
+
128
+ localState.props.onDrop({
129
+ type: 'drop',
130
+ x: e.x, // todo
131
+ y: e.y,
132
+ target,
133
+ items: e.items,
134
+ dropOperation: e.dropOperation
135
+ });
136
+
137
+ // 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.
141
+ 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
+
156
+ droppingState.current = null;
157
+ }, 50);
158
+ }, [localState]);
159
+
160
+ // eslint-disable-next-line arrow-body-style
161
+ useEffect(() => {
162
+ return () => {
163
+ if (droppingState.current) {
164
+ clearTimeout(droppingState.current.timeout);
165
+ }
166
+ };
167
+ }, []);
168
+
169
+ 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;
201
+ }
202
+ });
203
+
111
204
  useEffect(() => {
112
205
  let getNextTarget = (target: DropTarget, wrap = true): DropTarget => {
113
206
  if (!target) {
@@ -120,6 +213,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
120
213
  let nextKey = target.type === 'item'
121
214
  ? keyboardDelegate.getKeyBelow(target.key)
122
215
  : keyboardDelegate.getFirstKey();
216
+ let dropPosition: DropPosition = 'before';
123
217
 
124
218
  if (target.type === 'item') {
125
219
  let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
@@ -131,6 +225,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
131
225
  dropPosition: nextDropPosition
132
226
  };
133
227
  }
228
+
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';
233
+ }
134
234
  }
135
235
 
136
236
  if (nextKey == null) {
@@ -146,7 +246,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
146
246
  return {
147
247
  type: 'item',
148
248
  key: nextKey,
149
- dropPosition: DROP_POSITIONS[0]
249
+ dropPosition
150
250
  };
151
251
  };
152
252
 
@@ -155,6 +255,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
155
255
  let nextKey = target?.type === 'item'
156
256
  ? keyboardDelegate.getKeyAbove(target.key)
157
257
  : keyboardDelegate.getLastKey();
258
+ let dropPosition: DropPosition = !target || target.type === 'root' ? 'after' : 'on';
158
259
 
159
260
  if (target?.type === 'item') {
160
261
  let positionIndex = DROP_POSITIONS.indexOf(target.dropPosition);
@@ -166,6 +267,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
166
267
  dropPosition: nextDropPosition
167
268
  };
168
269
  }
270
+
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';
275
+ }
169
276
  }
170
277
 
171
278
  if (nextKey == null) {
@@ -181,7 +288,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
181
288
  return {
182
289
  type: 'item',
183
290
  key: nextKey,
184
- dropPosition: !target || target.type === 'root' ? 'after' : 'on'
291
+ dropPosition
185
292
  };
186
293
  };
187
294
 
@@ -205,7 +312,6 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
205
312
  seenRoot++;
206
313
  }
207
314
  } while (
208
- target &&
209
315
  operation === 'cancel' &&
210
316
  !localState.state.isDropTarget(target) &&
211
317
  seenRoot < 2
@@ -232,7 +338,52 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
232
338
  },
233
339
  onDropEnter(e, drag) {
234
340
  let types = getTypes(drag.items);
235
- let target = nextValidTarget(null, types, drag.allowedDropOperations, getNextTarget);
341
+ let selectionManager = localState.state.selectionManager;
342
+ let target: DropTarget;
343
+
344
+ // When entering the droppable collection for the first time, the default drop target
345
+ // is after the focused key.
346
+ let key = selectionManager.focusedKey;
347
+ let dropPosition: DropPosition = 'after';
348
+
349
+ // If the focused key is a cell, get the parent item instead.
350
+ // For now, we assume that individual cells cannot be dropped on.
351
+ let item = localState.state.collection.getItem(key);
352
+ if (item?.type === 'cell') {
353
+ key = item.parentKey;
354
+ }
355
+
356
+ // If the focused item is also selected, the default drop target is after the last selected item.
357
+ // But if the focused key is the first selected item, then default to before the first selected item.
358
+ // This is to make reordering lists slightly easier. If you select top down, we assume you want to
359
+ // move the items down. If you select bottom up, we assume you want to move the items up.
360
+ if (selectionManager.isSelected(key)) {
361
+ if (selectionManager.selectedKeys.size > 1 && selectionManager.firstSelectedKey === key) {
362
+ dropPosition = 'before';
363
+ } else {
364
+ key = selectionManager.lastSelectedKey;
365
+ }
366
+ }
367
+
368
+ if (key != null) {
369
+ target = {
370
+ type: 'item',
371
+ key,
372
+ dropPosition
373
+ };
374
+
375
+ // If the default target is not valid, find the next one that is.
376
+ if (localState.state.getDropOperation(target, types, drag.allowedDropOperations) === 'cancel') {
377
+ target = nextValidTarget(target, types, drag.allowedDropOperations, getNextTarget, false)
378
+ ?? nextValidTarget(target, types, drag.allowedDropOperations, getPreviousTarget, false);
379
+ }
380
+ }
381
+
382
+ // If no focused key, then start from the root.
383
+ if (!target) {
384
+ target = nextValidTarget(null, types, drag.allowedDropOperations, getNextTarget);
385
+ }
386
+
236
387
  localState.state.setTarget(target);
237
388
  },
238
389
  onDropExit() {
@@ -257,14 +408,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
257
408
  },
258
409
  onDrop(e, target) {
259
410
  if (localState.state.target && typeof localState.props.onDrop === 'function') {
260
- localState.props.onDrop({
261
- type: 'drop',
262
- x: e.x, // todo
263
- y: e.y,
264
- target: target || localState.state.target,
265
- items: e.items,
266
- dropOperation: e.dropOperation
267
- });
411
+ onDrop(e, target || localState.state.target);
268
412
  }
269
413
  },
270
414
  onKeyDown(e, drag) {
@@ -382,7 +526,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
382
526
  }
383
527
  }
384
528
  });
385
- }, [localState, ref]);
529
+ }, [localState, ref, onDrop]);
386
530
 
387
531
  let id = useDroppableCollectionId(state);
388
532
  return {
package/src/utils.ts CHANGED
@@ -154,7 +154,7 @@ export class DragTypes implements IDragTypes {
154
154
 
155
155
  // In Safari, when dragging files, the dataTransfer.items list is empty, but dataTransfer.types contains "Files".
156
156
  // Unfortunately, this doesn't tell us what types of files the user is dragging, so we need to assume that any
157
- // type the user checks for is included.
157
+ // type the user checks for is included. See https://bugs.webkit.org/show_bug.cgi?id=223517.
158
158
  this.includesUnknownTypes = !hasFiles && dataTransfer.types.includes('Files');
159
159
  }
160
160
 
@@ -208,10 +208,11 @@ export function readFromDataTransfer(dataTransfer: DataTransfer) {
208
208
  if (typeof item.webkitGetAsEntry === 'function') {
209
209
  let entry: FileSystemEntry = item.webkitGetAsEntry();
210
210
  if (!entry) {
211
- // For some reason, Chrome and Firefox include an item with type image/png when copy
212
- // and pasting any file or directory (no matter the type), but return `null` for both
211
+ // For some reason, Firefox includes an item with type image/png when copy
212
+ // and pasting any file or directory (no matter the type), but returns `null` for both
213
213
  // item.getAsFile() and item.webkitGetAsEntry(). Safari works as expected. Ignore this
214
- // item if this happens.
214
+ // item if this happens. See https://bugzilla.mozilla.org/show_bug.cgi?id=1699743.
215
+ // This was recently fixed in Chrome Canary: https://bugs.chromium.org/p/chromium/issues/detail?id=1175483.
215
216
  continue;
216
217
  }
217
218
 
@@ -276,25 +277,6 @@ function createDirectoryItem(entry: any): DirectoryItem {
276
277
  };
277
278
  }
278
279
 
279
- interface FileSystemFileEntry {
280
- isFile: true,
281
- isDirectory: false,
282
- name: string,
283
- file(successCallback: (file: File) => void, errorCallback?: (error: Error) => void): void
284
- }
285
-
286
- interface FileSystemDirectoryEntry {
287
- isDirectory: true,
288
- isFile: false,
289
- name: string,
290
- createReader(): FileSystemDirectoryReader
291
- }
292
-
293
- type FileSystemEntry = FileSystemFileEntry | FileSystemDirectoryEntry;
294
- interface FileSystemDirectoryReader {
295
- readEntries(successCallback: (entries: FileSystemEntry[]) => void, errorCallback?: (error: Error) => void): void
296
- }
297
-
298
280
  async function *getEntries(item: FileSystemDirectoryEntry): AsyncIterable<FileItem | DirectoryItem> {
299
281
  let reader = item.createReader();
300
282
 
@@ -308,7 +290,7 @@ async function *getEntries(item: FileSystemDirectoryEntry): AsyncIterable<FileIt
308
290
 
309
291
  for (let entry of entries) {
310
292
  if (entry.isFile) {
311
- let file = await getEntryFile(entry);
293
+ let file = await getEntryFile(entry as FileSystemFileEntry);
312
294
  yield createFileItem(file);
313
295
  } else if (entry.isDirectory) {
314
296
  yield createDirectoryItem(entry);