react-dropzone 12.0.5 → 13.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.
@@ -1,21 +1,17 @@
1
1
  By providing `accept` prop you can make the dropzone accept specific file types and reject the others.
2
2
 
3
- The value must be a comma-separated list of unique content type specifiers:
4
- * A file extension starting with the STOP character (U+002E). (e.g. .jpg, .png, .doc).
5
- * A valid MIME type with no extensions.
6
- * audio/* representing sound files.
7
- * video/* representing video files.
8
- * image/* representing image files.
3
+ The value must be an object with a common [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) as keys and an array of file extensions as values (similar to [showOpenFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker)'s types `accept` option).
4
+ ```js static
5
+ useDropzone({
6
+ accept: {
7
+ 'image/png': ['.png'],
8
+ 'text/html': ['.html', '.htm'],
9
+ }
10
+ })
11
+ ```
9
12
 
10
13
  For more information see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input.
11
14
 
12
- **IMPORTANT** As of [v12.0.0](https://github.com/react-dropzone/react-dropzone/releases/tag/v12.0.0), there has been some changes that affect how `accept` works.
13
- See [file dialog cancel callback](../../README.md#file-dialog-cancel-callback) to read more about why this change was introduced. In summary:
14
- 1. In browsers that **do not support** the FS access API, the `accept` attr works exactly the same as in previous versions of react-dropozone, for both file picker and dag 'n' drop
15
- 2. In browsers that **support** the FS access API, when passing file extensions to `accept` (e.g `".jpg,.png"`), the file picker will not apply those filters, but drag 'n' drop will - though, the [browser limitations](#browser-limitations) described in this document still apply
16
- 3. If you want the same behaviour in all browsers, regardless of using the FS access API or not, you need to use MIME types - [browser limitations](#browser-limitations) still apply
17
- 4. The use of FS access API can be turned off via `useDropzone({useFsAccessApi: false})`
18
-
19
15
  ```jsx harmony
20
16
  import React from 'react';
21
17
  import {useDropzone} from 'react-dropzone';
@@ -27,7 +23,10 @@ function Accept(props) {
27
23
  getRootProps,
28
24
  getInputProps
29
25
  } = useDropzone({
30
- accept: 'image/jpeg,image/png'
26
+ accept: {
27
+ 'image/jpeg': [],
28
+ 'image/png': []
29
+ }
31
30
  });
32
31
 
33
32
  const acceptedFileItems = acceptedFiles.map(file => (
@@ -85,7 +84,9 @@ function Accept(props) {
85
84
  isDragAccept,
86
85
  isDragReject
87
86
  } = useDropzone({
88
- accept: '.jpeg,.png'
87
+ accept: {
88
+ 'image/jpeg': ['.jpeg', '.png']
89
+ }
89
90
  });
90
91
 
91
92
  return (
@@ -117,7 +118,9 @@ function Accept(props) {
117
118
  isDragAccept,
118
119
  isDragReject
119
120
  } = useDropzone({
120
- accept: 'image/jpeg,image/png'
121
+ accept: {
122
+ 'image/*': ['.jpeg', '.png']
123
+ }
121
124
  });
122
125
 
123
126
  return (
@@ -77,7 +77,9 @@ const editImage = (image, done) => {
77
77
  function App() {
78
78
  const [files, setFiles] = useState([]);
79
79
  const { getRootProps, getInputProps } = useDropzone({
80
- accept: 'image/*',
80
+ accept: {
81
+ 'image/*': [],
82
+ },
81
83
  onDrop: (acceptedFiles) => {
82
84
  setFiles(
83
85
  acceptedFiles.map((file) =>
@@ -41,7 +41,9 @@ const img = {
41
41
  function Previews(props) {
42
42
  const [files, setFiles] = useState([]);
43
43
  const {getRootProps, getInputProps} = useDropzone({
44
- accept: 'image/*',
44
+ accept: {
45
+ 'image/*': []
46
+ },
45
47
  onDrop: acceptedFiles => {
46
48
  setFiles(acceptedFiles.map(file => Object.assign(file, {
47
49
  preview: URL.createObjectURL(file)
@@ -55,15 +57,17 @@ function Previews(props) {
55
57
  <img
56
58
  src={file.preview}
57
59
  style={img}
60
+ // Revoke data uri after image is loaded
61
+ onLoad={() => { URL.revokeObjectURL(file.preview) }}
58
62
  />
59
63
  </div>
60
64
  </div>
61
65
  ));
62
66
 
63
67
  useEffect(() => {
64
- // Make sure to revoke the data uris to avoid memory leaks
65
- files.forEach(file => URL.revokeObjectURL(file.preview));
66
- }, [files]);
68
+ // Make sure to revoke the data uris to avoid memory leaks, will run on unmount
69
+ return () => files.forEach(file => URL.revokeObjectURL(file.preview));
70
+ }, []);
67
71
 
68
72
  return (
69
73
  <section className="container">
@@ -41,7 +41,7 @@ function StyledDropzone(props) {
41
41
  isFocused,
42
42
  isDragAccept,
43
43
  isDragReject
44
- } = useDropzone({accept: 'image/*'});
44
+ } = useDropzone({accept: {'image/*': []}});
45
45
 
46
46
  const style = useMemo(() => ({
47
47
  ...baseStyle,
@@ -110,7 +110,7 @@ function StyledDropzone(props) {
110
110
  isFocused,
111
111
  isDragAccept,
112
112
  isDragReject
113
- } = useDropzone({accept: 'image/*'});
113
+ } = useDropzone({accept: {'image/*': []}});
114
114
 
115
115
  return (
116
116
  <div className="container">
package/package.json CHANGED
@@ -99,7 +99,7 @@
99
99
  },
100
100
  "dependencies": {
101
101
  "attr-accept": "^2.2.2",
102
- "file-selector": "^0.4.0",
102
+ "file-selector": "^0.5.0",
103
103
  "prop-types": "^15.8.1"
104
104
  },
105
105
  "devDependencies": {
@@ -171,7 +171,7 @@
171
171
  "webpack-blocks": "^2.1.0"
172
172
  },
173
173
  "typings": "typings/react-dropzone.d.ts",
174
- "version": "12.0.5",
174
+ "version": "13.0.0",
175
175
  "engines": {
176
176
  "node": ">= 10.13"
177
177
  },
@@ -1,3 +1,3 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`useDropzone() hook behavior renders the root and input nodes with the necessary props 1`] = `"<div role=\\"button\\" tabindex=\\"0\\"><input multiple=\\"\\" type=\\"file\\" style=\\"display: none;\\" autocomplete=\\"off\\" tabindex=\\"-1\\"></div>"`;
3
+ exports[`useDropzone() hook behavior renders the root and input nodes with the necessary props 1`] = `"<div role=\\"button\\" tabindex=\\"0\\"><input multiple=\\"\\" type=\\"file\\" style=\\"display: none;\\" tabindex=\\"-1\\"></div>"`;
package/src/index.js CHANGED
@@ -12,11 +12,11 @@ import React, {
12
12
  import PropTypes from "prop-types";
13
13
  import { fromEvent } from "file-selector";
14
14
  import {
15
+ acceptPropAsAcceptAttr,
15
16
  allFilesAccepted,
16
17
  composeEventHandlers,
17
18
  fileAccepted,
18
19
  fileMatchSize,
19
- filePickerOptionsTypes,
20
20
  canUseFileSystemAccessAPI,
21
21
  isAbort,
22
22
  isEvtWithFiles,
@@ -24,6 +24,7 @@ import {
24
24
  isPropagationStopped,
25
25
  isSecurityError,
26
26
  onDocumentDragOver,
27
+ pickerOptionsFromAccept,
27
28
  TOO_MANY_FILES_REJECTION,
28
29
  } from "./utils/index";
29
30
 
@@ -92,16 +93,12 @@ Dropzone.propTypes = {
92
93
 
93
94
  /**
94
95
  * Set accepted file types.
95
- * See https://github.com/okonet/attr-accept for more information.
96
+ * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
96
97
  * Keep in mind that mime type determination is not reliable across platforms. CSV files,
97
98
  * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
98
- * Windows. In some cases there might not be a mime type set at all.
99
- * See: https://github.com/react-dropzone/react-dropzone/issues/276
99
+ * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
100
100
  */
101
- accept: PropTypes.oneOfType([
102
- PropTypes.string,
103
- PropTypes.arrayOf(PropTypes.string),
104
- ]),
101
+ accept: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
105
102
 
106
103
  /**
107
104
  * Allow drag 'n' drop (or selection from the file dialog) of multiple files
@@ -248,6 +245,13 @@ Dropzone.propTypes = {
248
245
  */
249
246
  onDropRejected: PropTypes.func,
250
247
 
248
+ /**
249
+ * Cb for when there's some error from any of the promises.
250
+ *
251
+ * @param {Error} error
252
+ */
253
+ onError: PropTypes.func,
254
+
251
255
  /**
252
256
  * Custom validation function
253
257
  * @param {File} file
@@ -355,12 +359,11 @@ const initialState = {
355
359
  * @function useDropzone
356
360
  *
357
361
  * @param {object} props
358
- * @param {string|string[]} [props.accept] Set accepted file types.
359
- * See https://github.com/okonet/attr-accept for more information.
362
+ * @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
363
+ * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
360
364
  * Keep in mind that mime type determination is not reliable across platforms. CSV files,
361
365
  * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
362
- * Windows. In some cases there might not be a mime type set at all.
363
- * See: https://github.com/react-dropzone/react-dropzone/issues/276
366
+ * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
364
367
  * @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
365
368
  * @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
366
369
  * @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
@@ -383,7 +386,7 @@ const initialState = {
383
386
  * Note that this callback is invoked after the `getFilesFromEvent` callback is done.
384
387
  *
385
388
  * Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
386
- * `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
389
+ * `accept` must be an object with keys as a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) and the value an array of file extensions (optional).
387
390
  * If `multiple` is set to false and additional files are dropped,
388
391
  * all files besides the first will be rejected.
389
392
  * Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
@@ -405,10 +408,11 @@ const initialState = {
405
408
  * ```
406
409
  * @param {dropAcceptedCb} [props.onDropAccepted]
407
410
  * @param {dropRejectedCb} [props.onDropRejected]
411
+ * @param {(error: Error) => void} [props.onError]
408
412
  *
409
413
  * @returns {DropzoneState}
410
414
  */
411
- export function useDropzone(options = {}) {
415
+ export function useDropzone(props = {}) {
412
416
  const {
413
417
  accept,
414
418
  disabled,
@@ -431,12 +435,16 @@ export function useDropzone(options = {}) {
431
435
  noKeyboard,
432
436
  noDrag,
433
437
  noDragEventsBubbling,
438
+ onError,
434
439
  validator,
435
440
  } = {
436
441
  ...defaultProps,
437
- ...options,
442
+ ...props,
438
443
  };
439
444
 
445
+ const acceptAttr = useMemo(() => acceptPropAsAcceptAttr(accept), [accept]);
446
+ const pickerTypes = useMemo(() => pickerOptionsFromAccept(accept), [accept]);
447
+
440
448
  const onFileDialogOpenCb = useMemo(
441
449
  () => (typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop),
442
450
  [onFileDialogOpen]
@@ -507,6 +515,18 @@ export function useDropzone(options = {}) {
507
515
  };
508
516
  }, [rootRef, preventDropOnDocument]);
509
517
 
518
+ const onErrCb = useCallback(
519
+ (e) => {
520
+ if (onError) {
521
+ onError(e);
522
+ } else {
523
+ // Let the user know something's gone wrong if they haven't provided the onError cb.
524
+ console.error(e);
525
+ }
526
+ },
527
+ [onError]
528
+ );
529
+
510
530
  const onDragEnterCb = useCallback(
511
531
  (event) => {
512
532
  event.preventDefault();
@@ -517,24 +537,26 @@ export function useDropzone(options = {}) {
517
537
  dragTargetsRef.current = [...dragTargetsRef.current, event.target];
518
538
 
519
539
  if (isEvtWithFiles(event)) {
520
- Promise.resolve(getFilesFromEvent(event)).then((draggedFiles) => {
521
- if (isPropagationStopped(event) && !noDragEventsBubbling) {
522
- return;
523
- }
540
+ Promise.resolve(getFilesFromEvent(event))
541
+ .then((draggedFiles) => {
542
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
543
+ return;
544
+ }
524
545
 
525
- dispatch({
526
- draggedFiles,
527
- isDragActive: true,
528
- type: "setDraggedFiles",
529
- });
546
+ dispatch({
547
+ draggedFiles,
548
+ isDragActive: true,
549
+ type: "setDraggedFiles",
550
+ });
530
551
 
531
- if (onDragEnter) {
532
- onDragEnter(event);
533
- }
534
- });
552
+ if (onDragEnter) {
553
+ onDragEnter(event);
554
+ }
555
+ })
556
+ .catch((e) => onErrCb(e));
535
557
  }
536
558
  },
537
- [getFilesFromEvent, onDragEnter, noDragEventsBubbling]
559
+ [getFilesFromEvent, onDragEnter, onErrCb, noDragEventsBubbling]
538
560
  );
539
561
 
540
562
  const onDragOverCb = useCallback(
@@ -599,7 +621,7 @@ export function useDropzone(options = {}) {
599
621
  const fileRejections = [];
600
622
 
601
623
  files.forEach((file) => {
602
- const [accepted, acceptError] = fileAccepted(file, accept);
624
+ const [accepted, acceptError] = fileAccepted(file, acceptAttr);
603
625
  const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
604
626
  const customErrors = validator ? validator(file) : null;
605
627
 
@@ -648,7 +670,7 @@ export function useDropzone(options = {}) {
648
670
  [
649
671
  dispatch,
650
672
  multiple,
651
- accept,
673
+ acceptAttr,
652
674
  minSize,
653
675
  maxSize,
654
676
  maxFiles,
@@ -669,16 +691,18 @@ export function useDropzone(options = {}) {
669
691
  dragTargetsRef.current = [];
670
692
 
671
693
  if (isEvtWithFiles(event)) {
672
- Promise.resolve(getFilesFromEvent(event)).then((files) => {
673
- if (isPropagationStopped(event) && !noDragEventsBubbling) {
674
- return;
675
- }
676
- setFiles(files, event);
677
- });
694
+ Promise.resolve(getFilesFromEvent(event))
695
+ .then((files) => {
696
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
697
+ return;
698
+ }
699
+ setFiles(files, event);
700
+ })
701
+ .catch((e) => onErrCb(e));
678
702
  }
679
703
  dispatch({ type: "reset" });
680
704
  },
681
- [getFilesFromEvent, setFiles, noDragEventsBubbling]
705
+ [getFilesFromEvent, setFiles, onErrCb, noDragEventsBubbling]
682
706
  );
683
707
 
684
708
  // Fn for opening the file dialog programmatically
@@ -691,7 +715,7 @@ export function useDropzone(options = {}) {
691
715
  // https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
692
716
  const opts = {
693
717
  multiple,
694
- types: filePickerOptionsTypes(accept),
718
+ types: pickerTypes,
695
719
  };
696
720
  window
697
721
  .showOpenFilePicker(opts)
@@ -713,6 +737,8 @@ export function useDropzone(options = {}) {
713
737
  inputRef.current.value = null;
714
738
  inputRef.current.click();
715
739
  }
740
+ } else {
741
+ onErrCb(e);
716
742
  }
717
743
  });
718
744
  return;
@@ -730,7 +756,8 @@ export function useDropzone(options = {}) {
730
756
  onFileDialogCancelCb,
731
757
  useFsAccessApi,
732
758
  setFiles,
733
- accept,
759
+ onErrCb,
760
+ pickerTypes,
734
761
  multiple,
735
762
  ]);
736
763
 
@@ -859,7 +886,7 @@ export function useDropzone(options = {}) {
859
886
  () =>
860
887
  ({ refKey = "ref", onChange, onClick, ...rest } = {}) => {
861
888
  const inputProps = {
862
- accept,
889
+ accept: acceptAttr,
863
890
  multiple,
864
891
  type: "file",
865
892
  style: { display: "none" },
@@ -867,7 +894,6 @@ export function useDropzone(options = {}) {
867
894
  onClick: composeHandler(
868
895
  composeEventHandlers(onClick, onInputElementClick)
869
896
  ),
870
- autoComplete: "off",
871
897
  tabIndex: -1,
872
898
  [refKey]: inputRef,
873
899
  };
@@ -885,7 +911,7 @@ export function useDropzone(options = {}) {
885
911
  fileCount > 0 &&
886
912
  allFilesAccepted({
887
913
  files: draggedFiles,
888
- accept,
914
+ accept: acceptAttr,
889
915
  minSize,
890
916
  maxSize,
891
917
  multiple,