react-dropzone 11.5.3 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -3
- package/.eslintrc +2 -4
- package/.husky/commit-msg +5 -0
- package/.husky/pre-commit +5 -0
- package/.nvmrc +1 -1
- package/README.md +78 -29
- package/commitlint.config.js +1 -1
- package/dist/es/index.js +195 -151
- package/dist/es/utils/index.js +53 -17
- package/dist/index.js +16 -2
- package/examples/events/README.md +3 -1
- package/examples/no-jsx/README.md +32 -0
- package/package.json +80 -76
- package/rollup.config.js +22 -19
- package/src/.eslintrc +7 -4
- package/src/__snapshots__/index.spec.js.snap +1 -1
- package/src/index.js +372 -288
- package/src/index.spec.js +1837 -1369
- package/src/utils/index.js +117 -56
- package/src/utils/index.spec.js +414 -254
- package/styleguide.config.js +57 -50
- package/testSetup.js +1 -1
- package/typings/.eslintrc +28 -0
- package/typings/react-dropzone.d.ts +24 -14
- package/typings/tests/accept.tsx +10 -9
- package/typings/tests/all.tsx +6 -5
- package/typings/tests/basic.tsx +8 -7
- package/typings/tests/events.tsx +8 -6
- package/typings/tests/file-dialog.tsx +4 -3
- package/typings/tests/hook.tsx +6 -6
- package/typings/tests/plugin.tsx +11 -22
- package/typings/tests/refs.tsx +4 -4
- package/tslint.json +0 -101
package/src/index.js
CHANGED
|
@@ -7,21 +7,23 @@ import React, {
|
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
useMemo,
|
|
9
9
|
useReducer,
|
|
10
|
-
useRef
|
|
11
|
-
} from
|
|
12
|
-
import PropTypes from
|
|
13
|
-
import { fromEvent } from
|
|
10
|
+
useRef,
|
|
11
|
+
} from "react";
|
|
12
|
+
import PropTypes from "prop-types";
|
|
13
|
+
import { fromEvent } from "file-selector";
|
|
14
14
|
import {
|
|
15
15
|
allFilesAccepted,
|
|
16
16
|
composeEventHandlers,
|
|
17
17
|
fileAccepted,
|
|
18
18
|
fileMatchSize,
|
|
19
|
+
filePickerOptionsTypes,
|
|
20
|
+
canUseFileSystemAccessAPI,
|
|
19
21
|
isEvtWithFiles,
|
|
20
22
|
isIeOrEdge,
|
|
21
23
|
isPropagationStopped,
|
|
22
24
|
onDocumentDragOver,
|
|
23
|
-
TOO_MANY_FILES_REJECTION
|
|
24
|
-
} from
|
|
25
|
+
TOO_MANY_FILES_REJECTION,
|
|
26
|
+
} from "./utils/index";
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Convenience wrapper component for the `useDropzone` hook
|
|
@@ -38,15 +40,15 @@ import {
|
|
|
38
40
|
* ```
|
|
39
41
|
*/
|
|
40
42
|
const Dropzone = forwardRef(({ children, ...params }, ref) => {
|
|
41
|
-
const { open, ...props } = useDropzone(params)
|
|
43
|
+
const { open, ...props } = useDropzone(params);
|
|
42
44
|
|
|
43
|
-
useImperativeHandle(ref, () => ({ open }), [open])
|
|
45
|
+
useImperativeHandle(ref, () => ({ open }), [open]);
|
|
44
46
|
|
|
45
47
|
// TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element
|
|
46
|
-
return <Fragment>{children({ ...props, open })}</Fragment
|
|
47
|
-
})
|
|
48
|
+
return <Fragment>{children({ ...props, open })}</Fragment>;
|
|
49
|
+
});
|
|
48
50
|
|
|
49
|
-
Dropzone.displayName =
|
|
51
|
+
Dropzone.displayName = "Dropzone";
|
|
50
52
|
|
|
51
53
|
// Add default props for react-docgen
|
|
52
54
|
const defaultProps = {
|
|
@@ -61,10 +63,11 @@ const defaultProps = {
|
|
|
61
63
|
noKeyboard: false,
|
|
62
64
|
noDrag: false,
|
|
63
65
|
noDragEventsBubbling: false,
|
|
64
|
-
validator: null
|
|
65
|
-
|
|
66
|
+
validator: null,
|
|
67
|
+
useFsAccessApi: true,
|
|
68
|
+
};
|
|
66
69
|
|
|
67
|
-
Dropzone.defaultProps = defaultProps
|
|
70
|
+
Dropzone.defaultProps = defaultProps;
|
|
68
71
|
|
|
69
72
|
Dropzone.propTypes = {
|
|
70
73
|
/**
|
|
@@ -93,7 +96,10 @@ Dropzone.propTypes = {
|
|
|
93
96
|
* Windows. In some cases there might not be a mime type set at all.
|
|
94
97
|
* See: https://github.com/react-dropzone/react-dropzone/issues/276
|
|
95
98
|
*/
|
|
96
|
-
accept: PropTypes.oneOfType([
|
|
99
|
+
accept: PropTypes.oneOfType([
|
|
100
|
+
PropTypes.string,
|
|
101
|
+
PropTypes.arrayOf(PropTypes.string),
|
|
102
|
+
]),
|
|
97
103
|
|
|
98
104
|
/**
|
|
99
105
|
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
|
|
@@ -158,11 +164,17 @@ Dropzone.propTypes = {
|
|
|
158
164
|
*/
|
|
159
165
|
onFileDialogCancel: PropTypes.func,
|
|
160
166
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Cb for when opening the file dialog
|
|
169
|
+
*/
|
|
164
170
|
onFileDialogOpen: PropTypes.func,
|
|
165
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
174
|
+
* to open the file picker instead of using an `<input type="file">` click event.
|
|
175
|
+
*/
|
|
176
|
+
useFsAccessApi: PropTypes.bool,
|
|
177
|
+
|
|
166
178
|
/**
|
|
167
179
|
* Cb for when the `dragenter` event occurs.
|
|
168
180
|
*
|
|
@@ -239,10 +251,10 @@ Dropzone.propTypes = {
|
|
|
239
251
|
* @param {File} file
|
|
240
252
|
* @returns {FileError|FileError[]}
|
|
241
253
|
*/
|
|
242
|
-
validator: PropTypes.func
|
|
243
|
-
}
|
|
254
|
+
validator: PropTypes.func,
|
|
255
|
+
};
|
|
244
256
|
|
|
245
|
-
export default Dropzone
|
|
257
|
+
export default Dropzone;
|
|
246
258
|
|
|
247
259
|
/**
|
|
248
260
|
* A function that is invoked for the `dragenter`,
|
|
@@ -316,8 +328,8 @@ const initialState = {
|
|
|
316
328
|
isDragReject: false,
|
|
317
329
|
draggedFiles: [],
|
|
318
330
|
acceptedFiles: [],
|
|
319
|
-
fileRejections: []
|
|
320
|
-
}
|
|
331
|
+
fileRejections: [],
|
|
332
|
+
};
|
|
321
333
|
|
|
322
334
|
/**
|
|
323
335
|
* A React hook that creates a drag 'n' drop area.
|
|
@@ -359,6 +371,8 @@ const initialState = {
|
|
|
359
371
|
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
|
|
360
372
|
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
|
|
361
373
|
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
|
|
374
|
+
* @param {boolean} [props.useFsAccessApi] Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
375
|
+
* to open the file picker instead of using an `<input type="file">` click event.
|
|
362
376
|
* @param {Function} [props.onFileDialogOpen] Cb for when opening the file dialog
|
|
363
377
|
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
|
|
364
378
|
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
|
|
@@ -409,34 +423,33 @@ export function useDropzone(options = {}) {
|
|
|
409
423
|
onDropRejected,
|
|
410
424
|
onFileDialogCancel,
|
|
411
425
|
onFileDialogOpen,
|
|
426
|
+
useFsAccessApi,
|
|
412
427
|
preventDropOnDocument,
|
|
413
428
|
noClick,
|
|
414
429
|
noKeyboard,
|
|
415
430
|
noDrag,
|
|
416
431
|
noDragEventsBubbling,
|
|
417
|
-
validator
|
|
432
|
+
validator,
|
|
418
433
|
} = {
|
|
419
434
|
...defaultProps,
|
|
420
|
-
...options
|
|
421
|
-
}
|
|
435
|
+
...options,
|
|
436
|
+
};
|
|
422
437
|
|
|
423
|
-
const
|
|
424
|
-
|
|
438
|
+
const onFileDialogOpenCb = useMemo(
|
|
439
|
+
() => (typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop),
|
|
440
|
+
[onFileDialogOpen]
|
|
441
|
+
);
|
|
442
|
+
const onFileDialogCancelCb = useMemo(
|
|
443
|
+
() =>
|
|
444
|
+
typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop,
|
|
445
|
+
[onFileDialogCancel]
|
|
446
|
+
);
|
|
425
447
|
|
|
426
|
-
const
|
|
427
|
-
const
|
|
448
|
+
const rootRef = useRef(null);
|
|
449
|
+
const inputRef = useRef(null);
|
|
428
450
|
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
if (inputRef.current) {
|
|
432
|
-
dispatch({ type: 'openDialog' })
|
|
433
|
-
if (typeof onFileDialogOpen === 'function') {
|
|
434
|
-
onFileDialogOpen()
|
|
435
|
-
}
|
|
436
|
-
inputRef.current.value = null
|
|
437
|
-
inputRef.current.click()
|
|
438
|
-
}
|
|
439
|
-
}, [dispatch, onFileDialogOpen])
|
|
451
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
452
|
+
const { isFocused, isFileDialogActive, draggedFiles } = state;
|
|
440
453
|
|
|
441
454
|
// Update file dialog active state when the window is focused on
|
|
442
455
|
const onWindowFocus = () => {
|
|
@@ -444,299 +457,356 @@ export function useDropzone(options = {}) {
|
|
|
444
457
|
if (isFileDialogActive) {
|
|
445
458
|
setTimeout(() => {
|
|
446
459
|
if (inputRef.current) {
|
|
447
|
-
const { files } = inputRef.current
|
|
460
|
+
const { files } = inputRef.current;
|
|
448
461
|
|
|
449
462
|
if (!files.length) {
|
|
450
|
-
dispatch({ type:
|
|
451
|
-
|
|
452
|
-
if (typeof onFileDialogCancel === 'function') {
|
|
453
|
-
onFileDialogCancel()
|
|
454
|
-
}
|
|
463
|
+
dispatch({ type: "closeDialog" });
|
|
464
|
+
onFileDialogCancelCb();
|
|
455
465
|
}
|
|
456
466
|
}
|
|
457
|
-
}, 300)
|
|
467
|
+
}, 300);
|
|
458
468
|
}
|
|
459
|
-
}
|
|
469
|
+
};
|
|
460
470
|
useEffect(() => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
window.removeEventListener('focus', onWindowFocus, false)
|
|
464
|
-
}
|
|
465
|
-
}, [inputRef, isFileDialogActive, onFileDialogCancel])
|
|
466
|
-
|
|
467
|
-
// Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
|
|
468
|
-
const onKeyDownCb = useCallback(
|
|
469
|
-
event => {
|
|
470
|
-
// Ignore keyboard events bubbling up the DOM tree
|
|
471
|
-
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
|
|
472
|
-
return
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (event.keyCode === 32 || event.keyCode === 13) {
|
|
476
|
-
event.preventDefault()
|
|
477
|
-
openFileDialog()
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
[rootRef, inputRef, openFileDialog]
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
// Update focus state for the dropzone
|
|
484
|
-
const onFocusCb = useCallback(() => {
|
|
485
|
-
dispatch({ type: 'focus' })
|
|
486
|
-
}, [])
|
|
487
|
-
const onBlurCb = useCallback(() => {
|
|
488
|
-
dispatch({ type: 'blur' })
|
|
489
|
-
}, [])
|
|
490
|
-
|
|
491
|
-
// Cb to open the file dialog when click occurs on the dropzone
|
|
492
|
-
const onClickCb = useCallback(() => {
|
|
493
|
-
if (noClick) {
|
|
494
|
-
return
|
|
471
|
+
if (useFsAccessApi && canUseFileSystemAccessAPI()) {
|
|
472
|
+
return () => {};
|
|
495
473
|
}
|
|
496
474
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
} else {
|
|
503
|
-
openFileDialog()
|
|
504
|
-
}
|
|
505
|
-
}, [inputRef, noClick, openFileDialog])
|
|
475
|
+
window.addEventListener("focus", onWindowFocus, false);
|
|
476
|
+
return () => {
|
|
477
|
+
window.removeEventListener("focus", onWindowFocus, false);
|
|
478
|
+
};
|
|
479
|
+
}, [inputRef, isFileDialogActive, onFileDialogCancelCb, useFsAccessApi]);
|
|
506
480
|
|
|
507
|
-
const dragTargetsRef = useRef([])
|
|
508
|
-
const onDocumentDrop = event => {
|
|
481
|
+
const dragTargetsRef = useRef([]);
|
|
482
|
+
const onDocumentDrop = (event) => {
|
|
509
483
|
if (rootRef.current && rootRef.current.contains(event.target)) {
|
|
510
484
|
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
|
|
511
|
-
return
|
|
485
|
+
return;
|
|
512
486
|
}
|
|
513
|
-
event.preventDefault()
|
|
514
|
-
dragTargetsRef.current = []
|
|
515
|
-
}
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
dragTargetsRef.current = [];
|
|
489
|
+
};
|
|
516
490
|
|
|
517
491
|
useEffect(() => {
|
|
518
492
|
if (preventDropOnDocument) {
|
|
519
|
-
document.addEventListener(
|
|
520
|
-
document.addEventListener(
|
|
493
|
+
document.addEventListener("dragover", onDocumentDragOver, false);
|
|
494
|
+
document.addEventListener("drop", onDocumentDrop, false);
|
|
521
495
|
}
|
|
522
496
|
|
|
523
497
|
return () => {
|
|
524
498
|
if (preventDropOnDocument) {
|
|
525
|
-
document.removeEventListener(
|
|
526
|
-
document.removeEventListener(
|
|
499
|
+
document.removeEventListener("dragover", onDocumentDragOver);
|
|
500
|
+
document.removeEventListener("drop", onDocumentDrop);
|
|
527
501
|
}
|
|
528
|
-
}
|
|
529
|
-
}, [rootRef, preventDropOnDocument])
|
|
502
|
+
};
|
|
503
|
+
}, [rootRef, preventDropOnDocument]);
|
|
530
504
|
|
|
531
505
|
const onDragEnterCb = useCallback(
|
|
532
|
-
event => {
|
|
533
|
-
event.preventDefault()
|
|
506
|
+
(event) => {
|
|
507
|
+
event.preventDefault();
|
|
534
508
|
// Persist here because we need the event later after getFilesFromEvent() is done
|
|
535
|
-
event.persist()
|
|
536
|
-
stopPropagation(event)
|
|
509
|
+
event.persist();
|
|
510
|
+
stopPropagation(event);
|
|
537
511
|
|
|
538
|
-
dragTargetsRef.current = [...dragTargetsRef.current, event.target]
|
|
512
|
+
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
539
513
|
|
|
540
514
|
if (isEvtWithFiles(event)) {
|
|
541
|
-
Promise.resolve(getFilesFromEvent(event)).then(draggedFiles => {
|
|
515
|
+
Promise.resolve(getFilesFromEvent(event)).then((draggedFiles) => {
|
|
542
516
|
if (isPropagationStopped(event) && !noDragEventsBubbling) {
|
|
543
|
-
return
|
|
517
|
+
return;
|
|
544
518
|
}
|
|
545
519
|
|
|
546
520
|
dispatch({
|
|
547
521
|
draggedFiles,
|
|
548
522
|
isDragActive: true,
|
|
549
|
-
type:
|
|
550
|
-
})
|
|
523
|
+
type: "setDraggedFiles",
|
|
524
|
+
});
|
|
551
525
|
|
|
552
526
|
if (onDragEnter) {
|
|
553
|
-
onDragEnter(event)
|
|
527
|
+
onDragEnter(event);
|
|
554
528
|
}
|
|
555
|
-
})
|
|
529
|
+
});
|
|
556
530
|
}
|
|
557
531
|
},
|
|
558
532
|
[getFilesFromEvent, onDragEnter, noDragEventsBubbling]
|
|
559
|
-
)
|
|
533
|
+
);
|
|
560
534
|
|
|
561
535
|
const onDragOverCb = useCallback(
|
|
562
|
-
event => {
|
|
563
|
-
event.preventDefault()
|
|
564
|
-
event.persist()
|
|
565
|
-
stopPropagation(event)
|
|
536
|
+
(event) => {
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
event.persist();
|
|
539
|
+
stopPropagation(event);
|
|
566
540
|
|
|
567
541
|
const hasFiles = isEvtWithFiles(event);
|
|
568
542
|
if (hasFiles && event.dataTransfer) {
|
|
569
543
|
try {
|
|
570
|
-
event.dataTransfer.dropEffect =
|
|
544
|
+
event.dataTransfer.dropEffect = "copy";
|
|
571
545
|
} catch {} /* eslint-disable-line no-empty */
|
|
572
546
|
}
|
|
573
547
|
|
|
574
548
|
if (hasFiles && onDragOver) {
|
|
575
|
-
onDragOver(event)
|
|
549
|
+
onDragOver(event);
|
|
576
550
|
}
|
|
577
551
|
|
|
578
|
-
return false
|
|
552
|
+
return false;
|
|
579
553
|
},
|
|
580
554
|
[onDragOver, noDragEventsBubbling]
|
|
581
|
-
)
|
|
555
|
+
);
|
|
582
556
|
|
|
583
557
|
const onDragLeaveCb = useCallback(
|
|
584
|
-
event => {
|
|
585
|
-
event.preventDefault()
|
|
586
|
-
event.persist()
|
|
587
|
-
stopPropagation(event)
|
|
558
|
+
(event) => {
|
|
559
|
+
event.preventDefault();
|
|
560
|
+
event.persist();
|
|
561
|
+
stopPropagation(event);
|
|
588
562
|
|
|
589
563
|
// Only deactivate once the dropzone and all children have been left
|
|
590
564
|
const targets = dragTargetsRef.current.filter(
|
|
591
|
-
target => rootRef.current && rootRef.current.contains(target)
|
|
592
|
-
)
|
|
565
|
+
(target) => rootRef.current && rootRef.current.contains(target)
|
|
566
|
+
);
|
|
593
567
|
// Make sure to remove a target present multiple times only once
|
|
594
568
|
// (Firefox may fire dragenter/dragleave multiple times on the same element)
|
|
595
|
-
const targetIdx = targets.indexOf(event.target)
|
|
569
|
+
const targetIdx = targets.indexOf(event.target);
|
|
596
570
|
if (targetIdx !== -1) {
|
|
597
|
-
targets.splice(targetIdx, 1)
|
|
571
|
+
targets.splice(targetIdx, 1);
|
|
598
572
|
}
|
|
599
|
-
dragTargetsRef.current = targets
|
|
573
|
+
dragTargetsRef.current = targets;
|
|
600
574
|
if (targets.length > 0) {
|
|
601
|
-
return
|
|
575
|
+
return;
|
|
602
576
|
}
|
|
603
577
|
|
|
604
578
|
dispatch({
|
|
605
579
|
isDragActive: false,
|
|
606
|
-
type:
|
|
607
|
-
draggedFiles: []
|
|
608
|
-
})
|
|
580
|
+
type: "setDraggedFiles",
|
|
581
|
+
draggedFiles: [],
|
|
582
|
+
});
|
|
609
583
|
|
|
610
584
|
if (isEvtWithFiles(event) && onDragLeave) {
|
|
611
|
-
onDragLeave(event)
|
|
585
|
+
onDragLeave(event);
|
|
612
586
|
}
|
|
613
587
|
},
|
|
614
588
|
[rootRef, onDragLeave, noDragEventsBubbling]
|
|
615
|
-
)
|
|
589
|
+
);
|
|
616
590
|
|
|
617
|
-
const
|
|
618
|
-
event => {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
event.persist()
|
|
622
|
-
stopPropagation(event)
|
|
591
|
+
const setFiles = useCallback(
|
|
592
|
+
(files, event) => {
|
|
593
|
+
const acceptedFiles = [];
|
|
594
|
+
const fileRejections = [];
|
|
623
595
|
|
|
624
|
-
|
|
596
|
+
files.forEach((file) => {
|
|
597
|
+
const [accepted, acceptError] = fileAccepted(file, accept);
|
|
598
|
+
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
|
|
599
|
+
const customErrors = validator ? validator(file) : null;
|
|
625
600
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
601
|
+
if (accepted && sizeMatch && !customErrors) {
|
|
602
|
+
acceptedFiles.push(file);
|
|
603
|
+
} else {
|
|
604
|
+
let errors = [acceptError, sizeError];
|
|
631
605
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
files.forEach(file => {
|
|
636
|
-
const [accepted, acceptError] = fileAccepted(file, accept)
|
|
637
|
-
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize)
|
|
638
|
-
const customErrors = validator ? validator(file) : null;
|
|
639
|
-
|
|
640
|
-
if (accepted && sizeMatch && !customErrors) {
|
|
641
|
-
acceptedFiles.push(file)
|
|
642
|
-
} else {
|
|
643
|
-
let errors = [acceptError, sizeError];
|
|
644
|
-
|
|
645
|
-
if (customErrors) {
|
|
646
|
-
errors = errors.concat(customErrors);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
fileRejections.push({ file, errors: errors.filter(e => e) })
|
|
650
|
-
}
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
if ((!multiple && acceptedFiles.length > 1) || (multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles)) {
|
|
654
|
-
// Reject everything and empty accepted files
|
|
655
|
-
acceptedFiles.forEach(file => {
|
|
656
|
-
fileRejections.push({ file, errors: [TOO_MANY_FILES_REJECTION] })
|
|
657
|
-
})
|
|
658
|
-
acceptedFiles.splice(0)
|
|
606
|
+
if (customErrors) {
|
|
607
|
+
errors = errors.concat(customErrors);
|
|
659
608
|
}
|
|
660
609
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
610
|
+
fileRejections.push({ file, errors: errors.filter((e) => e) });
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
if (
|
|
615
|
+
(!multiple && acceptedFiles.length > 1) ||
|
|
616
|
+
(multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles)
|
|
617
|
+
) {
|
|
618
|
+
// Reject everything and empty accepted files
|
|
619
|
+
acceptedFiles.forEach((file) => {
|
|
620
|
+
fileRejections.push({ file, errors: [TOO_MANY_FILES_REJECTION] });
|
|
621
|
+
});
|
|
622
|
+
acceptedFiles.splice(0);
|
|
623
|
+
}
|
|
666
624
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
625
|
+
dispatch({
|
|
626
|
+
acceptedFiles,
|
|
627
|
+
fileRejections,
|
|
628
|
+
type: "setFiles",
|
|
629
|
+
});
|
|
670
630
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
631
|
+
if (onDrop) {
|
|
632
|
+
onDrop(acceptedFiles, fileRejections, event);
|
|
633
|
+
}
|
|
674
634
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
635
|
+
if (fileRejections.length > 0 && onDropRejected) {
|
|
636
|
+
onDropRejected(fileRejections, event);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (acceptedFiles.length > 0 && onDropAccepted) {
|
|
640
|
+
onDropAccepted(acceptedFiles, event);
|
|
679
641
|
}
|
|
680
|
-
dispatch({ type: 'reset' })
|
|
681
642
|
},
|
|
682
643
|
[
|
|
644
|
+
dispatch,
|
|
683
645
|
multiple,
|
|
684
646
|
accept,
|
|
685
647
|
minSize,
|
|
686
648
|
maxSize,
|
|
687
649
|
maxFiles,
|
|
688
|
-
getFilesFromEvent,
|
|
689
650
|
onDrop,
|
|
690
651
|
onDropAccepted,
|
|
691
652
|
onDropRejected,
|
|
692
|
-
|
|
693
|
-
validator
|
|
653
|
+
validator,
|
|
694
654
|
]
|
|
695
|
-
)
|
|
655
|
+
);
|
|
696
656
|
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
657
|
+
const onDropCb = useCallback(
|
|
658
|
+
(event) => {
|
|
659
|
+
event.preventDefault();
|
|
660
|
+
// Persist here because we need the event later after getFilesFromEvent() is done
|
|
661
|
+
event.persist();
|
|
662
|
+
stopPropagation(event);
|
|
700
663
|
|
|
701
|
-
|
|
702
|
-
return noKeyboard ? null : composeHandler(fn)
|
|
703
|
-
}
|
|
664
|
+
dragTargetsRef.current = [];
|
|
704
665
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
666
|
+
if (isEvtWithFiles(event)) {
|
|
667
|
+
Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
668
|
+
if (isPropagationStopped(event) && !noDragEventsBubbling) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
setFiles(files, event);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
dispatch({ type: "reset" });
|
|
675
|
+
},
|
|
676
|
+
[getFilesFromEvent, setFiles, noDragEventsBubbling]
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
// Fn for opening the file dialog programmatically
|
|
680
|
+
const openFileDialog = useCallback(() => {
|
|
681
|
+
if (useFsAccessApi && canUseFileSystemAccessAPI()) {
|
|
682
|
+
dispatch({ type: "openDialog" });
|
|
683
|
+
onFileDialogOpenCb();
|
|
684
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
|
|
685
|
+
const opts = {
|
|
686
|
+
multiple,
|
|
687
|
+
types: filePickerOptionsTypes(accept),
|
|
688
|
+
};
|
|
689
|
+
window
|
|
690
|
+
.showOpenFilePicker(opts)
|
|
691
|
+
.then((handles) => getFilesFromEvent(handles))
|
|
692
|
+
.then((files) => setFiles(files, null))
|
|
693
|
+
.catch((e) => onFileDialogCancelCb(e))
|
|
694
|
+
.finally(() => dispatch({ type: "closeDialog" }));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (inputRef.current) {
|
|
699
|
+
dispatch({ type: "openDialog" });
|
|
700
|
+
onFileDialogOpenCb();
|
|
701
|
+
inputRef.current.value = null;
|
|
702
|
+
inputRef.current.click();
|
|
703
|
+
}
|
|
704
|
+
}, [
|
|
705
|
+
dispatch,
|
|
706
|
+
onFileDialogOpenCb,
|
|
707
|
+
onFileDialogCancelCb,
|
|
708
|
+
useFsAccessApi,
|
|
709
|
+
setFiles,
|
|
710
|
+
accept,
|
|
711
|
+
multiple,
|
|
712
|
+
]);
|
|
713
|
+
|
|
714
|
+
// Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
|
|
715
|
+
const onKeyDownCb = useCallback(
|
|
716
|
+
(event) => {
|
|
717
|
+
// Ignore keyboard events bubbling up the DOM tree
|
|
718
|
+
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (event.keyCode === 32 || event.keyCode === 13) {
|
|
723
|
+
event.preventDefault();
|
|
724
|
+
openFileDialog();
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
[rootRef, inputRef, openFileDialog]
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
// Update focus state for the dropzone
|
|
731
|
+
const onFocusCb = useCallback(() => {
|
|
732
|
+
dispatch({ type: "focus" });
|
|
733
|
+
}, []);
|
|
734
|
+
const onBlurCb = useCallback(() => {
|
|
735
|
+
dispatch({ type: "blur" });
|
|
736
|
+
}, []);
|
|
737
|
+
|
|
738
|
+
// Cb to open the file dialog when click occurs on the dropzone
|
|
739
|
+
const onClickCb = useCallback(() => {
|
|
740
|
+
if (noClick) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
|
|
745
|
+
// to ensure React can handle state changes
|
|
746
|
+
// See: https://github.com/react-dropzone/react-dropzone/issues/450
|
|
747
|
+
if (isIeOrEdge()) {
|
|
748
|
+
setTimeout(openFileDialog, 0);
|
|
749
|
+
} else {
|
|
750
|
+
openFileDialog();
|
|
751
|
+
}
|
|
752
|
+
}, [inputRef, noClick, openFileDialog]);
|
|
753
|
+
|
|
754
|
+
const composeHandler = (fn) => {
|
|
755
|
+
return disabled ? null : fn;
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const composeKeyboardHandler = (fn) => {
|
|
759
|
+
return noKeyboard ? null : composeHandler(fn);
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
const composeDragHandler = (fn) => {
|
|
763
|
+
return noDrag ? null : composeHandler(fn);
|
|
764
|
+
};
|
|
708
765
|
|
|
709
|
-
const stopPropagation = event => {
|
|
766
|
+
const stopPropagation = (event) => {
|
|
710
767
|
if (noDragEventsBubbling) {
|
|
711
|
-
event.stopPropagation()
|
|
768
|
+
event.stopPropagation();
|
|
712
769
|
}
|
|
713
|
-
}
|
|
770
|
+
};
|
|
714
771
|
|
|
715
772
|
const getRootProps = useMemo(
|
|
716
|
-
() =>
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
773
|
+
() =>
|
|
774
|
+
({
|
|
775
|
+
refKey = "ref",
|
|
776
|
+
role,
|
|
777
|
+
onKeyDown,
|
|
778
|
+
onFocus,
|
|
779
|
+
onBlur,
|
|
780
|
+
onClick,
|
|
781
|
+
onDragEnter,
|
|
782
|
+
onDragOver,
|
|
783
|
+
onDragLeave,
|
|
784
|
+
onDrop,
|
|
785
|
+
...rest
|
|
786
|
+
} = {}) => ({
|
|
787
|
+
onKeyDown: composeKeyboardHandler(
|
|
788
|
+
composeEventHandlers(onKeyDown, onKeyDownCb)
|
|
789
|
+
),
|
|
790
|
+
onFocus: composeKeyboardHandler(
|
|
791
|
+
composeEventHandlers(onFocus, onFocusCb)
|
|
792
|
+
),
|
|
793
|
+
onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
|
|
794
|
+
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
|
|
795
|
+
onDragEnter: composeDragHandler(
|
|
796
|
+
composeEventHandlers(onDragEnter, onDragEnterCb)
|
|
797
|
+
),
|
|
798
|
+
onDragOver: composeDragHandler(
|
|
799
|
+
composeEventHandlers(onDragOver, onDragOverCb)
|
|
800
|
+
),
|
|
801
|
+
onDragLeave: composeDragHandler(
|
|
802
|
+
composeEventHandlers(onDragLeave, onDragLeaveCb)
|
|
803
|
+
),
|
|
804
|
+
onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
|
|
805
|
+
role: typeof role === "string" && role !== "" ? role : "button",
|
|
806
|
+
[refKey]: rootRef,
|
|
807
|
+
...(!disabled && !noKeyboard ? { tabIndex: 0 } : {}),
|
|
808
|
+
...rest,
|
|
809
|
+
}),
|
|
740
810
|
[
|
|
741
811
|
rootRef,
|
|
742
812
|
onKeyDownCb,
|
|
@@ -749,39 +819,51 @@ export function useDropzone(options = {}) {
|
|
|
749
819
|
onDropCb,
|
|
750
820
|
noKeyboard,
|
|
751
821
|
noDrag,
|
|
752
|
-
disabled
|
|
822
|
+
disabled,
|
|
753
823
|
]
|
|
754
|
-
)
|
|
824
|
+
);
|
|
755
825
|
|
|
756
|
-
const onInputElementClick = useCallback(event => {
|
|
757
|
-
event.stopPropagation()
|
|
758
|
-
}, [])
|
|
826
|
+
const onInputElementClick = useCallback((event) => {
|
|
827
|
+
event.stopPropagation();
|
|
828
|
+
}, []);
|
|
759
829
|
|
|
760
830
|
const getInputProps = useMemo(
|
|
761
|
-
() =>
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
831
|
+
() =>
|
|
832
|
+
({ refKey = "ref", onChange, onClick, ...rest } = {}) => {
|
|
833
|
+
const inputProps = {
|
|
834
|
+
accept,
|
|
835
|
+
multiple,
|
|
836
|
+
type: "file",
|
|
837
|
+
style: { display: "none" },
|
|
838
|
+
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
|
|
839
|
+
onClick: composeHandler(
|
|
840
|
+
composeEventHandlers(onClick, onInputElementClick)
|
|
841
|
+
),
|
|
842
|
+
autoComplete: "off",
|
|
843
|
+
tabIndex: -1,
|
|
844
|
+
[refKey]: inputRef,
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
...inputProps,
|
|
849
|
+
...rest,
|
|
850
|
+
};
|
|
851
|
+
},
|
|
779
852
|
[inputRef, accept, multiple, onDropCb, disabled]
|
|
780
|
-
)
|
|
853
|
+
);
|
|
781
854
|
|
|
782
|
-
const fileCount = draggedFiles.length
|
|
783
|
-
const isDragAccept =
|
|
784
|
-
|
|
855
|
+
const fileCount = draggedFiles.length;
|
|
856
|
+
const isDragAccept =
|
|
857
|
+
fileCount > 0 &&
|
|
858
|
+
allFilesAccepted({
|
|
859
|
+
files: draggedFiles,
|
|
860
|
+
accept,
|
|
861
|
+
minSize,
|
|
862
|
+
maxSize,
|
|
863
|
+
multiple,
|
|
864
|
+
maxFiles,
|
|
865
|
+
});
|
|
866
|
+
const isDragReject = fileCount > 0 && !isDragAccept;
|
|
785
867
|
|
|
786
868
|
return {
|
|
787
869
|
...state,
|
|
@@ -792,54 +874,56 @@ export function useDropzone(options = {}) {
|
|
|
792
874
|
getInputProps,
|
|
793
875
|
rootRef,
|
|
794
876
|
inputRef,
|
|
795
|
-
open: composeHandler(openFileDialog)
|
|
796
|
-
}
|
|
877
|
+
open: composeHandler(openFileDialog),
|
|
878
|
+
};
|
|
797
879
|
}
|
|
798
880
|
|
|
799
881
|
function reducer(state, action) {
|
|
800
882
|
/* istanbul ignore next */
|
|
801
883
|
switch (action.type) {
|
|
802
|
-
case
|
|
884
|
+
case "focus":
|
|
803
885
|
return {
|
|
804
886
|
...state,
|
|
805
|
-
isFocused: true
|
|
806
|
-
}
|
|
807
|
-
case
|
|
887
|
+
isFocused: true,
|
|
888
|
+
};
|
|
889
|
+
case "blur":
|
|
808
890
|
return {
|
|
809
891
|
...state,
|
|
810
|
-
isFocused: false
|
|
811
|
-
}
|
|
812
|
-
case
|
|
892
|
+
isFocused: false,
|
|
893
|
+
};
|
|
894
|
+
case "openDialog":
|
|
813
895
|
return {
|
|
814
|
-
...
|
|
815
|
-
isFileDialogActive: true
|
|
816
|
-
}
|
|
817
|
-
case
|
|
896
|
+
...initialState,
|
|
897
|
+
isFileDialogActive: true,
|
|
898
|
+
};
|
|
899
|
+
case "closeDialog":
|
|
818
900
|
return {
|
|
819
901
|
...state,
|
|
820
|
-
isFileDialogActive: false
|
|
821
|
-
}
|
|
822
|
-
case
|
|
902
|
+
isFileDialogActive: false,
|
|
903
|
+
};
|
|
904
|
+
case "setDraggedFiles":
|
|
823
905
|
/* eslint no-case-declarations: 0 */
|
|
824
|
-
const { isDragActive, draggedFiles } = action
|
|
906
|
+
const { isDragActive, draggedFiles } = action;
|
|
825
907
|
return {
|
|
826
908
|
...state,
|
|
827
909
|
draggedFiles,
|
|
828
|
-
isDragActive
|
|
829
|
-
}
|
|
830
|
-
case
|
|
910
|
+
isDragActive,
|
|
911
|
+
};
|
|
912
|
+
case "setFiles":
|
|
831
913
|
return {
|
|
832
914
|
...state,
|
|
833
915
|
acceptedFiles: action.acceptedFiles,
|
|
834
|
-
fileRejections: action.fileRejections
|
|
835
|
-
}
|
|
836
|
-
case
|
|
916
|
+
fileRejections: action.fileRejections,
|
|
917
|
+
};
|
|
918
|
+
case "reset":
|
|
837
919
|
return {
|
|
838
|
-
...initialState
|
|
839
|
-
}
|
|
920
|
+
...initialState,
|
|
921
|
+
};
|
|
840
922
|
default:
|
|
841
|
-
return state
|
|
923
|
+
return state;
|
|
842
924
|
}
|
|
843
925
|
}
|
|
844
926
|
|
|
845
|
-
|
|
927
|
+
function noop() {}
|
|
928
|
+
|
|
929
|
+
export { ErrorCode } from "./utils";
|