type FileSystemHandleLike = FileSystemFileHandleLike | FileSystemDirectoryHandleLike; type FileSystemFileHandleLike = { kind: "file"; getFile: () => Promise }; type FileSystemDirectoryHandleLike = { kind: "directory"; values?: () => AsyncIterable; }; type WebKitFileEntryLike = { isFile: true; isDirectory: false; file: ( success: (file: File) => void, error?: (error: DOMException) => void ) => void; }; type WebKitDirectoryEntryLike = { isFile: false; isDirectory: true }; type WebKitEntryLike = WebKitFileEntryLike | WebKitDirectoryEntryLike; type DropItemWithFileHandles = DataTransferItem & { getAsFileSystemHandle?: () => Promise; webkitGetAsEntry?: () => unknown; }; /** * Resolve a browser file drop exactly once, preferring the standard file list * before progressively trying item and directory-drag compatibility APIs. */ export async function resolveDroppedFiles( dataTransfer: Pick ): Promise { const directFiles = Array.from(dataTransfer.files); if (directFiles.length > 0) return directFiles; const items = Array.from(dataTransfer.items).filter( (item) => item.kind === "file" ) as DropItemWithFileHandles[]; const itemFiles = items .map((item) => item.getAsFile()) .filter((file): file is File => Boolean(file)); if (itemFiles.length > 0) return itemFiles; const handleFiles = await filesFromDataTransferHandles(items); if (handleFiles.length > 0) return handleFiles; return filesFromWebKitEntries(items); } async function filesFromDataTransferHandles( items: DropItemWithFileHandles[] ): Promise { const files: File[] = []; for (const item of items) { let handle: FileSystemHandleLike | null | undefined; try { handle = await item.getAsFileSystemHandle?.(); } catch { handle = null; } if (!handle) continue; files.push(...await filesFromFileSystemHandle(handle)); } return files; } async function filesFromFileSystemHandle( handle: FileSystemHandleLike ): Promise { if (handle.kind === "file") return [await handle.getFile()]; const values = handle.values?.(); if (!values) return []; const files: File[] = []; for await (const child of values) { files.push(...await filesFromFileSystemHandle(child)); } return files; } async function filesFromWebKitEntries( items: DropItemWithFileHandles[] ): Promise { const entries = items.flatMap((item) => { const entry = item.webkitGetAsEntry?.() as unknown; return isWebKitEntryLike(entry) ? [entry] : []; }); const files = await Promise.all(entries.map(fileFromWebKitEntry)); return files.filter((file): file is File => Boolean(file)); } function isWebKitEntryLike(value: unknown): value is WebKitEntryLike { if (!value || typeof value !== "object") return false; const entry = value as { isFile?: unknown; isDirectory?: unknown }; return ( typeof entry.isFile === "boolean" && typeof entry.isDirectory === "boolean" ); } function fileFromWebKitEntry(entry: WebKitEntryLike): Promise { if (!entry.isFile) return Promise.resolve(null); return new Promise((resolve, reject) => { entry.file(resolve, reject); }); }