@react-aria/dnd 3.10.0 → 3.11.0
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 +38 -19
- package/dist/useDrag.main.js.map +1 -1
- package/dist/useDrag.mjs +40 -21
- package/dist/useDrag.module.js +40 -21
- 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 +44 -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);
|
|
@@ -128,12 +128,13 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
setGlobalAllowedDropOperations(allowed);
|
|
131
|
-
|
|
131
|
+
let effectAllowed = EFFECT_ALLOWED[allowed] || 'none';
|
|
132
|
+
e.dataTransfer.effectAllowed = effectAllowed === 'cancel' ? 'none' : effectAllowed;
|
|
132
133
|
|
|
133
134
|
// If there is a preview option, use it to render a custom preview image that will
|
|
134
135
|
// appear under the pointer while dragging. If not, the element itself is dragged by the browser.
|
|
135
136
|
if (typeof options.preview?.current === 'function') {
|
|
136
|
-
options.preview.current(items, node => {
|
|
137
|
+
options.preview.current(items, (node, userX, userY) => {
|
|
137
138
|
if (!node) {
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
@@ -142,18 +143,35 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
142
143
|
// If the preview is much smaller, then just use the center point of the preview.
|
|
143
144
|
let size = node.getBoundingClientRect();
|
|
144
145
|
let rect = e.currentTarget.getBoundingClientRect();
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
let defaultX = e.clientX - rect.x;
|
|
147
|
+
let defaultY = e.clientY - rect.y;
|
|
148
|
+
if (defaultX > size.width || defaultY > size.height) {
|
|
149
|
+
defaultX = size.width / 2;
|
|
150
|
+
defaultY = size.height / 2;
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
// Start with default offsets.
|
|
154
|
+
let offsetX = defaultX;
|
|
155
|
+
let offsetY = defaultY;
|
|
156
|
+
|
|
157
|
+
// If the preview renderer supplied explicit offsets, use those.
|
|
158
|
+
if (typeof userX === 'number' && typeof userY === 'number') {
|
|
159
|
+
offsetX = userX;
|
|
160
|
+
offsetY = userY;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Clamp the offset so it stays within the preview bounds. Browsers
|
|
164
|
+
// automatically clamp out-of-range values, but doing it ourselves
|
|
165
|
+
// prevents the visible "snap" that can occur when the browser adjusts
|
|
166
|
+
// them after the first drag update.
|
|
167
|
+
offsetX = Math.max(0, Math.min(offsetX, size.width));
|
|
168
|
+
offsetY = Math.max(0, Math.min(offsetY, size.height));
|
|
169
|
+
|
|
152
170
|
// Rounding height to an even number prevents blurry preview seen on some screens
|
|
153
171
|
let height = 2 * Math.round(size.height / 2);
|
|
154
172
|
node.style.height = `${height}px`;
|
|
155
173
|
|
|
156
|
-
e.dataTransfer.setDragImage(node,
|
|
174
|
+
e.dataTransfer.setDragImage(node, offsetX, offsetY);
|
|
157
175
|
});
|
|
158
176
|
}
|
|
159
177
|
|
|
@@ -168,8 +186,9 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
168
186
|
|
|
169
187
|
// Wait a frame before we set dragging to true so that the browser has time to
|
|
170
188
|
// render the preview image before we update the element that has been dragged.
|
|
189
|
+
let target = e.target;
|
|
171
190
|
requestAnimationFrame(() => {
|
|
172
|
-
setDragging(
|
|
191
|
+
setDragging(target as Element);
|
|
173
192
|
});
|
|
174
193
|
};
|
|
175
194
|
|
|
@@ -213,7 +232,7 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
213
232
|
options.onDragEnd(event);
|
|
214
233
|
}
|
|
215
234
|
|
|
216
|
-
setDragging(
|
|
235
|
+
setDragging(null);
|
|
217
236
|
removeAllGlobalListeners();
|
|
218
237
|
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
219
238
|
setGlobalDropEffect(undefined);
|
|
@@ -221,10 +240,13 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
221
240
|
|
|
222
241
|
// 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
242
|
// In this case, we need to manually call onDragEnd on cleanup
|
|
224
|
-
|
|
225
|
-
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
226
245
|
return () => {
|
|
227
|
-
|
|
246
|
+
// Check that the dragged element has actually unmounted from the DOM and not a React Strict Mode false positive.
|
|
247
|
+
// https://github.com/facebook/react/issues/29585
|
|
248
|
+
// React 16 ran effect cleanups before removing elements from the DOM but did not have this issue.
|
|
249
|
+
if (isDraggingRef.current && (!isDraggingRef.current.isConnected || parseInt(ReactVersion, 10) < 17)) {
|
|
228
250
|
if (typeof state.options.onDragEnd === 'function') {
|
|
229
251
|
let event: DragEndEvent = {
|
|
230
252
|
type: 'dragend',
|
|
@@ -235,7 +257,7 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
235
257
|
state.options.onDragEnd(event);
|
|
236
258
|
}
|
|
237
259
|
|
|
238
|
-
setDragging(
|
|
260
|
+
setDragging(null);
|
|
239
261
|
setGlobalAllowedDropOperations(DROP_OPERATION.none);
|
|
240
262
|
setGlobalDropEffect(undefined);
|
|
241
263
|
}
|
|
@@ -267,14 +289,14 @@ export function useDrag(options: DragOptions): DragResult {
|
|
|
267
289
|
? state.options.getAllowedDropOperations()
|
|
268
290
|
: ['move', 'copy', 'link'],
|
|
269
291
|
onDragEnd(e) {
|
|
270
|
-
setDragging(
|
|
292
|
+
setDragging(null);
|
|
271
293
|
if (typeof state.options.onDragEnd === 'function') {
|
|
272
294
|
state.options.onDragEnd(e);
|
|
273
295
|
}
|
|
274
296
|
}
|
|
275
297
|
}, stringFormatter);
|
|
276
298
|
|
|
277
|
-
setDragging(
|
|
299
|
+
setDragging(target);
|
|
278
300
|
};
|
|
279
301
|
|
|
280
302
|
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
|
}
|