react-dropzone 11.5.3 → 11.6.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 CHANGED
@@ -1,5 +1,3 @@
1
- dist/
2
1
  coverage/
3
- typings/*
2
+ dist/
4
3
  styleguide/*
5
- !.babelrc.js
package/.eslintrc CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "parser": "babel-eslint",
2
+ "parser": "@babel/eslint-parser",
3
3
  "parserOptions": {
4
4
  "ecmaVersion": 2017,
5
5
  "sourceType": "module"
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ # Check commit msg format
5
+ yarn commitlint --edit $1
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ # Check whatever is staged for issues
5
+ yarn lint-staged
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- 12.18
1
+ 14.17.0
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  ![react-dropzone logo](https://raw.githubusercontent.com/react-dropzone/react-dropzone/master/logo/logo.png)
2
2
 
3
3
  # react-dropzone
4
-
5
4
  [![npm](https://img.shields.io/npm/v/react-dropzone.svg?style=flat-square)](https://www.npmjs.com/package/react-dropzone)
6
- [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/react-dropzone/react-dropzone/Test?label=tests&style=flat-square)](https://github.com/react-dropzone/react-dropzone/actions?query=workflow%3ATest)
5
+ [![Tests](https://img.shields.io/github/workflow/status/react-dropzone/react-dropzone/Test?label=tests&style=flat-square)](https://github.com/react-dropzone/react-dropzone/actions?query=workflow%3ATest)
7
6
  [![codecov](https://img.shields.io/codecov/c/gh/react-dropzone/react-dropzone/master.svg?style=flat-square)](https://codecov.io/gh/react-dropzone/react-dropzone)
8
- [![Open Collective](https://img.shields.io/opencollective/backers/react-dropzone.svg?style=flat-square)](#backers)
9
- [![Open Collective](https://img.shields.io/opencollective/sponsors/react-dropzone.svg?style=flat-square)](#sponsors)
10
- [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/react-dropzone/react-dropzone)
7
+ [![Open Collective Backers](https://img.shields.io/opencollective/backers/react-dropzone.svg?style=flat-square)](#backers)
8
+ [![Open Collective Sponsors](https://img.shields.io/opencollective/sponsors/react-dropzone.svg?style=flat-square)](#sponsors)
9
+ [![Gitpod](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/react-dropzone/react-dropzone)
11
10
 
12
11
  Simple React hook to create a HTML5-compliant drag'n'drop zone for files.
13
12
 
@@ -15,7 +14,6 @@ Documentation and examples at https://react-dropzone.js.org. Source code at http
15
14
 
16
15
 
17
16
  ## Installation
18
-
19
17
  Install it from npm and include it in your React build process (using [Webpack](http://webpack.github.io/), [Browserify](http://browserify.org/), etc).
20
18
 
21
19
  ```bash
@@ -53,8 +51,6 @@ function MyDropzone() {
53
51
  }
54
52
  ```
55
53
 
56
- **IMPORTANT**: Under the hood, this lib makes use of [hooks](https://reactjs.org/docs/hooks-intro.html), therefore, using it requires React `>= 16.8`.
57
-
58
54
  Or the wrapper component for the hook:
59
55
  ```jsx static
60
56
  import React from 'react'
@@ -72,10 +68,7 @@ import Dropzone from 'react-dropzone'
72
68
  </Dropzone>
73
69
  ```
74
70
 
75
-
76
- **Warning**: On most recent browsers versions, the files given by `onDrop` won't have properties `path` or `fullPath`, see [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).
77
-
78
- Furthermore, if you want to access file contents you have to use the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader):
71
+ If you want to access file contents you have to use the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader):
79
72
 
80
73
  ```jsx static
81
74
  import React, {useCallback} from 'react'
@@ -110,7 +103,6 @@ function MyDropzone() {
110
103
 
111
104
 
112
105
  ## Dropzone Props Getters
113
-
114
106
  The dropzone property getters are just two functions that return objects with properties which you need to use to create the drag 'n' drop zone.
115
107
  The root properties can be applied to whatever element you want, whereas the input properties must be applied to an `<input>`:
116
108
  ```jsx static
@@ -134,11 +126,13 @@ This is in order to avoid your props being overridden (or overriding the props r
134
126
  ```jsx static
135
127
  <div
136
128
  {...getRootProps({
137
- onClick: event => console.log(event)
129
+ onClick: event => console.log(event),
130
+ role: 'button',
131
+ 'aria-label': 'drag and drop area',
132
+ ...
138
133
  })}
139
134
  />
140
135
  ```
141
- > ♿ this is also where you pass accessibility props like `role`, `aria-labelledby` ...etc.
142
136
 
143
137
  In the example above, the provided `{onClick}` handler will be invoked before the internal one, therefore, internal callbacks can be prevented by simply using [stopPropagation](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation).
144
138
  See [Events](https://react-dropzone.js.org#events) for more examples.
@@ -146,10 +140,9 @@ See [Events](https://react-dropzone.js.org#events) for more examples.
146
140
  *Important*: if you omit rendering an `<input>` and/or binding the props from `getInputProps()`, opening a file dialog will not be possible.
147
141
 
148
142
  ## Refs
149
-
150
143
  Both `getRootProps` and `getInputProps` accept a custom `refKey` (defaults to `ref`) as one of the attributes passed down in the parameter.
151
144
 
152
- This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g.:
145
+ This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g:
153
146
 
154
147
  ```jsx static
155
148
  import React from 'react'
@@ -170,7 +163,7 @@ function Example() {
170
163
  }
171
164
  ```
172
165
 
173
- If you're working with [Material UI](https://material-ui.com) and would like to apply the root props on some component that does not expose a ref, use [RootRef](https://material-ui.com/api/root-ref):
166
+ If you're working with [Material UI v4](https://v4.mui.com/) and would like to apply the root props on some component that does not expose a ref, use [RootRef](https://v4.mui.com/api/root-ref/):
174
167
 
175
168
  ```jsx static
176
169
  import React from 'react'
@@ -190,7 +183,7 @@ function PaperDropzone() {
190
183
  }
191
184
  ```
192
185
 
193
- *Important*: do not set the `ref` prop on the elements where `getRootProps()`/`getInputProps()` props are set, instead, get the refs from the hook itself:
186
+ **IMPORTANT**: do not set the `ref` prop on the elements where `getRootProps()`/`getInputProps()` props are set, instead, get the refs from the hook itself:
194
187
 
195
188
  ```jsx static
196
189
  import React from 'react'
@@ -232,12 +225,11 @@ dropzoneRef.open()
232
225
 
233
226
 
234
227
  ## Testing
235
-
236
- *Important*: `react-dropzone` makes some of its drag 'n' drop callbacks asynchronous to enable promise based `getFilesFromEvent()` functions. In order to test components that use this library, you may want to use the [react-testing-library](https://github.com/testing-library/react-testing-library):
228
+ `react-dropzone` makes some of its drag 'n' drop callbacks asynchronous to enable promise based `getFilesFromEvent()` functions. In order to test components that use this library, you need to use the [react-testing-library](https://github.com/testing-library/react-testing-library):
237
229
  ```js static
238
230
  import React from 'react'
239
231
  import Dropzone from 'react-dropzone'
240
- import { act, fireEvent, render, waitFor } from '@testing-library/react'
232
+ import {act, fireEvent, render, waitFor} from '@testing-library/react'
241
233
 
242
234
  test('invoke onDragEnter when dragenter event occurs', async () => {
243
235
  const file = new File([
@@ -289,22 +281,79 @@ function mockData(files) {
289
281
  }
290
282
  ```
291
283
 
292
- *Note*: using [Enzyme](https://airbnb.io/enzyme) for testing is not supported at the moment, see [#2011](https://github.com/airbnb/enzyme/issues/2011).
284
+ **NOTE**: using [Enzyme](https://airbnb.io/enzyme) for testing is not supported at the moment, see [#2011](https://github.com/airbnb/enzyme/issues/2011).
293
285
 
294
- More examples for this can be found in `react-dropzone`s own [test suites](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.spec.js).
286
+ More examples for this can be found in `react-dropzone`'s own [test suites](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.spec.js).
295
287
 
296
- ## Need image editing?
288
+ ## Caveats
289
+ ### Required React Version
290
+ React [16.8](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html) or above is required because we use [hooks](https://reactjs.org/docs/hooks-intro.html) (the lib itself is a hook).
297
291
 
292
+ ### File Paths
293
+ Files returned by the hook or passed as arg to the `onDrop` cb won't have the properties `path` or `fullPath`.
294
+ For more inf check [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).
298
295
 
296
+ ### Not a File Uploader
297
+ This lib is not a file uploader; as such, it does not process files or provide any way to make HTTP requests to some server; if you're looking for that, checkout [filepond](https://pqina.nl/filepond) or [uppy.io](https://uppy.io/).
299
298
 
300
- React Dropzone integrates perfectly with [Pintura Image Editor](https://pqina.nl/pintura/?ref=react-dropzone), creating a modern image editing experience. Pintura supports crop aspect ratios, resizing, rotating, cropping, annotating, filtering, and much more.
299
+ ### Using \<label\> as Root
300
+ If you use [\<label\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) as the root element, the file dialog will be opened twice; see [#1107](https://github.com/react-dropzone/react-dropzone/issues/1107) why. To avoid this, use `noClick`:
301
+ ```jsx static
302
+ import React, {useCallback} from 'react'
303
+ import {useDropzone} from 'react-dropzone'
301
304
 
302
- Checkout the [Pintura integration example](https://codesandbox.io/s/react-dropzone-pintura-40xh4?file=/src/App.js).
305
+ function MyDropzone() {
306
+ const {getRootProps, getInputProps} = useDropzone({noClick: true})
303
307
 
304
- ## Supported Browsers
308
+ return (
309
+ <label {...getRootProps()}>
310
+ <input {...getInputProps()} />
311
+ </label>
312
+ )
313
+ }
314
+ ```
315
+
316
+ ### Using open() on Click
317
+ If you bind a click event on an inner element and use `open()`, it will trigger a click on the root element too, resulting in the file dialog opening twice. To prevent this, use the `noClick` on the root:
318
+ ```jsx static
319
+ import React, {useCallback} from 'react'
320
+ import {useDropzone} from 'react-dropzone'
305
321
 
322
+ function MyDropzone() {
323
+ const {getRootProps, getInputProps, open} = useDropzone({noClick: true})
324
+
325
+ return (
326
+ <div {...getRootProps()}>
327
+ <input {...getInputProps()} />
328
+ <button type="button" onClick={open}>
329
+ Open
330
+ </button>
331
+ </div>
332
+ )
333
+ }
334
+ ```
335
+
336
+ ### File Dialog Cancel Callback
337
+ The `onFileDialogCancel()` cb is unstable in most browsers, meaning, there's a good chance of it being triggered even though you have selected files.
338
+
339
+ We rely on using a timeout of `300ms` after the window is focused (the window `onfocus` event is triggered when the file select dialog is closed) to check if any files were selected and trigger `onFileDialogCancel` if none were selected.
340
+
341
+ As one can imagine, this doesn't really work if there's a lot of files or large files as by the time we trigger the check, the browser is still processing the files and no `onchange` events are triggered yet on the input. Check [#1031](https://github.com/react-dropzone/react-dropzone/issues/1031) for more info.
342
+
343
+ 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.
344
+
345
+ 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.
346
+
347
+ ## Supported Browsers
306
348
  We use [browserslist](https://github.com/browserslist/browserslist) config to state the browser support for this lib, so check it out on [browserslist.dev](https://browserslist.dev/?q=ZGVmYXVsdHM%3D).
307
349
 
350
+
351
+ ## Need image editing?
352
+ React Dropzone integrates perfectly with [Pintura Image Editor](https://pqina.nl/pintura/?ref=react-dropzone), creating a modern image editing experience. Pintura supports crop aspect ratios, resizing, rotating, cropping, annotating, filtering, and much more.
353
+
354
+ Checkout the [Pintura integration example](https://codesandbox.io/s/react-dropzone-pintura-40xh4?file=/src/App.js).
355
+
356
+
308
357
  ## Support
309
358
 
310
359
  ### Backers
@@ -378,5 +427,4 @@ Become a sponsor and get your logo on our README on Github with a link to your s
378
427
 
379
428
 
380
429
  ## License
381
-
382
430
  MIT
package/dist/es/index.js CHANGED
@@ -23,9 +23,9 @@ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Sy
23
23
 
24
24
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
25
25
 
26
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
26
+ 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; }
27
27
 
28
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
28
+ 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; }
29
29
 
30
30
  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; }
31
31
 
@@ -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, isEvtWithFiles, isIeOrEdge, isPropagationStopped, onDocumentDragOver, TOO_MANY_FILES_REJECTION } from './utils/index';
40
+ import { allFilesAccepted, composeEventHandlers, fileAccepted, fileMatchSize, filePickerOptionsTypes, canUseFileSystemAccessAPI, isEvtWithFiles, isIeOrEdge, isPropagationStopped, onDocumentDragOver, TOO_MANY_FILES_REJECTION } from './utils/index';
41
41
  /**
42
42
  * Convenience wrapper component for the `useDropzone` hook
43
43
  *
@@ -439,6 +439,12 @@ export function useDropzone() {
439
439
  noDragEventsBubbling = _defaultProps$options.noDragEventsBubbling,
440
440
  validator = _defaultProps$options.validator;
441
441
 
442
+ var onFileDialogOpenCb = useMemo(function () {
443
+ return typeof onFileDialogOpen === 'function' ? onFileDialogOpen : noop;
444
+ }, [onFileDialogOpen]);
445
+ var onFileDialogCancelCb = useMemo(function () {
446
+ return typeof onFileDialogCancel === 'function' ? onFileDialogCancel : noop;
447
+ }, [onFileDialogCancel]);
442
448
  var rootRef = useRef(null);
443
449
  var inputRef = useRef(null);
444
450
 
@@ -449,22 +455,7 @@ export function useDropzone() {
449
455
 
450
456
  var isFocused = state.isFocused,
451
457
  isFileDialogActive = state.isFileDialogActive,
452
- draggedFiles = state.draggedFiles; // Fn for opening the file dialog programmatically
453
-
454
- var openFileDialog = useCallback(function () {
455
- if (inputRef.current) {
456
- dispatch({
457
- type: 'openDialog'
458
- });
459
-
460
- if (typeof onFileDialogOpen === 'function') {
461
- onFileDialogOpen();
462
- }
463
-
464
- inputRef.current.value = null;
465
- inputRef.current.click();
466
- }
467
- }, [dispatch, onFileDialogOpen]); // Update file dialog active state when the window is focused on
458
+ draggedFiles = state.draggedFiles; // Update file dialog active state when the window is focused on
468
459
 
469
460
  var onWindowFocus = function onWindowFocus() {
470
461
  // Execute the timeout only if the file dialog is opened in the browser
@@ -477,10 +468,7 @@ export function useDropzone() {
477
468
  dispatch({
478
469
  type: 'closeDialog'
479
470
  });
480
-
481
- if (typeof onFileDialogCancel === 'function') {
482
- onFileDialogCancel();
483
- }
471
+ onFileDialogCancelCb();
484
472
  }
485
473
  }
486
474
  }, 300);
@@ -488,49 +476,15 @@ export function useDropzone() {
488
476
  };
489
477
 
490
478
  useEffect(function () {
479
+ if (canUseFileSystemAccessAPI()) {
480
+ return function () {};
481
+ }
482
+
491
483
  window.addEventListener('focus', onWindowFocus, false);
492
484
  return function () {
493
485
  window.removeEventListener('focus', onWindowFocus, false);
494
486
  };
495
- }, [inputRef, isFileDialogActive, onFileDialogCancel]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
496
-
497
- var onKeyDownCb = useCallback(function (event) {
498
- // Ignore keyboard events bubbling up the DOM tree
499
- if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
500
- return;
501
- }
502
-
503
- if (event.keyCode === 32 || event.keyCode === 13) {
504
- event.preventDefault();
505
- openFileDialog();
506
- }
507
- }, [rootRef, inputRef, openFileDialog]); // Update focus state for the dropzone
508
-
509
- var onFocusCb = useCallback(function () {
510
- dispatch({
511
- type: 'focus'
512
- });
513
- }, []);
514
- var onBlurCb = useCallback(function () {
515
- dispatch({
516
- type: 'blur'
517
- });
518
- }, []); // Cb to open the file dialog when click occurs on the dropzone
519
-
520
- var onClickCb = useCallback(function () {
521
- if (noClick) {
522
- return;
523
- } // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
524
- // to ensure React can handle state changes
525
- // See: https://github.com/react-dropzone/react-dropzone/issues/450
526
-
527
-
528
- if (isIeOrEdge()) {
529
- setTimeout(openFileDialog, 0);
530
- } else {
531
- openFileDialog();
532
- }
533
- }, [inputRef, noClick, openFileDialog]);
487
+ }, [inputRef, isFileDialogActive, onFileDialogCancelCb]);
534
488
  var dragTargetsRef = useRef([]);
535
489
 
536
490
  var onDocumentDrop = function onDocumentDrop(event) {
@@ -633,6 +587,69 @@ export function useDropzone() {
633
587
  onDragLeave(event);
634
588
  }
635
589
  }, [rootRef, onDragLeave, noDragEventsBubbling]);
590
+ var setFiles = useCallback(function (files, event) {
591
+ var acceptedFiles = [];
592
+ var fileRejections = [];
593
+ files.forEach(function (file) {
594
+ var _fileAccepted = fileAccepted(file, accept),
595
+ _fileAccepted2 = _slicedToArray(_fileAccepted, 2),
596
+ accepted = _fileAccepted2[0],
597
+ acceptError = _fileAccepted2[1];
598
+
599
+ var _fileMatchSize = fileMatchSize(file, minSize, maxSize),
600
+ _fileMatchSize2 = _slicedToArray(_fileMatchSize, 2),
601
+ sizeMatch = _fileMatchSize2[0],
602
+ sizeError = _fileMatchSize2[1];
603
+
604
+ var customErrors = validator ? validator(file) : null;
605
+
606
+ if (accepted && sizeMatch && !customErrors) {
607
+ acceptedFiles.push(file);
608
+ } else {
609
+ var errors = [acceptError, sizeError];
610
+
611
+ if (customErrors) {
612
+ errors = errors.concat(customErrors);
613
+ }
614
+
615
+ fileRejections.push({
616
+ file: file,
617
+ errors: errors.filter(function (e) {
618
+ return e;
619
+ })
620
+ });
621
+ }
622
+ });
623
+
624
+ if (!multiple && acceptedFiles.length > 1 || multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles) {
625
+ // Reject everything and empty accepted files
626
+ acceptedFiles.forEach(function (file) {
627
+ fileRejections.push({
628
+ file: file,
629
+ errors: [TOO_MANY_FILES_REJECTION]
630
+ });
631
+ });
632
+ acceptedFiles.splice(0);
633
+ }
634
+
635
+ dispatch({
636
+ acceptedFiles: acceptedFiles,
637
+ fileRejections: fileRejections,
638
+ type: 'setFiles'
639
+ });
640
+
641
+ if (onDrop) {
642
+ onDrop(acceptedFiles, fileRejections, event);
643
+ }
644
+
645
+ if (fileRejections.length > 0 && onDropRejected) {
646
+ onDropRejected(fileRejections, event);
647
+ }
648
+
649
+ if (acceptedFiles.length > 0 && onDropAccepted) {
650
+ onDropAccepted(acceptedFiles, event);
651
+ }
652
+ }, [dispatch, multiple, accept, minSize, maxSize, maxFiles, onDrop, onDropAccepted, onDropRejected, validator]);
636
653
  var onDropCb = useCallback(function (event) {
637
654
  event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
638
655
 
@@ -646,74 +663,87 @@ export function useDropzone() {
646
663
  return;
647
664
  }
648
665
 
649
- var acceptedFiles = [];
650
- var fileRejections = [];
651
- files.forEach(function (file) {
652
- var _fileAccepted = fileAccepted(file, accept),
653
- _fileAccepted2 = _slicedToArray(_fileAccepted, 2),
654
- accepted = _fileAccepted2[0],
655
- acceptError = _fileAccepted2[1];
656
-
657
- var _fileMatchSize = fileMatchSize(file, minSize, maxSize),
658
- _fileMatchSize2 = _slicedToArray(_fileMatchSize, 2),
659
- sizeMatch = _fileMatchSize2[0],
660
- sizeError = _fileMatchSize2[1];
661
-
662
- var customErrors = validator ? validator(file) : null;
663
-
664
- if (accepted && sizeMatch && !customErrors) {
665
- acceptedFiles.push(file);
666
- } else {
667
- var errors = [acceptError, sizeError];
668
-
669
- if (customErrors) {
670
- errors = errors.concat(customErrors);
671
- }
672
-
673
- fileRejections.push({
674
- file: file,
675
- errors: errors.filter(function (e) {
676
- return e;
677
- })
678
- });
679
- }
680
- });
666
+ setFiles(files, event);
667
+ });
668
+ }
681
669
 
682
- if (!multiple && acceptedFiles.length > 1 || multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles) {
683
- // Reject everything and empty accepted files
684
- acceptedFiles.forEach(function (file) {
685
- fileRejections.push({
686
- file: file,
687
- errors: [TOO_MANY_FILES_REJECTION]
688
- });
689
- });
690
- acceptedFiles.splice(0);
691
- }
670
+ dispatch({
671
+ type: 'reset'
672
+ });
673
+ }, [getFilesFromEvent, setFiles, noDragEventsBubbling]); // Fn for opening the file dialog programmatically
692
674
 
693
- dispatch({
694
- acceptedFiles: acceptedFiles,
695
- fileRejections: fileRejections,
696
- type: 'setFiles'
675
+ var openFileDialog = useCallback(function () {
676
+ if (canUseFileSystemAccessAPI()) {
677
+ dispatch({
678
+ type: 'openDialog'
679
+ });
680
+ onFileDialogOpenCb(); // https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
681
+
682
+ var opts = {
683
+ multiple: multiple,
684
+ types: filePickerOptionsTypes(accept)
685
+ };
686
+ window.showOpenFilePicker(opts).then(function (handles) {
687
+ return getFilesFromEvent(handles);
688
+ }).then(function (files) {
689
+ return setFiles(files, null);
690
+ }).catch(function (e) {
691
+ return onFileDialogCancelCb(e);
692
+ }).finally(function () {
693
+ return dispatch({
694
+ type: 'closeDialog'
697
695
  });
696
+ });
697
+ return;
698
+ }
698
699
 
699
- if (onDrop) {
700
- onDrop(acceptedFiles, fileRejections, event);
701
- }
700
+ if (inputRef.current) {
701
+ dispatch({
702
+ type: 'openDialog'
703
+ });
704
+ onFileDialogOpenCb();
705
+ inputRef.current.value = null;
706
+ inputRef.current.click();
707
+ }
708
+ }, [dispatch, onFileDialogOpenCb, onFileDialogCancelCb, setFiles, accept, multiple]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
702
709
 
703
- if (fileRejections.length > 0 && onDropRejected) {
704
- onDropRejected(fileRejections, event);
705
- }
710
+ var onKeyDownCb = useCallback(function (event) {
711
+ // Ignore keyboard events bubbling up the DOM tree
712
+ if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
713
+ return;
714
+ }
706
715
 
707
- if (acceptedFiles.length > 0 && onDropAccepted) {
708
- onDropAccepted(acceptedFiles, event);
709
- }
710
- });
716
+ if (event.keyCode === 32 || event.keyCode === 13) {
717
+ event.preventDefault();
718
+ openFileDialog();
711
719
  }
720
+ }, [rootRef, inputRef, openFileDialog]); // Update focus state for the dropzone
712
721
 
722
+ var onFocusCb = useCallback(function () {
713
723
  dispatch({
714
- type: 'reset'
724
+ type: 'focus'
725
+ });
726
+ }, []);
727
+ var onBlurCb = useCallback(function () {
728
+ dispatch({
729
+ type: 'blur'
715
730
  });
716
- }, [multiple, accept, minSize, maxSize, maxFiles, getFilesFromEvent, onDrop, onDropAccepted, onDropRejected, noDragEventsBubbling, validator]);
731
+ }, []); // Cb to open the file dialog when click occurs on the dropzone
732
+
733
+ var onClickCb = useCallback(function () {
734
+ if (noClick) {
735
+ return;
736
+ } // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
737
+ // to ensure React can handle state changes
738
+ // See: https://github.com/react-dropzone/react-dropzone/issues/450
739
+
740
+
741
+ if (isIeOrEdge()) {
742
+ setTimeout(openFileDialog, 0);
743
+ } else {
744
+ openFileDialog();
745
+ }
746
+ }, [inputRef, noClick, openFileDialog]);
717
747
 
718
748
  var composeHandler = function composeHandler(fn) {
719
749
  return disabled ? null : fn;
@@ -826,7 +856,7 @@ function reducer(state, action) {
826
856
  });
827
857
 
828
858
  case 'openDialog':
829
- return _objectSpread(_objectSpread({}, state), {}, {
859
+ return _objectSpread(_objectSpread({}, initialState), {}, {
830
860
  isFileDialogActive: true
831
861
  });
832
862
 
@@ -858,4 +888,6 @@ function reducer(state, action) {
858
888
  }
859
889
  }
860
890
 
891
+ function noop() {}
892
+
861
893
  export { ErrorCode } from './utils';
@@ -1,4 +1,10 @@
1
- function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
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; }
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; }
4
+
5
+ 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
+
7
+ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
2
8
 
3
9
  function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
4
10
 
@@ -166,4 +172,34 @@ export function composeEventHandlers() {
166
172
  return isPropagationStopped(event);
167
173
  });
168
174
  };
175
+ }
176
+ /**
177
+ * canUseFileSystemAccessAPI checks if the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
178
+ * is supported by the browser.
179
+ * @returns {boolean}
180
+ */
181
+
182
+ export function canUseFileSystemAccessAPI() {
183
+ return 'showOpenFilePicker' in window;
184
+ }
185
+ /**
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
+ */
191
+
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
+ }];
169
205
  }