or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-file-selector

Convert DataTransfer object to a list of File objects

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/file-selector@2.1.x

To install, run

npx @tessl/cli install tessl/npm-file-selector@2.1.0

index.mddocs/

File Selector

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.

Package Information

  • Package Name: file-selector
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install file-selector

Core Imports

import { fromEvent, FileWithPath } from "file-selector";

For CommonJS:

const { fromEvent, FileWithPath } = require("file-selector");

Basic Usage

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);

Architecture

File Selector is built around a single main function with several key components:

  • Main API: fromEvent() function provides unified interface for multiple input types
  • File Enhancement: Internal utilities add path information and MIME type detection to File objects
  • Browser Compatibility: Graceful degradation and polyfills for cross-browser support
  • Type Safety: Full TypeScript integration with enhanced File interface
  • Asynchronous Processing: Promise-based API for handling complex file system operations

Capabilities

File Event Processing

Converts 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:

  • DragEvent: Processes drag-and-drop operations, including folder drops
  • Input Change Event: Handles file input element changes
  • FileSystemFileHandle Array: Experimental support for modern File System API

Key Features:

  • Automatically flattens nested directory structures
  • Filters out system files (.DS_Store, Thumbs.db)
  • Maintains file path information
  • Provides fallbacks for older browsers
  • Handles MIME type detection based on file extensions

Enhanced File Interface

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:

  • Standard File properties (name, size, type, lastModified)
  • Path information for nested directory structures
  • FileSystemFileHandle for modern browser APIs
  • Consistent relative path handling across platforms

Internal Processing Features

The package includes several internal processing capabilities:

  • MIME Type Detection: Automatic file type detection based on extensions using a comprehensive map of 1200+ file types
  • Path Processing: Smart path resolution that handles relative paths, webkit directory uploads, and cross-platform compatibility
  • File Filtering: Automatic filtering of system files (.DS_Store, Thumbs.db)
  • Directory Flattening: Recursive processing of nested directory structures into flat file arrays

Types

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;
}

Browser Compatibility

The package provides broad browser support with graceful degradation:

Basic File Selection:

  • File API (widely supported)
  • Drag Event (widely supported)
  • DataTransfer (widely supported)
  • <input type="file"> (universal support)

Advanced Features:

  • FileSystem API (limited support, Chrome-based browsers)
  • DataTransferItem.webkitGetAsEntry() (WebKit browsers)
  • FileSystemFileHandle (modern browsers with File System API)

Legacy Support:

  • IE11 compatibility with polyfills
  • Fallback for missing DataTransfer.items
  • Cross-browser file system entry handling

Error Handling

The package handles errors gracefully:

  • Returns empty array for unrecognized input types
  • Handles null/undefined DataTransfer objects
  • Manages FileSystem API availability and errors
  • Provides fallbacks for unsupported browser features
  • Handles secure context requirements for advanced APIs

Usage Examples

Drag and Drop with Folder Support

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}`);
  });
});

File Input with Multiple Selection

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})`);
  });
});

Modern File System API

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");
  }
}

Directory Upload

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)`);
  });
});