@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.
- package/dist/main.js +1618 -1867
- package/dist/main.js.map +1 -1
- package/dist/module.js +1578 -1790
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/DragManager.ts +6 -12
- package/src/constants.ts +3 -1
- package/src/useDrop.ts +7 -2
- package/src/useDroppableCollection.ts +168 -24
- package/src/utils.ts +6 -24
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"
|
|
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","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.
|
|
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.
|
|
22
|
-
"@react-aria/interactions": "^3.
|
|
23
|
-
"@react-aria/live-announcer": "3.0.
|
|
24
|
-
"@react-aria/overlays": "^3.
|
|
25
|
-
"@react-aria/utils": "^3.
|
|
26
|
-
"@react-aria/visually-hidden": "^3.2.
|
|
27
|
-
"@react-stately/dnd": "3.0.0-alpha.
|
|
28
|
-
"@react-stately/selection": "^3.
|
|
29
|
-
"@react-types/button": "^3.2
|
|
30
|
-
"@react-types/shared": "^3.
|
|
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": "
|
|
39
|
+
"gitHead": "54c2366c4f31bd4bf619126131cd583c12972acc"
|
|
40
40
|
}
|
package/src/DragManager.ts
CHANGED
|
@@ -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
|
-
|
|
171
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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,
|
|
212
|
-
// and pasting any file or directory (no matter the type), but
|
|
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);
|