@react-md/form
Version:
This package is for creating all the different form input types.
456 lines (455 loc) • 16.2 kB
TypeScript
import type { ChangeEventHandler, DragEventHandler } from "react";
/** @remarks \@since 2.9.0 */
export interface BaseFileUploadStats {
/**
* A unique key associated with each upload generated by `nanoid`.
*/
key: string;
/**
* The file instance that is being uploaded.
*/
file: File;
/**
* The current upload progress as a percentage from 0 - 100 percent.
*/
progress: number;
}
/** @remarks \@since 2.9.0 */
export interface ProcessingFileUploadStats extends BaseFileUploadStats {
status: "pending" | "uploading";
}
/** @remarks \@since 2.9.0 */
export declare type FileReaderResult = FileReader["result"];
/** @remarks \@since 2.9.0 */
export interface CompletedFileUploadStats extends BaseFileUploadStats {
status: "complete";
/**
* The result after a `FileReader` has read a file completely.
*
* Note: This _should_ be an `ArrayBuffer` if the next step is to upload to a
* server.
*
* @see {@link FileReaderParser}
* @see {@link getFileParser}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader | FileReader}
*/
result: FileReaderResult;
}
/** @remarks \@since 2.9.0 */
export declare type FileUploadStats = ProcessingFileUploadStats | CompletedFileUploadStats;
/** @remarks \@since 2.9.0 */
export declare type FileUploadStatus = FileUploadStats["status"];
/** @remarks \@since 2.9.0 */
export interface FileUploadHandlers<E extends HTMLElement> {
onDrop?: DragEventHandler<E>;
onChange?: ChangeEventHandler<HTMLInputElement>;
}
/**
* An error that will be created if a user tries dragging and dropping files
* from a shared directory that they do not have access to. This error will not
* occur much.
*
* @remarks \@since 2.9.0
*/
export declare class FileAccessError extends Error {
/**
* A unique key generated by `nanoid` that can be used as a `React` key
*/
key: string;
/**
*
* @param message - An optional message for the error.
*/
constructor(message?: string);
}
/**
* An error that just requires a `File` to be passed as the first argument.
*
* @remarks \@since 2.9.0
*/
export declare class GenericFileError extends Error {
files: readonly File[];
reason?: string | undefined;
/**
* A unique key generated by `nanoid` that can be used as a `React` key
*/
key: string;
/**
*
* @param files - A list of files that caused the error.
* @param reason - An optional reason for the error
*/
constructor(files: readonly File[], reason?: string | undefined);
}
/**
* An error that is created during the upload process if the number of files
* exceeds the {@link FileUploadOptions.maxFiles} amount.
*
* @remarks \@since 2.9.0
*/
export declare class TooManyFilesError extends GenericFileError {
limit: number;
/**
*
* @param files - The list of files that could not be uploaded due to the file
* limit defined.
* @param limit - The max limit of files allowed.
*/
constructor(files: readonly File[], limit: number);
}
/**
* An error that will be created if a user tries to upload a file that
* is either:
* - less than the {@link FileValidationOptions.minFileSize}
* - greater than the {@link FileValidationOptions.maxFileSize}
* - including the file would be greater than the {@link FileValidationOptions.totalFileSize}
*
* @remarks \@since 2.9.0
*/
export declare class FileSizeError extends GenericFileError {
type: "min" | "max" | "total";
limit: number;
/**
*
* @param files - The list of files that have the file size error
* @param type - The file size error type
* @param limit - The number of bytes allowed based on the type
*/
constructor(files: readonly File[], type: "min" | "max" | "total", limit: number);
}
/**
* An error that will be created if a user tries to upload a file that does not
* end with one of the {@link FileValidationOptions.extensions}.
*
* @remarks \@since 2.9.0
*/
export declare class FileExtensionError extends GenericFileError {
extensions: readonly string[];
/**
*
* @param files - The file that caused the error
* @param extensions - The allowed list of file extensions
*/
constructor(files: readonly File[], extensions: readonly string[]);
}
/**
* Mostly an internal type that is used to allow custom validation errors
*
* @remarks \@since 2.9.0
*/
export declare type FileValidationError<E = GenericFileError> = FileAccessError | TooManyFilesError | FileSizeError | FileExtensionError | E;
/**
* A simple type-guard that can be used to check if the
* {@link FileValidationError} is the {@link GenericFileError} which can be
* useful when displaying the errors to the user.
*
* @param error - The error to check
* @returns true if the error is a {@link FileAccessError}
*/
export declare function isGenericFileError<CustomError>(error: FileValidationError<CustomError>): error is GenericFileError;
/**
* A simple type-guard that can be used to check if the
* {@link FileValidationError} is the {@link FileAccessError} which can be
* useful when displaying the errors to the user.
*
* @param error - The error to check
* @returns true if the error is a {@link FileAccessError}
*/
export declare function isFileAccessError<CustomError>(error: FileValidationError<CustomError>): error is FileAccessError;
/**
* A simple type-guard that can be used to check if the
* {@link FileValidationError} is the {@link TooManyFilesError} which can be
* useful when displaying the errors to the user.
*
* @param error - The error to check
* @returns true if the error is a {@link TooManyFilesError}
*/
export declare function isTooManyFilesError<CustomError>(error: FileValidationError<CustomError>): error is TooManyFilesError;
/**
* A simple type-guard that can be used to check if the
* {@link FileValidationError} is the {@link FileSizeError} which can be
* useful when displaying the errors to the user.
*
* @param error - The error to check
* @returns true if the error is a {@link FileSizeError}
*/
export declare function isFileSizeError<CustomError>(error: FileValidationError<CustomError>): error is FileSizeError;
/**
* A simple type-guard that can be used to check if the
* {@link FileValidationError} is the {@link FileExtensionError} which can be
* useful when displaying the errors to the user.
*
* @param error - The error to check
* @returns true if the error is a {@link FileExtensionError}
*/
export declare function isFileExtensionError<CustomError>(error: FileValidationError<CustomError>): error is FileExtensionError;
/**
* This function is used to determine if a file should be added to the
* {@link FileExtensionError}. The default implementation should work for most
* use cases except when files that do not have extensions can be uploaded. i.e.
* LICENSE files.
*
* @param file - The file being checked
* @param extensionRegExp - A regex that will only be defined if the
* `extensions` list had at least one value.
* @param extensions - The list of extensions allowed
* @returns true if the file has a valid name.
* @remarks \@since 3.1.0
*/
export declare type IsValidFileName = (file: File, extensionRegExp: RegExp | undefined, extensions: readonly string[]) => boolean;
/**
*
* @defaultValue `matcher?.test(file.name) ?? true`
* @remarks \@since 3.1.0
*/
export declare const isValidFileName: IsValidFileName;
/** @remarks \@since 2.9.0 */
export interface FileValidationOptions {
/**
* If the number of files should be limited, set this value to a number
* greater than `0`.
*
* Note: This still allows "infinite" files when set to `0` since the
* `<input>` element should normally be set to `disabled` if files should not
* be able to be uploaded.
*
* @defaultValue `-1`
*/
maxFiles?: number;
/**
* An optional minimum file size to enforce for each file. This will only be
* used when it is greater than `0`.
*
* @defaultValue `-1`
*/
minFileSize?: number;
/**
* An optional maximum file size to enforce for each file. This will only be
* used when it is greater than `0`.
*
* @defaultValue `-1`
*/
maxFileSize?: number;
/**
* An optional list of extensions to enforce when uploading files.
*
* Note: The extensions and file names will be compared ignoring case.
*
* @example
* Only Allow Images
* ```ts
* const extensions = ["png", "jpeg", "jpg", "gif"];
* ```
*/
extensions?: readonly string[];
/** {@inheritDoc IsValidFileName} */
isValidFileName?: IsValidFileName;
/**
* An optional total file size to enforce when the {@link maxFiles} option is
* not set to `1`.
*
* @defaultValue `-1`
*/
totalFileSize?: number;
}
/** @remarks \@since 2.9.0 */
export interface FilesValidationOptions extends Required<FileValidationOptions> {
/**
* The total number of bytes in the {@link FileUploadHookReturnValue.stats}
* list. This is really just:
*
* ```ts
* const totalBytes = stats.reduce((total, stat) => total + stat.file.size, 0);
* ```
*/
totalBytes: number;
/**
* The total number of files in the {@link FileUploadHookReturnValue.stats}.
*/
totalFiles: number;
}
/** @remarks \@since 2.9.0 */
export interface ValidatedFilesResult<CustomError> {
/**
* A filtered list of files that have been validated and can be queued for the
* upload process.
*/
pending: readonly File[];
/**
* A list of {@link FileValidationError} that occurred during the validation
* step.
*
* Note: If an error has occurred, the file **should not** be added to the
* {@link pending} list of files.
*/
errors: readonly FileValidationError<CustomError>[];
}
/**
* This function will be called whenever a file has been uploaded by the user
* either through an `<input type="file">` or drag and drop behavior.
*
* @example
* Simple Example
* ```ts
* const validateFiles: FilesValidator = (files, options) => {
* const invalid: File[] = [];
* const pending: File[] = [];
* for (const file of files) {
* if (!/\.(jpe?g|svg|png)$/i.test(name)) {
* invalid.push(file);
* } else {
* pending.push(file);
* }
* }
*
* const errors: FileValidationError[] = [];
* if (invalid.length) {
* errors.push(new GenericFileError(invalid))
* }
*
* return { pending, errors };
* };
* ```
*
* @typeparam E - An optional custom file validation error.
* @param files - The list of files to check
* @param options - The {@link FilesValidationOptions}
* @returns the {@link ValidatedFilesResult}
* @see {@link validateFiles} for the default implementation
* @remarks \@since 2.9.0
*/
export declare type FilesValidator<CustomError = never> = (files: readonly File[], options: FilesValidationOptions) => ValidatedFilesResult<CustomError>;
/**
* A pretty decent default implementation for validating files with the
* {@link useFileUpload} that ensures the {@link FilesValidationOptions} are
* enforced before allowing a file to be uploaded.
*
* @typeparam E - An optional custom file validation error.
* @param files - The list of files to check
* @param options - The {@link FilesValidationOptions}
* @returns the {@link ValidatedFilesResult}
* @remarks \@since 2.9.0
*/
export declare function validateFiles<CustomError>(files: readonly File[], { maxFiles, extensions, minFileSize, maxFileSize, totalBytes, totalFiles, totalFileSize, isValidFileName, }: FilesValidationOptions): ValidatedFilesResult<CustomError>;
/**
* This will first check if the mime-type of the file starts with `text/` and
* fallback to checking a few file names or extensions that should be considered
* text.
*
* This function is not guaranteed to be 100% correct and is only useful if
* trying to generate a preview of files uploaded to the browser.
*
* @param file - The file to check
* @returns `true` if the file should be considered as a text-content file.
* @remarks \@since 2.9.0
*/
export declare function isTextFile(file: File): boolean;
/**
* This will first check if the mime-type of the file starts with `text\/` and
* fallback to checking a few file names or extensions that should be considered
* text.
*
* This function is not guaranteed to be 100% correct and is only useful if
* trying to generate a preview of files uploaded to the browser.
*
* @param file - The file to check
* @returns `true` if the file should be considered as a text content file.
* @remarks \@since 2.9.0
*/
export declare function isImageFile(file: File): boolean;
/**
* This will first check if the mime-type of the file starts with `audio/` and
* fallback to checking a few file names or extensions that should be considered
* audio.
*
* This function is not guaranteed to be 100% correct and is only useful if
* trying to generate a preview of files uploaded to the browser.
*
* @param file - The file to check
* @returns `true` if the file should be considered as a audio content file.
* @remarks \@since 2.9.0
*/
export declare function isAudioFile(file: File): boolean;
/**
* This will first check if the mime-type of the file starts with `video/` and
* fallback to checking a few file names or extensions that should be considered
* video.
*
* This function is not guaranteed to be 100% correct and is only useful if
* trying to generate a preview of files uploaded to the browser.
*
* @param file - The file to check
* @returns `true` if the file should be considered as a video content file.
* @remarks \@since 2.9.0
*/
export declare function isVideoFile(file: File): boolean;
/**
* This function is not guaranteed to be 100% correct and is only useful if
* trying to generate a preview of files uploaded to the browser.
*
* @param file - The file to check
* @returns `true` if the file matches an image, audio, or video file.
* @remarks \@since 2.9.0
*/
export declare function isMediaFile(file: File): boolean;
/**
* One of the function names from a `FileReader` to upload a file to the
* client.
*
* Note: If this file does not need to be previewed in the browser and will
* immediately be uploaded to a server, use `readAsArrayBuffer`.
*
* @remarks \@since 2.9.0
*/
export declare type FileReaderParser = "readAsText" | "readAsDataURL" | "readAsBinaryString" | "readAsArrayBuffer";
/**
* A function that should return one of the {@link FileReaderParser} functions
* to start uploading a file to the browser.
*
* @example
* The Default File Upload Parser
* ```ts
* export const getFileParser: GetFileParser = (file) => {
* if (isMediaFile(file)) {
* return "readAsDataURL";
* }
*
* if (isTextFile(file)) {
* return "readAsText";
* }
*
* return "readAsArrayBuffer";
* };
* ```
*
* @param file - The file to get a parser for
* @returns the {@link FileReaderParser} string.
* @remarks \@since 2.9.0
*/
export declare type GetFileParser = (file: File) => FileReaderParser;
/**
* This function will attempt to read:
* - media (image, audio, and video) files as a data url so they can be
* previewed in `<img>`, `<audio>`, and `<video>` tags
* - text files as plain text
* - everything else as an `ArrayBuffer` which can be manually converted into a
* data url if needed with `URL.createObjectURL`
*
* @remarks \@since 2.9.0
*/
export declare const getFileParser: GetFileParser;
/** @remarks \@since 2.9.0 */
export interface SplitFileUploads {
readonly pending: readonly ProcessingFileUploadStats[];
readonly uploading: readonly ProcessingFileUploadStats[];
readonly complete: readonly CompletedFileUploadStats[];
}
/**
* This util will split all the current upload stats by status.
*
* @param stats - The {@link FileUploadStats} list generally returned by the
* {@link useFileUpload} hook.
* @returns the {@link SplitFileUploads}.
* @remarks \@since 2.9.0
*/
export declare function getSplitFileUploads(stats: readonly FileUploadStats[]): SplitFileUploads;