diff --git a/src/index.js b/src/index.js index 21f6834e..24875b8c 100755 --- a/src/index.js +++ b/src/index.js @@ -258,9 +258,9 @@ Dropzone.propTypes = { onError: PropTypes.func, /** - * Custom validation function + * Custom validation function. It must return null if there's no errors. * @param {File} file - * @returns {FileError|FileError[]} + * @returns {FileError|FileError[]|null} */ validator: PropTypes.func, }; @@ -577,6 +577,7 @@ export function useDropzone(props = {}) { maxSize, multiple, maxFiles, + validator, }); const isDragReject = fileCount > 0 && !isDragAccept; @@ -604,6 +605,7 @@ export function useDropzone(props = {}) { maxSize, multiple, maxFiles, + validator, ] ); diff --git a/src/index.spec.js b/src/index.spec.js index c371375f..e8b7b32d 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -3441,6 +3441,34 @@ describe("useDropzone() hook", () => { expect.anything() ); }); + + it("sets {isDragAccept, isDragReject}", async () => { + const data = createDtWithFiles(images); + const validator = () => ({ + code: "not-allowed", + message: "Cannot do this!", + }); + + const ui = ( + + {({ getRootProps, getInputProps, isDragAccept, isDragReject }) => ( +
+ + {isDragAccept && "dragAccept"} + {isDragReject && "dragReject"} +
+ )} +
+ ); + + const { container } = render(ui); + const dropzone = container.querySelector("div"); + + await act(() => fireEvent.dragEnter(dropzone, data)); + + expect(dropzone).not.toHaveTextContent("dragAccept"); + expect(dropzone).toHaveTextContent("dragReject"); + }); }); describe("accessibility", () => { diff --git a/src/utils/index.js b/src/utils/index.js index b5ca131b..a02b260c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -76,6 +76,18 @@ function isDefined(value) { return value !== undefined && value !== null; } +/** + * + * @param {object} options + * @param {File[]} options.files + * @param {string|string[]} [options.accept] + * @param {number} [options.minSize] + * @param {number} [options.maxSize] + * @param {boolean} [options.multiple] + * @param {number} [options.maxFiles] + * @param {(f: File) => FileError|FileError[]|null} [options.validator] + * @returns + */ export function allFilesAccepted({ files, accept, @@ -83,6 +95,7 @@ export function allFilesAccepted({ maxSize, multiple, maxFiles, + validator, }) { if ( (!multiple && files.length > 1) || @@ -94,7 +107,8 @@ export function allFilesAccepted({ return files.every((file) => { const [accepted] = fileAccepted(file, accept); const [sizeMatch] = fileMatchSize(file, minSize, maxSize); - return accepted && sizeMatch; + const customErrors = validator ? validator(file) : null; + return accepted && sizeMatch && !customErrors; }); } @@ -287,3 +301,13 @@ export function isExt(v) { /** * @typedef {Object.} AcceptProp */ + +/** + * @typedef {object} FileError + * @property {string} message + * @property {ErrorCode|string} code + */ + +/** + * @typedef {"file-invalid-type"|"file-too-large"|"file-too-small"|"too-many-files"} ErrorCode + */ diff --git a/src/utils/index.spec.js b/src/utils/index.spec.js index b704cb3d..b75195c3 100644 --- a/src/utils/index.spec.js +++ b/src/utils/index.spec.js @@ -344,6 +344,13 @@ describe("allFilesAccepted()", () => { expect( utils.allFilesAccepted({ files, multiple: true, maxFiles: 1 }) ).toEqual(false); + + expect( + utils.allFilesAccepted({ + files, + validator: () => ({ code: "not-allowed", message: "Cannot do this!" }), + }) + ).toEqual(false); }); });