UNPKG

use-file-picker

Version:

Simple react hook to open browser file selector.

359 lines (352 loc) 12.7 kB
'use strict'; var react = require('react'); var fileSelector = require('file-selector'); function openFileDialog(accept, multiple, callback, initializeWithCustomAttributes) { const inputElement = document.createElement("input"); inputElement.style.display = "none"; document.body.appendChild(inputElement); inputElement.type = "file"; if (accept !== "*") inputElement.accept = accept; inputElement.multiple = multiple; inputElement.addEventListener("change", (arg) => { callback(arg); document.body.removeChild(inputElement); }); inputElement.addEventListener("cancel", () => { document.body.removeChild(inputElement); }); if (initializeWithCustomAttributes) { initializeWithCustomAttributes(inputElement); } inputElement.dispatchEvent(new MouseEvent("click")); } const useValidators = ({ onFilesSelected: onFilesSelectedProp, onFilesSuccessfullySelected: onFilesSuccessfullySelectedProp, onFilesRejected: onFilesRejectedProp, onClear: onClearProp, validators }) => { const onFilesSelected = react.useCallback( (data) => { onFilesSelectedProp == null ? void 0 : onFilesSelectedProp(data); validators == null ? void 0 : validators.forEach((validator) => { validator.onFilesSelected(data); }); }, [onFilesSelectedProp, validators] ); const onFilesSuccessfullySelected = react.useCallback( (data) => { onFilesSuccessfullySelectedProp == null ? void 0 : onFilesSuccessfullySelectedProp(data); validators == null ? void 0 : validators.forEach((validator) => { validator.onFilesSuccessfullySelected(data); }); }, [validators, onFilesSuccessfullySelectedProp] ); const onFilesRejected = react.useCallback( (errors) => { onFilesRejectedProp == null ? void 0 : onFilesRejectedProp(errors); validators == null ? void 0 : validators.forEach((validator) => { validator.onFilesRejected(errors); }); }, [validators, onFilesRejectedProp] ); const onClear = react.useCallback(() => { onClearProp == null ? void 0 : onClearProp(); validators == null ? void 0 : validators.forEach((validator) => { var _a; (_a = validator.onClear) == null ? void 0 : _a.call(validator); }); }, [validators, onClearProp]); return { onFilesSelected, onFilesSuccessfullySelected, onFilesRejected, onClear }; }; var __defProp$1 = Object.defineProperty; var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; var __hasOwnProp$1 = Object.prototype.hasOwnProperty; var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$1 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); if (__getOwnPropSymbols$1) for (var prop of __getOwnPropSymbols$1(b)) { if (__propIsEnum$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); } return a; }; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp$1.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols$1) for (var prop of __getOwnPropSymbols$1(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum$1.call(source, prop)) target[prop] = source[prop]; } return target; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; const EMPTY_ARRAY = []; function useFilePicker(props = {}) { const { accept = "*", multiple = true, readAs = "Text", readFilesContent = true, validators = EMPTY_ARRAY, initializeWithCustomParameters } = props; const [plainFiles, setPlainFiles] = react.useState([]); const [filesContent, setFilesContent] = react.useState([]); const [fileErrors, setFileErrors] = react.useState([]); const [loading, setLoading] = react.useState(false); const { onFilesSelected, onFilesSuccessfullySelected, onFilesRejected, onClear } = useValidators(props); const clear = react.useCallback(() => { setPlainFiles([]); setFilesContent([]); setFileErrors([]); }, []); const clearWithEventListener = react.useCallback(() => { clear(); onClear == null ? void 0 : onClear(); }, [clear, onClear]); const parseFile = react.useCallback( (file) => new Promise( (resolve, reject) => { const reader = new FileReader(); const readStrategy = reader[`readAs${readAs}`]; readStrategy.call(reader, file, props.readAs === "Text" ? props.encoding : void 0); const addError = (_a) => { var others = __objRest(_a, []); reject(__spreadValues$1({}, others)); }; reader.onload = () => __async(null, null, function* () { return Promise.all( validators.map( (validator) => validator.validateAfterParsing(props, file, reader).catch((err) => Promise.reject(addError(err))) ) ).then( () => resolve({ content: reader.result, name: file.name, lastModified: file.lastModified, path: file.path, size: file.size, type: file.type }) ).catch(() => { }); }); reader.onerror = () => { addError({ name: "FileReaderError", readerError: reader.error, causedByFile: file }); }; } ), [props, readAs, validators] ); const openFilePicker = react.useCallback(() => { const fileExtensions = accept instanceof Array ? accept.join(",") : accept; openFileDialog( fileExtensions, multiple, (evt) => __async(null, null, function* () { clear(); setLoading(true); const plainFileObjects = yield fileSelector.fromEvent(evt); const validationsBeforeParsing = (yield Promise.all( validators.map( (validator) => validator.validateBeforeParsing(props, plainFileObjects).catch((err) => Array.isArray(err) ? err : [err]) ) )).flat(1).filter(Boolean); setPlainFiles(plainFileObjects); setFileErrors(validationsBeforeParsing); if (validationsBeforeParsing.length) { setPlainFiles([]); onFilesRejected == null ? void 0 : onFilesRejected({ errors: validationsBeforeParsing }); onFilesSelected == null ? void 0 : onFilesSelected({ errors: validationsBeforeParsing }); setLoading(false); return; } if (!readFilesContent) { onFilesSelected == null ? void 0 : onFilesSelected({ plainFiles: plainFileObjects, filesContent: [] }); setLoading(false); return; } const validationsAfterParsing = []; const filesContent2 = yield Promise.all( plainFileObjects.map( (file) => parseFile(file).catch( (fileError) => { validationsAfterParsing.push(...Array.isArray(fileError) ? fileError : [fileError]); } ) ) ); setLoading(false); if (validationsAfterParsing.length) { setPlainFiles([]); setFilesContent([]); setFileErrors((errors) => [...errors, ...validationsAfterParsing]); onFilesRejected == null ? void 0 : onFilesRejected({ errors: validationsAfterParsing }); onFilesSelected == null ? void 0 : onFilesSelected({ errors: validationsBeforeParsing.concat(validationsAfterParsing) }); return; } setFilesContent(filesContent2); setPlainFiles(plainFileObjects); setFileErrors([]); onFilesSuccessfullySelected == null ? void 0 : onFilesSuccessfullySelected({ filesContent: filesContent2, plainFiles: plainFileObjects }); onFilesSelected == null ? void 0 : onFilesSelected({ plainFiles: plainFileObjects, filesContent: filesContent2 }); }), initializeWithCustomParameters ); }, [ props, accept, clear, initializeWithCustomParameters, multiple, onFilesRejected, onFilesSelected, onFilesSuccessfullySelected, parseFile, readFilesContent, validators ]); return { openFilePicker, filesContent, errors: fileErrors, loading, plainFiles, clear: clearWithEventListener }; } var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); function useImperativeFilePicker(props) { const { onFilesSelected, onFilesSuccessfullySelected, validators, onFileRemoved } = props; const [allPlainFiles, setAllPlainFiles] = react.useState([]); const [allFilesContent, setAllFilesContent] = react.useState([]); const { openFilePicker, loading, errors, clear } = useFilePicker(__spreadProps(__spreadValues({}, props), { onFilesSelected: (data) => { var _a; if (!onFilesSelected) return; if ((_a = data.errors) == null ? void 0 : _a.length) { return onFilesSelected(data); } onFilesSelected({ errors: void 0, plainFiles: [...allPlainFiles, ...data.plainFiles || []], filesContent: [...allFilesContent, ...data.filesContent || []] }); }, onFilesSuccessfullySelected: (data) => { setAllPlainFiles((previousPlainFiles) => previousPlainFiles.concat(data.plainFiles)); setAllFilesContent((previousFilesContent) => previousFilesContent.concat(data.filesContent)); if (!onFilesSuccessfullySelected) return; onFilesSuccessfullySelected({ plainFiles: [...allPlainFiles, ...data.plainFiles || []], filesContent: [...allFilesContent, ...data.filesContent || []] }); } })); const clearAll = react.useCallback(() => { clear(); setAllPlainFiles([]); setAllFilesContent([]); }, [clear]); const removeFileByIndex = react.useCallback( (index) => { setAllFilesContent((previousFilesContent) => [ ...previousFilesContent.slice(0, index), ...previousFilesContent.slice(index + 1) ]); setAllPlainFiles((previousPlainFiles) => { const removedFile = previousPlainFiles[index]; if (!removedFile) { return previousPlainFiles; } validators == null ? void 0 : validators.forEach((validator) => { var _a; return (_a = validator.onFileRemoved) == null ? void 0 : _a.call(validator, removedFile, index); }); onFileRemoved == null ? void 0 : onFileRemoved(removedFile, index); return [...previousPlainFiles.slice(0, index), ...previousPlainFiles.slice(index + 1)]; }); }, [validators, onFileRemoved] ); const removeFileByReference = react.useCallback( (file) => { const index = allPlainFiles.findIndex((f) => f === file); if (index === -1) return; removeFileByIndex(index); }, [removeFileByIndex, allPlainFiles] ); return { openFilePicker, plainFiles: allPlainFiles, filesContent: allFilesContent, loading, errors, clear: clearAll, removeFileByIndex, removeFileByReference }; } exports.useFilePicker = useFilePicker; exports.useImperativeFilePicker = useImperativeFilePicker; //# sourceMappingURL=index.cjs.map