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/README.md +127 -112
- package/dist/index.cjs +43 -435
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +46 -436
- package/dist/index.js.map +1 -1
- package/package.json +33 -35
- package/src/index.tsx +774 -0
- package/src/utils/index.ts +305 -0
- package/src/index.jsx +0 -1103
- package/src/utils/index.js +0 -362
- package/typings/react-dropzone.d.ts +0 -102
- package/typings/tests/accept.tsx +0 -54
- package/typings/tests/all.tsx +0 -46
- package/typings/tests/basic.tsx +0 -53
- package/typings/tests/events.tsx +0 -31
- package/typings/tests/file-dialog.tsx +0 -20
- package/typings/tests/hook.tsx +0 -15
- package/typings/tests/plugin.tsx +0 -87
- package/typings/tests/refs.tsx +0 -18
- package/typings/tests/tsconfig.json +0 -23
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";
|