@react-aria/dnd 3.0.0-alpha.11 → 3.0.0-alpha.12
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 +186 -54
- package/dist/main.js.map +1 -1
- package/dist/module.js +186 -55
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +22 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/DragManager.ts +21 -1
- package/src/ListDropTargetDelegate.ts +90 -0
- package/src/index.ts +2 -0
- package/src/useDraggableItem.ts +1 -0
- package/src/useDrop.ts +107 -45
- package/src/useDropIndicator.ts +12 -3
- package/src/useDroppableCollection.ts +6 -6
- package/src/useDroppableItem.ts +4 -2
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import React, { HTMLAttributes, RefObject, Key } from "react";
|
|
2
|
-
import { DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropMoveEvent, DropOperation, DragTypes, DroppableCollectionProps,
|
|
2
|
+
import { DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropMoveEvent, DropOperation, DragTypes, DroppableCollectionProps, DropTargetDelegate, KeyboardDelegate, DropTarget, DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEvent, DOMAttributes, DropItem, Collection, Node } from "@react-types/shared";
|
|
3
3
|
import { DroppableCollectionState, DraggableCollectionState } from "@react-stately/dnd";
|
|
4
4
|
import { AriaButtonProps } from "@react-types/button";
|
|
5
5
|
export interface DropOptions {
|
|
6
6
|
ref: RefObject<HTMLElement>;
|
|
7
|
+
/**
|
|
8
|
+
* A function returning the drop operation to be performed when items matching the given types are dropped
|
|
9
|
+
* on the drop target.
|
|
10
|
+
*/
|
|
7
11
|
getDropOperation?: (types: DragTypes, allowedOperations: DropOperation[]) => DropOperation;
|
|
8
12
|
getDropOperationForPoint?: (types: DragTypes, allowedOperations: DropOperation[], x: number, y: number) => DropOperation;
|
|
13
|
+
/** Handler that is called when a valid drag enters the drop target. */
|
|
9
14
|
onDropEnter?: (e: DropEnterEvent) => void;
|
|
15
|
+
/** Handler that is called when a valid drag is moved within the drop target. */
|
|
10
16
|
onDropMove?: (e: DropMoveEvent) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Handler that is called after a valid drag is held over the drop target for a period of time.
|
|
19
|
+
* This typically opens the item so that the user can drop within it.
|
|
20
|
+
*/
|
|
11
21
|
onDropActivate?: (e: DropActivateEvent) => void;
|
|
22
|
+
/** Handler that is called when a valid drag exits the drop target. */
|
|
12
23
|
onDropExit?: (e: DropExitEvent) => void;
|
|
24
|
+
/** Handler that is called when a valid drag is dropped on the drop target. */
|
|
13
25
|
onDrop?: (e: DropEvent) => void;
|
|
14
26
|
}
|
|
15
27
|
export interface DropResult {
|
|
@@ -19,7 +31,7 @@ export interface DropResult {
|
|
|
19
31
|
export function useDrop(options: DropOptions): DropResult;
|
|
20
32
|
export interface DroppableCollectionOptions extends DroppableCollectionProps {
|
|
21
33
|
keyboardDelegate: KeyboardDelegate;
|
|
22
|
-
|
|
34
|
+
dropTargetDelegate: DropTargetDelegate;
|
|
23
35
|
}
|
|
24
36
|
export interface DroppableCollectionResult {
|
|
25
37
|
collectionProps: HTMLAttributes<HTMLElement>;
|
|
@@ -30,6 +42,7 @@ export interface DroppableItemOptions {
|
|
|
30
42
|
}
|
|
31
43
|
export interface DroppableItemResult {
|
|
32
44
|
dropProps: HTMLAttributes<HTMLElement>;
|
|
45
|
+
isDropTarget: boolean;
|
|
33
46
|
}
|
|
34
47
|
export function useDroppableItem(options: DroppableItemOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableItemResult;
|
|
35
48
|
export interface DropIndicatorProps {
|
|
@@ -37,6 +50,8 @@ export interface DropIndicatorProps {
|
|
|
37
50
|
}
|
|
38
51
|
export interface DropIndicatorAria {
|
|
39
52
|
dropIndicatorProps: HTMLAttributes<HTMLElement>;
|
|
53
|
+
isDropTarget: boolean;
|
|
54
|
+
isHidden: boolean;
|
|
40
55
|
}
|
|
41
56
|
export function useDropIndicator(props: DropIndicatorProps, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DropIndicatorAria;
|
|
42
57
|
export interface DragOptions {
|
|
@@ -75,5 +90,10 @@ export interface ClipboardResult {
|
|
|
75
90
|
clipboardProps: DOMAttributes;
|
|
76
91
|
}
|
|
77
92
|
export function useClipboard(options: ClipboardProps): ClipboardResult;
|
|
93
|
+
export class ListDropTargetDelegate implements DropTargetDelegate {
|
|
94
|
+
constructor(collection: Collection<Node<unknown>>, ref: RefObject<HTMLElement>);
|
|
95
|
+
getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget;
|
|
96
|
+
}
|
|
97
|
+
export type { DropTargetDelegate } from '@react-types/shared';
|
|
78
98
|
|
|
79
99
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;;;AKoBA;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;
|
|
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/useAutoScroll.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/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/useDrag.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useDraggableItem.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/DragPreview.tsx","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/useClipboard.ts","packages/@react-aria/dnd/src/packages/@react-aria/dnd/src/ListDropTargetDelegate.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,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 type {DroppableCollectionOptions, DroppableCollectionResult} from './useDroppableCollection';\nexport type {DroppableItemOptions, DroppableItemResult} from './useDroppableItem';\nexport type {DropIndicatorProps, DropIndicatorAria} from './useDropIndicator';\nexport type {DraggableItemProps, DraggableItemResult} from './useDraggableItem';\nexport type {DragPreviewProps} from './DragPreview';\nexport type {DragOptions, DragResult} from './useDrag';\nexport type {DropOptions, DropResult} from './useDrop';\nexport type {ClipboardProps, ClipboardResult} from './useClipboard';\nexport type {DropTargetDelegate} from '@react-types/shared';\n\nexport {useDrag} from './useDrag';\nexport {useDrop} from './useDrop';\nexport {useDroppableCollection} from './useDroppableCollection';\nexport {useDroppableItem} from './useDroppableItem';\nexport {useDropIndicator} from './useDropIndicator';\nexport {useDraggableItem} from './useDraggableItem';\nexport {useClipboard} from './useClipboard';\nexport {DragPreview} from './DragPreview';\nexport {ListDropTargetDelegate} from './ListDropTargetDelegate';\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.12",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.6.2",
|
|
21
21
|
"@internationalized/string": "^3.0.0",
|
|
22
|
-
"@react-aria/i18n": "^3.
|
|
23
|
-
"@react-aria/interactions": "^3.
|
|
22
|
+
"@react-aria/i18n": "^3.6.0",
|
|
23
|
+
"@react-aria/interactions": "^3.11.0",
|
|
24
24
|
"@react-aria/live-announcer": "^3.1.1",
|
|
25
|
-
"@react-aria/overlays": "^3.10.
|
|
26
|
-
"@react-aria/utils": "^3.13.
|
|
27
|
-
"@react-aria/visually-hidden": "^3.4.
|
|
28
|
-
"@react-stately/dnd": "3.0.0-alpha.
|
|
29
|
-
"@react-stately/selection": "^3.10.
|
|
30
|
-
"@react-types/button": "^3.6.
|
|
31
|
-
"@react-types/shared": "^3.14.
|
|
25
|
+
"@react-aria/overlays": "^3.10.1",
|
|
26
|
+
"@react-aria/utils": "^3.13.3",
|
|
27
|
+
"@react-aria/visually-hidden": "^3.4.1",
|
|
28
|
+
"@react-stately/dnd": "3.0.0-alpha.10",
|
|
29
|
+
"@react-stately/selection": "^3.10.3",
|
|
30
|
+
"@react-types/button": "^3.6.1",
|
|
31
|
+
"@react-types/shared": "^3.14.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "b03ef51e6317547dd0a840f151e59d039b1e1fd3"
|
|
41
41
|
}
|
package/src/DragManager.ts
CHANGED
|
@@ -75,7 +75,8 @@ export function beginDragging(target: DragTarget, stringFormatter: LocalizedStri
|
|
|
75
75
|
getDragModality() === 'keyboard' ||
|
|
76
76
|
(getDragModality() === 'touch' && getInteractionModality() === 'virtual')
|
|
77
77
|
) {
|
|
78
|
-
dragSession.
|
|
78
|
+
let target = dragSession.findNearestDropTarget();
|
|
79
|
+
dragSession.setCurrentDropTarget(target);
|
|
79
80
|
}
|
|
80
81
|
});
|
|
81
82
|
|
|
@@ -411,6 +412,25 @@ class DragSession {
|
|
|
411
412
|
}
|
|
412
413
|
}
|
|
413
414
|
|
|
415
|
+
findNearestDropTarget(): DropTarget {
|
|
416
|
+
let dragTargetRect = this.dragTarget.element.getBoundingClientRect();
|
|
417
|
+
|
|
418
|
+
let minDistance = Infinity;
|
|
419
|
+
let nearest = null;
|
|
420
|
+
for (let dropTarget of this.validDropTargets) {
|
|
421
|
+
let rect = dropTarget.element.getBoundingClientRect();
|
|
422
|
+
let dx = rect.left - dragTargetRect.left;
|
|
423
|
+
let dy = rect.top - dragTargetRect.top;
|
|
424
|
+
let dist = (dx * dx) + (dy * dy);
|
|
425
|
+
if (dist < minDistance) {
|
|
426
|
+
minDistance = dist;
|
|
427
|
+
nearest = dropTarget;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return nearest;
|
|
432
|
+
}
|
|
433
|
+
|
|
414
434
|
setCurrentDropTarget(dropTarget: DropTarget, item?: DroppableItem) {
|
|
415
435
|
if (dropTarget !== this.currentDropTarget) {
|
|
416
436
|
if (this.currentDropTarget && typeof this.currentDropTarget.onDropExit === 'function') {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {Collection, DropTarget, DropTargetDelegate, Node} from '@react-types/shared';
|
|
2
|
+
import {RefObject} from 'react';
|
|
3
|
+
|
|
4
|
+
export class ListDropTargetDelegate implements DropTargetDelegate {
|
|
5
|
+
private collection: Collection<Node<unknown>>;
|
|
6
|
+
private ref: RefObject<HTMLElement>;
|
|
7
|
+
|
|
8
|
+
constructor(collection: Collection<Node<unknown>>, ref: RefObject<HTMLElement>) {
|
|
9
|
+
this.collection = collection;
|
|
10
|
+
this.ref = ref;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
|
|
14
|
+
if (this.collection.size === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let rect = this.ref.current.getBoundingClientRect();
|
|
19
|
+
x += rect.x;
|
|
20
|
+
y += rect.y;
|
|
21
|
+
|
|
22
|
+
let elements = this.ref.current.querySelectorAll('[data-key]');
|
|
23
|
+
let elementMap = new Map<string, HTMLElement>();
|
|
24
|
+
for (let item of elements) {
|
|
25
|
+
if (item instanceof HTMLElement) {
|
|
26
|
+
elementMap.set(item.dataset.key, item);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let items = [...this.collection];
|
|
31
|
+
let low = 0;
|
|
32
|
+
let high = items.length;
|
|
33
|
+
while (low < high) {
|
|
34
|
+
let mid = Math.floor((low + high) / 2);
|
|
35
|
+
let item = items[mid];
|
|
36
|
+
let element = elementMap.get(String(item.key));
|
|
37
|
+
let rect = element.getBoundingClientRect();
|
|
38
|
+
|
|
39
|
+
if (y < rect.top) {
|
|
40
|
+
high = mid;
|
|
41
|
+
} else if (y > rect.bottom) {
|
|
42
|
+
low = mid + 1;
|
|
43
|
+
} else {
|
|
44
|
+
let target: DropTarget = {
|
|
45
|
+
type: 'item',
|
|
46
|
+
key: item.key,
|
|
47
|
+
dropPosition: 'on'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (isValidDropTarget(target)) {
|
|
51
|
+
// Otherwise, if dropping on the item is accepted, try the before/after positions if within 5px
|
|
52
|
+
// of the top or bottom of the item.
|
|
53
|
+
if (y <= rect.top + 5 && isValidDropTarget({...target, dropPosition: 'before'})) {
|
|
54
|
+
target.dropPosition = 'before';
|
|
55
|
+
} else if (y >= rect.bottom - 5 && isValidDropTarget({...target, dropPosition: 'after'})) {
|
|
56
|
+
target.dropPosition = 'after';
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// If dropping on the item isn't accepted, try the target before or after depending on the y position.
|
|
60
|
+
let midY = rect.top + rect.height / 2;
|
|
61
|
+
if (y <= midY && isValidDropTarget({...target, dropPosition: 'before'})) {
|
|
62
|
+
target.dropPosition = 'before';
|
|
63
|
+
} else if (y >= midY && isValidDropTarget({...target, dropPosition: 'after'})) {
|
|
64
|
+
target.dropPosition = 'after';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return target;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let item = items[Math.min(low, items.length - 1)];
|
|
73
|
+
let element = elementMap.get(String(item.key));
|
|
74
|
+
rect = element.getBoundingClientRect();
|
|
75
|
+
|
|
76
|
+
if (Math.abs(y - rect.top) < Math.abs(y - rect.bottom)) {
|
|
77
|
+
return {
|
|
78
|
+
type: 'item',
|
|
79
|
+
key: item.key,
|
|
80
|
+
dropPosition: 'before'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type: 'item',
|
|
86
|
+
key: item.key,
|
|
87
|
+
dropPosition: 'after'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type {DragPreviewProps} from './DragPreview';
|
|
|
18
18
|
export type {DragOptions, DragResult} from './useDrag';
|
|
19
19
|
export type {DropOptions, DropResult} from './useDrop';
|
|
20
20
|
export type {ClipboardProps, ClipboardResult} from './useClipboard';
|
|
21
|
+
export type {DropTargetDelegate} from '@react-types/shared';
|
|
21
22
|
|
|
22
23
|
export {useDrag} from './useDrag';
|
|
23
24
|
export {useDrop} from './useDrop';
|
|
@@ -27,3 +28,4 @@ export {useDropIndicator} from './useDropIndicator';
|
|
|
27
28
|
export {useDraggableItem} from './useDraggableItem';
|
|
28
29
|
export {useClipboard} from './useClipboard';
|
|
29
30
|
export {DragPreview} from './DragPreview';
|
|
31
|
+
export {ListDropTargetDelegate} from './ListDropTargetDelegate';
|
package/src/useDraggableItem.ts
CHANGED
|
@@ -34,6 +34,7 @@ export function useDraggableItem(props: DraggableItemProps, state: DraggableColl
|
|
|
34
34
|
return state.getItems(props.key);
|
|
35
35
|
},
|
|
36
36
|
preview: state.preview,
|
|
37
|
+
getAllowedDropOperations: state.getAllowedDropOperations,
|
|
37
38
|
onDragStart(e) {
|
|
38
39
|
state.startDrag(props.key, e);
|
|
39
40
|
},
|
package/src/useDrop.ts
CHANGED
|
@@ -20,14 +20,24 @@ import {useVirtualDrop} from './useVirtualDrop';
|
|
|
20
20
|
|
|
21
21
|
export interface DropOptions {
|
|
22
22
|
ref: RefObject<HTMLElement>,
|
|
23
|
+
/**
|
|
24
|
+
* A function returning the drop operation to be performed when items matching the given types are dropped
|
|
25
|
+
* on the drop target.
|
|
26
|
+
*/
|
|
23
27
|
getDropOperation?: (types: IDragTypes, allowedOperations: DropOperation[]) => DropOperation,
|
|
24
28
|
getDropOperationForPoint?: (types: IDragTypes, allowedOperations: DropOperation[], x: number, y: number) => DropOperation,
|
|
29
|
+
/** Handler that is called when a valid drag enters the drop target. */
|
|
25
30
|
onDropEnter?: (e: DropEnterEvent) => void,
|
|
31
|
+
/** Handler that is called when a valid drag is moved within the drop target. */
|
|
26
32
|
onDropMove?: (e: DropMoveEvent) => void,
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Handler that is called after a valid drag is held over the drop target for a period of time.
|
|
35
|
+
* This typically opens the item so that the user can drop within it.
|
|
36
|
+
*/
|
|
29
37
|
onDropActivate?: (e: DropActivateEvent) => void,
|
|
38
|
+
/** Handler that is called when a valid drag exits the drop target. */
|
|
30
39
|
onDropExit?: (e: DropExitEvent) => void,
|
|
40
|
+
/** Handler that is called when a valid drag is dropped on the drop target. */
|
|
31
41
|
onDrop?: (e: DropEvent) => void
|
|
32
42
|
}
|
|
33
43
|
|
|
@@ -43,16 +53,43 @@ export function useDrop(options: DropOptions): DropResult {
|
|
|
43
53
|
let state = useRef({
|
|
44
54
|
x: 0,
|
|
45
55
|
y: 0,
|
|
46
|
-
|
|
56
|
+
dragOverElements: new Set<Element>(),
|
|
47
57
|
dropEffect: 'none' as DataTransfer['dropEffect'],
|
|
58
|
+
effectAllowed: 'none' as DataTransfer['effectAllowed'],
|
|
48
59
|
dropActivateTimer: null
|
|
49
60
|
}).current;
|
|
50
61
|
|
|
62
|
+
let fireDropEnter = (e: DragEvent) => {
|
|
63
|
+
setDropTarget(true);
|
|
64
|
+
|
|
65
|
+
if (typeof options.onDropEnter === 'function') {
|
|
66
|
+
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
67
|
+
options.onDropEnter({
|
|
68
|
+
type: 'dropenter',
|
|
69
|
+
x: e.clientX - rect.x,
|
|
70
|
+
y: e.clientY - rect.y
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let fireDropExit = (e: DragEvent) => {
|
|
76
|
+
setDropTarget(false);
|
|
77
|
+
|
|
78
|
+
if (typeof options.onDropExit === 'function') {
|
|
79
|
+
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
80
|
+
options.onDropExit({
|
|
81
|
+
type: 'dropexit',
|
|
82
|
+
x: e.clientX - rect.x,
|
|
83
|
+
y: e.clientY - rect.y
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
51
88
|
let onDragOver = (e: DragEvent) => {
|
|
52
89
|
e.preventDefault();
|
|
53
90
|
e.stopPropagation();
|
|
54
91
|
|
|
55
|
-
if (e.clientX === state.x && e.clientY === state.y) {
|
|
92
|
+
if (e.clientX === state.x && e.clientY === state.y && e.dataTransfer.effectAllowed === state.effectAllowed) {
|
|
56
93
|
e.dataTransfer.dropEffect = state.dropEffect;
|
|
57
94
|
return;
|
|
58
95
|
}
|
|
@@ -60,17 +97,42 @@ export function useDrop(options: DropOptions): DropResult {
|
|
|
60
97
|
state.x = e.clientX;
|
|
61
98
|
state.y = e.clientY;
|
|
62
99
|
|
|
100
|
+
let prevDropEffect = state.dropEffect;
|
|
101
|
+
|
|
102
|
+
// Update drop effect if allowed drop operations changed (e.g. user pressed modifier key).
|
|
103
|
+
if (e.dataTransfer.effectAllowed !== state.effectAllowed) {
|
|
104
|
+
let allowedOperations = effectAllowedToOperations(e.dataTransfer.effectAllowed);
|
|
105
|
+
let dropOperation = allowedOperations[0];
|
|
106
|
+
if (typeof options.getDropOperation === 'function') {
|
|
107
|
+
let types = new DragTypes(e.dataTransfer);
|
|
108
|
+
dropOperation = getDropOperation(e.dataTransfer.effectAllowed, options.getDropOperation(types, allowedOperations));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none';
|
|
112
|
+
}
|
|
113
|
+
|
|
63
114
|
if (typeof options.getDropOperationForPoint === 'function') {
|
|
64
115
|
let allowedOperations = effectAllowedToOperations(e.dataTransfer.effectAllowed);
|
|
65
116
|
let types = new DragTypes(e.dataTransfer);
|
|
66
117
|
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
67
|
-
let dropOperation =
|
|
118
|
+
let dropOperation = getDropOperation(
|
|
119
|
+
e.dataTransfer.effectAllowed,
|
|
120
|
+
options.getDropOperationForPoint(types, allowedOperations, state.x - rect.x, state.y - rect.y)
|
|
121
|
+
);
|
|
68
122
|
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none';
|
|
69
123
|
}
|
|
70
124
|
|
|
125
|
+
state.effectAllowed = e.dataTransfer.effectAllowed;
|
|
71
126
|
e.dataTransfer.dropEffect = state.dropEffect;
|
|
72
127
|
|
|
73
|
-
|
|
128
|
+
// If the drop operation changes, update state and fire events appropriately.
|
|
129
|
+
if (state.dropEffect === 'none' && prevDropEffect !== 'none') {
|
|
130
|
+
fireDropExit(e);
|
|
131
|
+
} else if (state.dropEffect !== 'none' && prevDropEffect === 'none') {
|
|
132
|
+
fireDropEnter(e);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof options.onDropMove === 'function' && state.dropEffect !== 'none') {
|
|
74
136
|
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
75
137
|
options.onDropMove({
|
|
76
138
|
type: 'dropmove',
|
|
@@ -95,8 +157,8 @@ export function useDrop(options: DropOptions): DropResult {
|
|
|
95
157
|
|
|
96
158
|
let onDragEnter = (e: DragEvent) => {
|
|
97
159
|
e.stopPropagation();
|
|
98
|
-
state.
|
|
99
|
-
if (state.
|
|
160
|
+
state.dragOverElements.add(e.target as Element);
|
|
161
|
+
if (state.dragOverElements.size > 1) {
|
|
100
162
|
return;
|
|
101
163
|
}
|
|
102
164
|
|
|
@@ -105,52 +167,55 @@ export function useDrop(options: DropOptions): DropResult {
|
|
|
105
167
|
|
|
106
168
|
if (typeof options.getDropOperation === 'function') {
|
|
107
169
|
let types = new DragTypes(e.dataTransfer);
|
|
108
|
-
dropOperation = options.getDropOperation(types, allowedOperations);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (dropOperation !== 'cancel') {
|
|
112
|
-
setDropTarget(true);
|
|
170
|
+
dropOperation = getDropOperation(e.dataTransfer.effectAllowed, options.getDropOperation(types, allowedOperations));
|
|
113
171
|
}
|
|
114
172
|
|
|
115
173
|
if (typeof options.getDropOperationForPoint === 'function') {
|
|
116
174
|
let types = new DragTypes(e.dataTransfer);
|
|
117
175
|
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
118
|
-
dropOperation =
|
|
176
|
+
dropOperation = getDropOperation(
|
|
177
|
+
e.dataTransfer.effectAllowed,
|
|
178
|
+
options.getDropOperationForPoint(types, allowedOperations, e.clientX - rect.x, e.clientY - rect.y)
|
|
179
|
+
);
|
|
119
180
|
}
|
|
120
181
|
|
|
182
|
+
state.x = e.clientX;
|
|
183
|
+
state.y = e.clientY;
|
|
184
|
+
state.effectAllowed = e.dataTransfer.effectAllowed;
|
|
121
185
|
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none';
|
|
122
186
|
e.dataTransfer.dropEffect = state.dropEffect;
|
|
123
187
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
options.onDropEnter({
|
|
127
|
-
type: 'dropenter',
|
|
128
|
-
x: e.clientX - rect.x,
|
|
129
|
-
y: e.clientY - rect.y
|
|
130
|
-
});
|
|
188
|
+
if (dropOperation !== 'cancel') {
|
|
189
|
+
fireDropEnter(e);
|
|
131
190
|
}
|
|
132
|
-
|
|
133
|
-
state.x = e.clientX;
|
|
134
|
-
state.y = e.clientY;
|
|
135
191
|
};
|
|
136
192
|
|
|
137
193
|
let onDragLeave = (e: DragEvent) => {
|
|
138
194
|
e.stopPropagation();
|
|
139
|
-
|
|
140
|
-
|
|
195
|
+
|
|
196
|
+
// We would use e.relatedTarget to detect if the drag is still inside the drop target,
|
|
197
|
+
// but it is always null in WebKit. https://bugs.webkit.org/show_bug.cgi?id=66547
|
|
198
|
+
// Instead, we track all of the targets of dragenter events in a set, and remove them
|
|
199
|
+
// in dragleave. When the set becomes empty, we've left the drop target completely.
|
|
200
|
+
// We must also remove any elements that are no longer in the DOM, because dragleave
|
|
201
|
+
// events will never be fired for these. This can happen, for example, with drop
|
|
202
|
+
// indicators between items, which disappear when the drop target changes.
|
|
203
|
+
|
|
204
|
+
state.dragOverElements.delete(e.target as Element);
|
|
205
|
+
for (let element of state.dragOverElements) {
|
|
206
|
+
if (!e.currentTarget.contains(element)) {
|
|
207
|
+
state.dragOverElements.delete(element);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (state.dragOverElements.size > 0) {
|
|
141
212
|
return;
|
|
142
213
|
}
|
|
143
214
|
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
options.onDropExit({
|
|
147
|
-
type: 'dropexit',
|
|
148
|
-
x: e.clientX - rect.x,
|
|
149
|
-
y: e.clientY - rect.y
|
|
150
|
-
});
|
|
215
|
+
if (state.dropEffect !== 'none') {
|
|
216
|
+
fireDropExit(e);
|
|
151
217
|
}
|
|
152
218
|
|
|
153
|
-
setDropTarget(false);
|
|
154
219
|
clearTimeout(state.dropActivateTimer);
|
|
155
220
|
};
|
|
156
221
|
|
|
@@ -180,17 +245,8 @@ export function useDrop(options: DropOptions): DropResult {
|
|
|
180
245
|
}, 0);
|
|
181
246
|
}
|
|
182
247
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
options.onDropExit({
|
|
186
|
-
type: 'dropexit',
|
|
187
|
-
x: e.clientX - rect.x,
|
|
188
|
-
y: e.clientY - rect.y
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
state.dragEnterCount = 0;
|
|
193
|
-
setDropTarget(false);
|
|
248
|
+
state.dragOverElements.clear();
|
|
249
|
+
fireDropExit(e);
|
|
194
250
|
clearTimeout(state.dropActivateTimer);
|
|
195
251
|
};
|
|
196
252
|
|
|
@@ -255,3 +311,9 @@ function effectAllowedToOperations(effectAllowed: string) {
|
|
|
255
311
|
|
|
256
312
|
return allowedOperations;
|
|
257
313
|
}
|
|
314
|
+
|
|
315
|
+
function getDropOperation(effectAllowed: string, operation: DropOperation) {
|
|
316
|
+
let allowedOperationsBits = DROP_OPERATION_ALLOWED[effectAllowed];
|
|
317
|
+
let op = DROP_OPERATION[operation];
|
|
318
|
+
return allowedOperationsBits & op ? operation : 'cancel';
|
|
319
|
+
}
|
package/src/useDropIndicator.ts
CHANGED
|
@@ -26,7 +26,9 @@ export interface DropIndicatorProps {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface DropIndicatorAria {
|
|
29
|
-
dropIndicatorProps: HTMLAttributes<HTMLElement
|
|
29
|
+
dropIndicatorProps: HTMLAttributes<HTMLElement>,
|
|
30
|
+
isDropTarget: boolean,
|
|
31
|
+
isHidden: boolean
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export function useDropIndicator(props: DropIndicatorProps, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DropIndicatorAria {
|
|
@@ -72,6 +74,8 @@ export function useDropIndicator(props: DropIndicatorProps, state: DroppableColl
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
let isDropTarget = state.isDropTarget(target);
|
|
78
|
+
let ariaHidden = !dragSession ? 'true' : dropProps['aria-hidden'];
|
|
75
79
|
return {
|
|
76
80
|
dropIndicatorProps: {
|
|
77
81
|
...dropProps,
|
|
@@ -79,8 +83,13 @@ export function useDropIndicator(props: DropIndicatorProps, state: DroppableColl
|
|
|
79
83
|
'aria-roledescription': stringFormatter.format('dropIndicator'),
|
|
80
84
|
'aria-label': label,
|
|
81
85
|
'aria-labelledby': labelledBy,
|
|
82
|
-
'aria-hidden':
|
|
86
|
+
'aria-hidden': ariaHidden,
|
|
83
87
|
tabIndex: -1
|
|
84
|
-
}
|
|
88
|
+
},
|
|
89
|
+
isDropTarget,
|
|
90
|
+
// If aria-hidden, we are either not in a drag session or the drop target is invalid.
|
|
91
|
+
// In that case, there's no need to render anything at all unless we need to show the indicator visually.
|
|
92
|
+
// This can happen when dragging using the native DnD API as opposed to keyboard dragging.
|
|
93
|
+
isHidden: !isDropTarget && !!ariaHidden
|
|
85
94
|
};
|
|
86
95
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
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 {Collection, DropEvent, DropOperation, DroppableCollectionProps, DropPosition, DropTarget, DropTargetDelegate, KeyboardDelegate, Node} from '@react-types/shared';
|
|
14
14
|
import * as DragManager from './DragManager';
|
|
15
15
|
import {DroppableCollectionState} from '@react-stately/dnd';
|
|
16
16
|
import {getTypes} from './utils';
|
|
@@ -23,7 +23,7 @@ import {useDroppableCollectionId} from './utils';
|
|
|
23
23
|
|
|
24
24
|
export interface DroppableCollectionOptions extends DroppableCollectionProps {
|
|
25
25
|
keyboardDelegate: KeyboardDelegate,
|
|
26
|
-
|
|
26
|
+
dropTargetDelegate: DropTargetDelegate
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface DroppableCollectionResult {
|
|
@@ -52,16 +52,16 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
|
|
|
52
52
|
let autoScroll = useAutoScroll(ref);
|
|
53
53
|
let {dropProps} = useDrop({
|
|
54
54
|
ref,
|
|
55
|
-
onDropEnter(
|
|
56
|
-
|
|
57
|
-
state.setTarget(target);
|
|
55
|
+
onDropEnter() {
|
|
56
|
+
state.setTarget(localState.nextTarget);
|
|
58
57
|
},
|
|
59
58
|
onDropMove(e) {
|
|
60
59
|
state.setTarget(localState.nextTarget);
|
|
61
60
|
autoScroll.move(e.x, e.y);
|
|
62
61
|
},
|
|
63
62
|
getDropOperationForPoint(types, allowedOperations, x, y) {
|
|
64
|
-
let
|
|
63
|
+
let isValidDropTarget = (target) => state.getDropOperation(target, types, allowedOperations) !== 'cancel';
|
|
64
|
+
let target = props.dropTargetDelegate.getDropTargetFromPoint(x, y, isValidDropTarget);
|
|
65
65
|
if (!target) {
|
|
66
66
|
localState.dropOperation = 'cancel';
|
|
67
67
|
localState.nextTarget = null;
|
package/src/useDroppableItem.ts
CHANGED
|
@@ -22,7 +22,8 @@ export interface DroppableItemOptions {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface DroppableItemResult {
|
|
25
|
-
dropProps: HTMLAttributes<HTMLElement
|
|
25
|
+
dropProps: HTMLAttributes<HTMLElement>,
|
|
26
|
+
isDropTarget: boolean
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export function useDroppableItem(options: DroppableItemOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableItemResult {
|
|
@@ -62,6 +63,7 @@ export function useDroppableItem(options: DroppableItemOptions, state: Droppable
|
|
|
62
63
|
dropProps: {
|
|
63
64
|
...dropProps,
|
|
64
65
|
'aria-hidden': !dragSession || isValidDropTarget ? undefined : 'true'
|
|
65
|
-
}
|
|
66
|
+
},
|
|
67
|
+
isDropTarget
|
|
66
68
|
};
|
|
67
69
|
}
|