diff --git a/package-lock.json b/package-lock.json index 494659c0..61960faa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8566,7 +8566,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -8587,12 +8588,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8607,17 +8610,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8734,7 +8740,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8746,6 +8753,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8760,6 +8768,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8767,12 +8776,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8791,6 +8802,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8871,7 +8883,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8883,6 +8896,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -8968,7 +8982,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -9004,6 +9019,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9023,6 +9039,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9066,12 +9083,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/index.js b/src/index.js index fcd7dbe5..0138a6bf 100755 --- a/src/index.js +++ b/src/index.js @@ -202,7 +202,14 @@ Dropzone.propTypes = { * @param {object[]} files * @param {(DragEvent|Event)} event */ - onDropRejected: PropTypes.func + onDropRejected: PropTypes.func, + + /** + * Cb for when a user removes one or more files. + * + * @param {object[]} files + */ + onRemoveFiles: PropTypes.func } export default Dropzone @@ -269,6 +276,7 @@ export default Dropzone * @property {File[]} draggedFiles Files in active drag * @property {File[]} acceptedFiles Accepted files * @property {File[]} rejectedFiles Rejected files + * @property {Function} removeFiles Accepts an array of File objects to be removed */ const initialState = { @@ -368,6 +376,7 @@ export function useDropzone({ onDropAccepted, onDropRejected, onFileDialogCancel, + onRemoveFiles, preventDropOnDocument = true, noClick = false, noKeyboard = false, @@ -710,6 +719,26 @@ export function useDropzone({ [inputRef, accept, multiple, onDropCb, disabled] ) + const removeFiles = React.useCallback( + files => { + const acceptedFiles = state.acceptedFiles.filter(acceptedFile => { + return !files.find(f => f === acceptedFile); + }); + const rejectedFiles = state.rejectedFiles.filter(rejectedFile => { + return !files.find(f => f === rejectedFile); + }); + + dispatch({ + acceptedFiles, + rejectedFiles, + type: 'setFiles' + }) + + onRemoveFiles && onRemoveFiles(files) + }, + [state, onRemoveFiles] + ) + const fileCount = draggedFiles.length const isMultipleAllowed = multiple || fileCount <= 1 const isDragAccept = fileCount > 0 && allFilesAccepted(draggedFiles, accept, minSize, maxSize) @@ -724,7 +753,8 @@ export function useDropzone({ getInputProps, rootRef, inputRef, - open: composeHandler(openFileDialog) + open: composeHandler(openFileDialog), + removeFiles } } diff --git a/src/index.spec.js b/src/index.spec.js index c6bf15ae..0cc30160 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -1914,6 +1914,83 @@ describe('useDropzone() hook', () => { expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything()) }) + test('removeFiles removes file from files arrays', async () => { + const FileList = (props = { files: [] }) => ( + + ) + + const getAcceptedFiles = node => node.querySelectorAll(`[data-type="accepted"]`) + const getRejectedFiles = node => node.querySelectorAll(`[data-type="rejected"]`) + + const ui = ( + + {({ getRootProps, getInputProps, acceptedFiles, rejectedFiles, removeFiles }) => ( +
+ + + + +
+ )} +
+ ) + const { container } = render(ui) + const dropzone = container.querySelector('div') + const input = container.querySelector('input') + + Object.defineProperty(input, 'files', { value: [...files, ...images] }) + + dispatchEvt(input, 'change') + await flushPromises(ui, container) + + const button = container.querySelector('button') + dispatchEvt(button, 'click') + + await flushPromises(ui, container) + + const acceptedFileList = getAcceptedFiles(dropzone) + expect(acceptedFileList).toHaveLength(1) + const rejectedFileList = getRejectedFiles(dropzone) + expect(rejectedFileList).toHaveLength(0) + }) + + test('onRemoveFiles callback is invoked when removeFiles is called', async () => { + const onRemoveSpy = jest.fn() + + const ui = ( + + {({ getRootProps, getInputProps, acceptedFiles, removeFiles }) => ( +
+ + +
+ )} +
+ ) + const { container } = render(ui) + const input = container.querySelector('input') + + Object.defineProperty(input, 'files', { value: files }) + + dispatchEvt(input, 'change') + await flushPromises(ui, container) + + const button = container.querySelector('button') + dispatchEvt(button, 'click') + + expect(onRemoveSpy).toHaveBeenCalledWith(files) + }) + it('sets {acceptedFiles, rejectedFiles}', async () => { const FileList = (props = { files: [] }) => (