UNPKG

file-selector

Version:

Convert DataTransfer object to a list of File objects

1 lines 17.2 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 const h = await getFsHandle(item);\n if (h === null) {\n throw new UnexpectedObjectError(item);\n }\n // It seems that the handle can be `undefined` (see https://github.com/react-dropzone/file-selector/issues/120),\n // so we check if it isn't; if it is, the code path continues to the next API (`getAsFile`).\n // The handle is also `undefined` when the File System Access API isn't available or we're not\n // in a secure context, in which case we likewise fall back to `getAsFile`.\n if (h !== undefined) {\n const file = await h.getFile();\n file.handle = h;\n return toFileWithPath(file);\n }\n const file = item.getAsFile();\n if (!file) {\n throw new UnexpectedObjectError(item);\n }\n const fwp = toFileWithPath(file, entry?.fullPath ?? undefined);\n return fwp;\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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"}