2023-04-21 14:03:44 -07:00
|
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
|
|
|
|
|
|
/** Controls the state of files being dragged over a node. */
|
|
|
|
function useDraggedFiles<R extends HTMLElement>(node: React.RefObject<R>, onDrop?: (files: FileList) => void) {
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
2023-04-21 14:28:54 -07:00
|
|
|
const [isDraggedOver, setIsDraggedOver] = useState(false);
|
2023-04-21 14:03:44 -07:00
|
|
|
|
|
|
|
const handleDocumentDragEnter = useCallback((e: DragEvent) => {
|
|
|
|
if (isDraggingFiles(e)) {
|
|
|
|
setIsDragging(true);
|
|
|
|
}
|
|
|
|
}, [setIsDragging]);
|
|
|
|
|
|
|
|
const handleDocumentDragLeave = useCallback((e: DragEvent) => {
|
2023-04-21 14:28:54 -07:00
|
|
|
if (isDraggedOffscreen(e)) {
|
2023-04-21 14:03:44 -07:00
|
|
|
setIsDragging(false);
|
|
|
|
}
|
|
|
|
}, [setIsDragging]);
|
|
|
|
|
|
|
|
const handleDocumentDrop = useCallback((e: DragEvent) => {
|
|
|
|
setIsDragging(false);
|
2023-04-21 14:28:54 -07:00
|
|
|
setIsDraggedOver(false);
|
2023-04-21 14:03:44 -07:00
|
|
|
}, [setIsDragging]);
|
|
|
|
|
2023-04-21 14:28:54 -07:00
|
|
|
const handleDragEnter = useCallback((e: DragEvent) => {
|
|
|
|
if (isDraggingFiles(e)) {
|
|
|
|
setIsDraggedOver(true);
|
|
|
|
}
|
|
|
|
}, [setIsDraggedOver]);
|
|
|
|
|
|
|
|
const handleDragLeave = useCallback((e: DragEvent) => {
|
|
|
|
if (!node.current || isDraggedOutOfNode(e, node.current)) {
|
|
|
|
setIsDraggedOver(false);
|
|
|
|
}
|
|
|
|
}, [setIsDraggedOver]);
|
|
|
|
|
2023-04-21 14:03:44 -07:00
|
|
|
const handleDrop = useCallback((e: DragEvent) => {
|
|
|
|
if (isDraggingFiles(e) && onDrop) {
|
|
|
|
onDrop(e.dataTransfer.files);
|
|
|
|
}
|
|
|
|
setIsDragging(false);
|
2023-04-21 14:28:54 -07:00
|
|
|
setIsDraggedOver(false);
|
2023-04-21 14:03:44 -07:00
|
|
|
e.preventDefault();
|
|
|
|
}, [onDrop]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
document.addEventListener('dragenter', handleDocumentDragEnter);
|
|
|
|
document.addEventListener('dragleave', handleDocumentDragLeave);
|
|
|
|
document.addEventListener('drop', handleDocumentDrop);
|
|
|
|
return () => {
|
|
|
|
document.removeEventListener('dragenter', handleDocumentDragEnter);
|
|
|
|
document.removeEventListener('dragleave', handleDocumentDragLeave);
|
|
|
|
document.removeEventListener('drop', handleDocumentDrop);
|
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2023-04-21 14:28:54 -07:00
|
|
|
node.current?.addEventListener('dragenter', handleDragEnter);
|
|
|
|
node.current?.addEventListener('dragleave', handleDragLeave);
|
2023-04-21 14:03:44 -07:00
|
|
|
node.current?.addEventListener('drop', handleDrop);
|
|
|
|
return () => {
|
2023-04-21 14:28:54 -07:00
|
|
|
node.current?.removeEventListener('dragenter', handleDragEnter);
|
|
|
|
node.current?.removeEventListener('dragleave', handleDragLeave);
|
2023-04-21 14:03:44 -07:00
|
|
|
node.current?.removeEventListener('drop', handleDrop);
|
|
|
|
};
|
|
|
|
}, [node.current]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
/** Whether the document is being dragged over. */
|
|
|
|
isDragging,
|
2023-04-21 14:28:54 -07:00
|
|
|
/** Whether the node is being dragged over. */
|
|
|
|
isDraggedOver,
|
2023-04-21 14:03:44 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Ensure only files are being dragged, and not eg highlighted text. */
|
|
|
|
function isDraggingFiles(e: DragEvent): e is DragEvent & { dataTransfer: DataTransfer } {
|
|
|
|
if (e.dataTransfer) {
|
|
|
|
const { types } = e.dataTransfer;
|
|
|
|
return types.length === 1 && types[0] === 'Files';
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check whether the cursor is in the screen. Mostly useful for dragleave events. */
|
2023-04-21 14:28:54 -07:00
|
|
|
function isDraggedOffscreen(e: DragEvent): boolean {
|
2023-04-21 14:03:44 -07:00
|
|
|
return e.screenX === 0 && e.screenY === 0;
|
|
|
|
}
|
|
|
|
|
2023-04-21 14:28:54 -07:00
|
|
|
/** Check whether the cursor is dragged out of the node. */
|
|
|
|
function isDraggedOutOfNode(e: DragEvent, node: Node): boolean {
|
|
|
|
return !node.contains(document.elementFromPoint(e.clientX, e.clientY));
|
|
|
|
}
|
|
|
|
|
2023-04-21 14:03:44 -07:00
|
|
|
export { useDraggedFiles };
|