@react-aria/dnd 3.0.0-alpha.1 → 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 +2853 -1965
- package/dist/main.js.map +1 -1
- package/dist/module.js +2844 -1908
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +55 -31
- package/dist/types.d.ts.map +1 -1
- package/package.json +15 -14
- package/src/DragManager.ts +109 -43
- package/src/DragPreview.tsx +54 -0
- package/src/ListDropTargetDelegate.ts +90 -0
- package/src/constants.ts +3 -1
- package/src/index.ts +19 -7
- package/src/useClipboard.ts +5 -5
- package/src/useDrag.ts +40 -39
- package/src/useDraggableItem.ts +10 -11
- package/src/useDrop.ts +116 -49
- package/src/useDropIndicator.ts +22 -13
- package/src/useDroppableCollection.ts +11 -16
- package/src/useDroppableItem.ts +6 -4
- package/src/useVirtualDrop.ts +5 -5
- package/src/utils.ts +1 -20
package/dist/types.d.ts
CHANGED
|
@@ -1,75 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { HTMLAttributes, RefObject, Key } from "react";
|
|
1
|
+
import React, { HTMLAttributes, RefObject, Key } from "react";
|
|
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";
|
|
4
3
|
import { DroppableCollectionState, DraggableCollectionState } from "@react-stately/dnd";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
onDragMove?: (e: DragMoveEvent) => void;
|
|
8
|
-
onDragEnd?: (e: DragEndEvent) => void;
|
|
9
|
-
getItems: () => DragItem[];
|
|
10
|
-
renderPreview?: (items: DragItem[]) => JSX.Element;
|
|
11
|
-
getAllowedDropOperations?: () => DropOperation[];
|
|
12
|
-
}
|
|
13
|
-
interface DragResult {
|
|
14
|
-
dragProps: HTMLAttributes<HTMLElement>;
|
|
15
|
-
dragButtonProps: AriaButtonProps;
|
|
16
|
-
isDragging: boolean;
|
|
17
|
-
}
|
|
18
|
-
export function useDrag(options: DragOptions): DragResult;
|
|
19
|
-
interface DropOptions {
|
|
4
|
+
import { AriaButtonProps } from "@react-types/button";
|
|
5
|
+
export interface DropOptions {
|
|
20
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
|
+
*/
|
|
21
11
|
getDropOperation?: (types: DragTypes, allowedOperations: DropOperation[]) => DropOperation;
|
|
22
12
|
getDropOperationForPoint?: (types: DragTypes, allowedOperations: DropOperation[], x: number, y: number) => DropOperation;
|
|
13
|
+
/** Handler that is called when a valid drag enters the drop target. */
|
|
23
14
|
onDropEnter?: (e: DropEnterEvent) => void;
|
|
15
|
+
/** Handler that is called when a valid drag is moved within the drop target. */
|
|
24
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
|
+
*/
|
|
25
21
|
onDropActivate?: (e: DropActivateEvent) => void;
|
|
22
|
+
/** Handler that is called when a valid drag exits the drop target. */
|
|
26
23
|
onDropExit?: (e: DropExitEvent) => void;
|
|
24
|
+
/** Handler that is called when a valid drag is dropped on the drop target. */
|
|
27
25
|
onDrop?: (e: DropEvent) => void;
|
|
28
26
|
}
|
|
29
|
-
interface DropResult {
|
|
27
|
+
export interface DropResult {
|
|
30
28
|
dropProps: HTMLAttributes<HTMLElement>;
|
|
31
29
|
isDropTarget: boolean;
|
|
32
30
|
}
|
|
33
31
|
export function useDrop(options: DropOptions): DropResult;
|
|
34
|
-
interface DroppableCollectionOptions extends DroppableCollectionProps {
|
|
32
|
+
export interface DroppableCollectionOptions extends DroppableCollectionProps {
|
|
35
33
|
keyboardDelegate: KeyboardDelegate;
|
|
36
|
-
|
|
34
|
+
dropTargetDelegate: DropTargetDelegate;
|
|
37
35
|
}
|
|
38
|
-
interface DroppableCollectionResult {
|
|
36
|
+
export interface DroppableCollectionResult {
|
|
39
37
|
collectionProps: HTMLAttributes<HTMLElement>;
|
|
40
38
|
}
|
|
41
39
|
export function useDroppableCollection(props: DroppableCollectionOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableCollectionResult;
|
|
42
|
-
interface DroppableItemOptions {
|
|
40
|
+
export interface DroppableItemOptions {
|
|
43
41
|
target: DropTarget;
|
|
44
42
|
}
|
|
45
|
-
interface DroppableItemResult {
|
|
43
|
+
export interface DroppableItemResult {
|
|
46
44
|
dropProps: HTMLAttributes<HTMLElement>;
|
|
45
|
+
isDropTarget: boolean;
|
|
47
46
|
}
|
|
48
47
|
export function useDroppableItem(options: DroppableItemOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableItemResult;
|
|
49
|
-
interface DropIndicatorProps {
|
|
48
|
+
export interface DropIndicatorProps {
|
|
50
49
|
target: DropTarget;
|
|
51
50
|
}
|
|
52
|
-
interface DropIndicatorAria {
|
|
51
|
+
export interface DropIndicatorAria {
|
|
53
52
|
dropIndicatorProps: HTMLAttributes<HTMLElement>;
|
|
53
|
+
isDropTarget: boolean;
|
|
54
|
+
isHidden: boolean;
|
|
54
55
|
}
|
|
55
56
|
export function useDropIndicator(props: DropIndicatorProps, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DropIndicatorAria;
|
|
56
|
-
interface
|
|
57
|
+
export interface DragOptions {
|
|
58
|
+
onDragStart?: (e: DragStartEvent) => void;
|
|
59
|
+
onDragMove?: (e: DragMoveEvent) => void;
|
|
60
|
+
onDragEnd?: (e: DragEndEvent) => void;
|
|
61
|
+
getItems: () => DragItem[];
|
|
62
|
+
preview?: RefObject<DragPreviewRenderer>;
|
|
63
|
+
getAllowedDropOperations?: () => DropOperation[];
|
|
64
|
+
}
|
|
65
|
+
export interface DragResult {
|
|
66
|
+
dragProps: HTMLAttributes<HTMLElement>;
|
|
67
|
+
dragButtonProps: AriaButtonProps;
|
|
68
|
+
isDragging: boolean;
|
|
69
|
+
}
|
|
70
|
+
export function useDrag(options: DragOptions): DragResult;
|
|
71
|
+
export interface DraggableItemProps {
|
|
57
72
|
key: Key;
|
|
58
73
|
}
|
|
59
|
-
interface DraggableItemResult {
|
|
74
|
+
export interface DraggableItemResult {
|
|
60
75
|
dragProps: HTMLAttributes<HTMLElement>;
|
|
61
76
|
dragButtonProps: AriaButtonProps;
|
|
62
77
|
}
|
|
63
78
|
export function useDraggableItem(props: DraggableItemProps, state: DraggableCollectionState): DraggableItemResult;
|
|
64
|
-
interface
|
|
79
|
+
export interface DragPreviewProps {
|
|
80
|
+
children: (items: DragItem[]) => JSX.Element;
|
|
81
|
+
}
|
|
82
|
+
export let DragPreview: React.ForwardRefExoticComponent<DragPreviewProps & React.RefAttributes<DragPreviewRenderer>>;
|
|
83
|
+
export interface ClipboardProps {
|
|
65
84
|
getItems?: () => DragItem[];
|
|
66
85
|
onCopy?: () => void;
|
|
67
86
|
onCut?: () => void;
|
|
68
87
|
onPaste?: (items: DropItem[]) => void;
|
|
69
88
|
}
|
|
70
|
-
interface ClipboardResult {
|
|
71
|
-
clipboardProps:
|
|
89
|
+
export interface ClipboardResult {
|
|
90
|
+
clipboardProps: DOMAttributes;
|
|
72
91
|
}
|
|
73
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';
|
|
74
98
|
|
|
75
99
|
//# sourceMappingURL=types.d.ts.map
|
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/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",
|
|
@@ -18,23 +18,24 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.6.2",
|
|
21
|
-
"@
|
|
22
|
-
"@react-aria/
|
|
23
|
-
"@react-aria/
|
|
24
|
-
"@react-aria/
|
|
25
|
-
"@react-aria/
|
|
26
|
-
"@react-aria/
|
|
27
|
-
"@react-
|
|
28
|
-
"@react-stately/
|
|
29
|
-
"@react-
|
|
30
|
-
"@react-types/
|
|
21
|
+
"@internationalized/string": "^3.0.0",
|
|
22
|
+
"@react-aria/i18n": "^3.6.0",
|
|
23
|
+
"@react-aria/interactions": "^3.11.0",
|
|
24
|
+
"@react-aria/live-announcer": "^3.1.1",
|
|
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"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
|
-
"react": "^16.8.0 || ^17.0.0-rc.1",
|
|
34
|
-
"react-dom": "^16.8.0 || ^17.0.0-rc.1"
|
|
34
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
|
|
35
|
+
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
|
35
36
|
},
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
38
39
|
},
|
|
39
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "b03ef51e6317547dd0a840f151e59d039b1e1fd3"
|
|
40
41
|
}
|
package/src/DragManager.ts
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import {announce} from '@react-aria/live-announcer';
|
|
14
14
|
import {ariaHideOutside} from '@react-aria/overlays';
|
|
15
|
-
import {DragEndEvent, DragItem, DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropItem, DropOperation, DropTarget as DroppableCollectionTarget} from '@react-types/shared';
|
|
15
|
+
import {DragEndEvent, DragItem, DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropItem, DropOperation, DropTarget as DroppableCollectionTarget, FocusableElement} from '@react-types/shared';
|
|
16
16
|
import {getDragModality, getTypes} from './utils';
|
|
17
|
+
import {getInteractionModality} from '@react-aria/interactions';
|
|
18
|
+
import type {LocalizedStringFormatter} from '@internationalized/string';
|
|
17
19
|
import {useEffect, useState} from 'react';
|
|
18
20
|
|
|
19
21
|
let dropTargets = new Map<Element, DropTarget>();
|
|
@@ -22,7 +24,7 @@ let dragSession: DragSession = null;
|
|
|
22
24
|
let subscriptions = new Set<() => void>();
|
|
23
25
|
|
|
24
26
|
interface DropTarget {
|
|
25
|
-
element:
|
|
27
|
+
element: FocusableElement,
|
|
26
28
|
getDropOperation?: (types: Set<string>, allowedOperations: DropOperation[]) => DropOperation,
|
|
27
29
|
onDropEnter?: (e: DropEnterEvent, dragTarget: DragTarget) => void,
|
|
28
30
|
onDropExit?: (e: DropExitEvent) => void,
|
|
@@ -42,7 +44,7 @@ export function registerDropTarget(target: DropTarget) {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
interface DroppableItem {
|
|
45
|
-
element:
|
|
47
|
+
element: FocusableElement,
|
|
46
48
|
target: DroppableCollectionTarget,
|
|
47
49
|
getDropOperation?: (types: Set<string>, allowedOperations: DropOperation[]) => DropOperation
|
|
48
50
|
}
|
|
@@ -55,23 +57,26 @@ export function registerDropItem(item: DroppableItem) {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
interface DragTarget {
|
|
58
|
-
element:
|
|
60
|
+
element: FocusableElement,
|
|
59
61
|
items: DragItem[],
|
|
60
62
|
allowedDropOperations: DropOperation[],
|
|
61
63
|
onDragEnd?: (e: DragEndEvent) => void
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
export function beginDragging(target: DragTarget,
|
|
66
|
+
export function beginDragging(target: DragTarget, stringFormatter: LocalizedStringFormatter) {
|
|
65
67
|
if (dragSession) {
|
|
66
68
|
throw new Error('Cannot begin dragging while already dragging');
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
dragSession = new DragSession(target,
|
|
71
|
+
dragSession = new DragSession(target, stringFormatter);
|
|
70
72
|
requestAnimationFrame(() => {
|
|
71
73
|
dragSession.setup();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (
|
|
75
|
+
getDragModality() === 'keyboard' ||
|
|
76
|
+
(getDragModality() === 'touch' && getInteractionModality() === 'virtual')
|
|
77
|
+
) {
|
|
78
|
+
let target = dragSession.findNearestDropTarget();
|
|
79
|
+
dragSession.setCurrentDropTarget(target);
|
|
75
80
|
}
|
|
76
81
|
});
|
|
77
82
|
|
|
@@ -101,6 +106,16 @@ function endDragging() {
|
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
export function isValidDropTarget(element: Element): boolean {
|
|
110
|
+
for (let target of dropTargets.keys()) {
|
|
111
|
+
if (target.contains(element)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
const CANCELED_EVENTS = [
|
|
105
120
|
'pointerdown',
|
|
106
121
|
'pointermove',
|
|
@@ -143,19 +158,22 @@ class DragSession {
|
|
|
143
158
|
currentDropItem: DroppableItem;
|
|
144
159
|
dropOperation: DropOperation;
|
|
145
160
|
private mutationObserver: MutationObserver;
|
|
146
|
-
private mutationImmediate: NodeJS.Immediate;
|
|
147
161
|
private restoreAriaHidden: () => void;
|
|
148
|
-
private
|
|
162
|
+
private stringFormatter: LocalizedStringFormatter;
|
|
163
|
+
private isVirtualClick: boolean;
|
|
164
|
+
private initialFocused: boolean;
|
|
149
165
|
|
|
150
|
-
constructor(target: DragTarget,
|
|
166
|
+
constructor(target: DragTarget, stringFormatter: LocalizedStringFormatter) {
|
|
151
167
|
this.dragTarget = target;
|
|
152
|
-
this.
|
|
168
|
+
this.stringFormatter = stringFormatter;
|
|
153
169
|
|
|
154
170
|
this.onKeyDown = this.onKeyDown.bind(this);
|
|
155
171
|
this.onFocus = this.onFocus.bind(this);
|
|
156
172
|
this.onBlur = this.onBlur.bind(this);
|
|
157
173
|
this.onClick = this.onClick.bind(this);
|
|
174
|
+
this.onPointerDown = this.onPointerDown.bind(this);
|
|
158
175
|
this.cancelEvent = this.cancelEvent.bind(this);
|
|
176
|
+
this.initialFocused = false;
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
setup() {
|
|
@@ -163,25 +181,18 @@ class DragSession {
|
|
|
163
181
|
window.addEventListener('focus', this.onFocus, true);
|
|
164
182
|
window.addEventListener('blur', this.onBlur, true);
|
|
165
183
|
document.addEventListener('click', this.onClick, true);
|
|
184
|
+
document.addEventListener('pointerdown', this.onPointerDown, true);
|
|
166
185
|
|
|
167
186
|
for (let event of CANCELED_EVENTS) {
|
|
168
187
|
document.addEventListener(event, this.cancelEvent, true);
|
|
169
188
|
}
|
|
170
189
|
|
|
171
|
-
this.mutationObserver = new MutationObserver(() =>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// the next tick to update valid drop targets.
|
|
175
|
-
// See https://github.com/jsdom/jsdom/issues/3096
|
|
176
|
-
if (typeof setImmediate === 'function') {
|
|
177
|
-
this.mutationImmediate = setImmediate(() => this.updateValidDropTargets());
|
|
178
|
-
} else {
|
|
179
|
-
this.updateValidDropTargets();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
190
|
+
this.mutationObserver = new MutationObserver(() =>
|
|
191
|
+
this.updateValidDropTargets()
|
|
192
|
+
);
|
|
182
193
|
this.updateValidDropTargets();
|
|
183
194
|
|
|
184
|
-
announce(this.
|
|
195
|
+
announce(this.stringFormatter.format(MESSAGES[getDragModality()]));
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
teardown() {
|
|
@@ -189,6 +200,7 @@ class DragSession {
|
|
|
189
200
|
window.removeEventListener('focus', this.onFocus, true);
|
|
190
201
|
window.removeEventListener('blur', this.onBlur, true);
|
|
191
202
|
document.removeEventListener('click', this.onClick, true);
|
|
203
|
+
document.removeEventListener('pointerdown', this.onPointerDown, true);
|
|
192
204
|
|
|
193
205
|
for (let event of CANCELED_EVENTS) {
|
|
194
206
|
document.removeEventListener(event, this.cancelEvent, true);
|
|
@@ -196,9 +208,6 @@ class DragSession {
|
|
|
196
208
|
|
|
197
209
|
this.mutationObserver.disconnect();
|
|
198
210
|
this.restoreAriaHidden();
|
|
199
|
-
if (this.mutationImmediate) {
|
|
200
|
-
clearImmediate(this.mutationImmediate);
|
|
201
|
-
}
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
onKeyDown(e: KeyboardEvent) {
|
|
@@ -274,25 +283,34 @@ class DragSession {
|
|
|
274
283
|
|
|
275
284
|
onClick(e: MouseEvent) {
|
|
276
285
|
this.cancelEvent(e);
|
|
286
|
+
if (e.detail === 0 || this.isVirtualClick) {
|
|
287
|
+
if (e.target === this.dragTarget.element) {
|
|
288
|
+
this.cancel();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
277
291
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return;
|
|
292
|
+
let dropTarget = this.validDropTargets.find(target => target.element.contains(e.target as HTMLElement));
|
|
293
|
+
if (dropTarget) {
|
|
294
|
+
let item = dropItems.get(e.target as HTMLElement);
|
|
295
|
+
this.setCurrentDropTarget(dropTarget, item);
|
|
296
|
+
this.drop(item);
|
|
297
|
+
}
|
|
285
298
|
}
|
|
299
|
+
}
|
|
286
300
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
301
|
+
onPointerDown(e: PointerEvent) {
|
|
302
|
+
// Android Talkback double tap has e.detail = 1 for onClick. Detect the virtual click in onPointerDown before onClick fires
|
|
303
|
+
// so we can properly perform cancel and drop operations.
|
|
304
|
+
this.cancelEvent(e);
|
|
305
|
+
this.isVirtualClick = isVirtualPointerEvent(e);
|
|
293
306
|
}
|
|
294
307
|
|
|
295
308
|
cancelEvent(e: Event) {
|
|
309
|
+
// Allow focusin and focusout on the drag target so focus ring works properly.
|
|
310
|
+
if ((e.type === 'focusin' || e.type === 'focusout') && e.target === this.dragTarget?.element) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
296
314
|
// Allow default for events that might cancel a click event
|
|
297
315
|
if (!CLICK_EVENTS.includes(e.type)) {
|
|
298
316
|
e.preventDefault();
|
|
@@ -394,6 +412,25 @@ class DragSession {
|
|
|
394
412
|
}
|
|
395
413
|
}
|
|
396
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
|
+
|
|
397
434
|
setCurrentDropTarget(dropTarget: DropTarget, item?: DroppableItem) {
|
|
398
435
|
if (dropTarget !== this.currentDropTarget) {
|
|
399
436
|
if (this.currentDropTarget && typeof this.currentDropTarget.onDropExit === 'function') {
|
|
@@ -431,6 +468,13 @@ class DragSession {
|
|
|
431
468
|
|
|
432
469
|
item?.element.focus();
|
|
433
470
|
this.currentDropItem = item;
|
|
471
|
+
|
|
472
|
+
// Annouce first drop target after drag start announcement finishes.
|
|
473
|
+
// Otherwise, it will never get announced because drag start announcement is assertive.
|
|
474
|
+
if (!this.initialFocused) {
|
|
475
|
+
announce(item?.element.getAttribute('aria-label'), 'polite');
|
|
476
|
+
this.initialFocused = true;
|
|
477
|
+
}
|
|
434
478
|
}
|
|
435
479
|
}
|
|
436
480
|
|
|
@@ -448,6 +492,12 @@ class DragSession {
|
|
|
448
492
|
});
|
|
449
493
|
}
|
|
450
494
|
|
|
495
|
+
// Blur and re-focus the drop target so that the focus ring appears.
|
|
496
|
+
if (this.currentDropTarget) {
|
|
497
|
+
this.currentDropTarget.element.blur();
|
|
498
|
+
this.currentDropTarget.element.focus();
|
|
499
|
+
}
|
|
500
|
+
|
|
451
501
|
this.setCurrentDropTarget(null);
|
|
452
502
|
endDragging();
|
|
453
503
|
}
|
|
@@ -458,7 +508,7 @@ class DragSession {
|
|
|
458
508
|
this.dragTarget.element.focus();
|
|
459
509
|
}
|
|
460
510
|
|
|
461
|
-
announce(this.
|
|
511
|
+
announce(this.stringFormatter.format('dropCanceled'));
|
|
462
512
|
}
|
|
463
513
|
|
|
464
514
|
drop(item?: DroppableItem) {
|
|
@@ -496,7 +546,7 @@ class DragSession {
|
|
|
496
546
|
}
|
|
497
547
|
|
|
498
548
|
this.end();
|
|
499
|
-
announce(this.
|
|
549
|
+
announce(this.stringFormatter.format('dropComplete'));
|
|
500
550
|
}
|
|
501
551
|
|
|
502
552
|
activate() {
|
|
@@ -525,3 +575,19 @@ function findValidDropTargets(options: DragTarget) {
|
|
|
525
575
|
return true;
|
|
526
576
|
});
|
|
527
577
|
}
|
|
578
|
+
|
|
579
|
+
function isVirtualPointerEvent(event: PointerEvent) {
|
|
580
|
+
// If the pointer size is zero, then we assume it's from a screen reader.
|
|
581
|
+
// Android TalkBack double tap will sometimes return a event with width and height of 1
|
|
582
|
+
// and pointerType === 'mouse' so we need to check for a specific combination of event attributes.
|
|
583
|
+
// Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0
|
|
584
|
+
// instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216
|
|
585
|
+
return (
|
|
586
|
+
(event.width === 0 && event.height === 0) ||
|
|
587
|
+
(event.width === 1 &&
|
|
588
|
+
event.height === 1 &&
|
|
589
|
+
event.pressure === 0 &&
|
|
590
|
+
event.detail === 0
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {DragItem, DragPreviewRenderer} from '@react-types/shared';
|
|
14
|
+
import {flushSync} from 'react-dom';
|
|
15
|
+
import React, {RefObject, useImperativeHandle, useRef, useState} from 'react';
|
|
16
|
+
|
|
17
|
+
export interface DragPreviewProps {
|
|
18
|
+
children: (items: DragItem[]) => JSX.Element
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function DragPreview(props: DragPreviewProps, ref: RefObject<DragPreviewRenderer>) {
|
|
22
|
+
let render = props.children;
|
|
23
|
+
let [children, setChildren] = useState<JSX.Element>(null);
|
|
24
|
+
let domRef = useRef(null);
|
|
25
|
+
|
|
26
|
+
useImperativeHandle(ref, () => (items: DragItem[], callback: (node: HTMLElement) => void) => {
|
|
27
|
+
// This will be called during the onDragStart event by useDrag. We need to render the
|
|
28
|
+
// preview synchronously before this event returns so we can call event.dataTransfer.setDragImage.
|
|
29
|
+
flushSync(() => {
|
|
30
|
+
setChildren(render(items));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Yield back to useDrag to set the drag image.
|
|
34
|
+
callback(domRef.current);
|
|
35
|
+
|
|
36
|
+
// Remove the preview from the DOM after a frame so the browser has time to paint.
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
setChildren(null);
|
|
39
|
+
});
|
|
40
|
+
}, [render]);
|
|
41
|
+
|
|
42
|
+
if (!children) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div style={{zIndex: -100, position: 'absolute', top: 0, left: -100000}} ref={domRef}>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let _DragPreview = React.forwardRef(DragPreview);
|
|
54
|
+
export {_DragPreview as DragPreview};
|
|
@@ -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/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',
|