UNPKG

@shipstatic/drop

Version:

Headless React hook for file dropping, processing, ZIP extraction, and validation - purpose-built for Ship SDK

207 lines (199 loc) 6.92 kB
import { StaticFile } from '@shipstatic/types'; import { Ship, formatFileSize as formatFileSize$1 } from '@shipstatic/ship'; /** * Core types for @shipstatic/dropzone * Imports types from @shipstatic/types (single source of truth) * and defines dropzone-specific types */ declare const FILE_STATUSES: { readonly PENDING: "pending"; readonly PROCESSING: "processing"; readonly VALIDATION_FAILED: "validation_failed"; readonly PROCESSING_ERROR: "processing_error"; readonly EMPTY_FILE: "empty_file"; readonly READY: "ready"; readonly UPLOADING: "uploading"; readonly COMPLETE: "complete"; readonly ERROR: "error"; }; type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES]; /** * Client-side error structure * Matches ValidationError from @shipstatic/ship for consistency */ interface ClientError { error: string; details: string; errors?: string[]; isClientError: true; } /** * Processed file entry ready for upload * Extends StaticFile from SDK, adding UI-specific properties * This means ProcessedFile IS a StaticFile - can be passed directly to ship.deployments.create() */ interface ProcessedFile extends StaticFile { /** Unique identifier for React keys and tracking */ id: string; /** Original File object (alias for 'content' from StaticFile for better DX) */ file: File; /** Filename without path */ name: string; /** MIME type for UI icons/previews */ type: string; /** Last modified timestamp */ lastModified: number; /** Current processing/upload status */ status: FileStatus; /** Human-readable status message for UI */ statusMessage?: string; /** Upload progress (0-100) - only set during upload */ progress?: number; } /** * State machine values for the drop hook */ type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error'; /** * Status information with title and details */ interface DropStatus { title: string; details: string; } /** * State machine state for the drop hook */ interface DropState { value: DropStateValue; files: ProcessedFile[]; sourceName: string; status: DropStatus | null; } interface DropOptions { /** Ship SDK instance (required for validation) */ ship: Ship; /** Callback when files are processed and ready */ onFilesReady?: (files: ProcessedFile[]) => void; /** Callback when validation fails */ onValidationError?: (error: ClientError) => void; /** Whether to strip common directory prefix from paths (default: true) */ stripPrefix?: boolean; } interface DropReturn { /** Current state of the drop hook */ state: DropState; /** Whether currently processing files (ZIP extraction, etc.) */ isProcessing: boolean; /** Whether user is currently dragging over the dropzone */ isDragging: boolean; /** Get props to spread on dropzone element (handles drag & drop) */ getDropzoneProps: () => { onDragOver: (e: React.DragEvent) => void; onDragLeave: (e: React.DragEvent) => void; onDrop: (e: React.DragEvent) => void; onClick: () => void; }; /** Get props to spread on hidden file input element */ getInputProps: () => { ref: React.RefObject<HTMLInputElement | null>; type: 'file'; style: { display: string; }; multiple: boolean; webkitdirectory: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; }; /** Programmatically trigger file picker */ open: () => void; /** Manually process files (for advanced usage) */ processFiles: (files: File[]) => Promise<void>; /** Clear all files and reset state */ clearAll: () => void; /** Get only valid files ready for upload */ getValidFiles: () => ProcessedFile[]; /** Update upload state for a specific file (status, progress, message) */ updateFileStatus: (fileId: string, state: { status: FileStatus; statusMessage?: string; progress?: number; }) => void; } /** * Headless drop hook for file upload workflows * * @example * ```tsx * const drop = useDrop({ ship }); * * return ( * <div {...drop.getDropzoneProps()} style={{...}}> * <input {...drop.getInputProps()} /> * {drop.isDragging ? "📂 Drop" : "📁 Click"} * </div> * ); * ``` */ declare function useDrop(options: DropOptions): DropReturn; /** * Unified file processing utilities * Converts Files directly to ProcessedFiles */ /** * Format file size to human-readable string * Re-exported from Ship SDK for convenience */ declare const formatFileSize: typeof formatFileSize$1; /** * Create a ProcessedFile from a File object * This is the single conversion point from File to ProcessedFile * * Note: MD5 calculation is handled by Ship SDK during deployment. * Drop focuses on file processing, path normalization, and UI state management. * * Path resolution priority: * 1. options.path (if provided) * 2. file.webkitRelativePath (if non-empty, preserves folder structure) * 3. file.name (fallback) */ declare function createProcessedFile(file: File, options?: { /** Custom path (defaults to webkitRelativePath or file.name) */ path?: string; }): Promise<ProcessedFile>; /** * Get only the valid files (status: READY) from a list * Re-exported from Ship SDK for convenience */ declare const getValidFiles: (files: ProcessedFile[]) => ProcessedFile[]; /** * Strip common directory prefix from file paths * Only strips if ALL files share the same prefix */ declare function stripCommonPrefix(files: ProcessedFile[]): ProcessedFile[]; interface ZipExtractionResult { /** Extracted files as regular File objects */ files: File[]; /** Any errors encountered during extraction */ errors: string[]; } /** * Extracts all files from a ZIP archive * Returns regular File objects that can be processed like any other file */ declare function extractZipToFiles(zipFile: File): Promise<ZipExtractionResult>; /** * Sanitize and normalize a file path to prevent directory traversal attacks * Removes: .., ., leading/trailing slashes, absolute paths, and empty segments * * @example * normalizePath('../../etc/passwd') → 'etc/passwd' * normalizePath('foo/./bar/../baz.txt') → 'foo/baz.txt' * normalizePath('/absolute/path.txt') → 'absolute/path.txt' */ declare function normalizePath(path: string): string; /** * Check if a file is a ZIP file based on MIME type or extension */ declare function isZipFile(file: File): boolean; export { type ClientError, type DropOptions, type DropReturn, type DropState, type DropStateValue, type DropStatus, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };