event-to-files
Version:
Get the files from an input change event or from a file or directory drag and drop event
130 lines (122 loc) • 4.41 kB
JavaScript
/**
`getFiles` returns the list of files from a `change` or `drop` Event.
@remarks
`getFiles` supports only the `change` event type on file inputs and the `drop` event type (`DragEvent`).
Calling `getFiles` with other types of events will return an empty list.
For directory drop events, `getFiles` relies on the `webkitGetAsEntry` API;
if this API is unavailable in the browser, `getFiles` will return an empty list.
@param event - The `change` or `drop` `Event` for which files should be retrieved.
@returns A list of `EventFile`s that contain the `File`s retrieved from an `Event`.
@example
```typescript
import { getFiles } from "event-to-files";
// For an `<input type="file" />`.
const fileInput = document.getElementById("file-input");
fileInput.addEventListener("change", async (event) => {
event.preventDefault();
const files = await getFiles(event);
for (const { file } of files) {
console.log(file.name);
}
});
// For a drag and drop event.
const dropZone = document.getElementById("drop-zone");
dropZone.addEventListener("dragover", (event) => {
// Cancel the `dragover` event to let the `drop` event fire later.
event.preventDefault();
});
dropZone.addEventListener("drop", async (event) => {
event.preventDefault();
const files = await getFiles(event);
for (const { file, entry } of files) {
console.log(file.name);
// If a directory was dragged and dropped,
// we can get the file's full path in the directory.
if (entry?.fullPath) {
console.log(entry.fullPath);
}
}
});
```
*/
export let getFiles = async (event) => {
// Get the list of files from the `<input type="file" />` `change` event.
if (event.type === "change") {
return [...(event.target?.files ?? [])].map((file) => ({ file }));
}
// Get the list of files from the drag and drop `DragEvent`.
if (event.type === "drop") {
return (await Promise.all(
// Get the data transfer items of kind `file` and read the files.
[...(event.dataTransfer?.items ?? [])]
.filter(({ kind }) => kind === "file")
.map(itemToFiles))).flat();
}
// Unsupported event, no files.
return [];
};
let itemToFiles = async (item) => {
// Note: `webkitGetAsEntry` may not be available in all browsers.
let entry = item.webkitGetAsEntry?.();
// Note: `getAsFile` returns a non-null `File` even for directories.
let file = item.getAsFile();
// No entry or file for this item.
if (!entry && !file)
return [];
// A file exists but not its entry; it could be a real file or a directory.
if (!entry && file)
return [{ file }];
// A true file exists, return it with its entry.
if (entry?.isFile && file)
return [{ file, entry: entry }];
// Try to get the files from the entry.
return await entryToFiles(entry);
};
let entryToFiles = async (entry) => {
// Entry is a single file.
if (entry?.isFile) {
return [await fileEntryToFile(entry)];
}
// Entry is a directory.
if (entry?.isDirectory) {
return await dirEntryToFiles(entry);
}
// Unknown or null entry, no files.
return [];
};
let fileEntryToFile = (entry) => {
return new Promise((resolve, reject) => {
entry.file((file) => {
resolve({ file, entry });
}, reject);
});
};
let dirEntryToFiles = async (dirEntry) => {
let files = [];
// Loop until we read all directories.
let dirEntries = [dirEntry];
while (dirEntries.length) {
// Loop until we read all entries in the current directory.
let reader = dirEntries.pop().createReader();
while (true) {
// Read some entries from the directory.
let entries = await new Promise((resolve, reject) => {
reader.readEntries(resolve, reject);
});
// No more entries in this directory.
if (!entries.length)
break;
// Handle found entries.
for (let entry of entries) {
if (entry.isFile) {
files.push(await fileEntryToFile(entry));
}
else if (entry.isDirectory) {
dirEntries.push(entry);
}
}
}
}
// Return directory files.
return files;
};