import { useCallback, DragEventHandler, RefObject, useState, useEffect, useRef } from 'react'; import { collectDroppedFiles } from '../utils/fileEntries'; export const useFileDropHandler = (onDrop: (file: File[]) => void): DragEventHandler => useCallback( (evt) => { // `collectDroppedFiles` synchronously captures the entry list from the // DataTransfer before traversing folders asynchronously. collectDroppedFiles(evt.dataTransfer) .then((files) => { if (files) onDrop(files); }) .catch(() => undefined); }, [onDrop], ); export const useFileDropZone = ( zoneRef: RefObject, onDrop: (file: File[]) => void, ): boolean => { const dragCounterRef = useRef(0); const [active, setActive] = useState(false); useEffect(() => { const target = zoneRef.current; const handleDrop = (evt: DragEvent) => { evt.preventDefault(); dragCounterRef.current = 0; setActive(false); if (!evt.dataTransfer) return; // Capture entries synchronously (inside the event) then traverse any // dropped folders asynchronously — the DataTransferItemList is emptied // once this handler returns. collectDroppedFiles(evt.dataTransfer) .then((files) => { if (files) onDrop(files); }) .catch(() => undefined); }; target?.addEventListener('drop', handleDrop); return () => { target?.removeEventListener('drop', handleDrop); }; }, [zoneRef, onDrop]); useEffect(() => { const target = zoneRef.current; const handleDragEnter = (evt: DragEvent) => { if (evt.dataTransfer?.types.includes('Files')) { dragCounterRef.current += 1; setActive(true); } }; const handleDragLeave = (evt: DragEvent) => { if (evt.relatedTarget === null) { // Mouse left the browser window — reset unconditionally dragCounterRef.current = 0; setActive(false); return; } dragCounterRef.current -= 1; if (dragCounterRef.current <= 0) { dragCounterRef.current = 0; setActive(false); } }; const handleDragOver = (evt: DragEvent) => { evt.preventDefault(); }; target?.addEventListener('dragenter', handleDragEnter); target?.addEventListener('dragleave', handleDragLeave); target?.addEventListener('dragover', handleDragOver); return () => { target?.removeEventListener('dragenter', handleDragEnter); target?.removeEventListener('dragleave', handleDragLeave); target?.removeEventListener('dragover', handleDragOver); }; }, [zoneRef]); return active; };