UNPKG

file-selector

Version:

Convert DataTransfer object to a list of File objects

1 lines 17.5 kB
{"version":3,"file":"index.cjs","names":["DEFAULT_MIME_TYPES","DEFAULT_MIME_TYPES"],"sources":["../src/error.ts","../src/file.ts","../src/file-selector.ts"],"sourcesContent":["export class UnexpectedObjectError extends Error {\n item: DataTransferItem;\n constructor(item: DataTransferItem) {\n super(\"DataTransferItem is not a file\");\n this.item = item;\n this.name = \"UnexpectedObjectError\";\n }\n}\n","import {DEFAULT_MIME_TYPES} from \"./mime-default\";\n\nexport function toFileWithPath(file: File, path?: string, h?: FileSystemHandle): FileWithPath {\n const f = file as FileWithPath;\n const {webkitRelativePath} = file;\n const p =\n typeof path === \"string\"\n ? path\n : // If <input webkitdirectory> is set,\n // the File will have a {webkitRelativePath} property\n // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory\n typeof webkitRelativePath === \"string\" && webkitRelativePath.length > 0\n ? webkitRelativePath\n : `./${file.name}`;\n if (typeof f.path !== \"string\") {\n // on electron, path is already set to the absolute path\n setObjProp(f, \"path\", p);\n }\n if (h !== undefined) {\n Object.defineProperty(f, \"handle\", {\n value: h,\n writable: false,\n configurable: false,\n enumerable: true\n });\n }\n // Always populate a relative path so that even electron apps have access to a relativePath value\n setObjProp(f, \"relativePath\", p);\n return f;\n}\n\nexport interface FileWithPath extends File {\n readonly path: string;\n readonly relativePath: string;\n readonly handle?: FileSystemFileHandle;\n}\n\n/**\n * Sets a File's `type` from its extension using the provided `mimeTypes` lookup,\n * but only when the browser didn't already set a type. Returns the same File.\n */\nexport function withMimeType(file: File, mimeTypes: Map<string, string> = DEFAULT_MIME_TYPES): FileWithPath {\n const {name} = file;\n const hasExtension = name && name.lastIndexOf(\".\") !== -1;\n\n if (hasExtension && !file.type) {\n const ext = name.split(\".\").pop()!.toLowerCase();\n const type = mimeTypes.get(ext);\n if (type) {\n Object.defineProperty(file, \"type\", {\n value: type,\n writable: false,\n configurable: false,\n enumerable: true\n });\n }\n }\n\n return file as FileWithPath;\n}\n\nfunction setObjProp(f: FileWithPath, key: string, value: string) {\n Object.defineProperty(f, key, {\n value,\n writable: false,\n configurable: false,\n enumerable: true\n });\n}\n","import {UnexpectedObjectError} from \"./error\";\nimport {type FileWithPath, toFileWithPath, withMimeType} from \"./file\";\nimport {DEFAULT_MIME_TYPES} from \"./mime-default\";\n\nconst FILES_TO_IGNORE = [\n // Thumbnail cache files for macOS and Windows\n \".DS_Store\", // macOs\n \"Thumbs.db\" // Windows\n];\n\nexport interface FromEventOptions {\n /**\n * Extension-to-MIME lookup used to set `type` on files the browser left typeless.\n * Defaults to a small built-in set of common types ({@link DEFAULT_MIME_TYPES}).\n *\n * The full extension-to-MIME table (~1,200 entries) is not bundled into the main entry;\n * import it from the `file-selector/mime` subpath when broader coverage is needed:\n *\n * ```ts\n * import {fromEvent} from 'file-selector';\n * import {COMMON_MIME_TYPES} from 'file-selector/mime';\n * await fromEvent(evt, {mimeTypes: COMMON_MIME_TYPES});\n * ```\n *\n * See https://github.com/react-dropzone/file-selector/issues/127\n */\n mimeTypes?: Map<string, string>;\n}\n\n/**\n * Convert a DragEvent's DataTrasfer object to a list of File objects\n * NOTE: If some of the items are folders,\n * everything will be flattened and placed in the same list but the paths will be kept as a {path} property.\n *\n * EXPERIMENTAL: A list of https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle objects can also be passed as an arg\n * and a list of File objects will be returned.\n *\n * @param evt\n * @param options\n */\nexport async function fromEvent(\n evt: Event | any,\n {mimeTypes = DEFAULT_MIME_TYPES}: FromEventOptions = {}\n): Promise<(FileWithPath | DataTransferItem)[]> {\n const items = await getFilesOrItems(evt);\n // Guess a MIME type from the file extension once, over the final flat list, for any file the\n // browser left typeless. DataTransferItems (returned for non-'drop' drag events) pass through as-is.\n return items.map(item => (item instanceof File ? withMimeType(item, mimeTypes) : item));\n}\n\nasync function getFilesOrItems(evt: Event | any): Promise<(FileWithPath | DataTransferItem)[]> {\n if (isObject<DragEvent>(evt) && isDataTransfer(evt.dataTransfer)) {\n return getDataTransferFiles(evt.dataTransfer, evt.type);\n } else if (isChangeEvt(evt)) {\n return getInputFiles(evt);\n } else if (Array.isArray(evt) && evt.every(item => \"getFile\" in item && typeof item.getFile === \"function\")) {\n return getFsHandleFiles(evt);\n }\n return [];\n}\n\nfunction isDataTransfer(value: any): value is DataTransfer {\n return isObject(value);\n}\n\nfunction isChangeEvt(value: any): value is Event {\n return isObject<Event>(value) && isObject(value.target);\n}\n\nfunction isObject<T>(v: any): v is T {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction getInputFiles(evt: Event) {\n return fromList<FileWithPath>((evt.target as HTMLInputElement).files).map(file => toFileWithPath(file));\n}\n\n// Ee expect each handle to be https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle\nasync function getFsHandleFiles(handles: any[]) {\n const files = await Promise.all(handles.map(h => h.getFile()));\n return files.map(file => toFileWithPath(file));\n}\n\nasync function getDataTransferFiles(dt: DataTransfer, type: string) {\n const items = fromList<DataTransferItem>(dt.items).filter(item => item.kind === \"file\");\n // According to https://html.spec.whatwg.org/multipage/dnd.html#dndevents,\n // only 'dragstart' and 'drop' has access to the data (source node)\n if (type !== \"drop\") {\n return items;\n }\n const files = await Promise.all(items.map(toFilePromises));\n return noIgnoredFiles(flatten<FileWithPath>(files));\n}\n\nfunction noIgnoredFiles(files: FileWithPath[]) {\n return files.filter(file => FILES_TO_IGNORE.indexOf(file.name) === -1);\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/FileList\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItemList\nfunction fromList<T>(items: DataTransferItemList | FileList | null): T[] {\n // {items} can be null, e.g. an <input type=\"file\"> with no selection\n if (items === null) {\n return [];\n }\n return Array.from(items as unknown as ArrayLike<T>);\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem\nasync function toFilePromises(item: DataTransferItem) {\n if (typeof item.webkitGetAsEntry !== \"function\") {\n return fromDataTransferItem(item);\n }\n\n const entry = item.webkitGetAsEntry();\n\n // Safari supports dropping an image node from a different window and can be retrieved using\n // the DataTransferItem.getAsFile() API\n // NOTE: FileSystemEntry.file() throws if trying to get the file\n if (entry?.isDirectory) {\n // Prefer the File System Access API for directory traversal where it's available.\n // The legacy Entry API (FileSystemDirectoryReader.readEntries) throws on some platforms,\n // e.g. dragging a folder from Windows Explorer into a Chromium browser, which would\n // otherwise reject the entire drop and lose every file.\n // See https://github.com/react-dropzone/file-selector/issues/130\n const handle = await getFsHandle(item);\n if (handle?.kind === \"directory\") {\n return fromDirHandle(handle, `/${handle.name}`);\n }\n return fromDirEntry(entry) as any;\n }\n\n return fromDataTransferItem(item, entry);\n}\n\nfunction flatten<T>(items: any[]): T[] {\n const result: T[] = [];\n for (const item of items) {\n if (Array.isArray(item)) {\n result.push(...flatten<T>(item));\n } else {\n result.push(item);\n }\n }\n return result;\n}\n\nasync function fromDataTransferItem(item: DataTransferItem, entry?: FileSystemEntry | null) {\n // Read the File synchronously, before awaiting anything. A DataTransferItem's accessors are\n // only valid during the synchronous part of the drop event; awaiting `getAsFileSystemHandle()`\n // (which resolves on a later task) neuters the item in Chromium, after which `getAsFile()`\n // returns `null`. Capturing it up front lets us recover the drop when no handle is available.\n // See https://github.com/react-dropzone/react-dropzone/issues/1435\n const syncFile = item.getAsFile();\n\n const h = await getFsHandle(item);\n if (h !== null && h !== undefined) {\n // Hand back the File we captured synchronously from the DataTransferItem when we have it,\n // rather than `handle.getFile()`. Both wrap the same dropped file, but only the original\n // preserves the File object identity that Electron's `webUtils.getPathForFile()` needs to\n // resolve an on-disk path; a File from `FileSystemFileHandle.getFile()` loses it\n // (electron/electron#33647), which broke drag-and-drop path access in Electron.\n // See https://github.com/react-dropzone/react-dropzone/issues/1411\n // `syncFile` is only null in rare cases (e.g. a cross-window drop where getAsFile() returned\n // null), so fall back to the handle's File there. Either way, attach the durable handle.\n const file = syncFile ?? (await h.getFile());\n file.handle = h;\n return toFileWithPath(file);\n }\n // No usable handle. `h` is `null` when Chromium has no backing handle for the item (e.g. a file\n // dragged out of an archive — see the issue above) and `undefined` when the File System Access\n // API is unavailable or we're not in a secure context (see\n // https://github.com/react-dropzone/file-selector/issues/120). In both cases fall back to the\n // File we captured synchronously rather than throwing or re-reading a now-neutered item.\n if (!syncFile) {\n throw new UnexpectedObjectError(item);\n }\n return toFileWithPath(syncFile, entry?.fullPath ?? undefined);\n}\n\n// Resolve the https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle for a dropped item.\n// Returns `undefined` when the File System Access API isn't available or we're not in a secure context.\n//\n// We check for a secure context because, due to a bug in Chrome (as far as we know),\n// the browser crashes when calling this API (yet to be confirmed as a consistent behaviour).\n// See:\n// - https://issues.chromium.org/issues/40186242\n// - https://github.com/react-dropzone/react-dropzone/issues/1397\nasync function getFsHandle(item: DataTransferItem) {\n if (globalThis.isSecureContext && typeof (item as any).getAsFileSystemHandle === \"function\") {\n return (item as any).getAsFileSystemHandle();\n }\n return undefined;\n}\n\n// Traverse a directory recursively using the File System Access API, returning a flat list of files.\n// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle\nasync function fromDirHandle(handle: any, path: string): Promise<FileWithPath[]> {\n const files: FileWithPath[] = [];\n for await (const child of handle.values()) {\n const childPath = `${path}/${child.name}`;\n if (child.kind === \"directory\") {\n files.push(...(await fromDirHandle(child, childPath)));\n } else {\n const file = await child.getFile();\n files.push(toFileWithPath(file, childPath, child));\n }\n }\n return files;\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemEntry\nasync function fromEntry(entry: any) {\n return entry.isDirectory ? fromDirEntry(entry) : fromFileEntry(entry);\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry\nfunction fromDirEntry(entry: any) {\n const reader = entry.createReader();\n\n return new Promise<FileArray[]>((resolve, reject) => {\n const entries: Promise<FileValue[]>[] = [];\n\n function readEntries() {\n // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/createReader\n // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries\n reader.readEntries(\n async (batch: any[]) => {\n if (!batch.length) {\n // Done reading directory\n try {\n const files = await Promise.all(entries);\n resolve(files);\n } catch (err) {\n reject(err);\n }\n } else {\n const items = Promise.all(batch.map(fromEntry));\n entries.push(items);\n\n // Continue reading\n readEntries();\n }\n },\n (err: any) => {\n reject(err);\n }\n );\n }\n\n readEntries();\n });\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry\nasync function fromFileEntry(entry: any) {\n return new Promise<FileWithPath>((resolve, reject) => {\n entry.file(\n (file: FileWithPath) => {\n const fwp = toFileWithPath(file, entry.fullPath);\n resolve(fwp);\n },\n (err: any) => {\n reject(err);\n }\n );\n });\n}\n\n// Infinite type recursion\n// https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540\ninterface FileArray extends Array<FileValue> {}\ntype FileValue = FileWithPath | FileArray[];\n"],"mappings":"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"}