File Selector is a TypeScript utility library that converts DataTransfer objects or file input elements to a list of File objects. It provides a unified interface for handling files from drag-and-drop events, file input change events, and FileSystemFileHandle items, with built-in browser compatibility handling.
npm install file-selectorimport { fromEvent, FileWithPath } from "file-selector";For CommonJS:
const { fromEvent, FileWithPath } = require("file-selector");import { fromEvent } from "file-selector";
// Handle drag-and-drop events
document.addEventListener("drop", async (evt) => {
const files = await fromEvent(evt);
console.log(files);
});
// Handle file input changes
const input = document.getElementById("myInput");
input.addEventListener("change", async (evt) => {
const files = await fromEvent(evt);
console.log(files);
});
// Handle FileSystemFileHandle arrays (experimental)
const handles = await window.showOpenFilePicker({ multiple: true });
const files = await fromEvent(handles);
console.log(files);File Selector is built around a single main function with several key components:
fromEvent() function provides unified interface for multiple input typesConverts various file input sources to a standardized array of File objects with path information.
/**
* Convert a DragEvent's DataTransfer object, file input change event,
* or FileSystemFileHandle array to a list of File objects
*
* @param evt - Event or any - Can be DragEvent, input change event, or array of FileSystemFileHandle
* @returns Promise resolving to array of FileWithPath or DataTransferItem objects
*/
async function fromEvent(evt: Event | any): Promise<(FileWithPath | DataTransferItem)[]>;The fromEvent function handles multiple input types:
Key Features:
.DS_Store, Thumbs.db)Extended File interface with additional path properties for better file handling.
interface FileWithPath extends File {
/** File path (may be absolute path on Electron) */
readonly path?: string;
/** Optional FileSystemFileHandle for modern File System API */
readonly handle?: FileSystemFileHandle;
/** Relative path for the file, always populated */
readonly relativePath?: string;
}The FileWithPath interface provides:
The package includes several internal processing capabilities:
.DS_Store, Thumbs.db)interface FileWithPath extends File {
readonly path?: string;
readonly handle?: FileSystemFileHandle;
readonly relativePath?: string;
}
// Standard browser interfaces that the package works with:
interface DataTransferItem {
readonly kind: string;
readonly type: string;
getAsFile(): File | null;
getAsString(callback: (data: string) => void): void;
webkitGetAsEntry?(): FileSystemEntry | null;
}
interface FileSystemFileHandle {
getFile(): Promise<File>;
}
interface FileSystemEntry {
readonly isDirectory: boolean;
readonly isFile: boolean;
readonly name: string;
readonly fullPath: string;
}The package provides broad browser support with graceful degradation:
Basic File Selection:
<input type="file"> (universal support)Advanced Features:
Legacy Support:
The package handles errors gracefully:
import { fromEvent, FileWithPath } from "file-selector";
const dropZone = document.getElementById("drop-zone");
dropZone.addEventListener("dragover", (evt) => {
evt.preventDefault();
});
dropZone.addEventListener("drop", async (evt) => {
evt.preventDefault();
const files = await fromEvent(evt) as FileWithPath[];
// Process files with path information
files.forEach((file) => {
console.log(`File: ${file.name}`);
console.log(`Path: ${file.path}`);
console.log(`Relative Path: ${file.relativePath}`);
console.log(`Size: ${file.size} bytes`);
console.log(`Type: ${file.type}`);
});
});import { fromEvent } from "file-selector";
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.multiple = true;
fileInput.addEventListener("change", async (evt) => {
const files = await fromEvent(evt);
if (files.length === 0) {
console.log("No files selected");
return;
}
console.log(`Selected ${files.length} files:`);
files.forEach((file, index) => {
console.log(`${index + 1}. ${file.name} (${file.type})`);
});
});import { fromEvent } from "file-selector";
// Check for File System API support
if (typeof window.showOpenFilePicker === "function") {
try {
// Open file picker
const handles = await window.showOpenFilePicker({
multiple: true,
types: [{
description: "Images",
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".gif"]
}
}]
});
// Convert handles to files
const files = await fromEvent(handles);
console.log("Selected files:", files);
} catch (err) {
console.log("User cancelled file selection");
}
}import { fromEvent } from "file-selector";
const directoryInput = document.createElement("input");
directoryInput.type = "file";
directoryInput.webkitdirectory = true;
directoryInput.addEventListener("change", async (evt) => {
const files = await fromEvent(evt);
// Group files by directory
const filesByDir = files.reduce((acc, file) => {
const dir = file.relativePath?.split("/")[0] || "root";
if (!acc[dir]) acc[dir] = [];
acc[dir].push(file);
return acc;
}, {} as Record<string, FileWithPath[]>);
Object.entries(filesByDir).forEach(([dir, dirFiles]) => {
console.log(`Directory: ${dir} (${dirFiles.length} files)`);
});
});