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.tsx ADDED
@@ -0,0 +1,774 @@
1
+ import {fromEvent} from "file-selector";
2
+ import type {FileWithPath} from "file-selector";
3
+ import type * as React from "react";
4
+ import {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef} from "react";
5
+ import {
6
+ acceptPropAsAcceptAttr,
7
+ allFilesAccepted,
8
+ canUseFileSystemAccessAPI,
9
+ composeEventHandlers,
10
+ ErrorCode,
11
+ fileAccepted,
12
+ fileMatchSize,
13
+ isAbort,
14
+ isEvtWithFiles,
15
+ isIeOrEdge,
16
+ isPropagationStopped,
17
+ isSecurityError,
18
+ onDocumentDragOver,
19
+ pickerOptionsFromAccept,
20
+ TOO_MANY_FILES_REJECTION
21
+ } from "./utils";
22
+ import type {Accept, FileError} from "./utils";
23
+
24
+ export type {Accept, FileError, FileWithPath};
25
+ export {ErrorCode};
26
+
27
+ export interface DropzoneProps extends DropzoneOptions {
28
+ children?: (state: DropzoneState) => React.ReactElement;
29
+ }
30
+
31
+ export interface FileRejection {
32
+ file: FileWithPath;
33
+ errors: readonly FileError[];
34
+ }
35
+
36
+ type SharedProps = "multiple" | "onDragEnter" | "onDragOver" | "onDragLeave";
37
+
38
+ export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, SharedProps> & {
39
+ accept?: Accept;
40
+ minSize?: number;
41
+ maxSize?: number;
42
+ maxFiles?: number;
43
+ preventDropOnDocument?: boolean;
44
+ noClick?: boolean;
45
+ noKeyboard?: boolean;
46
+ noDrag?: boolean;
47
+ noDragEventsBubbling?: boolean;
48
+ disabled?: boolean;
49
+ onDrop?: <T extends File>(acceptedFiles: T[], fileRejections: FileRejection[], event: DropEvent) => void;
50
+ onDropAccepted?: <T extends File>(files: T[], event: DropEvent) => void;
51
+ onDropRejected?: (fileRejections: FileRejection[], event: DropEvent) => void;
52
+ getFilesFromEvent?: (event: DropEvent) => Promise<Array<File | DataTransferItem>>;
53
+ onFileDialogCancel?: () => void;
54
+ onFileDialogOpen?: () => void;
55
+ onError?: (err: Error) => void;
56
+ validator?: <T extends File>(file: T) => FileError | readonly FileError[] | null;
57
+ useFsAccessApi?: boolean;
58
+ autoFocus?: boolean;
59
+ };
60
+
61
+ export type DropEvent =
62
+ | React.DragEvent<HTMLElement>
63
+ | React.ChangeEvent<HTMLInputElement>
64
+ | DragEvent
65
+ | Event
66
+ | Array<FileSystemFileHandle>;
67
+
68
+ export interface DropzoneRef {
69
+ open: () => void;
70
+ }
71
+
72
+ export type DropzoneState = DropzoneRef & {
73
+ isFocused: boolean;
74
+ isDragActive: boolean;
75
+ isDragAccept: boolean;
76
+ isDragReject: boolean;
77
+ isDragGlobal: boolean;
78
+ isFileDialogActive: boolean;
79
+ acceptedFiles: readonly FileWithPath[];
80
+ fileRejections: readonly FileRejection[];
81
+ rootRef: React.RefObject<HTMLElement>;
82
+ inputRef: React.RefObject<HTMLInputElement>;
83
+ getRootProps: <T extends DropzoneRootProps>(props?: T) => T;
84
+ getInputProps: <T extends DropzoneInputProps>(props?: T) => T;
85
+ };
86
+
87
+ export interface DropzoneRootProps extends React.HTMLAttributes<HTMLElement> {
88
+ refKey?: string;
89
+ [key: string]: any;
90
+ }
91
+
92
+ export interface DropzoneInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
93
+ refKey?: string;
94
+ }
95
+
96
+ /**
97
+ * Convenience wrapper component for the `useDropzone` hook
98
+ *
99
+ * ```jsx
100
+ * <Dropzone>
101
+ * {({getRootProps, getInputProps}) => (
102
+ * <div {...getRootProps()}>
103
+ * <input {...getInputProps()} />
104
+ * <p>Drag 'n' drop some files here, or click to select files</p>
105
+ * </div>
106
+ * )}
107
+ * </Dropzone>
108
+ * ```
109
+ */
110
+ const Dropzone: React.ForwardRefExoticComponent<DropzoneProps & React.RefAttributes<DropzoneRef>> = forwardRef<
111
+ DropzoneRef,
112
+ DropzoneProps
113
+ >(({children, ...params}, ref) => {
114
+ const {open, ...props} = useDropzone(params);
115
+
116
+ useImperativeHandle(ref, () => ({open}), [open]);
117
+
118
+ return <>{children?.({...props, open})}</>;
119
+ });
120
+
121
+ Dropzone.displayName = "Dropzone";
122
+
123
+ export default Dropzone;
124
+
125
+ interface DropzoneInternalState {
126
+ isFocused: boolean;
127
+ isFileDialogActive: boolean;
128
+ isDragActive: boolean;
129
+ isDragAccept: boolean;
130
+ isDragReject: boolean;
131
+ isDragGlobal: boolean;
132
+ acceptedFiles: FileWithPath[];
133
+ fileRejections: FileRejection[];
134
+ }
135
+
136
+ const initialState: DropzoneInternalState = {
137
+ isFocused: false,
138
+ isFileDialogActive: false,
139
+ isDragActive: false,
140
+ isDragAccept: false,
141
+ isDragReject: false,
142
+ isDragGlobal: false,
143
+ acceptedFiles: [],
144
+ fileRejections: []
145
+ };
146
+
147
+ /**
148
+ * A React hook that creates a drag 'n' drop area.
149
+ *
150
+ * ```jsx
151
+ * function MyDropzone(props) {
152
+ * const {getRootProps, getInputProps} = useDropzone({
153
+ * onDrop: acceptedFiles => {
154
+ * // do something with the File objects, e.g. upload to some server
155
+ * }
156
+ * });
157
+ * return (
158
+ * <div {...getRootProps()}>
159
+ * <input {...getInputProps()} />
160
+ * <p>Drag and drop some files here, or click to select files</p>
161
+ * </div>
162
+ * )
163
+ * }
164
+ * ```
165
+ */
166
+ export function useDropzone(props: DropzoneOptions = {}): DropzoneState {
167
+ const {
168
+ accept,
169
+ disabled = false,
170
+ getFilesFromEvent = fromEvent,
171
+ maxSize = Number.POSITIVE_INFINITY,
172
+ minSize = 0,
173
+ multiple = true,
174
+ maxFiles = 0,
175
+ onDragEnter,
176
+ onDragLeave,
177
+ onDragOver,
178
+ onDrop,
179
+ onDropAccepted,
180
+ onDropRejected,
181
+ onFileDialogCancel,
182
+ onFileDialogOpen,
183
+ useFsAccessApi = false,
184
+ autoFocus = false,
185
+ preventDropOnDocument = true,
186
+ noClick = false,
187
+ noKeyboard = false,
188
+ noDrag = false,
189
+ noDragEventsBubbling = false,
190
+ onError,
191
+ validator
192
+ } = props;
193
+
194
+ const acceptAttr = useMemo(() => acceptPropAsAcceptAttr(accept), [accept]);
195
+ const pickerTypes = useMemo(() => pickerOptionsFromAccept(accept), [accept]);
196
+
197
+ const onFileDialogOpenCb = useMemo<(...args: any[]) => void>(
198
+ () => (typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop),
199
+ [onFileDialogOpen]
200
+ );
201
+ const onFileDialogCancelCb = useMemo<(...args: any[]) => void>(
202
+ () => (typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop),
203
+ [onFileDialogCancel]
204
+ );
205
+
206
+ const rootRef = useRef<HTMLElement>(null);
207
+ const inputRef = useRef<HTMLInputElement>(null);
208
+
209
+ const [state, dispatch] = useReducer(reducer, initialState);
210
+ const {isFocused, isFileDialogActive} = state;
211
+
212
+ const fsAccessApiWorksRef = useRef(
213
+ typeof window !== "undefined" && window.isSecureContext && useFsAccessApi && canUseFileSystemAccessAPI()
214
+ );
215
+
216
+ // Update file dialog active state when the window is focused on
217
+ const onWindowFocus = () => {
218
+ // Execute the timeout only if the file dialog is opened in the browser
219
+ if (!fsAccessApiWorksRef.current && isFileDialogActive) {
220
+ setTimeout(() => {
221
+ if (inputRef.current) {
222
+ const {files} = inputRef.current;
223
+
224
+ if (!files?.length) {
225
+ dispatch({type: "closeDialog"});
226
+ onFileDialogCancelCb();
227
+ }
228
+ }
229
+ }, 300);
230
+ }
231
+ };
232
+ useEffect(() => {
233
+ window.addEventListener("focus", onWindowFocus, false);
234
+ return () => {
235
+ window.removeEventListener("focus", onWindowFocus, false);
236
+ };
237
+ }, [inputRef, isFileDialogActive, onFileDialogCancelCb, fsAccessApiWorksRef]);
238
+
239
+ const dragTargetsRef = useRef<EventTarget[]>([]);
240
+ const globalDragTargetsRef = useRef<EventTarget[]>([]);
241
+ const onDocumentDrop = (event: DragEvent) => {
242
+ if (rootRef.current && event.target && rootRef.current.contains(event.target as Node)) {
243
+ // If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
244
+ return;
245
+ }
246
+ event.preventDefault();
247
+ dragTargetsRef.current = [];
248
+ };
249
+
250
+ useEffect(() => {
251
+ if (preventDropOnDocument) {
252
+ document.addEventListener("dragover", onDocumentDragOver, false);
253
+ document.addEventListener("drop", onDocumentDrop, false);
254
+ }
255
+
256
+ return () => {
257
+ if (preventDropOnDocument) {
258
+ document.removeEventListener("dragover", onDocumentDragOver);
259
+ document.removeEventListener("drop", onDocumentDrop);
260
+ }
261
+ };
262
+ }, [rootRef, preventDropOnDocument]);
263
+
264
+ // Track global drag state for document-level drag events
265
+ useEffect(() => {
266
+ const onDocumentDragEnter = (event: DragEvent) => {
267
+ if (event.target) {
268
+ globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
269
+ }
270
+
271
+ if (isEvtWithFiles(event)) {
272
+ dispatch({isDragGlobal: true, type: "setDragGlobal"});
273
+ }
274
+ };
275
+
276
+ const onDocumentDragLeave = (event: DragEvent) => {
277
+ // Only deactivate once we've left all children
278
+ globalDragTargetsRef.current = globalDragTargetsRef.current.filter(el => el !== event.target && el !== null);
279
+
280
+ if (globalDragTargetsRef.current.length > 0) {
281
+ return;
282
+ }
283
+
284
+ dispatch({isDragGlobal: false, type: "setDragGlobal"});
285
+ };
286
+
287
+ const onDocumentDragEnd = () => {
288
+ globalDragTargetsRef.current = [];
289
+ dispatch({isDragGlobal: false, type: "setDragGlobal"});
290
+ };
291
+
292
+ const onDocumentDropGlobal = () => {
293
+ globalDragTargetsRef.current = [];
294
+ dispatch({isDragGlobal: false, type: "setDragGlobal"});
295
+ };
296
+
297
+ document.addEventListener("dragenter", onDocumentDragEnter, false);
298
+ document.addEventListener("dragleave", onDocumentDragLeave, false);
299
+ document.addEventListener("dragend", onDocumentDragEnd, false);
300
+ document.addEventListener("drop", onDocumentDropGlobal, false);
301
+
302
+ return () => {
303
+ document.removeEventListener("dragenter", onDocumentDragEnter);
304
+ document.removeEventListener("dragleave", onDocumentDragLeave);
305
+ document.removeEventListener("dragend", onDocumentDragEnd);
306
+ document.removeEventListener("drop", onDocumentDropGlobal);
307
+ };
308
+ }, [rootRef]);
309
+
310
+ // Auto focus the root when autoFocus is true
311
+ useEffect(() => {
312
+ if (!disabled && autoFocus && rootRef.current) {
313
+ rootRef.current.focus();
314
+ }
315
+ return () => {};
316
+ }, [rootRef, autoFocus, disabled]);
317
+
318
+ const onErrCb = useCallback(
319
+ (e: Error) => {
320
+ if (onError) {
321
+ onError(e);
322
+ } else {
323
+ // Let the user know something's gone wrong if they haven't provided the onError cb.
324
+ console.error(e);
325
+ }
326
+ },
327
+ [onError]
328
+ );
329
+
330
+ const onDragEnterCb = useCallback(
331
+ (event: any) => {
332
+ event.preventDefault();
333
+ // Persist here because we need the event later after getFilesFromEvent() is done
334
+ event.persist?.();
335
+ stopPropagation(event);
336
+
337
+ dragTargetsRef.current = [...dragTargetsRef.current, event.target];
338
+
339
+ if (isEvtWithFiles(event)) {
340
+ Promise.resolve(getFilesFromEvent(event))
341
+ .then(files => {
342
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
343
+ return;
344
+ }
345
+
346
+ const fileCount = files.length;
347
+ const isDragAccept =
348
+ fileCount > 0 &&
349
+ allFilesAccepted({
350
+ files: files as File[],
351
+ accept: acceptAttr,
352
+ minSize,
353
+ maxSize,
354
+ multiple,
355
+ maxFiles,
356
+ validator
357
+ });
358
+ const isDragReject = fileCount > 0 && !isDragAccept;
359
+
360
+ dispatch({
361
+ isDragAccept,
362
+ isDragReject,
363
+ isDragActive: true,
364
+ type: "setDraggedFiles"
365
+ });
366
+
367
+ if (onDragEnter) {
368
+ onDragEnter(event);
369
+ }
370
+ })
371
+ .catch(e => onErrCb(e));
372
+ }
373
+ },
374
+ [
375
+ getFilesFromEvent,
376
+ onDragEnter,
377
+ onErrCb,
378
+ noDragEventsBubbling,
379
+ acceptAttr,
380
+ minSize,
381
+ maxSize,
382
+ multiple,
383
+ maxFiles,
384
+ validator
385
+ ]
386
+ );
387
+
388
+ const onDragOverCb = useCallback(
389
+ (event: any) => {
390
+ event.preventDefault();
391
+ event.persist?.();
392
+ stopPropagation(event);
393
+
394
+ const hasFiles = isEvtWithFiles(event);
395
+ if (hasFiles && event.dataTransfer) {
396
+ try {
397
+ event.dataTransfer.dropEffect = "copy";
398
+ } catch {
399
+ /* no-op */
400
+ }
401
+ }
402
+
403
+ if (hasFiles && onDragOver) {
404
+ onDragOver(event);
405
+ }
406
+
407
+ return false;
408
+ },
409
+ [onDragOver, noDragEventsBubbling]
410
+ );
411
+
412
+ const onDragLeaveCb = useCallback(
413
+ (event: any) => {
414
+ event.preventDefault();
415
+ event.persist?.();
416
+ stopPropagation(event);
417
+
418
+ // Only deactivate once the dropzone and all children have been left
419
+ const targets = dragTargetsRef.current.filter(target => rootRef.current?.contains(target as Node));
420
+ // Make sure to remove a target present multiple times only once
421
+ // (Firefox may fire dragenter/dragleave multiple times on the same element)
422
+ const targetIdx = targets.indexOf(event.target);
423
+ if (targetIdx !== -1) {
424
+ targets.splice(targetIdx, 1);
425
+ }
426
+ dragTargetsRef.current = targets;
427
+ if (targets.length > 0) {
428
+ return;
429
+ }
430
+
431
+ dispatch({
432
+ type: "setDraggedFiles",
433
+ isDragActive: false,
434
+ isDragAccept: false,
435
+ isDragReject: false
436
+ });
437
+
438
+ if (isEvtWithFiles(event) && onDragLeave) {
439
+ onDragLeave(event);
440
+ }
441
+ },
442
+ [rootRef, onDragLeave, noDragEventsBubbling]
443
+ );
444
+
445
+ const setFiles = useCallback(
446
+ (files: FileWithPath[], event: any) => {
447
+ const acceptedFiles: FileWithPath[] = [];
448
+ const fileRejections: FileRejection[] = [];
449
+
450
+ files.forEach(file => {
451
+ const [accepted, acceptError] = fileAccepted(file, acceptAttr);
452
+ const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
453
+ const customErrors = validator ? validator(file) : null;
454
+
455
+ if (accepted && sizeMatch && !customErrors) {
456
+ acceptedFiles.push(file);
457
+ } else {
458
+ let errors: Array<FileError | null> = [acceptError, sizeError];
459
+
460
+ if (customErrors) {
461
+ errors = errors.concat(customErrors);
462
+ }
463
+
464
+ fileRejections.push({file, errors: errors.filter((e): e is FileError => e != null)});
465
+ }
466
+ });
467
+
468
+ if ((!multiple && acceptedFiles.length > 1) || (multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles)) {
469
+ // Reject everything and empty accepted files
470
+ acceptedFiles.forEach(file => {
471
+ fileRejections.push({file, errors: [TOO_MANY_FILES_REJECTION]});
472
+ });
473
+ acceptedFiles.splice(0);
474
+ }
475
+
476
+ dispatch({
477
+ acceptedFiles,
478
+ fileRejections,
479
+ type: "setFiles"
480
+ });
481
+
482
+ if (onDrop) {
483
+ onDrop(acceptedFiles, fileRejections, event);
484
+ }
485
+
486
+ if (fileRejections.length > 0 && onDropRejected) {
487
+ onDropRejected(fileRejections, event);
488
+ }
489
+
490
+ if (acceptedFiles.length > 0 && onDropAccepted) {
491
+ onDropAccepted(acceptedFiles, event);
492
+ }
493
+ },
494
+ [dispatch, multiple, acceptAttr, minSize, maxSize, maxFiles, onDrop, onDropAccepted, onDropRejected, validator]
495
+ );
496
+
497
+ const onDropCb = useCallback(
498
+ (event: any) => {
499
+ event.preventDefault();
500
+ // Persist here because we need the event later after getFilesFromEvent() is done
501
+ event.persist?.();
502
+ stopPropagation(event);
503
+
504
+ dragTargetsRef.current = [];
505
+
506
+ if (isEvtWithFiles(event)) {
507
+ Promise.resolve(getFilesFromEvent(event))
508
+ .then(files => {
509
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
510
+ return;
511
+ }
512
+ setFiles(files as FileWithPath[], event);
513
+ })
514
+ .catch(e => onErrCb(e));
515
+ }
516
+ dispatch({type: "reset"});
517
+ },
518
+ [getFilesFromEvent, setFiles, onErrCb, noDragEventsBubbling]
519
+ );
520
+
521
+ // Fn for opening the file dialog programmatically
522
+ const openFileDialog = useCallback(() => {
523
+ // No point to use FS access APIs if context is not secure
524
+ // https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#feature_detection
525
+ if (fsAccessApiWorksRef.current) {
526
+ dispatch({type: "openDialog"});
527
+ onFileDialogOpenCb();
528
+ // https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
529
+ const opts = {
530
+ multiple,
531
+ types: pickerTypes
532
+ };
533
+ (window as any)
534
+ .showOpenFilePicker(opts)
535
+ .then((handles: any) => getFilesFromEvent(handles))
536
+ .then((files: Array<File | DataTransferItem>) => {
537
+ setFiles(files as FileWithPath[], null);
538
+ dispatch({type: "closeDialog"});
539
+ })
540
+ .catch((e: any) => {
541
+ // AbortError means the user canceled
542
+ if (isAbort(e)) {
543
+ onFileDialogCancelCb(e);
544
+ dispatch({type: "closeDialog"});
545
+ } else if (isSecurityError(e)) {
546
+ fsAccessApiWorksRef.current = false;
547
+ // CORS, so cannot use this API
548
+ // Try using the input
549
+ if (inputRef.current) {
550
+ inputRef.current.value = "";
551
+ inputRef.current.click();
552
+ } else {
553
+ onErrCb(
554
+ new Error(
555
+ "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."
556
+ )
557
+ );
558
+ }
559
+ } else {
560
+ onErrCb(e);
561
+ }
562
+ });
563
+ return;
564
+ }
565
+
566
+ if (inputRef.current) {
567
+ dispatch({type: "openDialog"});
568
+ onFileDialogOpenCb();
569
+ inputRef.current.value = "";
570
+ inputRef.current.click();
571
+ }
572
+ }, [dispatch, onFileDialogOpenCb, onFileDialogCancelCb, useFsAccessApi, setFiles, onErrCb, pickerTypes, multiple]);
573
+
574
+ // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
575
+ const onKeyDownCb = useCallback(
576
+ (event: any) => {
577
+ // Ignore keyboard events bubbling up the DOM tree
578
+ if (!rootRef.current?.isEqualNode(event.target)) {
579
+ return;
580
+ }
581
+
582
+ if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
583
+ event.preventDefault();
584
+ openFileDialog();
585
+ }
586
+ },
587
+ [rootRef, openFileDialog]
588
+ );
589
+
590
+ // Update focus state for the dropzone
591
+ const onFocusCb = useCallback(() => {
592
+ dispatch({type: "focus"});
593
+ }, []);
594
+ const onBlurCb = useCallback(() => {
595
+ dispatch({type: "blur"});
596
+ }, []);
597
+
598
+ // Cb to open the file dialog when click occurs on the dropzone
599
+ const onClickCb = useCallback(() => {
600
+ if (noClick) {
601
+ return;
602
+ }
603
+
604
+ // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
605
+ // to ensure React can handle state changes
606
+ // See: https://github.com/react-dropzone/react-dropzone/issues/450
607
+ if (isIeOrEdge()) {
608
+ setTimeout(openFileDialog, 0);
609
+ } else {
610
+ openFileDialog();
611
+ }
612
+ }, [noClick, openFileDialog]);
613
+
614
+ const composeHandler = (fn: any) => {
615
+ return disabled ? null : fn;
616
+ };
617
+
618
+ const composeKeyboardHandler = (fn: any) => {
619
+ return noKeyboard ? null : composeHandler(fn);
620
+ };
621
+
622
+ const composeDragHandler = (fn: any) => {
623
+ return noDrag ? null : composeHandler(fn);
624
+ };
625
+
626
+ const stopPropagation = (event: any) => {
627
+ if (noDragEventsBubbling) {
628
+ event.stopPropagation();
629
+ }
630
+ };
631
+
632
+ const getRootProps = useMemo(
633
+ () =>
634
+ ({
635
+ refKey = "ref",
636
+ role,
637
+ onKeyDown,
638
+ onFocus,
639
+ onBlur,
640
+ onClick,
641
+ onDragEnter,
642
+ onDragOver,
643
+ onDragLeave,
644
+ onDrop,
645
+ ...rest
646
+ }: DropzoneRootProps = {}) => ({
647
+ onKeyDown: composeKeyboardHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
648
+ onFocus: composeKeyboardHandler(composeEventHandlers(onFocus, onFocusCb)),
649
+ onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
650
+ onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
651
+ onDragEnter: composeDragHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
652
+ onDragOver: composeDragHandler(composeEventHandlers(onDragOver, onDragOverCb)),
653
+ onDragLeave: composeDragHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
654
+ onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
655
+ role: typeof role === "string" && role !== "" ? role : "presentation",
656
+ [refKey]: rootRef,
657
+ ...(!disabled && !noKeyboard ? {tabIndex: 0} : {}),
658
+ ...rest
659
+ }),
660
+ [
661
+ rootRef,
662
+ onKeyDownCb,
663
+ onFocusCb,
664
+ onBlurCb,
665
+ onClickCb,
666
+ onDragEnterCb,
667
+ onDragOverCb,
668
+ onDragLeaveCb,
669
+ onDropCb,
670
+ noKeyboard,
671
+ noDrag,
672
+ disabled
673
+ ]
674
+ );
675
+
676
+ const onInputElementClick = useCallback((event: any) => {
677
+ event.stopPropagation();
678
+ }, []);
679
+
680
+ const getInputProps = useMemo(
681
+ () =>
682
+ ({refKey = "ref", onChange, onClick, ...rest}: DropzoneInputProps = {}) => {
683
+ const inputProps = {
684
+ accept: acceptAttr,
685
+ multiple,
686
+ type: "file",
687
+ style: {
688
+ border: 0,
689
+ clip: "rect(0, 0, 0, 0)",
690
+ clipPath: "inset(50%)",
691
+ height: "1px",
692
+ margin: "0 -1px -1px 0",
693
+ overflow: "hidden",
694
+ padding: 0,
695
+ position: "absolute",
696
+ width: "1px",
697
+ whiteSpace: "nowrap"
698
+ },
699
+ onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
700
+ onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
701
+ tabIndex: -1,
702
+ [refKey]: inputRef
703
+ };
704
+
705
+ return {
706
+ ...inputProps,
707
+ ...rest
708
+ };
709
+ },
710
+ [inputRef, accept, multiple, onDropCb, disabled]
711
+ );
712
+
713
+ return {
714
+ ...state,
715
+ isFocused: isFocused && !disabled,
716
+ getRootProps,
717
+ getInputProps,
718
+ rootRef,
719
+ inputRef,
720
+ open: composeHandler(openFileDialog)
721
+ } as unknown as DropzoneState;
722
+ }
723
+
724
+ function reducer(state: DropzoneInternalState, action: any): DropzoneInternalState {
725
+ switch (action.type) {
726
+ case "focus":
727
+ return {
728
+ ...state,
729
+ isFocused: true
730
+ };
731
+ case "blur":
732
+ return {
733
+ ...state,
734
+ isFocused: false
735
+ };
736
+ case "openDialog":
737
+ return {
738
+ ...initialState,
739
+ isFileDialogActive: true
740
+ };
741
+ case "closeDialog":
742
+ return {
743
+ ...state,
744
+ isFileDialogActive: false
745
+ };
746
+ case "setDraggedFiles":
747
+ return {
748
+ ...state,
749
+ isDragActive: action.isDragActive,
750
+ isDragAccept: action.isDragAccept,
751
+ isDragReject: action.isDragReject
752
+ };
753
+ case "setFiles":
754
+ return {
755
+ ...state,
756
+ acceptedFiles: action.acceptedFiles,
757
+ fileRejections: action.fileRejections,
758
+ isDragReject: false
759
+ };
760
+ case "setDragGlobal":
761
+ return {
762
+ ...state,
763
+ isDragGlobal: action.isDragGlobal
764
+ };
765
+ case "reset":
766
+ return {
767
+ ...initialState
768
+ };
769
+ default:
770
+ return state;
771
+ }
772
+ }
773
+
774
+ function noop() {}