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.
package/README.md CHANGED
@@ -343,8 +343,6 @@ As one can imagine, this doesn't really work if there's a lot of files or large
343
343
 
344
344
  Fortunately, there's the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API), which is currently a working draft and some browsers support it (see [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker#browser_compatibility)), that provides a reliable way to prompt the user for file selection and capture cancellation.
345
345
 
346
- And this lib makes use of it if available. Though, there's a small catch: using file extensions for the `accept` property is not supported; you must use MIME types as described in [common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types). Also check [accepting specific file types](https://react-dropzone.js.org/#section-accepting-specific-file-types) for more info on the subject of `accept` limitations.
347
-
348
346
  Also keep in mind that the FS access API can only be used in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
349
347
 
350
348
  **NOTE** You can disable using the FS access API with the `useFsAccessApi` property: `useDropzone({useFsAccessApi: false})`.
package/dist/es/index.js CHANGED
@@ -37,7 +37,7 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
37
37
  import React, { forwardRef, Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from "react";
38
38
  import PropTypes from "prop-types";
39
39
  import { fromEvent } from "file-selector";
40
- import { allFilesAccepted, composeEventHandlers, fileAccepted, fileMatchSize, filePickerOptionsTypes, canUseFileSystemAccessAPI, isAbort, isEvtWithFiles, isIeOrEdge, isPropagationStopped, isSecurityError, onDocumentDragOver, TOO_MANY_FILES_REJECTION } from "./utils/index";
40
+ import { acceptPropAsAcceptAttr, allFilesAccepted, composeEventHandlers, fileAccepted, fileMatchSize, canUseFileSystemAccessAPI, isAbort, isEvtWithFiles, isIeOrEdge, isPropagationStopped, isSecurityError, onDocumentDragOver, pickerOptionsFromAccept, TOO_MANY_FILES_REJECTION } from "./utils/index";
41
41
  /**
42
42
  * Convenience wrapper component for the `useDropzone` hook
43
43
  *
@@ -110,13 +110,12 @@ Dropzone.propTypes = {
110
110
 
111
111
  /**
112
112
  * Set accepted file types.
113
- * See https://github.com/okonet/attr-accept for more information.
113
+ * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
114
114
  * Keep in mind that mime type determination is not reliable across platforms. CSV files,
115
115
  * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
116
- * Windows. In some cases there might not be a mime type set at all.
117
- * See: https://github.com/react-dropzone/react-dropzone/issues/276
116
+ * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
118
117
  */
119
- accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
118
+ accept: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
120
119
 
121
120
  /**
122
121
  * Allow drag 'n' drop (or selection from the file dialog) of multiple files
@@ -264,6 +263,13 @@ Dropzone.propTypes = {
264
263
  */
265
264
  onDropRejected: PropTypes.func,
266
265
 
266
+ /**
267
+ * Cb for when there's some error from any of the promises.
268
+ *
269
+ * @param {Error} error
270
+ */
271
+ onError: PropTypes.func,
272
+
267
273
  /**
268
274
  * Custom validation function
269
275
  * @param {File} file
@@ -368,12 +374,11 @@ var initialState = {
368
374
  * @function useDropzone
369
375
  *
370
376
  * @param {object} props
371
- * @param {string|string[]} [props.accept] Set accepted file types.
372
- * See https://github.com/okonet/attr-accept for more information.
377
+ * @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
378
+ * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
373
379
  * Keep in mind that mime type determination is not reliable across platforms. CSV files,
374
380
  * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
375
- * Windows. In some cases there might not be a mime type set at all.
376
- * See: https://github.com/react-dropzone/react-dropzone/issues/276
381
+ * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
377
382
  * @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
378
383
  * @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
379
384
  * @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
@@ -396,7 +401,7 @@ var initialState = {
396
401
  * Note that this callback is invoked after the `getFilesFromEvent` callback is done.
397
402
  *
398
403
  * Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
399
- * `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.
404
+ * `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).
400
405
  * If `multiple` is set to false and additional files are dropped,
401
406
  * all files besides the first will be rejected.
402
407
  * Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
@@ -418,37 +423,45 @@ var initialState = {
418
423
  * ```
419
424
  * @param {dropAcceptedCb} [props.onDropAccepted]
420
425
  * @param {dropRejectedCb} [props.onDropRejected]
426
+ * @param {(error: Error) => void} [props.onError]
421
427
  *
422
428
  * @returns {DropzoneState}
423
429
  */
424
430
 
425
431
  export function useDropzone() {
426
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
427
-
428
- var _defaultProps$options = _objectSpread(_objectSpread({}, defaultProps), options),
429
- accept = _defaultProps$options.accept,
430
- disabled = _defaultProps$options.disabled,
431
- getFilesFromEvent = _defaultProps$options.getFilesFromEvent,
432
- maxSize = _defaultProps$options.maxSize,
433
- minSize = _defaultProps$options.minSize,
434
- multiple = _defaultProps$options.multiple,
435
- maxFiles = _defaultProps$options.maxFiles,
436
- onDragEnter = _defaultProps$options.onDragEnter,
437
- onDragLeave = _defaultProps$options.onDragLeave,
438
- onDragOver = _defaultProps$options.onDragOver,
439
- onDrop = _defaultProps$options.onDrop,
440
- onDropAccepted = _defaultProps$options.onDropAccepted,
441
- onDropRejected = _defaultProps$options.onDropRejected,
442
- onFileDialogCancel = _defaultProps$options.onFileDialogCancel,
443
- onFileDialogOpen = _defaultProps$options.onFileDialogOpen,
444
- useFsAccessApi = _defaultProps$options.useFsAccessApi,
445
- preventDropOnDocument = _defaultProps$options.preventDropOnDocument,
446
- noClick = _defaultProps$options.noClick,
447
- noKeyboard = _defaultProps$options.noKeyboard,
448
- noDrag = _defaultProps$options.noDrag,
449
- noDragEventsBubbling = _defaultProps$options.noDragEventsBubbling,
450
- validator = _defaultProps$options.validator;
451
-
432
+ var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
433
+
434
+ var _defaultProps$props = _objectSpread(_objectSpread({}, defaultProps), props),
435
+ accept = _defaultProps$props.accept,
436
+ disabled = _defaultProps$props.disabled,
437
+ getFilesFromEvent = _defaultProps$props.getFilesFromEvent,
438
+ maxSize = _defaultProps$props.maxSize,
439
+ minSize = _defaultProps$props.minSize,
440
+ multiple = _defaultProps$props.multiple,
441
+ maxFiles = _defaultProps$props.maxFiles,
442
+ onDragEnter = _defaultProps$props.onDragEnter,
443
+ onDragLeave = _defaultProps$props.onDragLeave,
444
+ onDragOver = _defaultProps$props.onDragOver,
445
+ onDrop = _defaultProps$props.onDrop,
446
+ onDropAccepted = _defaultProps$props.onDropAccepted,
447
+ onDropRejected = _defaultProps$props.onDropRejected,
448
+ onFileDialogCancel = _defaultProps$props.onFileDialogCancel,
449
+ onFileDialogOpen = _defaultProps$props.onFileDialogOpen,
450
+ useFsAccessApi = _defaultProps$props.useFsAccessApi,
451
+ preventDropOnDocument = _defaultProps$props.preventDropOnDocument,
452
+ noClick = _defaultProps$props.noClick,
453
+ noKeyboard = _defaultProps$props.noKeyboard,
454
+ noDrag = _defaultProps$props.noDrag,
455
+ noDragEventsBubbling = _defaultProps$props.noDragEventsBubbling,
456
+ onError = _defaultProps$props.onError,
457
+ validator = _defaultProps$props.validator;
458
+
459
+ var acceptAttr = useMemo(function () {
460
+ return acceptPropAsAcceptAttr(accept);
461
+ }, [accept]);
462
+ var pickerTypes = useMemo(function () {
463
+ return pickerOptionsFromAccept(accept);
464
+ }, [accept]);
452
465
  var onFileDialogOpenCb = useMemo(function () {
453
466
  return typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop;
454
467
  }, [onFileDialogOpen]);
@@ -517,6 +530,14 @@ export function useDropzone() {
517
530
  }
518
531
  };
519
532
  }, [rootRef, preventDropOnDocument]);
533
+ var onErrCb = useCallback(function (e) {
534
+ if (onError) {
535
+ onError(e);
536
+ } else {
537
+ // Let the user know something's gone wrong if they haven't provided the onError cb.
538
+ console.error(e);
539
+ }
540
+ }, [onError]);
520
541
  var onDragEnterCb = useCallback(function (event) {
521
542
  event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
522
543
 
@@ -539,9 +560,11 @@ export function useDropzone() {
539
560
  if (onDragEnter) {
540
561
  onDragEnter(event);
541
562
  }
563
+ }).catch(function (e) {
564
+ return onErrCb(e);
542
565
  });
543
566
  }
544
- }, [getFilesFromEvent, onDragEnter, noDragEventsBubbling]);
567
+ }, [getFilesFromEvent, onDragEnter, onErrCb, noDragEventsBubbling]);
545
568
  var onDragOverCb = useCallback(function (event) {
546
569
  event.preventDefault();
547
570
  event.persist();
@@ -598,7 +621,7 @@ export function useDropzone() {
598
621
  var acceptedFiles = [];
599
622
  var fileRejections = [];
600
623
  files.forEach(function (file) {
601
- var _fileAccepted = fileAccepted(file, accept),
624
+ var _fileAccepted = fileAccepted(file, acceptAttr),
602
625
  _fileAccepted2 = _slicedToArray(_fileAccepted, 2),
603
626
  accepted = _fileAccepted2[0],
604
627
  acceptError = _fileAccepted2[1];
@@ -656,7 +679,7 @@ export function useDropzone() {
656
679
  if (acceptedFiles.length > 0 && onDropAccepted) {
657
680
  onDropAccepted(acceptedFiles, event);
658
681
  }
659
- }, [dispatch, multiple, accept, minSize, maxSize, maxFiles, onDrop, onDropAccepted, onDropRejected, validator]);
682
+ }, [dispatch, multiple, acceptAttr, minSize, maxSize, maxFiles, onDrop, onDropAccepted, onDropRejected, validator]);
660
683
  var onDropCb = useCallback(function (event) {
661
684
  event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
662
685
 
@@ -671,13 +694,15 @@ export function useDropzone() {
671
694
  }
672
695
 
673
696
  setFiles(files, event);
697
+ }).catch(function (e) {
698
+ return onErrCb(e);
674
699
  });
675
700
  }
676
701
 
677
702
  dispatch({
678
703
  type: "reset"
679
704
  });
680
- }, [getFilesFromEvent, setFiles, noDragEventsBubbling]); // Fn for opening the file dialog programmatically
705
+ }, [getFilesFromEvent, setFiles, onErrCb, noDragEventsBubbling]); // Fn for opening the file dialog programmatically
681
706
 
682
707
  var openFileDialog = useCallback(function () {
683
708
  // No point to use FS access APIs if context is not secure
@@ -690,7 +715,7 @@ export function useDropzone() {
690
715
 
691
716
  var opts = {
692
717
  multiple: multiple,
693
- types: filePickerOptionsTypes(accept)
718
+ types: pickerTypes
694
719
  };
695
720
  window.showOpenFilePicker(opts).then(function (handles) {
696
721
  return getFilesFromEvent(handles);
@@ -714,6 +739,8 @@ export function useDropzone() {
714
739
  inputRef.current.value = null;
715
740
  inputRef.current.click();
716
741
  }
742
+ } else {
743
+ onErrCb(e);
717
744
  }
718
745
  });
719
746
  return;
@@ -727,7 +754,7 @@ export function useDropzone() {
727
754
  inputRef.current.value = null;
728
755
  inputRef.current.click();
729
756
  }
730
- }, [dispatch, onFileDialogOpenCb, onFileDialogCancelCb, useFsAccessApi, setFiles, accept, multiple]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
757
+ }, [dispatch, onFileDialogOpenCb, onFileDialogCancelCb, useFsAccessApi, setFiles, onErrCb, pickerTypes, multiple]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
731
758
 
732
759
  var onKeyDownCb = useCallback(function (event) {
733
760
  // Ignore keyboard events bubbling up the DOM tree
@@ -829,7 +856,7 @@ export function useDropzone() {
829
856
  rest = _objectWithoutProperties(_ref3, _excluded4);
830
857
 
831
858
  var inputProps = _defineProperty({
832
- accept: accept,
859
+ accept: acceptAttr,
833
860
  multiple: multiple,
834
861
  type: "file",
835
862
  style: {
@@ -837,7 +864,6 @@ export function useDropzone() {
837
864
  },
838
865
  onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
839
866
  onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
840
- autoComplete: "off",
841
867
  tabIndex: -1
842
868
  }, refKey, inputRef);
843
869
 
@@ -847,7 +873,7 @@ export function useDropzone() {
847
873
  var fileCount = draggedFiles.length;
848
874
  var isDragAccept = fileCount > 0 && allFilesAccepted({
849
875
  files: draggedFiles,
850
- accept: accept,
876
+ accept: acceptAttr,
851
877
  minSize: minSize,
852
878
  maxSize: maxSize,
853
879
  multiple: multiple,
@@ -1,6 +1,10 @@
1
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
1
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
2
2
 
3
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
3
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+
5
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
6
+
7
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
4
8
 
5
9
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
10
 
@@ -183,25 +187,67 @@ export function canUseFileSystemAccessAPI() {
183
187
  return "showOpenFilePicker" in window;
184
188
  }
185
189
  /**
186
- * filePickerOptionsTypes returns the {types} option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
187
- * based on the accept attr (see https://github.com/react-dropzone/attr-accept)
188
- * E.g: converts ['image/*', 'text/*'] to {'image/*': [], 'text/*': []}
189
- * @param {string|string[]} accept
190
+ * Convert the `{accept}` dropzone prop to the
191
+ * `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
192
+ *
193
+ * @param {AcceptProp} accept
194
+ * @returns {{accept: string[]}[]}
190
195
  */
191
196
 
192
- export function filePickerOptionsTypes(accept) {
193
- accept = typeof accept === "string" ? accept.split(",") : accept;
194
- return [{
195
- description: "everything",
196
- // TODO: Need to handle filtering more elegantly than this!
197
- accept: Array.isArray(accept) ? // Accept just MIME types as per spec
198
- // NOTE: accept can be https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers
199
- accept.filter(function (item) {
200
- return item === "audio/*" || item === "video/*" || item === "image/*" || item === "text/*" || /\w+\/[-+.\w]+/g.test(item);
201
- }).reduce(function (a, b) {
202
- return _objectSpread(_objectSpread({}, a), {}, _defineProperty({}, b, []));
203
- }, {}) : {}
204
- }];
197
+ export function pickerOptionsFromAccept(accept) {
198
+ if (isDefined(accept)) {
199
+ return Object.entries(accept).filter(function (_ref2) {
200
+ var _ref3 = _slicedToArray(_ref2, 2),
201
+ mimeType = _ref3[0],
202
+ ext = _ref3[1];
203
+
204
+ var ok = true;
205
+
206
+ if (!isMIMEType(mimeType)) {
207
+ console.warn("Skipped \"".concat(mimeType, "\" because it is not a valid MIME type. Check https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types for a list of valid MIME types."));
208
+ ok = false;
209
+ }
210
+
211
+ if (!Array.isArray(ext) || !ext.every(isExt)) {
212
+ console.warn("Skipped \"".concat(mimeType, "\" because an invalid file extension was provided."));
213
+ ok = false;
214
+ }
215
+
216
+ return ok;
217
+ }).map(function (_ref4) {
218
+ var _ref5 = _slicedToArray(_ref4, 2),
219
+ mimeType = _ref5[0],
220
+ ext = _ref5[1];
221
+
222
+ return {
223
+ accept: _defineProperty({}, mimeType, ext)
224
+ };
225
+ });
226
+ }
227
+
228
+ return accept;
229
+ }
230
+ /**
231
+ * Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
232
+ * @param {AcceptProp} accept
233
+ * @returns {string}
234
+ */
235
+
236
+ export function acceptPropAsAcceptAttr(accept) {
237
+ if (isDefined(accept)) {
238
+ return Object.entries(accept).reduce(function (a, _ref6) {
239
+ var _ref7 = _slicedToArray(_ref6, 2),
240
+ mimeType = _ref7[0],
241
+ ext = _ref7[1];
242
+
243
+ return [].concat(_toConsumableArray(a), [mimeType], _toConsumableArray(ext));
244
+ }, []) // Silently discard invalid entries as pickerOptionsFromAccept warns about these
245
+ .filter(function (v) {
246
+ return isMIMEType(v) || isExt(v);
247
+ }).join(",");
248
+ }
249
+
250
+ return undefined;
205
251
  }
206
252
  /**
207
253
  * Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
@@ -224,4 +270,26 @@ export function isAbort(v) {
224
270
 
225
271
  export function isSecurityError(v) {
226
272
  return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
227
- }
273
+ }
274
+ /**
275
+ * Check if v is a MIME type string.
276
+ *
277
+ * See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
278
+ *
279
+ * @param {string} v
280
+ */
281
+
282
+ export function isMIMEType(v) {
283
+ return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || /\w+\/[-+.\w]+/g.test(v);
284
+ }
285
+ /**
286
+ * Check if v is a file extension.
287
+ * @param {string} v
288
+ */
289
+
290
+ export function isExt(v) {
291
+ return /^.*\.[\w]+$/.test(v);
292
+ }
293
+ /**
294
+ * @typedef {Object.<string, string[]>} AcceptProp
295
+ */