react-dropzone 16.0.0 → 18.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 +130 -113
- 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 +36 -39
- 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/dist/index.js
CHANGED
|
@@ -1,44 +1,40 @@
|
|
|
1
|
-
import { Fragment, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from "react";
|
|
2
|
-
import PropTypes from "prop-types";
|
|
3
1
|
import { fromEvent } from "file-selector";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from "react";
|
|
3
|
+
import attrAccept from "attr-accept";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/utils/index.ts
|
|
6
|
+
const accepts = typeof attrAccept === "function" ? attrAccept : attrAccept.default;
|
|
8
7
|
const FILE_INVALID_TYPE = "file-invalid-type";
|
|
9
8
|
const FILE_TOO_LARGE = "file-too-large";
|
|
10
9
|
const FILE_TOO_SMALL = "file-too-small";
|
|
11
10
|
const TOO_MANY_FILES = "too-many-files";
|
|
12
|
-
|
|
13
|
-
FileInvalidType
|
|
14
|
-
FileTooLarge
|
|
15
|
-
FileTooSmall
|
|
16
|
-
TooManyFiles
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param {string} accept
|
|
21
|
-
*/
|
|
22
|
-
const getInvalidTypeRejectionErr = (accept = "") => {
|
|
11
|
+
let ErrorCode = /* @__PURE__ */ function(ErrorCode) {
|
|
12
|
+
ErrorCode["FileInvalidType"] = "file-invalid-type";
|
|
13
|
+
ErrorCode["FileTooLarge"] = "file-too-large";
|
|
14
|
+
ErrorCode["FileTooSmall"] = "file-too-small";
|
|
15
|
+
ErrorCode["TooManyFiles"] = "too-many-files";
|
|
16
|
+
return ErrorCode;
|
|
17
|
+
}({});
|
|
18
|
+
function getInvalidTypeRejectionErr(accept = "") {
|
|
23
19
|
const acceptArr = accept.split(",");
|
|
24
20
|
const msg = acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
|
|
25
21
|
return {
|
|
26
22
|
code: FILE_INVALID_TYPE,
|
|
27
23
|
message: `File type must be ${msg}`
|
|
28
24
|
};
|
|
29
|
-
}
|
|
30
|
-
|
|
25
|
+
}
|
|
26
|
+
function getTooLargeRejectionErr(maxSize) {
|
|
31
27
|
return {
|
|
32
28
|
code: FILE_TOO_LARGE,
|
|
33
29
|
message: `File is larger than ${maxSize} ${maxSize === 1 ? "byte" : "bytes"}`
|
|
34
30
|
};
|
|
35
|
-
}
|
|
36
|
-
|
|
31
|
+
}
|
|
32
|
+
function getTooSmallRejectionErr(minSize) {
|
|
37
33
|
return {
|
|
38
34
|
code: FILE_TOO_SMALL,
|
|
39
35
|
message: `File is smaller than ${minSize} ${minSize === 1 ? "byte" : "bytes"}`
|
|
40
36
|
};
|
|
41
|
-
}
|
|
37
|
+
}
|
|
42
38
|
const TOO_MANY_FILES_REJECTION = {
|
|
43
39
|
code: TOO_MANY_FILES,
|
|
44
40
|
message: "Too many files"
|
|
@@ -49,16 +45,6 @@ const TOO_MANY_FILES_REJECTION = {
|
|
|
49
45
|
* During drag events, browsers may return DataTransferItem objects instead of File objects.
|
|
50
46
|
* Some browsers (e.g., Chrome) return an empty MIME type for certain file types (like .md files)
|
|
51
47
|
* on DataTransferItem during drag events, even though the type is correctly set during drop.
|
|
52
|
-
*
|
|
53
|
-
* This function detects such cases by checking for:
|
|
54
|
-
* 1. Empty type string
|
|
55
|
-
* 2. Presence of getAsFile method (indicates it's a DataTransferItem, not a File)
|
|
56
|
-
*
|
|
57
|
-
* We accept these during drag to provide proper UI feedback, while maintaining
|
|
58
|
-
* strict validation during drop when real File objects are available.
|
|
59
|
-
*
|
|
60
|
-
* @param {File | DataTransferItem} file
|
|
61
|
-
* @returns {boolean}
|
|
62
48
|
*/
|
|
63
49
|
function isDataTransferItemWithEmptyType(file) {
|
|
64
50
|
return file.type === "" && typeof file.getAsFile === "function";
|
|
@@ -71,13 +57,9 @@ function isDataTransferItemWithEmptyType(file) {
|
|
|
71
57
|
*
|
|
72
58
|
* Chrome/other browsers may return an empty MIME type for files during drag events,
|
|
73
59
|
* so we accept those as well (we'll validate properly on drop).
|
|
74
|
-
*
|
|
75
|
-
* @param {File} file
|
|
76
|
-
* @param {string} accept
|
|
77
|
-
* @returns
|
|
78
60
|
*/
|
|
79
61
|
function fileAccepted(file, accept) {
|
|
80
|
-
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept) || isDataTransferItemWithEmptyType(file);
|
|
62
|
+
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept ?? "") || isDataTransferItemWithEmptyType(file);
|
|
81
63
|
return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
|
|
82
64
|
}
|
|
83
65
|
function fileMatchSize(file, minSize, maxSize) {
|
|
@@ -93,19 +75,7 @@ function fileMatchSize(file, minSize, maxSize) {
|
|
|
93
75
|
function isDefined(value) {
|
|
94
76
|
return value !== void 0 && value !== null;
|
|
95
77
|
}
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
* @param {object} options
|
|
99
|
-
* @param {File[]} options.files
|
|
100
|
-
* @param {string} [options.accept]
|
|
101
|
-
* @param {number} [options.minSize]
|
|
102
|
-
* @param {number} [options.maxSize]
|
|
103
|
-
* @param {boolean} [options.multiple]
|
|
104
|
-
* @param {number} [options.maxFiles]
|
|
105
|
-
* @param {(f: File) => FileError|FileError[]|null} [options.validator]
|
|
106
|
-
* @returns
|
|
107
|
-
*/
|
|
108
|
-
function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles, validator }) {
|
|
78
|
+
function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles = 0, validator }) {
|
|
109
79
|
if (!multiple && files.length > 1 || multiple && maxFiles >= 1 && files.length > maxFiles) return false;
|
|
110
80
|
return files.every((file) => {
|
|
111
81
|
const [accepted] = fileAccepted(file, accept);
|
|
@@ -136,14 +106,11 @@ function isIeOrEdge(userAgent = window.navigator.userAgent) {
|
|
|
136
106
|
return isIe(userAgent) || isEdge(userAgent);
|
|
137
107
|
}
|
|
138
108
|
/**
|
|
139
|
-
* This is intended to be used to compose event handlers
|
|
109
|
+
* This is intended to be used to compose event handlers.
|
|
140
110
|
* They are executed in order until one of them calls `event.isPropagationStopped()`.
|
|
141
111
|
* Note that the check is done on the first invoke too,
|
|
142
112
|
* meaning that if propagation was stopped before invoking the fns,
|
|
143
113
|
* no handlers will be executed.
|
|
144
|
-
*
|
|
145
|
-
* @param {Function} fns the event hanlder functions
|
|
146
|
-
* @return {Function} the event handler to add to an element
|
|
147
114
|
*/
|
|
148
115
|
function composeEventHandlers(...fns) {
|
|
149
116
|
return (event, ...args) => fns.some((fn) => {
|
|
@@ -152,19 +119,13 @@ function composeEventHandlers(...fns) {
|
|
|
152
119
|
});
|
|
153
120
|
}
|
|
154
121
|
/**
|
|
155
|
-
* canUseFileSystemAccessAPI checks if the
|
|
156
|
-
* is supported by the browser.
|
|
157
|
-
* @returns {boolean}
|
|
122
|
+
* canUseFileSystemAccessAPI checks if the File System Access API is supported by the browser.
|
|
158
123
|
*/
|
|
159
124
|
function canUseFileSystemAccessAPI() {
|
|
160
125
|
return "showOpenFilePicker" in window;
|
|
161
126
|
}
|
|
162
127
|
/**
|
|
163
|
-
* Convert the `{accept}` dropzone prop to the
|
|
164
|
-
* `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
|
|
165
|
-
*
|
|
166
|
-
* @param {AcceptProp} accept
|
|
167
|
-
* @returns {{accept: string[]}[]}
|
|
128
|
+
* Convert the `{accept}` dropzone prop to the `{types}` option for showOpenFilePicker.
|
|
168
129
|
*/
|
|
169
130
|
function pickerOptionsFromAccept(accept) {
|
|
170
131
|
if (isDefined(accept)) return [{
|
|
@@ -180,75 +141,47 @@ function pickerOptionsFromAccept(accept) {
|
|
|
180
141
|
ok = false;
|
|
181
142
|
}
|
|
182
143
|
return ok;
|
|
183
|
-
}).reduce((agg, [mimeType, ext]) =>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
144
|
+
}).reduce((agg, [mimeType, ext]) => {
|
|
145
|
+
agg[mimeType] = ext;
|
|
146
|
+
return agg;
|
|
147
|
+
}, {})
|
|
187
148
|
}];
|
|
188
|
-
return accept;
|
|
189
149
|
}
|
|
190
150
|
/**
|
|
191
151
|
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
|
|
192
|
-
* @param {AcceptProp} accept
|
|
193
|
-
* @returns {string}
|
|
194
152
|
*/
|
|
195
153
|
function acceptPropAsAcceptAttr(accept) {
|
|
196
|
-
if (isDefined(accept)) return Object.entries(accept).reduce((a, [mimeType, ext]) =>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
], []).filter((v) => isMIMEType(v) || isExt(v)).join(",");
|
|
154
|
+
if (isDefined(accept)) return Object.entries(accept).reduce((a, [mimeType, ext]) => {
|
|
155
|
+
a.push(mimeType, ...ext);
|
|
156
|
+
return a;
|
|
157
|
+
}, []).filter((v) => isMIMEType(v) || isExt(v)).join(",");
|
|
201
158
|
}
|
|
202
159
|
/**
|
|
203
160
|
* Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
|
|
204
|
-
*
|
|
205
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
|
|
206
|
-
* @param {any} v
|
|
207
|
-
* @returns {boolean} True if v is an abort exception.
|
|
208
161
|
*/
|
|
209
162
|
function isAbort(v) {
|
|
210
163
|
return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
|
|
211
164
|
}
|
|
212
165
|
/**
|
|
213
166
|
* Check if v is a security error.
|
|
214
|
-
*
|
|
215
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
|
|
216
|
-
* @param {any} v
|
|
217
|
-
* @returns {boolean} True if v is a security error.
|
|
218
167
|
*/
|
|
219
168
|
function isSecurityError(v) {
|
|
220
169
|
return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
|
|
221
170
|
}
|
|
222
171
|
/**
|
|
223
172
|
* Check if v is a MIME type string.
|
|
224
|
-
*
|
|
225
|
-
* See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
|
|
226
|
-
*
|
|
227
|
-
* @param {string} v
|
|
228
173
|
*/
|
|
229
174
|
function isMIMEType(v) {
|
|
230
175
|
return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || v === "application/*" || /\w+\/[-+.\w]+/g.test(v);
|
|
231
176
|
}
|
|
232
177
|
/**
|
|
233
178
|
* Check if v is a file extension.
|
|
234
|
-
* @param {string} v
|
|
235
179
|
*/
|
|
236
180
|
function isExt(v) {
|
|
237
181
|
return /^.*\.[\w]+$/.test(v);
|
|
238
182
|
}
|
|
239
|
-
/**
|
|
240
|
-
* @typedef {Object.<string, string[]>} AcceptProp
|
|
241
|
-
*/
|
|
242
|
-
/**
|
|
243
|
-
* @typedef {object} FileError
|
|
244
|
-
* @property {string} message
|
|
245
|
-
* @property {ErrorCode|string} code
|
|
246
|
-
*/
|
|
247
|
-
/**
|
|
248
|
-
* @typedef {"file-invalid-type"|"file-too-large"|"file-too-small"|"too-many-files"} ErrorCode
|
|
249
|
-
*/
|
|
250
183
|
//#endregion
|
|
251
|
-
//#region src/index.
|
|
184
|
+
//#region src/index.tsx
|
|
252
185
|
/**
|
|
253
186
|
* Convenience wrapper component for the `useDropzone` hook
|
|
254
187
|
*
|
|
@@ -266,261 +199,12 @@ function isExt(v) {
|
|
|
266
199
|
const Dropzone = forwardRef(({ children, ...params }, ref) => {
|
|
267
200
|
const { open, ...props } = useDropzone(params);
|
|
268
201
|
useImperativeHandle(ref, () => ({ open }), [open]);
|
|
269
|
-
return /* @__PURE__ */ jsx(Fragment, { children: children({
|
|
202
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children?.({
|
|
270
203
|
...props,
|
|
271
204
|
open
|
|
272
205
|
}) });
|
|
273
206
|
});
|
|
274
207
|
Dropzone.displayName = "Dropzone";
|
|
275
|
-
const defaultProps = {
|
|
276
|
-
disabled: false,
|
|
277
|
-
getFilesFromEvent: fromEvent,
|
|
278
|
-
maxSize: Infinity,
|
|
279
|
-
minSize: 0,
|
|
280
|
-
multiple: true,
|
|
281
|
-
maxFiles: 0,
|
|
282
|
-
preventDropOnDocument: true,
|
|
283
|
-
noClick: false,
|
|
284
|
-
noKeyboard: false,
|
|
285
|
-
noDrag: false,
|
|
286
|
-
noDragEventsBubbling: false,
|
|
287
|
-
validator: null,
|
|
288
|
-
useFsAccessApi: false,
|
|
289
|
-
autoFocus: false
|
|
290
|
-
};
|
|
291
|
-
Dropzone.defaultProps = defaultProps;
|
|
292
|
-
Dropzone.propTypes = {
|
|
293
|
-
/**
|
|
294
|
-
* Render function that exposes the dropzone state and prop getter fns
|
|
295
|
-
*
|
|
296
|
-
* @param {object} params
|
|
297
|
-
* @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
|
|
298
|
-
* @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
|
|
299
|
-
* @param {Function} params.open Open the native file selection dialog
|
|
300
|
-
* @param {boolean} params.isFocused Dropzone area is in focus
|
|
301
|
-
* @param {boolean} params.isFileDialogActive File dialog is opened
|
|
302
|
-
* @param {boolean} params.isDragActive Active drag is in progress
|
|
303
|
-
* @param {boolean} params.isDragAccept Dragged files are accepted
|
|
304
|
-
* @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.
|
|
305
|
-
* @param {boolean} params.isDragGlobal Files are being dragged anywhere on the document
|
|
306
|
-
* @param {File[]} params.acceptedFiles Accepted files
|
|
307
|
-
* @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.
|
|
308
|
-
*/
|
|
309
|
-
children: PropTypes.func,
|
|
310
|
-
/**
|
|
311
|
-
* Set accepted file types.
|
|
312
|
-
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
|
|
313
|
-
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
|
|
314
|
-
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
|
|
315
|
-
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
|
|
316
|
-
*/
|
|
317
|
-
accept: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
|
|
318
|
-
/**
|
|
319
|
-
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
|
|
320
|
-
*/
|
|
321
|
-
multiple: PropTypes.bool,
|
|
322
|
-
/**
|
|
323
|
-
* If false, allow dropped items to take over the current browser window
|
|
324
|
-
*/
|
|
325
|
-
preventDropOnDocument: PropTypes.bool,
|
|
326
|
-
/**
|
|
327
|
-
* If true, disables click to open the native file selection dialog
|
|
328
|
-
*/
|
|
329
|
-
noClick: PropTypes.bool,
|
|
330
|
-
/**
|
|
331
|
-
* If true, disables SPACE/ENTER to open the native file selection dialog.
|
|
332
|
-
* Note that it also stops tracking the focus state.
|
|
333
|
-
*/
|
|
334
|
-
noKeyboard: PropTypes.bool,
|
|
335
|
-
/**
|
|
336
|
-
* If true, disables drag 'n' drop
|
|
337
|
-
*/
|
|
338
|
-
noDrag: PropTypes.bool,
|
|
339
|
-
/**
|
|
340
|
-
* If true, stops drag event propagation to parents
|
|
341
|
-
*/
|
|
342
|
-
noDragEventsBubbling: PropTypes.bool,
|
|
343
|
-
/**
|
|
344
|
-
* Minimum file size (in bytes)
|
|
345
|
-
*/
|
|
346
|
-
minSize: PropTypes.number,
|
|
347
|
-
/**
|
|
348
|
-
* Maximum file size (in bytes)
|
|
349
|
-
*/
|
|
350
|
-
maxSize: PropTypes.number,
|
|
351
|
-
/**
|
|
352
|
-
* Maximum accepted number of files
|
|
353
|
-
* The default value is 0 which means there is no limitation to how many files are accepted.
|
|
354
|
-
*/
|
|
355
|
-
maxFiles: PropTypes.number,
|
|
356
|
-
/**
|
|
357
|
-
* Enable/disable the dropzone
|
|
358
|
-
*/
|
|
359
|
-
disabled: PropTypes.bool,
|
|
360
|
-
/**
|
|
361
|
-
* Use this to provide a custom file aggregator
|
|
362
|
-
*
|
|
363
|
-
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
|
|
364
|
-
*/
|
|
365
|
-
getFilesFromEvent: PropTypes.func,
|
|
366
|
-
/**
|
|
367
|
-
* Cb for when closing the file dialog with no selection
|
|
368
|
-
*/
|
|
369
|
-
onFileDialogCancel: PropTypes.func,
|
|
370
|
-
/**
|
|
371
|
-
* Cb for when opening the file dialog
|
|
372
|
-
*/
|
|
373
|
-
onFileDialogOpen: PropTypes.func,
|
|
374
|
-
/**
|
|
375
|
-
* Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
376
|
-
* to open the file picker instead of using an `<input type="file">` click event.
|
|
377
|
-
*/
|
|
378
|
-
useFsAccessApi: PropTypes.bool,
|
|
379
|
-
/**
|
|
380
|
-
* Set to true to focus the root element on render
|
|
381
|
-
*/
|
|
382
|
-
autoFocus: PropTypes.bool,
|
|
383
|
-
/**
|
|
384
|
-
* Cb for when the `dragenter` event occurs.
|
|
385
|
-
*
|
|
386
|
-
* @param {DragEvent} event
|
|
387
|
-
*/
|
|
388
|
-
onDragEnter: PropTypes.func,
|
|
389
|
-
/**
|
|
390
|
-
* Cb for when the `dragleave` event occurs
|
|
391
|
-
*
|
|
392
|
-
* @param {DragEvent} event
|
|
393
|
-
*/
|
|
394
|
-
onDragLeave: PropTypes.func,
|
|
395
|
-
/**
|
|
396
|
-
* Cb for when the `dragover` event occurs
|
|
397
|
-
*
|
|
398
|
-
* @param {DragEvent} event
|
|
399
|
-
*/
|
|
400
|
-
onDragOver: PropTypes.func,
|
|
401
|
-
/**
|
|
402
|
-
* Cb for when the `drop` event occurs.
|
|
403
|
-
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
|
|
404
|
-
*
|
|
405
|
-
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
|
|
406
|
-
* `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.
|
|
407
|
-
* If `multiple` is set to false and additional files are dropped,
|
|
408
|
-
* all files besides the first will be rejected.
|
|
409
|
-
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
|
|
410
|
-
*
|
|
411
|
-
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
|
|
412
|
-
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
|
|
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
|
-
*
|
|
427
|
-
* @param {File[]} acceptedFiles
|
|
428
|
-
* @param {FileRejection[]} fileRejections
|
|
429
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
430
|
-
*/
|
|
431
|
-
onDrop: PropTypes.func,
|
|
432
|
-
/**
|
|
433
|
-
* Cb for when the `drop` event occurs.
|
|
434
|
-
* Note that if no files are accepted, this callback is not invoked.
|
|
435
|
-
*
|
|
436
|
-
* @param {File[]} files
|
|
437
|
-
* @param {(DragEvent|Event)} event
|
|
438
|
-
*/
|
|
439
|
-
onDropAccepted: PropTypes.func,
|
|
440
|
-
/**
|
|
441
|
-
* Cb for when the `drop` event occurs.
|
|
442
|
-
* Note that if no files are rejected, this callback is not invoked.
|
|
443
|
-
*
|
|
444
|
-
* @param {FileRejection[]} fileRejections
|
|
445
|
-
* @param {(DragEvent|Event)} event
|
|
446
|
-
*/
|
|
447
|
-
onDropRejected: PropTypes.func,
|
|
448
|
-
/**
|
|
449
|
-
* Cb for when there's some error from any of the promises.
|
|
450
|
-
*
|
|
451
|
-
* @param {Error} error
|
|
452
|
-
*/
|
|
453
|
-
onError: PropTypes.func,
|
|
454
|
-
/**
|
|
455
|
-
* Custom validation function. It must return null if there's no errors.
|
|
456
|
-
* @param {File} file
|
|
457
|
-
* @returns {FileError|FileError[]|null}
|
|
458
|
-
*/
|
|
459
|
-
validator: PropTypes.func
|
|
460
|
-
};
|
|
461
|
-
/**
|
|
462
|
-
* A function that is invoked for the `dragenter`,
|
|
463
|
-
* `dragover` and `dragleave` events.
|
|
464
|
-
* It is not invoked if the items are not files (such as link, text, etc.).
|
|
465
|
-
*
|
|
466
|
-
* @callback dragCb
|
|
467
|
-
* @param {DragEvent} event
|
|
468
|
-
*/
|
|
469
|
-
/**
|
|
470
|
-
* A function that is invoked for the `drop` or input change event.
|
|
471
|
-
* It is not invoked if the items are not files (such as link, text, etc.).
|
|
472
|
-
*
|
|
473
|
-
* @callback dropCb
|
|
474
|
-
* @param {File[]} acceptedFiles List of accepted files
|
|
475
|
-
* @param {FileRejection[]} fileRejections List of rejected files and why they were rejected. This is the authoritative source for post-drop file rejections.
|
|
476
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
477
|
-
*/
|
|
478
|
-
/**
|
|
479
|
-
* A function that is invoked for the `drop` or input change event.
|
|
480
|
-
* It is not invoked if the items are files (such as link, text, etc.).
|
|
481
|
-
*
|
|
482
|
-
* @callback dropAcceptedCb
|
|
483
|
-
* @param {File[]} files List of accepted files that meet the given criteria
|
|
484
|
-
* (`accept`, `multiple`, `minSize`, `maxSize`)
|
|
485
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
486
|
-
*/
|
|
487
|
-
/**
|
|
488
|
-
* A function that is invoked for the `drop` or input change event.
|
|
489
|
-
*
|
|
490
|
-
* @callback dropRejectedCb
|
|
491
|
-
* @param {File[]} files List of rejected files that do not meet the given criteria
|
|
492
|
-
* (`accept`, `multiple`, `minSize`, `maxSize`)
|
|
493
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
494
|
-
*/
|
|
495
|
-
/**
|
|
496
|
-
* A function that is used aggregate files,
|
|
497
|
-
* in a asynchronous fashion, from drag or input change events.
|
|
498
|
-
*
|
|
499
|
-
* @callback getFilesFromEvent
|
|
500
|
-
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
|
|
501
|
-
* @returns {(File[]|Promise<File[]>)}
|
|
502
|
-
*/
|
|
503
|
-
/**
|
|
504
|
-
* An object with the current dropzone state.
|
|
505
|
-
*
|
|
506
|
-
* @typedef {object} DropzoneState
|
|
507
|
-
* @property {boolean} isFocused Dropzone area is in focus
|
|
508
|
-
* @property {boolean} isFileDialogActive File dialog is opened
|
|
509
|
-
* @property {boolean} isDragActive Active drag is in progress
|
|
510
|
-
* @property {boolean} isDragAccept Dragged files are accepted
|
|
511
|
-
* @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.
|
|
512
|
-
* @property {boolean} isDragGlobal Files are being dragged anywhere on the document
|
|
513
|
-
* @property {File[]} acceptedFiles Accepted files
|
|
514
|
-
* @property {FileRejection[]} fileRejections Rejected files and why they were rejected. This persists after drop and is the source of truth for post-drop rejections.
|
|
515
|
-
*/
|
|
516
|
-
/**
|
|
517
|
-
* An object with the dropzone methods.
|
|
518
|
-
*
|
|
519
|
-
* @typedef {object} DropzoneMethods
|
|
520
|
-
* @property {Function} getRootProps Returns the props you should apply to the root drop container you render
|
|
521
|
-
* @property {Function} getInputProps Returns the props you should apply to hidden file input you render
|
|
522
|
-
* @property {Function} open Open the native file selection dialog
|
|
523
|
-
*/
|
|
524
208
|
const initialState = {
|
|
525
209
|
isFocused: false,
|
|
526
210
|
isFileDialogActive: false,
|
|
@@ -549,81 +233,13 @@ const initialState = {
|
|
|
549
233
|
* )
|
|
550
234
|
* }
|
|
551
235
|
* ```
|
|
552
|
-
*
|
|
553
|
-
* @function useDropzone
|
|
554
|
-
*
|
|
555
|
-
* @param {object} props
|
|
556
|
-
* @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
|
|
557
|
-
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
|
|
558
|
-
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
|
|
559
|
-
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
|
|
560
|
-
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
|
|
561
|
-
* @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
|
|
562
|
-
* @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
|
|
563
|
-
* @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
|
|
564
|
-
* @param {boolean} [props.noKeyboard=false] If true, disables SPACE/ENTER to open the native file selection dialog.
|
|
565
|
-
* Note that it also stops tracking the focus state.
|
|
566
|
-
* @param {boolean} [props.noDrag=false] If true, disables drag 'n' drop
|
|
567
|
-
* @param {boolean} [props.noDragEventsBubbling=false] If true, stops drag event propagation to parents
|
|
568
|
-
* @param {number} [props.minSize=0] Minimum file size (in bytes)
|
|
569
|
-
* @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
|
|
570
|
-
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
|
|
571
|
-
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
|
|
572
|
-
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
|
|
573
|
-
* @param {boolean} [props.useFsAccessApi] Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
574
|
-
* to open the file picker instead of using an `<input type="file">` click event.
|
|
575
|
-
* @param {boolean} autoFocus Set to true to auto focus the root element.
|
|
576
|
-
* @param {Function} [props.onFileDialogOpen] Cb for when opening the file dialog
|
|
577
|
-
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
|
|
578
|
-
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
|
|
579
|
-
* @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
|
|
580
|
-
* @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
|
|
581
|
-
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
|
|
582
|
-
*
|
|
583
|
-
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
|
|
584
|
-
* `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).
|
|
585
|
-
* If `multiple` is set to false and additional files are dropped,
|
|
586
|
-
* all files besides the first will be rejected.
|
|
587
|
-
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
|
|
588
|
-
*
|
|
589
|
-
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
|
|
590
|
-
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
|
|
591
|
-
*
|
|
592
|
-
* The second parameter (fileRejections) is the authoritative list of rejected files after a drop.
|
|
593
|
-
* Use this parameter or the fileRejections state property to handle post-drop file rejections,
|
|
594
|
-
* as isDragReject only indicates rejection state during active drag operations.
|
|
595
|
-
*
|
|
596
|
-
* `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.
|
|
597
|
-
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
|
|
598
|
-
*
|
|
599
|
-
* ```js
|
|
600
|
-
* function onDrop(acceptedFiles) {
|
|
601
|
-
* const req = request.post('/upload')
|
|
602
|
-
* acceptedFiles.forEach(file => {
|
|
603
|
-
* req.attach(file.name, file)
|
|
604
|
-
* })
|
|
605
|
-
* req.end(callback)
|
|
606
|
-
* }
|
|
607
|
-
* ```
|
|
608
|
-
* @param {dropAcceptedCb} [props.onDropAccepted]
|
|
609
|
-
* @param {dropRejectedCb} [props.onDropRejected]
|
|
610
|
-
* @param {(error: Error) => void} [props.onError]
|
|
611
|
-
*
|
|
612
|
-
* @returns {DropzoneState & DropzoneMethods}
|
|
613
236
|
*/
|
|
614
237
|
function useDropzone(props = {}) {
|
|
615
|
-
const { accept, disabled, getFilesFromEvent, maxSize, minSize, multiple, maxFiles, onDragEnter, onDragLeave, onDragOver, onDrop, onDropAccepted, onDropRejected, onFileDialogCancel, onFileDialogOpen, useFsAccessApi, autoFocus, preventDropOnDocument, noClick, noKeyboard, noDrag, noDragEventsBubbling, onError, validator } =
|
|
616
|
-
...defaultProps,
|
|
617
|
-
...props
|
|
618
|
-
};
|
|
238
|
+
const { accept, disabled = false, getFilesFromEvent = fromEvent, maxSize = Number.POSITIVE_INFINITY, minSize = 0, multiple = true, maxFiles = 0, onDragEnter, onDragLeave, onDragOver, onDrop, onDropAccepted, onDropRejected, onFileDialogCancel, onFileDialogOpen, useFsAccessApi = false, autoFocus = false, preventDropOnDocument = true, noClick = false, noKeyboard = false, noDrag = false, noDragEventsBubbling = false, onError, validator } = props;
|
|
619
239
|
const acceptAttr = useMemo(() => acceptPropAsAcceptAttr(accept), [accept]);
|
|
620
240
|
const pickerTypes = useMemo(() => pickerOptionsFromAccept(accept), [accept]);
|
|
621
241
|
const onFileDialogOpenCb = useMemo(() => typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop, [onFileDialogOpen]);
|
|
622
242
|
const onFileDialogCancelCb = useMemo(() => typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop, [onFileDialogCancel]);
|
|
623
|
-
/**
|
|
624
|
-
* @constant
|
|
625
|
-
* @type {React.MutableRefObject<HTMLElement>}
|
|
626
|
-
*/
|
|
627
243
|
const rootRef = useRef(null);
|
|
628
244
|
const inputRef = useRef(null);
|
|
629
245
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
@@ -633,7 +249,7 @@ function useDropzone(props = {}) {
|
|
|
633
249
|
if (!fsAccessApiWorksRef.current && isFileDialogActive) setTimeout(() => {
|
|
634
250
|
if (inputRef.current) {
|
|
635
251
|
const { files } = inputRef.current;
|
|
636
|
-
if (!files
|
|
252
|
+
if (!files?.length) {
|
|
637
253
|
dispatch({ type: "closeDialog" });
|
|
638
254
|
onFileDialogCancelCb();
|
|
639
255
|
}
|
|
@@ -654,7 +270,7 @@ function useDropzone(props = {}) {
|
|
|
654
270
|
const dragTargetsRef = useRef([]);
|
|
655
271
|
const globalDragTargetsRef = useRef([]);
|
|
656
272
|
const onDocumentDrop = (event) => {
|
|
657
|
-
if (rootRef.current && rootRef.current.contains(event.target)) return;
|
|
273
|
+
if (rootRef.current && event.target && rootRef.current.contains(event.target)) return;
|
|
658
274
|
event.preventDefault();
|
|
659
275
|
dragTargetsRef.current = [];
|
|
660
276
|
};
|
|
@@ -672,7 +288,7 @@ function useDropzone(props = {}) {
|
|
|
672
288
|
}, [rootRef, preventDropOnDocument]);
|
|
673
289
|
useEffect(() => {
|
|
674
290
|
const onDocumentDragEnter = (event) => {
|
|
675
|
-
globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
291
|
+
if (event.target) globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
676
292
|
if (isEvtWithFiles(event)) dispatch({
|
|
677
293
|
isDragGlobal: true,
|
|
678
294
|
type: "setDragGlobal"
|
|
@@ -725,7 +341,7 @@ function useDropzone(props = {}) {
|
|
|
725
341
|
}, [onError]);
|
|
726
342
|
const onDragEnterCb = useCallback((event) => {
|
|
727
343
|
event.preventDefault();
|
|
728
|
-
event.persist();
|
|
344
|
+
event.persist?.();
|
|
729
345
|
stopPropagation(event);
|
|
730
346
|
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
731
347
|
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
@@ -762,7 +378,7 @@ function useDropzone(props = {}) {
|
|
|
762
378
|
]);
|
|
763
379
|
const onDragOverCb = useCallback((event) => {
|
|
764
380
|
event.preventDefault();
|
|
765
|
-
event.persist();
|
|
381
|
+
event.persist?.();
|
|
766
382
|
stopPropagation(event);
|
|
767
383
|
const hasFiles = isEvtWithFiles(event);
|
|
768
384
|
if (hasFiles && event.dataTransfer) try {
|
|
@@ -773,9 +389,9 @@ function useDropzone(props = {}) {
|
|
|
773
389
|
}, [onDragOver, noDragEventsBubbling]);
|
|
774
390
|
const onDragLeaveCb = useCallback((event) => {
|
|
775
391
|
event.preventDefault();
|
|
776
|
-
event.persist();
|
|
392
|
+
event.persist?.();
|
|
777
393
|
stopPropagation(event);
|
|
778
|
-
const targets = dragTargetsRef.current.filter((target) => rootRef.current
|
|
394
|
+
const targets = dragTargetsRef.current.filter((target) => rootRef.current?.contains(target));
|
|
779
395
|
const targetIdx = targets.indexOf(event.target);
|
|
780
396
|
if (targetIdx !== -1) targets.splice(targetIdx, 1);
|
|
781
397
|
dragTargetsRef.current = targets;
|
|
@@ -805,7 +421,7 @@ function useDropzone(props = {}) {
|
|
|
805
421
|
if (customErrors) errors = errors.concat(customErrors);
|
|
806
422
|
fileRejections.push({
|
|
807
423
|
file,
|
|
808
|
-
errors: errors.filter((e) => e)
|
|
424
|
+
errors: errors.filter((e) => e != null)
|
|
809
425
|
});
|
|
810
426
|
}
|
|
811
427
|
});
|
|
@@ -840,7 +456,7 @@ function useDropzone(props = {}) {
|
|
|
840
456
|
]);
|
|
841
457
|
const onDropCb = useCallback((event) => {
|
|
842
458
|
event.preventDefault();
|
|
843
|
-
event.persist();
|
|
459
|
+
event.persist?.();
|
|
844
460
|
stopPropagation(event);
|
|
845
461
|
dragTargetsRef.current = [];
|
|
846
462
|
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
@@ -872,7 +488,7 @@ function useDropzone(props = {}) {
|
|
|
872
488
|
} else if (isSecurityError(e)) {
|
|
873
489
|
fsAccessApiWorksRef.current = false;
|
|
874
490
|
if (inputRef.current) {
|
|
875
|
-
inputRef.current.value =
|
|
491
|
+
inputRef.current.value = "";
|
|
876
492
|
inputRef.current.click();
|
|
877
493
|
} else onErrCb(/* @__PURE__ */ new Error("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."));
|
|
878
494
|
} else onErrCb(e);
|
|
@@ -882,7 +498,7 @@ function useDropzone(props = {}) {
|
|
|
882
498
|
if (inputRef.current) {
|
|
883
499
|
dispatch({ type: "openDialog" });
|
|
884
500
|
onFileDialogOpenCb();
|
|
885
|
-
inputRef.current.value =
|
|
501
|
+
inputRef.current.value = "";
|
|
886
502
|
inputRef.current.click();
|
|
887
503
|
}
|
|
888
504
|
}, [
|
|
@@ -896,7 +512,7 @@ function useDropzone(props = {}) {
|
|
|
896
512
|
multiple
|
|
897
513
|
]);
|
|
898
514
|
const onKeyDownCb = useCallback((event) => {
|
|
899
|
-
if (!rootRef.current
|
|
515
|
+
if (!rootRef.current?.isEqualNode(event.target)) return;
|
|
900
516
|
if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
|
|
901
517
|
event.preventDefault();
|
|
902
518
|
openFileDialog();
|
|
@@ -995,13 +611,7 @@ function useDropzone(props = {}) {
|
|
|
995
611
|
open: composeHandler(openFileDialog)
|
|
996
612
|
};
|
|
997
613
|
}
|
|
998
|
-
/**
|
|
999
|
-
* @param {DropzoneState} state
|
|
1000
|
-
* @param {{type: string} & DropzoneState} action
|
|
1001
|
-
* @returns {DropzoneState}
|
|
1002
|
-
*/
|
|
1003
614
|
function reducer(state, action) {
|
|
1004
|
-
/* istanbul ignore next */
|
|
1005
615
|
switch (action.type) {
|
|
1006
616
|
case "focus": return {
|
|
1007
617
|
...state,
|