@react-aria/dnd 3.10.1 → 3.11.1
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/DragManager.main.js +9 -6
- package/dist/DragManager.main.js.map +1 -1
- package/dist/DragManager.mjs +9 -6
- package/dist/DragManager.module.js +9 -6
- package/dist/DragManager.module.js.map +1 -1
- package/dist/DragPreview.main.js +11 -2
- package/dist/DragPreview.main.js.map +1 -1
- package/dist/DragPreview.mjs +11 -2
- package/dist/DragPreview.module.js +11 -2
- package/dist/DragPreview.module.js.map +1 -1
- package/dist/constants.main.js +0 -1
- package/dist/constants.main.js.map +1 -1
- package/dist/constants.mjs +1 -2
- package/dist/constants.module.js +1 -2
- package/dist/constants.module.js.map +1 -1
- package/dist/types.d.ts +11 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/useDrag.main.js +41 -20
- package/dist/useDrag.main.js.map +1 -1
- package/dist/useDrag.mjs +43 -22
- package/dist/useDrag.module.js +43 -22
- package/dist/useDrag.module.js.map +1 -1
- package/dist/utils.main.js.map +1 -1
- package/dist/utils.module.js.map +1 -1
- package/package.json +11 -11
- package/src/DragManager.ts +23 -23
- package/src/DragPreview.tsx +26 -5
- package/src/constants.ts +37 -10
- package/src/useDrag.ts +46 -22
- package/src/utils.ts +3 -3
package/src/DragManager.ts
CHANGED
|
@@ -182,7 +182,7 @@ class DragSession {
|
|
|
182
182
|
this.initialFocused = false;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
setup() {
|
|
185
|
+
setup(): void {
|
|
186
186
|
document.addEventListener('keydown', this.onKeyDown, true);
|
|
187
187
|
document.addEventListener('keyup', this.onKeyUp, true);
|
|
188
188
|
window.addEventListener('focus', this.onFocus, true);
|
|
@@ -202,7 +202,7 @@ class DragSession {
|
|
|
202
202
|
announce(this.stringFormatter.format(MESSAGES[getDragModality()]));
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
teardown() {
|
|
205
|
+
teardown(): void {
|
|
206
206
|
document.removeEventListener('keydown', this.onKeyDown, true);
|
|
207
207
|
document.removeEventListener('keyup', this.onKeyUp, true);
|
|
208
208
|
window.removeEventListener('focus', this.onFocus, true);
|
|
@@ -218,7 +218,7 @@ class DragSession {
|
|
|
218
218
|
this.restoreAriaHidden?.();
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
onKeyDown(e: KeyboardEvent) {
|
|
221
|
+
onKeyDown(e: KeyboardEvent): void {
|
|
222
222
|
this.cancelEvent(e);
|
|
223
223
|
|
|
224
224
|
if (e.key === 'Escape') {
|
|
@@ -239,7 +239,7 @@ class DragSession {
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
onKeyUp(e: KeyboardEvent) {
|
|
242
|
+
onKeyUp(e: KeyboardEvent): void {
|
|
243
243
|
this.cancelEvent(e);
|
|
244
244
|
|
|
245
245
|
if (e.key === 'Enter') {
|
|
@@ -255,7 +255,7 @@ class DragSession {
|
|
|
255
255
|
return this.currentDropItem?.activateButtonRef?.current ?? this.currentDropTarget?.activateButtonRef?.current ?? null;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
onFocus(e: FocusEvent) {
|
|
258
|
+
onFocus(e: FocusEvent): void {
|
|
259
259
|
let activateButton = this.getCurrentActivateButton();
|
|
260
260
|
if (e.target === activateButton) {
|
|
261
261
|
// TODO: canceling this breaks the focus ring. Revisit when we support tabbing.
|
|
@@ -295,7 +295,7 @@ class DragSession {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
onBlur(e: FocusEvent) {
|
|
298
|
+
onBlur(e: FocusEvent): void {
|
|
299
299
|
let activateButton = this.getCurrentActivateButton();
|
|
300
300
|
if (e.relatedTarget === activateButton) {
|
|
301
301
|
this.cancelEvent(e);
|
|
@@ -317,7 +317,7 @@ class DragSession {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
onClick(e: MouseEvent) {
|
|
320
|
+
onClick(e: MouseEvent): void {
|
|
321
321
|
this.cancelEvent(e);
|
|
322
322
|
if (isVirtualClick(e) || this.isVirtualClick) {
|
|
323
323
|
let dropElements = dropItems.values();
|
|
@@ -341,14 +341,14 @@ class DragSession {
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
onPointerDown(e: PointerEvent) {
|
|
344
|
+
onPointerDown(e: PointerEvent): void {
|
|
345
345
|
// Android Talkback double tap has e.detail = 1 for onClick. Detect the virtual click in onPointerDown before onClick fires
|
|
346
346
|
// so we can properly perform cancel and drop operations.
|
|
347
347
|
this.cancelEvent(e);
|
|
348
348
|
this.isVirtualClick = isVirtualPointerEvent(e);
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
cancelEvent(e: Event) {
|
|
351
|
+
cancelEvent(e: Event): void {
|
|
352
352
|
// Allow focusin and focusout on the drag target so focus ring works properly.
|
|
353
353
|
if ((e.type === 'focusin' || e.type === 'focusout') && (e.target === this.dragTarget?.element || e.target === this.getCurrentActivateButton())) {
|
|
354
354
|
return;
|
|
@@ -363,7 +363,7 @@ class DragSession {
|
|
|
363
363
|
e.stopImmediatePropagation();
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
updateValidDropTargets() {
|
|
366
|
+
updateValidDropTargets(): void {
|
|
367
367
|
if (!this.mutationObserver) {
|
|
368
368
|
return;
|
|
369
369
|
}
|
|
@@ -408,12 +408,12 @@ class DragSession {
|
|
|
408
408
|
this.dragTarget.element,
|
|
409
409
|
...validDropItems.flatMap(item => item.activateButtonRef?.current ? [item.element, item.activateButtonRef?.current] : [item.element]),
|
|
410
410
|
...visibleDropTargets.flatMap(target => target.activateButtonRef?.current ? [target.element, target.activateButtonRef?.current] : [target.element])
|
|
411
|
-
]);
|
|
411
|
+
], {shouldUseInert: true});
|
|
412
412
|
|
|
413
|
-
this.mutationObserver.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['aria-hidden']});
|
|
413
|
+
this.mutationObserver.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['aria-hidden', 'inert']});
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
next() {
|
|
416
|
+
next(): void {
|
|
417
417
|
// TODO: Allow tabbing to the activate button. Revisit once we fix the focus ring.
|
|
418
418
|
// For now, the activate button is reachable by screen readers and ArrowLeft/ArrowRight
|
|
419
419
|
// is usable specifically by Tree. Will need tabbing for other components.
|
|
@@ -437,7 +437,7 @@ class DragSession {
|
|
|
437
437
|
// If we've reached the end of the valid drop targets, cycle back to the original drag target.
|
|
438
438
|
// This lets the user cancel the drag in case they don't have an Escape key (e.g. iPad keyboard case).
|
|
439
439
|
if (index === this.validDropTargets.length - 1) {
|
|
440
|
-
if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
|
|
440
|
+
if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
|
|
441
441
|
this.setCurrentDropTarget(null);
|
|
442
442
|
this.dragTarget.element.focus();
|
|
443
443
|
} else {
|
|
@@ -448,7 +448,7 @@ class DragSession {
|
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
previous() {
|
|
451
|
+
previous(): void {
|
|
452
452
|
// let activateButton = this.getCurrentActivateButton();
|
|
453
453
|
// if (activateButton && document.activeElement === activateButton) {
|
|
454
454
|
// let target = this.currentDropItem ?? this.currentDropTarget;
|
|
@@ -472,7 +472,7 @@ class DragSession {
|
|
|
472
472
|
// If we've reached the start of the valid drop targets, cycle back to the original drag target.
|
|
473
473
|
// This lets the user cancel the drag in case they don't have an Escape key (e.g. iPad keyboard case).
|
|
474
474
|
if (index === 0) {
|
|
475
|
-
if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
|
|
475
|
+
if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
|
|
476
476
|
this.setCurrentDropTarget(null);
|
|
477
477
|
this.dragTarget.element.focus();
|
|
478
478
|
} else {
|
|
@@ -503,7 +503,7 @@ class DragSession {
|
|
|
503
503
|
return nearest;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
setCurrentDropTarget(dropTarget: DropTarget | null, item?: DroppableItem) {
|
|
506
|
+
setCurrentDropTarget(dropTarget: DropTarget | null, item?: DroppableItem): void {
|
|
507
507
|
if (dropTarget !== this.currentDropTarget) {
|
|
508
508
|
if (this.currentDropTarget && typeof this.currentDropTarget.onDropExit === 'function') {
|
|
509
509
|
let rect = this.currentDropTarget.element.getBoundingClientRect();
|
|
@@ -551,7 +551,7 @@ class DragSession {
|
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
end() {
|
|
554
|
+
end(): void {
|
|
555
555
|
this.teardown();
|
|
556
556
|
endDragging();
|
|
557
557
|
|
|
@@ -576,17 +576,17 @@ class DragSession {
|
|
|
576
576
|
this.setCurrentDropTarget(null);
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
-
cancel() {
|
|
579
|
+
cancel(): void {
|
|
580
580
|
this.setCurrentDropTarget(null);
|
|
581
581
|
this.end();
|
|
582
|
-
if (!this.dragTarget.element.closest('[aria-hidden="true"]')) {
|
|
582
|
+
if (!this.dragTarget.element.closest('[aria-hidden="true"], [inert]')) {
|
|
583
583
|
this.dragTarget.element.focus();
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
announce(this.stringFormatter.format('dropCanceled'));
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
drop(item?: DroppableItem) {
|
|
589
|
+
drop(item?: DroppableItem): void {
|
|
590
590
|
if (!this.currentDropTarget) {
|
|
591
591
|
this.cancel();
|
|
592
592
|
return;
|
|
@@ -624,7 +624,7 @@ class DragSession {
|
|
|
624
624
|
announce(this.stringFormatter.format('dropComplete'));
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
-
activate(dropTarget: DropTarget | null, dropItem: DroppableItem | null | undefined) {
|
|
627
|
+
activate(dropTarget: DropTarget | null, dropItem: DroppableItem | null | undefined): void {
|
|
628
628
|
if (dropTarget && typeof dropTarget.onDropActivate === 'function') {
|
|
629
629
|
let target = dropItem?.target ?? null;
|
|
630
630
|
let rect = dropTarget.element.getBoundingClientRect();
|
|
@@ -640,7 +640,7 @@ class DragSession {
|
|
|
640
640
|
function findValidDropTargets(options: DragTarget) {
|
|
641
641
|
let types = getTypes(options.items);
|
|
642
642
|
return [...dropTargets.values()].filter(target => {
|
|
643
|
-
if (target.element.closest('[aria-hidden="true"]')) {
|
|
643
|
+
if (target.element.closest('[aria-hidden="true"], [inert]')) {
|
|
644
644
|
return false;
|
|
645
645
|
}
|
|
646
646
|
|
package/src/DragPreview.tsx
CHANGED
|
@@ -15,24 +15,45 @@ import {flushSync} from 'react-dom';
|
|
|
15
15
|
import React, {ForwardedRef, JSX, useEffect, useImperativeHandle, useRef, useState} from 'react';
|
|
16
16
|
|
|
17
17
|
export interface DragPreviewProps {
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A render function which returns a preview element, or an object containing the element
|
|
20
|
+
* and a custom offset. If an object is returned, the provided `x` and `y` values will be
|
|
21
|
+
* used as the drag preview offset instead of the default calculation.
|
|
22
|
+
*/
|
|
23
|
+
children: (items: DragItem[]) => JSX.Element | {element: JSX.Element, x: number, y: number} | null
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
export const DragPreview
|
|
26
|
+
export const DragPreview:
|
|
27
|
+
React.ForwardRefExoticComponent<DragPreviewProps & React.RefAttributes<DragPreviewRenderer | null>> =
|
|
28
|
+
React.forwardRef(function DragPreview(props: DragPreviewProps, ref: ForwardedRef<DragPreviewRenderer | null>) {
|
|
22
29
|
let render = props.children;
|
|
23
30
|
let [children, setChildren] = useState<JSX.Element | null>(null);
|
|
24
31
|
let domRef = useRef<HTMLDivElement | null>(null);
|
|
25
32
|
let raf = useRef<ReturnType<typeof requestAnimationFrame> | undefined>(undefined);
|
|
26
33
|
|
|
27
|
-
useImperativeHandle(ref, () => (items: DragItem[], callback: (node: HTMLElement | null) => void) => {
|
|
34
|
+
useImperativeHandle(ref, () => (items: DragItem[], callback: (node: HTMLElement | null, x?: number, y?: number) => void) => {
|
|
28
35
|
// This will be called during the onDragStart event by useDrag. We need to render the
|
|
29
36
|
// preview synchronously before this event returns so we can call event.dataTransfer.setDragImage.
|
|
37
|
+
|
|
38
|
+
let result = render(items);
|
|
39
|
+
let element: JSX.Element | null;
|
|
40
|
+
let offsetX: number | undefined;
|
|
41
|
+
let offsetY: number | undefined;
|
|
42
|
+
|
|
43
|
+
if (result && typeof result === 'object' && 'element' in result) {
|
|
44
|
+
element = result.element;
|
|
45
|
+
offsetX = result.x;
|
|
46
|
+
offsetY = result.y;
|
|
47
|
+
} else {
|
|
48
|
+
element = result as JSX.Element | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
flushSync(() => {
|
|
31
|
-
setChildren(
|
|
52
|
+
setChildren(element);
|
|
32
53
|
});
|
|
33
54
|
|
|
34
55
|
// Yield back to useDrag to set the drag image.
|
|
35
|
-
callback(domRef.current);
|
|
56
|
+
callback(domRef.current, offsetX, offsetY);
|
|
36
57
|
|
|
37
58
|
// Remove the preview from the DOM after a frame so the browser has time to paint.
|
|
38
59
|
raf.current = requestAnimationFrame(() => {
|
package/src/constants.ts
CHANGED
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
import {DropOperation} from '@react-types/shared';
|
|
14
14
|
|
|
15
|
+
export interface IDropOperation {
|
|
16
|
+
readonly none: 0,
|
|
17
|
+
readonly cancel: 0,
|
|
18
|
+
readonly move: number,
|
|
19
|
+
readonly copy: number,
|
|
20
|
+
readonly link: number,
|
|
21
|
+
readonly all: number
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
export enum DROP_OPERATION {
|
|
16
25
|
none = 0,
|
|
17
26
|
cancel = 0,
|
|
@@ -20,9 +29,15 @@ export enum DROP_OPERATION {
|
|
|
20
29
|
link = 1 << 2,
|
|
21
30
|
all = move | copy | link
|
|
22
31
|
}
|
|
23
|
-
|
|
32
|
+
interface DropOperationAllowed extends IDropOperation {
|
|
33
|
+
readonly copyMove: number,
|
|
34
|
+
readonly copyLink: number,
|
|
35
|
+
readonly linkMove: number,
|
|
36
|
+
readonly all: number,
|
|
37
|
+
readonly uninitialized: number
|
|
38
|
+
}
|
|
24
39
|
// See https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed
|
|
25
|
-
export const DROP_OPERATION_ALLOWED = {
|
|
40
|
+
export const DROP_OPERATION_ALLOWED: DropOperationAllowed = {
|
|
26
41
|
...DROP_OPERATION,
|
|
27
42
|
copyMove: DROP_OPERATION.copy | DROP_OPERATION.move,
|
|
28
43
|
copyLink: DROP_OPERATION.copy | DROP_OPERATION.link,
|
|
@@ -31,28 +46,40 @@ export const DROP_OPERATION_ALLOWED = {
|
|
|
31
46
|
uninitialized: DROP_OPERATION.all
|
|
32
47
|
};
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
interface EffectAllowed {
|
|
50
|
+
0: 'none' | 'cancel',
|
|
51
|
+
1: 'move',
|
|
52
|
+
2: 'copy',
|
|
53
|
+
3: 'copyMove',
|
|
54
|
+
4: 'link',
|
|
55
|
+
5: 'linkMove',
|
|
56
|
+
6: 'copyLink',
|
|
57
|
+
7: 'all'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const EFFECT_ALLOWED: EffectAllowed = invert(DROP_OPERATION_ALLOWED) as unknown as EffectAllowed;
|
|
35
61
|
EFFECT_ALLOWED[DROP_OPERATION.all] = 'all'; // ensure we don't map to 'uninitialized'.
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
type DropEffect = 'none' | 'link' | 'copy' | 'move';
|
|
64
|
+
|
|
65
|
+
export const DROP_EFFECT_TO_DROP_OPERATION: {[K in DropEffect]: DropOperation} = {
|
|
39
66
|
none: 'cancel',
|
|
40
67
|
link: 'link',
|
|
41
68
|
copy: 'copy',
|
|
42
69
|
move: 'move'
|
|
43
70
|
};
|
|
44
71
|
|
|
45
|
-
export const DROP_OPERATION_TO_DROP_EFFECT = invert(DROP_EFFECT_TO_DROP_OPERATION);
|
|
72
|
+
export const DROP_OPERATION_TO_DROP_EFFECT: {[K in DropOperation]: DropEffect} = invert(DROP_EFFECT_TO_DROP_OPERATION);
|
|
46
73
|
|
|
47
|
-
function invert(object) {
|
|
48
|
-
let res = {}
|
|
74
|
+
function invert<T extends string | number, C extends string | number>(object: Record<T, C>): Record<C, T> {
|
|
75
|
+
let res: Record<C, T> = {} as Record<C, T>;
|
|
49
76
|
for (let key in object) {
|
|
50
|
-
res[object[key]] = key;
|
|
77
|
+
res[object[key]] = key as T;
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
return res;
|
|
54
81
|
}
|
|
55
82
|
|
|
56
|
-
export const NATIVE_DRAG_TYPES = new Set(['text/plain', 'text/uri-list', 'text/html']);
|
|
83
|
+
export const NATIVE_DRAG_TYPES: Set<string> = new Set(['text/plain', 'text/uri-list', 'text/html']);
|
|
57
84
|
export const CUSTOM_DRAG_TYPE = 'application/vnd.react-aria.items+json';
|
|
58
85
|
export const GENERIC_TYPE = 'application/octet-stream';
|
package/src/useDrag.ts
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
import {AriaButtonProps} from '@react-types/button';
|
|
14
14
|
import {DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEvent, DropOperation, PressEvent, RefObject} from '@react-types/shared';
|
|
15
|
-
import {DragEvent, HTMLAttributes, useRef, useState} from 'react';
|
|
15
|
+
import {DragEvent, HTMLAttributes, version as ReactVersion, useEffect, useRef, useState} from 'react';
|
|
16
16
|
import * as DragManager from './DragManager';
|
|
17
17
|
import {DROP_EFFECT_TO_DROP_OPERATION, DROP_OPERATION, EFFECT_ALLOWED} from './constants';
|
|
18
18
|
import {globalDropEffect, setGlobalAllowedDropOperations, setGlobalDropEffect, useDragModality, writeToDataTransfer} from './utils';
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
import intlMessages from '../intl/*.json';
|
|
21
|
-
import {isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners
|
|
21
|
+
import {isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners} from '@react-aria/utils';
|
|
22
22
|
import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
23
23
|
|
|
24
24
|
export interface DragOptions {
|
|
@@ -82,11 +82,11 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
82
82
|
y: 0
|
|
83
83
|
}).current;
|
|
84
84
|
state.options = options;
|
|
85
|
-
let isDraggingRef = useRef(
|
|
85
|
+
let isDraggingRef = useRef<Element | null>(null);
|
|
86
86
|
let [isDragging, setDraggingState] = useState(false);
|
|
87
|
-
let setDragging = (
|
|
88
|
-
isDraggingRef.current =
|
|
89
|
-
setDraggingState(
|
|
87
|
+
let setDragging = (element: Element | null) => {
|
|
88
|
+
isDraggingRef.current = element;
|
|
89
|
+
setDraggingState(!!element);
|
|
90
90
|
};
|
|
91
91
|
let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
|
|
92
92
|
let modalityOnPointerDown = useRef<string>(null);
|
|
@@ -116,6 +116,8 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
let items = options.getItems();
|
|
119
|
+
// Clear existing data (e.g. selected text on the page would be included in some browsers)
|
|
120
|
+
e.dataTransfer.clearData?.();
|
|
119
121
|
writeToDataTransfer(e.dataTransfer, items);
|
|
120
122
|
|
|
121
123
|
let allowed = DROP_OPERATION.all;
|
|
@@ -128,12 +130,13 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
setGlobalAllowedDropOperations(allowed);
|
|
131
|
-
|
|
133
|
+
let effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
|
|
134
|
+
e.dataTransfer.effectAllowed = effectAllowed === 'cancel' ? 'none' : effectAllowed;
|
|
132
135
|
|
|
133
136
|
// If there is a preview option, use it to render a custom preview image that will
|
|
134
137
|
// appear under the pointer while dragging. If not, the element itself is dragged by the browser.
|
|
135
138
|
if (typeof options.preview?.current === 'function') {
|
|
136
|
-
options.preview.current(items, node => {
|
|
139
|
+
options.preview.current(items, (node, userX, userY) => {
|
|
137
140
|
if (!node) {
|
|
138
141
|
return;
|
|
139
142
|
}
|
|
@@ -142,18 +145,35 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
142
145
|
// If the preview is much smaller, then just use the center point of the preview.
|
|
143
146
|
let size = node.getBoundingClientRect();
|
|
144
147
|
let rect = e.currentTarget.getBoundingClientRect();
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
let defaultX = e.clientX - rect.x;
|
|
149
|
+
let defaultY = e.clientY - rect.y;
|
|
150
|
+
if (defaultX > size.width || defaultY > size.height) {
|
|
151
|
+
defaultX = size.width / 2;
|
|
152
|
+
defaultY = size.height / 2;
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
// Start with default offsets.
|
|
156
|
+
let offsetX = defaultX;
|
|
157
|
+
let offsetY = defaultY;
|
|
158
|
+
|
|
159
|
+
// If the preview renderer supplied explicit offsets, use those.
|
|
160
|
+
if (typeof userX === 'number' && typeof userY === 'number') {
|
|
161
|
+
offsetX = userX;
|
|
162
|
+
offsetY = userY;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Clamp the offset so it stays within the preview bounds. Browsers
|
|
166
|
+
// automatically clamp out-of-range values, but doing it ourselves
|
|
167
|
+
// prevents the visible "snap" that can occur when the browser adjusts
|
|
168
|
+
// them after the first drag update.
|
|
169
|
+
offsetX = Math.max(0, Math.min(offsetX, size.width));
|
|
170
|
+
offsetY = Math.max(0, Math.min(offsetY, size.height));
|
|
171
|
+
|
|
152
172
|
// Rounding height to an even number prevents blurry preview seen on some screens
|
|
153
173
|
let height = 2 * Math.round(size.height / 2);
|
|
154
174
|
node.style.height = `${height}px`;
|
|
155
175
|
|
|
156
|
-
e.dataTransfer.setDragImage(node,
|
|
176
|
+
e.dataTransfer.setDragImage(node, offsetX, offsetY);
|
|
157
177
|
});
|
|
158
178
|
}
|
|
159
179
|
|
|
@@ -168,8 +188,9 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
168
188
|
|
|
169
189
|
// Wait a frame before we set dragging to true so that the browser has time to
|
|
170
190
|
// render the preview image before we update the element that has been dragged.
|
|
191
|
+
let target = e.target;
|
|
171
192
|
requestAnimationFrame(() => {
|
|
172
|
-
setDragging(
|
|
193
|
+
setDragging(target as Element);
|
|
173
194
|
});
|
|
174
195
|
};
|
|
175
196
|
|
|
@@ -213,7 +234,7 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
213
234
|
options.onDragEnd(event);
|
|
214
235
|
}
|
|
215
236
|
|
|
216
|
-
setDragging(
|
|
237
|
+
setDragging(null);
|
|
217
238
|
removeAllGlobalListeners();
|
|
218
239
|
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
219
240
|
setGlobalDropEffect(undefined);
|
|
@@ -221,10 +242,13 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
221
242
|
|
|
222
243
|
// If the dragged element is removed from the DOM via onDrop, onDragEnd won't fire: https://bugzilla.mozilla.org/show_bug.cgi?id=460801
|
|
223
244
|
// In this case, we need to manually call onDragEnd on cleanup
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
226
247
|
return () => {
|
|
227
|
-
|
|
248
|
+
// Check that the dragged element has actually unmounted from the DOM and not a React Strict Mode false positive.
|
|
249
|
+
// https://github.com/facebook/react/issues/29585
|
|
250
|
+
// React 16 ran effect cleanups before removing elements from the DOM but did not have this issue.
|
|
251
|
+
if (isDraggingRef.current && (!isDraggingRef.current.isConnected || parseInt(ReactVersion, 10) < 17)) {
|
|
228
252
|
if (typeof state.options.onDragEnd === 'function') {
|
|
229
253
|
let event: DragEndEvent = {
|
|
230
254
|
type: 'dragend',
|
|
@@ -235,7 +259,7 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
235
259
|
state.options.onDragEnd(event);
|
|
236
260
|
}
|
|
237
261
|
|
|
238
|
-
setDragging(
|
|
262
|
+
setDragging(null);
|
|
239
263
|
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
240
264
|
setGlobalDropEffect(undefined);
|
|
241
265
|
}
|
|
@@ -267,14 +291,14 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
267
291
|
? state.options.getAllowedDropOperations()
|
|
268
292
|
: ['move', 'copy', 'link'],
|
|
269
293
|
onDragEnd(e) {
|
|
270
|
-
setDragging(
|
|
294
|
+
setDragging(null);
|
|
271
295
|
if (typeof state.options.onDragEnd === 'function') {
|
|
272
296
|
state.options.onDragEnd(e);
|
|
273
297
|
}
|
|
274
298
|
}
|
|
275
299
|
}, stringFormatter);
|
|
276
300
|
|
|
277
|
-
setDragging(
|
|
301
|
+
setDragging(target);
|
|
278
302
|
};
|
|
279
303
|
|
|
280
304
|
let modality = useDragModality();
|
package/src/utils.ts
CHANGED
|
@@ -20,8 +20,8 @@ interface DroppableCollectionMap {
|
|
|
20
20
|
ref: RefObject<HTMLElement | null>
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const droppableCollectionMap = new WeakMap<DroppableCollectionState, DroppableCollectionMap>();
|
|
24
|
-
export const DIRECTORY_DRAG_TYPE = Symbol();
|
|
23
|
+
export const droppableCollectionMap: WeakMap<DroppableCollectionState, DroppableCollectionMap> = new WeakMap<DroppableCollectionState, DroppableCollectionMap>();
|
|
24
|
+
export const DIRECTORY_DRAG_TYPE: symbol = Symbol();
|
|
25
25
|
|
|
26
26
|
export function getDroppableCollectionId(state: DroppableCollectionState): string {
|
|
27
27
|
let {id} = droppableCollectionMap.get(state) || {};
|
|
@@ -379,7 +379,7 @@ export function setGlobalDropEffect(dropEffect: DropEffect | undefined): void {
|
|
|
379
379
|
globalDropEffect = dropEffect;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
export let globalAllowedDropOperations = DROP_OPERATION.none;
|
|
382
|
+
export let globalAllowedDropOperations: DROP_OPERATION = DROP_OPERATION.none;
|
|
383
383
|
export function setGlobalAllowedDropOperations(o: DROP_OPERATION): void {
|
|
384
384
|
globalAllowedDropOperations = o;
|
|
385
385
|
}
|