react-dropzone 16.0.0 → 17.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/src/index.jsx DELETED
@@ -1,1103 +0,0 @@
1
- /* eslint prefer-template: 0 */
2
- import React, {
3
- forwardRef,
4
- Fragment,
5
- useCallback,
6
- useEffect,
7
- useImperativeHandle,
8
- useMemo,
9
- useReducer,
10
- useRef,
11
- } from "react";
12
- import PropTypes from "prop-types";
13
- import { fromEvent } from "file-selector";
14
- import {
15
- acceptPropAsAcceptAttr,
16
- allFilesAccepted,
17
- composeEventHandlers,
18
- fileAccepted,
19
- fileMatchSize,
20
- canUseFileSystemAccessAPI,
21
- isAbort,
22
- isEvtWithFiles,
23
- isIeOrEdge,
24
- isPropagationStopped,
25
- isSecurityError,
26
- onDocumentDragOver,
27
- pickerOptionsFromAccept,
28
- TOO_MANY_FILES_REJECTION,
29
- } from "./utils/index.js";
30
-
31
- /**
32
- * Convenience wrapper component for the `useDropzone` hook
33
- *
34
- * ```jsx
35
- * <Dropzone>
36
- * {({getRootProps, getInputProps}) => (
37
- * <div {...getRootProps()}>
38
- * <input {...getInputProps()} />
39
- * <p>Drag 'n' drop some files here, or click to select files</p>
40
- * </div>
41
- * )}
42
- * </Dropzone>
43
- * ```
44
- */
45
- const Dropzone = forwardRef(({ children, ...params }, ref) => {
46
- const { open, ...props } = useDropzone(params);
47
-
48
- useImperativeHandle(ref, () => ({ open }), [open]);
49
-
50
- // TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element
51
- return <Fragment>{children({ ...props, open })}</Fragment>;
52
- });
53
-
54
- Dropzone.displayName = "Dropzone";
55
-
56
- // Add default props for react-docgen
57
- const defaultProps = {
58
- disabled: false,
59
- getFilesFromEvent: fromEvent,
60
- maxSize: Infinity,
61
- minSize: 0,
62
- multiple: true,
63
- maxFiles: 0,
64
- preventDropOnDocument: true,
65
- noClick: false,
66
- noKeyboard: false,
67
- noDrag: false,
68
- noDragEventsBubbling: false,
69
- validator: null,
70
- useFsAccessApi: false,
71
- autoFocus: false,
72
- };
73
-
74
- Dropzone.defaultProps = defaultProps;
75
-
76
- Dropzone.propTypes = {
77
- /**
78
- * Render function that exposes the dropzone state and prop getter fns
79
- *
80
- * @param {object} params
81
- * @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
82
- * @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
83
- * @param {Function} params.open Open the native file selection dialog
84
- * @param {boolean} params.isFocused Dropzone area is in focus
85
- * @param {boolean} params.isFileDialogActive File dialog is opened
86
- * @param {boolean} params.isDragActive Active drag is in progress
87
- * @param {boolean} params.isDragAccept Dragged files are accepted
88
- * @param {boolean} params.isDragReject True only during an active drag when some dragged files would be rejected. After drop, this resets to false. Use fileRejections for post-drop errors.
89
- * @param {boolean} params.isDragGlobal Files are being dragged anywhere on the document
90
- * @param {File[]} params.acceptedFiles Accepted files
91
- * @param {FileRejection[]} params.fileRejections Rejected files and why they were rejected. This persists after drop and is the source of truth for post-drop rejections.
92
- */
93
- children: PropTypes.func,
94
-
95
- /**
96
- * Set accepted file types.
97
- * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
98
- * Keep in mind that mime type determination is not reliable across platforms. CSV files,
99
- * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
100
- * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
101
- */
102
- accept: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
103
-
104
- /**
105
- * Allow drag 'n' drop (or selection from the file dialog) of multiple files
106
- */
107
- multiple: PropTypes.bool,
108
-
109
- /**
110
- * If false, allow dropped items to take over the current browser window
111
- */
112
- preventDropOnDocument: PropTypes.bool,
113
-
114
- /**
115
- * If true, disables click to open the native file selection dialog
116
- */
117
- noClick: PropTypes.bool,
118
-
119
- /**
120
- * If true, disables SPACE/ENTER to open the native file selection dialog.
121
- * Note that it also stops tracking the focus state.
122
- */
123
- noKeyboard: PropTypes.bool,
124
-
125
- /**
126
- * If true, disables drag 'n' drop
127
- */
128
- noDrag: PropTypes.bool,
129
-
130
- /**
131
- * If true, stops drag event propagation to parents
132
- */
133
- noDragEventsBubbling: PropTypes.bool,
134
-
135
- /**
136
- * Minimum file size (in bytes)
137
- */
138
- minSize: PropTypes.number,
139
-
140
- /**
141
- * Maximum file size (in bytes)
142
- */
143
- maxSize: PropTypes.number,
144
- /**
145
- * Maximum accepted number of files
146
- * The default value is 0 which means there is no limitation to how many files are accepted.
147
- */
148
- maxFiles: PropTypes.number,
149
-
150
- /**
151
- * Enable/disable the dropzone
152
- */
153
- disabled: PropTypes.bool,
154
-
155
- /**
156
- * Use this to provide a custom file aggregator
157
- *
158
- * @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
159
- */
160
- getFilesFromEvent: PropTypes.func,
161
-
162
- /**
163
- * Cb for when closing the file dialog with no selection
164
- */
165
- onFileDialogCancel: PropTypes.func,
166
-
167
- /**
168
- * Cb for when opening the file dialog
169
- */
170
- onFileDialogOpen: PropTypes.func,
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
-
178
- /**
179
- * Set to true to focus the root element on render
180
- */
181
- autoFocus: PropTypes.bool,
182
-
183
- /**
184
- * Cb for when the `dragenter` event occurs.
185
- *
186
- * @param {DragEvent} event
187
- */
188
- onDragEnter: PropTypes.func,
189
-
190
- /**
191
- * Cb for when the `dragleave` event occurs
192
- *
193
- * @param {DragEvent} event
194
- */
195
- onDragLeave: PropTypes.func,
196
-
197
- /**
198
- * Cb for when the `dragover` event occurs
199
- *
200
- * @param {DragEvent} event
201
- */
202
- onDragOver: PropTypes.func,
203
-
204
- /**
205
- * Cb for when the `drop` event occurs.
206
- * Note that this callback is invoked after the `getFilesFromEvent` callback is done.
207
- *
208
- * Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
209
- * `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.
210
- * If `multiple` is set to false and additional files are dropped,
211
- * all files besides the first will be rejected.
212
- * Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
213
- *
214
- * Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
215
- * If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
216
- *
217
- * `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
218
- * For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
219
- *
220
- * ```js
221
- * function onDrop(acceptedFiles) {
222
- * const req = request.post('/upload')
223
- * acceptedFiles.forEach(file => {
224
- * req.attach(file.name, file)
225
- * })
226
- * req.end(callback)
227
- * }
228
- * ```
229
- *
230
- * @param {File[]} acceptedFiles
231
- * @param {FileRejection[]} fileRejections
232
- * @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
233
- */
234
- onDrop: PropTypes.func,
235
-
236
- /**
237
- * Cb for when the `drop` event occurs.
238
- * Note that if no files are accepted, this callback is not invoked.
239
- *
240
- * @param {File[]} files
241
- * @param {(DragEvent|Event)} event
242
- */
243
- onDropAccepted: PropTypes.func,
244
-
245
- /**
246
- * Cb for when the `drop` event occurs.
247
- * Note that if no files are rejected, this callback is not invoked.
248
- *
249
- * @param {FileRejection[]} fileRejections
250
- * @param {(DragEvent|Event)} event
251
- */
252
- onDropRejected: PropTypes.func,
253
-
254
- /**
255
- * Cb for when there's some error from any of the promises.
256
- *
257
- * @param {Error} error
258
- */
259
- onError: PropTypes.func,
260
-
261
- /**
262
- * Custom validation function. It must return null if there's no errors.
263
- * @param {File} file
264
- * @returns {FileError|FileError[]|null}
265
- */
266
- validator: PropTypes.func,
267
- };
268
-
269
- export default Dropzone;
270
-
271
- /**
272
- * A function that is invoked for the `dragenter`,
273
- * `dragover` and `dragleave` events.
274
- * It is not invoked if the items are not files (such as link, text, etc.).
275
- *
276
- * @callback dragCb
277
- * @param {DragEvent} event
278
- */
279
-
280
- /**
281
- * A function that is invoked for the `drop` or input change event.
282
- * It is not invoked if the items are not files (such as link, text, etc.).
283
- *
284
- * @callback dropCb
285
- * @param {File[]} acceptedFiles List of accepted files
286
- * @param {FileRejection[]} fileRejections List of rejected files and why they were rejected. This is the authoritative source for post-drop file rejections.
287
- * @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
288
- */
289
-
290
- /**
291
- * A function that is invoked for the `drop` or input change event.
292
- * It is not invoked if the items are files (such as link, text, etc.).
293
- *
294
- * @callback dropAcceptedCb
295
- * @param {File[]} files List of accepted files that meet the given criteria
296
- * (`accept`, `multiple`, `minSize`, `maxSize`)
297
- * @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
298
- */
299
-
300
- /**
301
- * A function that is invoked for the `drop` or input change event.
302
- *
303
- * @callback dropRejectedCb
304
- * @param {File[]} files List of rejected files that do not meet the given criteria
305
- * (`accept`, `multiple`, `minSize`, `maxSize`)
306
- * @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
307
- */
308
-
309
- /**
310
- * A function that is used aggregate files,
311
- * in a asynchronous fashion, from drag or input change events.
312
- *
313
- * @callback getFilesFromEvent
314
- * @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
315
- * @returns {(File[]|Promise<File[]>)}
316
- */
317
-
318
- /**
319
- * An object with the current dropzone state.
320
- *
321
- * @typedef {object} DropzoneState
322
- * @property {boolean} isFocused Dropzone area is in focus
323
- * @property {boolean} isFileDialogActive File dialog is opened
324
- * @property {boolean} isDragActive Active drag is in progress
325
- * @property {boolean} isDragAccept Dragged files are accepted
326
- * @property {boolean} isDragReject True only during an active drag when some dragged files would be rejected. After drop, this resets to false. Use fileRejections for post-drop errors.
327
- * @property {boolean} isDragGlobal Files are being dragged anywhere on the document
328
- * @property {File[]} acceptedFiles Accepted files
329
- * @property {FileRejection[]} fileRejections Rejected files and why they were rejected. This persists after drop and is the source of truth for post-drop rejections.
330
- */
331
-
332
- /**
333
- * An object with the dropzone methods.
334
- *
335
- * @typedef {object} DropzoneMethods
336
- * @property {Function} getRootProps Returns the props you should apply to the root drop container you render
337
- * @property {Function} getInputProps Returns the props you should apply to hidden file input you render
338
- * @property {Function} open Open the native file selection dialog
339
- */
340
-
341
- const initialState = {
342
- isFocused: false,
343
- isFileDialogActive: false,
344
- isDragActive: false,
345
- isDragAccept: false,
346
- isDragReject: false,
347
- isDragGlobal: false,
348
- acceptedFiles: [],
349
- fileRejections: [],
350
- };
351
-
352
- /**
353
- * A React hook that creates a drag 'n' drop area.
354
- *
355
- * ```jsx
356
- * function MyDropzone(props) {
357
- * const {getRootProps, getInputProps} = useDropzone({
358
- * onDrop: acceptedFiles => {
359
- * // do something with the File objects, e.g. upload to some server
360
- * }
361
- * });
362
- * return (
363
- * <div {...getRootProps()}>
364
- * <input {...getInputProps()} />
365
- * <p>Drag and drop some files here, or click to select files</p>
366
- * </div>
367
- * )
368
- * }
369
- * ```
370
- *
371
- * @function useDropzone
372
- *
373
- * @param {object} props
374
- * @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
375
- * Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
376
- * Keep in mind that mime type determination is not reliable across platforms. CSV files,
377
- * for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
378
- * Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
379
- * @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
380
- * @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
381
- * @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
382
- * @param {boolean} [props.noKeyboard=false] If true, disables SPACE/ENTER to open the native file selection dialog.
383
- * Note that it also stops tracking the focus state.
384
- * @param {boolean} [props.noDrag=false] If true, disables drag 'n' drop
385
- * @param {boolean} [props.noDragEventsBubbling=false] If true, stops drag event propagation to parents
386
- * @param {number} [props.minSize=0] Minimum file size (in bytes)
387
- * @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
388
- * @param {boolean} [props.disabled=false] Enable/disable the dropzone
389
- * @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
390
- * @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
391
- * @param {boolean} [props.useFsAccessApi] Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
392
- * to open the file picker instead of using an `<input type="file">` click event.
393
- * @param {boolean} autoFocus Set to true to auto focus the root element.
394
- * @param {Function} [props.onFileDialogOpen] Cb for when opening the file dialog
395
- * @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
396
- * @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
397
- * @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
398
- * @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
399
- * Note that this callback is invoked after the `getFilesFromEvent` callback is done.
400
- *
401
- * Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
402
- * `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).
403
- * If `multiple` is set to false and additional files are dropped,
404
- * all files besides the first will be rejected.
405
- * Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
406
- *
407
- * Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
408
- * If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
409
- *
410
- * The second parameter (fileRejections) is the authoritative list of rejected files after a drop.
411
- * Use this parameter or the fileRejections state property to handle post-drop file rejections,
412
- * as isDragReject only indicates rejection state during active drag operations.
413
- *
414
- * `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
415
- * For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
416
- *
417
- * ```js
418
- * function onDrop(acceptedFiles) {
419
- * const req = request.post('/upload')
420
- * acceptedFiles.forEach(file => {
421
- * req.attach(file.name, file)
422
- * })
423
- * req.end(callback)
424
- * }
425
- * ```
426
- * @param {dropAcceptedCb} [props.onDropAccepted]
427
- * @param {dropRejectedCb} [props.onDropRejected]
428
- * @param {(error: Error) => void} [props.onError]
429
- *
430
- * @returns {DropzoneState & DropzoneMethods}
431
- */
432
- export function useDropzone(props = {}) {
433
- const {
434
- accept,
435
- disabled,
436
- getFilesFromEvent,
437
- maxSize,
438
- minSize,
439
- multiple,
440
- maxFiles,
441
- onDragEnter,
442
- onDragLeave,
443
- onDragOver,
444
- onDrop,
445
- onDropAccepted,
446
- onDropRejected,
447
- onFileDialogCancel,
448
- onFileDialogOpen,
449
- useFsAccessApi,
450
- autoFocus,
451
- preventDropOnDocument,
452
- noClick,
453
- noKeyboard,
454
- noDrag,
455
- noDragEventsBubbling,
456
- onError,
457
- validator,
458
- } = {
459
- ...defaultProps,
460
- ...props,
461
- };
462
-
463
- const acceptAttr = useMemo(() => acceptPropAsAcceptAttr(accept), [accept]);
464
- const pickerTypes = useMemo(() => pickerOptionsFromAccept(accept), [accept]);
465
-
466
- const onFileDialogOpenCb = useMemo(
467
- () => (typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop),
468
- [onFileDialogOpen]
469
- );
470
- const onFileDialogCancelCb = useMemo(
471
- () =>
472
- typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop,
473
- [onFileDialogCancel]
474
- );
475
-
476
- /**
477
- * @constant
478
- * @type {React.MutableRefObject<HTMLElement>}
479
- */
480
- const rootRef = useRef(null);
481
-
482
- const inputRef = useRef(null);
483
-
484
- const [state, dispatch] = useReducer(reducer, initialState);
485
- const { isFocused, isFileDialogActive } = state;
486
-
487
- const fsAccessApiWorksRef = useRef(
488
- typeof window !== "undefined" &&
489
- window.isSecureContext &&
490
- useFsAccessApi &&
491
- canUseFileSystemAccessAPI()
492
- );
493
-
494
- // Update file dialog active state when the window is focused on
495
- const onWindowFocus = () => {
496
- // Execute the timeout only if the file dialog is opened in the browser
497
- if (!fsAccessApiWorksRef.current && isFileDialogActive) {
498
- setTimeout(() => {
499
- if (inputRef.current) {
500
- const { files } = inputRef.current;
501
-
502
- if (!files.length) {
503
- dispatch({ type: "closeDialog" });
504
- onFileDialogCancelCb();
505
- }
506
- }
507
- }, 300);
508
- }
509
- };
510
- useEffect(() => {
511
- window.addEventListener("focus", onWindowFocus, false);
512
- return () => {
513
- window.removeEventListener("focus", onWindowFocus, false);
514
- };
515
- }, [inputRef, isFileDialogActive, onFileDialogCancelCb, fsAccessApiWorksRef]);
516
-
517
- const dragTargetsRef = useRef([]);
518
- const globalDragTargetsRef = useRef([]);
519
- const onDocumentDrop = (event) => {
520
- if (rootRef.current && rootRef.current.contains(event.target)) {
521
- // If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
522
- return;
523
- }
524
- event.preventDefault();
525
- dragTargetsRef.current = [];
526
- };
527
-
528
- useEffect(() => {
529
- if (preventDropOnDocument) {
530
- document.addEventListener("dragover", onDocumentDragOver, false);
531
- document.addEventListener("drop", onDocumentDrop, false);
532
- }
533
-
534
- return () => {
535
- if (preventDropOnDocument) {
536
- document.removeEventListener("dragover", onDocumentDragOver);
537
- document.removeEventListener("drop", onDocumentDrop);
538
- }
539
- };
540
- }, [rootRef, preventDropOnDocument]);
541
-
542
- // Track global drag state for document-level drag events
543
- useEffect(() => {
544
- const onDocumentDragEnter = (event) => {
545
- globalDragTargetsRef.current = [
546
- ...globalDragTargetsRef.current,
547
- event.target,
548
- ];
549
-
550
- if (isEvtWithFiles(event)) {
551
- dispatch({ isDragGlobal: true, type: "setDragGlobal" });
552
- }
553
- };
554
-
555
- const onDocumentDragLeave = (event) => {
556
- // Only deactivate once we've left all children
557
- globalDragTargetsRef.current = globalDragTargetsRef.current.filter(
558
- (el) => el !== event.target && el !== null
559
- );
560
-
561
- if (globalDragTargetsRef.current.length > 0) {
562
- return;
563
- }
564
-
565
- dispatch({ isDragGlobal: false, type: "setDragGlobal" });
566
- };
567
-
568
- const onDocumentDragEnd = () => {
569
- globalDragTargetsRef.current = [];
570
- dispatch({ isDragGlobal: false, type: "setDragGlobal" });
571
- };
572
-
573
- const onDocumentDropGlobal = () => {
574
- globalDragTargetsRef.current = [];
575
- dispatch({ isDragGlobal: false, type: "setDragGlobal" });
576
- };
577
-
578
- document.addEventListener("dragenter", onDocumentDragEnter, false);
579
- document.addEventListener("dragleave", onDocumentDragLeave, false);
580
- document.addEventListener("dragend", onDocumentDragEnd, false);
581
- document.addEventListener("drop", onDocumentDropGlobal, false);
582
-
583
- return () => {
584
- document.removeEventListener("dragenter", onDocumentDragEnter);
585
- document.removeEventListener("dragleave", onDocumentDragLeave);
586
- document.removeEventListener("dragend", onDocumentDragEnd);
587
- document.removeEventListener("drop", onDocumentDropGlobal);
588
- };
589
- }, [rootRef]);
590
-
591
- // Auto focus the root when autoFocus is true
592
- useEffect(() => {
593
- if (!disabled && autoFocus && rootRef.current) {
594
- rootRef.current.focus();
595
- }
596
- return () => {};
597
- }, [rootRef, autoFocus, disabled]);
598
-
599
- const onErrCb = useCallback(
600
- (e) => {
601
- if (onError) {
602
- onError(e);
603
- } else {
604
- // Let the user know something's gone wrong if they haven't provided the onError cb.
605
- console.error(e);
606
- }
607
- },
608
- [onError]
609
- );
610
-
611
- const onDragEnterCb = useCallback(
612
- (event) => {
613
- event.preventDefault();
614
- // Persist here because we need the event later after getFilesFromEvent() is done
615
- event.persist();
616
- stopPropagation(event);
617
-
618
- dragTargetsRef.current = [...dragTargetsRef.current, event.target];
619
-
620
- if (isEvtWithFiles(event)) {
621
- Promise.resolve(getFilesFromEvent(event))
622
- .then((files) => {
623
- if (isPropagationStopped(event) && !noDragEventsBubbling) {
624
- return;
625
- }
626
-
627
- const fileCount = files.length;
628
- const isDragAccept =
629
- fileCount > 0 &&
630
- allFilesAccepted({
631
- files,
632
- accept: acceptAttr,
633
- minSize,
634
- maxSize,
635
- multiple,
636
- maxFiles,
637
- validator,
638
- });
639
- const isDragReject = fileCount > 0 && !isDragAccept;
640
-
641
- dispatch({
642
- isDragAccept,
643
- isDragReject,
644
- isDragActive: true,
645
- type: "setDraggedFiles",
646
- });
647
-
648
- if (onDragEnter) {
649
- onDragEnter(event);
650
- }
651
- })
652
- .catch((e) => onErrCb(e));
653
- }
654
- },
655
- [
656
- getFilesFromEvent,
657
- onDragEnter,
658
- onErrCb,
659
- noDragEventsBubbling,
660
- acceptAttr,
661
- minSize,
662
- maxSize,
663
- multiple,
664
- maxFiles,
665
- validator,
666
- ]
667
- );
668
-
669
- const onDragOverCb = useCallback(
670
- (event) => {
671
- event.preventDefault();
672
- event.persist();
673
- stopPropagation(event);
674
-
675
- const hasFiles = isEvtWithFiles(event);
676
- if (hasFiles && event.dataTransfer) {
677
- try {
678
- event.dataTransfer.dropEffect = "copy";
679
- } catch {} /* eslint-disable-line no-empty */
680
- }
681
-
682
- if (hasFiles && onDragOver) {
683
- onDragOver(event);
684
- }
685
-
686
- return false;
687
- },
688
- [onDragOver, noDragEventsBubbling]
689
- );
690
-
691
- const onDragLeaveCb = useCallback(
692
- (event) => {
693
- event.preventDefault();
694
- event.persist();
695
- stopPropagation(event);
696
-
697
- // Only deactivate once the dropzone and all children have been left
698
- const targets = dragTargetsRef.current.filter(
699
- (target) => rootRef.current && rootRef.current.contains(target)
700
- );
701
- // Make sure to remove a target present multiple times only once
702
- // (Firefox may fire dragenter/dragleave multiple times on the same element)
703
- const targetIdx = targets.indexOf(event.target);
704
- if (targetIdx !== -1) {
705
- targets.splice(targetIdx, 1);
706
- }
707
- dragTargetsRef.current = targets;
708
- if (targets.length > 0) {
709
- return;
710
- }
711
-
712
- dispatch({
713
- type: "setDraggedFiles",
714
- isDragActive: false,
715
- isDragAccept: false,
716
- isDragReject: false,
717
- });
718
-
719
- if (isEvtWithFiles(event) && onDragLeave) {
720
- onDragLeave(event);
721
- }
722
- },
723
- [rootRef, onDragLeave, noDragEventsBubbling]
724
- );
725
-
726
- const setFiles = useCallback(
727
- (files, event) => {
728
- const acceptedFiles = [];
729
- const fileRejections = [];
730
-
731
- files.forEach((file) => {
732
- const [accepted, acceptError] = fileAccepted(file, acceptAttr);
733
- const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
734
- const customErrors = validator ? validator(file) : null;
735
-
736
- if (accepted && sizeMatch && !customErrors) {
737
- acceptedFiles.push(file);
738
- } else {
739
- let errors = [acceptError, sizeError];
740
-
741
- if (customErrors) {
742
- errors = errors.concat(customErrors);
743
- }
744
-
745
- fileRejections.push({ file, errors: errors.filter((e) => e) });
746
- }
747
- });
748
-
749
- if (
750
- (!multiple && acceptedFiles.length > 1) ||
751
- (multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles)
752
- ) {
753
- // Reject everything and empty accepted files
754
- acceptedFiles.forEach((file) => {
755
- fileRejections.push({ file, errors: [TOO_MANY_FILES_REJECTION] });
756
- });
757
- acceptedFiles.splice(0);
758
- }
759
-
760
- dispatch({
761
- acceptedFiles,
762
- fileRejections,
763
- type: "setFiles",
764
- });
765
-
766
- if (onDrop) {
767
- onDrop(acceptedFiles, fileRejections, event);
768
- }
769
-
770
- if (fileRejections.length > 0 && onDropRejected) {
771
- onDropRejected(fileRejections, event);
772
- }
773
-
774
- if (acceptedFiles.length > 0 && onDropAccepted) {
775
- onDropAccepted(acceptedFiles, event);
776
- }
777
- },
778
- [
779
- dispatch,
780
- multiple,
781
- acceptAttr,
782
- minSize,
783
- maxSize,
784
- maxFiles,
785
- onDrop,
786
- onDropAccepted,
787
- onDropRejected,
788
- validator,
789
- ]
790
- );
791
-
792
- const onDropCb = useCallback(
793
- (event) => {
794
- event.preventDefault();
795
- // Persist here because we need the event later after getFilesFromEvent() is done
796
- event.persist();
797
- stopPropagation(event);
798
-
799
- dragTargetsRef.current = [];
800
-
801
- if (isEvtWithFiles(event)) {
802
- Promise.resolve(getFilesFromEvent(event))
803
- .then((files) => {
804
- if (isPropagationStopped(event) && !noDragEventsBubbling) {
805
- return;
806
- }
807
- setFiles(files, event);
808
- })
809
- .catch((e) => onErrCb(e));
810
- }
811
- dispatch({ type: "reset" });
812
- },
813
- [getFilesFromEvent, setFiles, onErrCb, noDragEventsBubbling]
814
- );
815
-
816
- // Fn for opening the file dialog programmatically
817
- const openFileDialog = useCallback(() => {
818
- // No point to use FS access APIs if context is not secure
819
- // https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#feature_detection
820
- if (fsAccessApiWorksRef.current) {
821
- dispatch({ type: "openDialog" });
822
- onFileDialogOpenCb();
823
- // https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
824
- const opts = {
825
- multiple,
826
- types: pickerTypes,
827
- };
828
- window
829
- .showOpenFilePicker(opts)
830
- .then((handles) => getFilesFromEvent(handles))
831
- .then((files) => {
832
- setFiles(files, null);
833
- dispatch({ type: "closeDialog" });
834
- })
835
- .catch((e) => {
836
- // AbortError means the user canceled
837
- if (isAbort(e)) {
838
- onFileDialogCancelCb(e);
839
- dispatch({ type: "closeDialog" });
840
- } else if (isSecurityError(e)) {
841
- fsAccessApiWorksRef.current = false;
842
- // CORS, so cannot use this API
843
- // Try using the input
844
- if (inputRef.current) {
845
- inputRef.current.value = null;
846
- inputRef.current.click();
847
- } else {
848
- onErrCb(
849
- new Error(
850
- "Cannot open the file picker because the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API is not supported and no <input> was provided."
851
- )
852
- );
853
- }
854
- } else {
855
- onErrCb(e);
856
- }
857
- });
858
- return;
859
- }
860
-
861
- if (inputRef.current) {
862
- dispatch({ type: "openDialog" });
863
- onFileDialogOpenCb();
864
- inputRef.current.value = null;
865
- inputRef.current.click();
866
- }
867
- }, [
868
- dispatch,
869
- onFileDialogOpenCb,
870
- onFileDialogCancelCb,
871
- useFsAccessApi,
872
- setFiles,
873
- onErrCb,
874
- pickerTypes,
875
- multiple,
876
- ]);
877
-
878
- // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
879
- const onKeyDownCb = useCallback(
880
- (event) => {
881
- // Ignore keyboard events bubbling up the DOM tree
882
- if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
883
- return;
884
- }
885
-
886
- if (
887
- event.key === " " ||
888
- event.key === "Enter" ||
889
- event.keyCode === 32 ||
890
- event.keyCode === 13
891
- ) {
892
- event.preventDefault();
893
- openFileDialog();
894
- }
895
- },
896
- [rootRef, openFileDialog]
897
- );
898
-
899
- // Update focus state for the dropzone
900
- const onFocusCb = useCallback(() => {
901
- dispatch({ type: "focus" });
902
- }, []);
903
- const onBlurCb = useCallback(() => {
904
- dispatch({ type: "blur" });
905
- }, []);
906
-
907
- // Cb to open the file dialog when click occurs on the dropzone
908
- const onClickCb = useCallback(() => {
909
- if (noClick) {
910
- return;
911
- }
912
-
913
- // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
914
- // to ensure React can handle state changes
915
- // See: https://github.com/react-dropzone/react-dropzone/issues/450
916
- if (isIeOrEdge()) {
917
- setTimeout(openFileDialog, 0);
918
- } else {
919
- openFileDialog();
920
- }
921
- }, [noClick, openFileDialog]);
922
-
923
- const composeHandler = (fn) => {
924
- return disabled ? null : fn;
925
- };
926
-
927
- const composeKeyboardHandler = (fn) => {
928
- return noKeyboard ? null : composeHandler(fn);
929
- };
930
-
931
- const composeDragHandler = (fn) => {
932
- return noDrag ? null : composeHandler(fn);
933
- };
934
-
935
- const stopPropagation = (event) => {
936
- if (noDragEventsBubbling) {
937
- event.stopPropagation();
938
- }
939
- };
940
-
941
- const getRootProps = useMemo(
942
- () =>
943
- ({
944
- refKey = "ref",
945
- role,
946
- onKeyDown,
947
- onFocus,
948
- onBlur,
949
- onClick,
950
- onDragEnter,
951
- onDragOver,
952
- onDragLeave,
953
- onDrop,
954
- ...rest
955
- } = {}) => ({
956
- onKeyDown: composeKeyboardHandler(
957
- composeEventHandlers(onKeyDown, onKeyDownCb)
958
- ),
959
- onFocus: composeKeyboardHandler(
960
- composeEventHandlers(onFocus, onFocusCb)
961
- ),
962
- onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
963
- onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
964
- onDragEnter: composeDragHandler(
965
- composeEventHandlers(onDragEnter, onDragEnterCb)
966
- ),
967
- onDragOver: composeDragHandler(
968
- composeEventHandlers(onDragOver, onDragOverCb)
969
- ),
970
- onDragLeave: composeDragHandler(
971
- composeEventHandlers(onDragLeave, onDragLeaveCb)
972
- ),
973
- onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
974
- role: typeof role === "string" && role !== "" ? role : "presentation",
975
- [refKey]: rootRef,
976
- ...(!disabled && !noKeyboard ? { tabIndex: 0 } : {}),
977
- ...rest,
978
- }),
979
- [
980
- rootRef,
981
- onKeyDownCb,
982
- onFocusCb,
983
- onBlurCb,
984
- onClickCb,
985
- onDragEnterCb,
986
- onDragOverCb,
987
- onDragLeaveCb,
988
- onDropCb,
989
- noKeyboard,
990
- noDrag,
991
- disabled,
992
- ]
993
- );
994
-
995
- const onInputElementClick = useCallback((event) => {
996
- event.stopPropagation();
997
- }, []);
998
-
999
- const getInputProps = useMemo(
1000
- () =>
1001
- ({ refKey = "ref", onChange, onClick, ...rest } = {}) => {
1002
- const inputProps = {
1003
- accept: acceptAttr,
1004
- multiple,
1005
- type: "file",
1006
- style: {
1007
- border: 0,
1008
- clip: "rect(0, 0, 0, 0)",
1009
- clipPath: "inset(50%)",
1010
- height: "1px",
1011
- margin: "0 -1px -1px 0",
1012
- overflow: "hidden",
1013
- padding: 0,
1014
- position: "absolute",
1015
- width: "1px",
1016
- whiteSpace: "nowrap",
1017
- },
1018
- onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
1019
- onClick: composeHandler(
1020
- composeEventHandlers(onClick, onInputElementClick)
1021
- ),
1022
- tabIndex: -1,
1023
- [refKey]: inputRef,
1024
- };
1025
-
1026
- return {
1027
- ...inputProps,
1028
- ...rest,
1029
- };
1030
- },
1031
- [inputRef, accept, multiple, onDropCb, disabled]
1032
- );
1033
-
1034
- return {
1035
- ...state,
1036
- isFocused: isFocused && !disabled,
1037
- getRootProps,
1038
- getInputProps,
1039
- rootRef,
1040
- inputRef,
1041
- open: composeHandler(openFileDialog),
1042
- };
1043
- }
1044
-
1045
- /**
1046
- * @param {DropzoneState} state
1047
- * @param {{type: string} & DropzoneState} action
1048
- * @returns {DropzoneState}
1049
- */
1050
- function reducer(state, action) {
1051
- /* istanbul ignore next */
1052
- switch (action.type) {
1053
- case "focus":
1054
- return {
1055
- ...state,
1056
- isFocused: true,
1057
- };
1058
- case "blur":
1059
- return {
1060
- ...state,
1061
- isFocused: false,
1062
- };
1063
- case "openDialog":
1064
- return {
1065
- ...initialState,
1066
- isFileDialogActive: true,
1067
- };
1068
- case "closeDialog":
1069
- return {
1070
- ...state,
1071
- isFileDialogActive: false,
1072
- };
1073
- case "setDraggedFiles":
1074
- return {
1075
- ...state,
1076
- isDragActive: action.isDragActive,
1077
- isDragAccept: action.isDragAccept,
1078
- isDragReject: action.isDragReject,
1079
- };
1080
- case "setFiles":
1081
- return {
1082
- ...state,
1083
- acceptedFiles: action.acceptedFiles,
1084
- fileRejections: action.fileRejections,
1085
- isDragReject: false,
1086
- };
1087
- case "setDragGlobal":
1088
- return {
1089
- ...state,
1090
- isDragGlobal: action.isDragGlobal,
1091
- };
1092
- case "reset":
1093
- return {
1094
- ...initialState,
1095
- };
1096
- default:
1097
- return state;
1098
- }
1099
- }
1100
-
1101
- function noop() {}
1102
-
1103
- export { ErrorCode } from "./utils/index.js";