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.cjs
CHANGED
|
@@ -24,50 +24,44 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
let react = require("react");
|
|
28
|
-
react = __toESM(react, 1);
|
|
29
|
-
let prop_types = require("prop-types");
|
|
30
|
-
prop_types = __toESM(prop_types, 1);
|
|
31
27
|
let file_selector = require("file-selector");
|
|
28
|
+
let react = require("react");
|
|
32
29
|
let attr_accept = require("attr-accept");
|
|
33
30
|
attr_accept = __toESM(attr_accept, 1);
|
|
34
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
-
//#region src/utils/index.
|
|
32
|
+
//#region src/utils/index.ts
|
|
36
33
|
const accepts = typeof attr_accept.default === "function" ? attr_accept.default : attr_accept.default.default;
|
|
37
34
|
const FILE_INVALID_TYPE = "file-invalid-type";
|
|
38
35
|
const FILE_TOO_LARGE = "file-too-large";
|
|
39
36
|
const FILE_TOO_SMALL = "file-too-small";
|
|
40
37
|
const TOO_MANY_FILES = "too-many-files";
|
|
41
|
-
|
|
42
|
-
FileInvalidType
|
|
43
|
-
FileTooLarge
|
|
44
|
-
FileTooSmall
|
|
45
|
-
TooManyFiles
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* @param {string} accept
|
|
50
|
-
*/
|
|
51
|
-
const getInvalidTypeRejectionErr = (accept = "") => {
|
|
38
|
+
let ErrorCode = /* @__PURE__ */ function(ErrorCode) {
|
|
39
|
+
ErrorCode["FileInvalidType"] = "file-invalid-type";
|
|
40
|
+
ErrorCode["FileTooLarge"] = "file-too-large";
|
|
41
|
+
ErrorCode["FileTooSmall"] = "file-too-small";
|
|
42
|
+
ErrorCode["TooManyFiles"] = "too-many-files";
|
|
43
|
+
return ErrorCode;
|
|
44
|
+
}({});
|
|
45
|
+
function getInvalidTypeRejectionErr(accept = "") {
|
|
52
46
|
const acceptArr = accept.split(",");
|
|
53
47
|
const msg = acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
|
|
54
48
|
return {
|
|
55
49
|
code: FILE_INVALID_TYPE,
|
|
56
50
|
message: `File type must be ${msg}`
|
|
57
51
|
};
|
|
58
|
-
}
|
|
59
|
-
|
|
52
|
+
}
|
|
53
|
+
function getTooLargeRejectionErr(maxSize) {
|
|
60
54
|
return {
|
|
61
55
|
code: FILE_TOO_LARGE,
|
|
62
56
|
message: `File is larger than ${maxSize} ${maxSize === 1 ? "byte" : "bytes"}`
|
|
63
57
|
};
|
|
64
|
-
}
|
|
65
|
-
|
|
58
|
+
}
|
|
59
|
+
function getTooSmallRejectionErr(minSize) {
|
|
66
60
|
return {
|
|
67
61
|
code: FILE_TOO_SMALL,
|
|
68
62
|
message: `File is smaller than ${minSize} ${minSize === 1 ? "byte" : "bytes"}`
|
|
69
63
|
};
|
|
70
|
-
}
|
|
64
|
+
}
|
|
71
65
|
const TOO_MANY_FILES_REJECTION = {
|
|
72
66
|
code: TOO_MANY_FILES,
|
|
73
67
|
message: "Too many files"
|
|
@@ -78,16 +72,6 @@ const TOO_MANY_FILES_REJECTION = {
|
|
|
78
72
|
* During drag events, browsers may return DataTransferItem objects instead of File objects.
|
|
79
73
|
* Some browsers (e.g., Chrome) return an empty MIME type for certain file types (like .md files)
|
|
80
74
|
* on DataTransferItem during drag events, even though the type is correctly set during drop.
|
|
81
|
-
*
|
|
82
|
-
* This function detects such cases by checking for:
|
|
83
|
-
* 1. Empty type string
|
|
84
|
-
* 2. Presence of getAsFile method (indicates it's a DataTransferItem, not a File)
|
|
85
|
-
*
|
|
86
|
-
* We accept these during drag to provide proper UI feedback, while maintaining
|
|
87
|
-
* strict validation during drop when real File objects are available.
|
|
88
|
-
*
|
|
89
|
-
* @param {File | DataTransferItem} file
|
|
90
|
-
* @returns {boolean}
|
|
91
75
|
*/
|
|
92
76
|
function isDataTransferItemWithEmptyType(file) {
|
|
93
77
|
return file.type === "" && typeof file.getAsFile === "function";
|
|
@@ -100,13 +84,9 @@ function isDataTransferItemWithEmptyType(file) {
|
|
|
100
84
|
*
|
|
101
85
|
* Chrome/other browsers may return an empty MIME type for files during drag events,
|
|
102
86
|
* so we accept those as well (we'll validate properly on drop).
|
|
103
|
-
*
|
|
104
|
-
* @param {File} file
|
|
105
|
-
* @param {string} accept
|
|
106
|
-
* @returns
|
|
107
87
|
*/
|
|
108
88
|
function fileAccepted(file, accept) {
|
|
109
|
-
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept) || isDataTransferItemWithEmptyType(file);
|
|
89
|
+
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept ?? "") || isDataTransferItemWithEmptyType(file);
|
|
110
90
|
return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
|
|
111
91
|
}
|
|
112
92
|
function fileMatchSize(file, minSize, maxSize) {
|
|
@@ -122,19 +102,7 @@ function fileMatchSize(file, minSize, maxSize) {
|
|
|
122
102
|
function isDefined(value) {
|
|
123
103
|
return value !== void 0 && value !== null;
|
|
124
104
|
}
|
|
125
|
-
|
|
126
|
-
*
|
|
127
|
-
* @param {object} options
|
|
128
|
-
* @param {File[]} options.files
|
|
129
|
-
* @param {string} [options.accept]
|
|
130
|
-
* @param {number} [options.minSize]
|
|
131
|
-
* @param {number} [options.maxSize]
|
|
132
|
-
* @param {boolean} [options.multiple]
|
|
133
|
-
* @param {number} [options.maxFiles]
|
|
134
|
-
* @param {(f: File) => FileError|FileError[]|null} [options.validator]
|
|
135
|
-
* @returns
|
|
136
|
-
*/
|
|
137
|
-
function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles, validator }) {
|
|
105
|
+
function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles = 0, validator }) {
|
|
138
106
|
if (!multiple && files.length > 1 || multiple && maxFiles >= 1 && files.length > maxFiles) return false;
|
|
139
107
|
return files.every((file) => {
|
|
140
108
|
const [accepted] = fileAccepted(file, accept);
|
|
@@ -165,14 +133,11 @@ function isIeOrEdge(userAgent = window.navigator.userAgent) {
|
|
|
165
133
|
return isIe(userAgent) || isEdge(userAgent);
|
|
166
134
|
}
|
|
167
135
|
/**
|
|
168
|
-
* This is intended to be used to compose event handlers
|
|
136
|
+
* This is intended to be used to compose event handlers.
|
|
169
137
|
* They are executed in order until one of them calls `event.isPropagationStopped()`.
|
|
170
138
|
* Note that the check is done on the first invoke too,
|
|
171
139
|
* meaning that if propagation was stopped before invoking the fns,
|
|
172
140
|
* no handlers will be executed.
|
|
173
|
-
*
|
|
174
|
-
* @param {Function} fns the event hanlder functions
|
|
175
|
-
* @return {Function} the event handler to add to an element
|
|
176
141
|
*/
|
|
177
142
|
function composeEventHandlers(...fns) {
|
|
178
143
|
return (event, ...args) => fns.some((fn) => {
|
|
@@ -181,19 +146,13 @@ function composeEventHandlers(...fns) {
|
|
|
181
146
|
});
|
|
182
147
|
}
|
|
183
148
|
/**
|
|
184
|
-
* canUseFileSystemAccessAPI checks if the
|
|
185
|
-
* is supported by the browser.
|
|
186
|
-
* @returns {boolean}
|
|
149
|
+
* canUseFileSystemAccessAPI checks if the File System Access API is supported by the browser.
|
|
187
150
|
*/
|
|
188
151
|
function canUseFileSystemAccessAPI() {
|
|
189
152
|
return "showOpenFilePicker" in window;
|
|
190
153
|
}
|
|
191
154
|
/**
|
|
192
|
-
* Convert the `{accept}` dropzone prop to the
|
|
193
|
-
* `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
|
|
194
|
-
*
|
|
195
|
-
* @param {AcceptProp} accept
|
|
196
|
-
* @returns {{accept: string[]}[]}
|
|
155
|
+
* Convert the `{accept}` dropzone prop to the `{types}` option for showOpenFilePicker.
|
|
197
156
|
*/
|
|
198
157
|
function pickerOptionsFromAccept(accept) {
|
|
199
158
|
if (isDefined(accept)) return [{
|
|
@@ -209,75 +168,47 @@ function pickerOptionsFromAccept(accept) {
|
|
|
209
168
|
ok = false;
|
|
210
169
|
}
|
|
211
170
|
return ok;
|
|
212
|
-
}).reduce((agg, [mimeType, ext]) =>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
171
|
+
}).reduce((agg, [mimeType, ext]) => {
|
|
172
|
+
agg[mimeType] = ext;
|
|
173
|
+
return agg;
|
|
174
|
+
}, {})
|
|
216
175
|
}];
|
|
217
|
-
return accept;
|
|
218
176
|
}
|
|
219
177
|
/**
|
|
220
178
|
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
|
|
221
|
-
* @param {AcceptProp} accept
|
|
222
|
-
* @returns {string}
|
|
223
179
|
*/
|
|
224
180
|
function acceptPropAsAcceptAttr(accept) {
|
|
225
|
-
if (isDefined(accept)) return Object.entries(accept).reduce((a, [mimeType, ext]) =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
], []).filter((v) => isMIMEType(v) || isExt(v)).join(",");
|
|
181
|
+
if (isDefined(accept)) return Object.entries(accept).reduce((a, [mimeType, ext]) => {
|
|
182
|
+
a.push(mimeType, ...ext);
|
|
183
|
+
return a;
|
|
184
|
+
}, []).filter((v) => isMIMEType(v) || isExt(v)).join(",");
|
|
230
185
|
}
|
|
231
186
|
/**
|
|
232
187
|
* Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
|
|
233
|
-
*
|
|
234
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
|
|
235
|
-
* @param {any} v
|
|
236
|
-
* @returns {boolean} True if v is an abort exception.
|
|
237
188
|
*/
|
|
238
189
|
function isAbort(v) {
|
|
239
190
|
return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
|
|
240
191
|
}
|
|
241
192
|
/**
|
|
242
193
|
* Check if v is a security error.
|
|
243
|
-
*
|
|
244
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/DOMException.
|
|
245
|
-
* @param {any} v
|
|
246
|
-
* @returns {boolean} True if v is a security error.
|
|
247
194
|
*/
|
|
248
195
|
function isSecurityError(v) {
|
|
249
196
|
return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
|
|
250
197
|
}
|
|
251
198
|
/**
|
|
252
199
|
* Check if v is a MIME type string.
|
|
253
|
-
*
|
|
254
|
-
* See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
|
|
255
|
-
*
|
|
256
|
-
* @param {string} v
|
|
257
200
|
*/
|
|
258
201
|
function isMIMEType(v) {
|
|
259
202
|
return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || v === "application/*" || /\w+\/[-+.\w]+/g.test(v);
|
|
260
203
|
}
|
|
261
204
|
/**
|
|
262
205
|
* Check if v is a file extension.
|
|
263
|
-
* @param {string} v
|
|
264
206
|
*/
|
|
265
207
|
function isExt(v) {
|
|
266
208
|
return /^.*\.[\w]+$/.test(v);
|
|
267
209
|
}
|
|
268
|
-
/**
|
|
269
|
-
* @typedef {Object.<string, string[]>} AcceptProp
|
|
270
|
-
*/
|
|
271
|
-
/**
|
|
272
|
-
* @typedef {object} FileError
|
|
273
|
-
* @property {string} message
|
|
274
|
-
* @property {ErrorCode|string} code
|
|
275
|
-
*/
|
|
276
|
-
/**
|
|
277
|
-
* @typedef {"file-invalid-type"|"file-too-large"|"file-too-small"|"too-many-files"} ErrorCode
|
|
278
|
-
*/
|
|
279
210
|
//#endregion
|
|
280
|
-
//#region src/index.
|
|
211
|
+
//#region src/index.tsx
|
|
281
212
|
/**
|
|
282
213
|
* Convenience wrapper component for the `useDropzone` hook
|
|
283
214
|
*
|
|
@@ -295,261 +226,12 @@ function isExt(v) {
|
|
|
295
226
|
const Dropzone = (0, react.forwardRef)(({ children, ...params }, ref) => {
|
|
296
227
|
const { open, ...props } = useDropzone(params);
|
|
297
228
|
(0, react.useImperativeHandle)(ref, () => ({ open }), [open]);
|
|
298
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
229
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: children?.({
|
|
299
230
|
...props,
|
|
300
231
|
open
|
|
301
232
|
}) });
|
|
302
233
|
});
|
|
303
234
|
Dropzone.displayName = "Dropzone";
|
|
304
|
-
const defaultProps = {
|
|
305
|
-
disabled: false,
|
|
306
|
-
getFilesFromEvent: file_selector.fromEvent,
|
|
307
|
-
maxSize: Infinity,
|
|
308
|
-
minSize: 0,
|
|
309
|
-
multiple: true,
|
|
310
|
-
maxFiles: 0,
|
|
311
|
-
preventDropOnDocument: true,
|
|
312
|
-
noClick: false,
|
|
313
|
-
noKeyboard: false,
|
|
314
|
-
noDrag: false,
|
|
315
|
-
noDragEventsBubbling: false,
|
|
316
|
-
validator: null,
|
|
317
|
-
useFsAccessApi: false,
|
|
318
|
-
autoFocus: false
|
|
319
|
-
};
|
|
320
|
-
Dropzone.defaultProps = defaultProps;
|
|
321
|
-
Dropzone.propTypes = {
|
|
322
|
-
/**
|
|
323
|
-
* Render function that exposes the dropzone state and prop getter fns
|
|
324
|
-
*
|
|
325
|
-
* @param {object} params
|
|
326
|
-
* @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
|
|
327
|
-
* @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
|
|
328
|
-
* @param {Function} params.open Open the native file selection dialog
|
|
329
|
-
* @param {boolean} params.isFocused Dropzone area is in focus
|
|
330
|
-
* @param {boolean} params.isFileDialogActive File dialog is opened
|
|
331
|
-
* @param {boolean} params.isDragActive Active drag is in progress
|
|
332
|
-
* @param {boolean} params.isDragAccept Dragged files are accepted
|
|
333
|
-
* @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.
|
|
334
|
-
* @param {boolean} params.isDragGlobal Files are being dragged anywhere on the document
|
|
335
|
-
* @param {File[]} params.acceptedFiles Accepted files
|
|
336
|
-
* @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.
|
|
337
|
-
*/
|
|
338
|
-
children: prop_types.default.func,
|
|
339
|
-
/**
|
|
340
|
-
* Set accepted file types.
|
|
341
|
-
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
|
|
342
|
-
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
|
|
343
|
-
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
|
|
344
|
-
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
|
|
345
|
-
*/
|
|
346
|
-
accept: prop_types.default.objectOf(prop_types.default.arrayOf(prop_types.default.string)),
|
|
347
|
-
/**
|
|
348
|
-
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
|
|
349
|
-
*/
|
|
350
|
-
multiple: prop_types.default.bool,
|
|
351
|
-
/**
|
|
352
|
-
* If false, allow dropped items to take over the current browser window
|
|
353
|
-
*/
|
|
354
|
-
preventDropOnDocument: prop_types.default.bool,
|
|
355
|
-
/**
|
|
356
|
-
* If true, disables click to open the native file selection dialog
|
|
357
|
-
*/
|
|
358
|
-
noClick: prop_types.default.bool,
|
|
359
|
-
/**
|
|
360
|
-
* If true, disables SPACE/ENTER to open the native file selection dialog.
|
|
361
|
-
* Note that it also stops tracking the focus state.
|
|
362
|
-
*/
|
|
363
|
-
noKeyboard: prop_types.default.bool,
|
|
364
|
-
/**
|
|
365
|
-
* If true, disables drag 'n' drop
|
|
366
|
-
*/
|
|
367
|
-
noDrag: prop_types.default.bool,
|
|
368
|
-
/**
|
|
369
|
-
* If true, stops drag event propagation to parents
|
|
370
|
-
*/
|
|
371
|
-
noDragEventsBubbling: prop_types.default.bool,
|
|
372
|
-
/**
|
|
373
|
-
* Minimum file size (in bytes)
|
|
374
|
-
*/
|
|
375
|
-
minSize: prop_types.default.number,
|
|
376
|
-
/**
|
|
377
|
-
* Maximum file size (in bytes)
|
|
378
|
-
*/
|
|
379
|
-
maxSize: prop_types.default.number,
|
|
380
|
-
/**
|
|
381
|
-
* Maximum accepted number of files
|
|
382
|
-
* The default value is 0 which means there is no limitation to how many files are accepted.
|
|
383
|
-
*/
|
|
384
|
-
maxFiles: prop_types.default.number,
|
|
385
|
-
/**
|
|
386
|
-
* Enable/disable the dropzone
|
|
387
|
-
*/
|
|
388
|
-
disabled: prop_types.default.bool,
|
|
389
|
-
/**
|
|
390
|
-
* Use this to provide a custom file aggregator
|
|
391
|
-
*
|
|
392
|
-
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
|
|
393
|
-
*/
|
|
394
|
-
getFilesFromEvent: prop_types.default.func,
|
|
395
|
-
/**
|
|
396
|
-
* Cb for when closing the file dialog with no selection
|
|
397
|
-
*/
|
|
398
|
-
onFileDialogCancel: prop_types.default.func,
|
|
399
|
-
/**
|
|
400
|
-
* Cb for when opening the file dialog
|
|
401
|
-
*/
|
|
402
|
-
onFileDialogOpen: prop_types.default.func,
|
|
403
|
-
/**
|
|
404
|
-
* Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
405
|
-
* to open the file picker instead of using an `<input type="file">` click event.
|
|
406
|
-
*/
|
|
407
|
-
useFsAccessApi: prop_types.default.bool,
|
|
408
|
-
/**
|
|
409
|
-
* Set to true to focus the root element on render
|
|
410
|
-
*/
|
|
411
|
-
autoFocus: prop_types.default.bool,
|
|
412
|
-
/**
|
|
413
|
-
* Cb for when the `dragenter` event occurs.
|
|
414
|
-
*
|
|
415
|
-
* @param {DragEvent} event
|
|
416
|
-
*/
|
|
417
|
-
onDragEnter: prop_types.default.func,
|
|
418
|
-
/**
|
|
419
|
-
* Cb for when the `dragleave` event occurs
|
|
420
|
-
*
|
|
421
|
-
* @param {DragEvent} event
|
|
422
|
-
*/
|
|
423
|
-
onDragLeave: prop_types.default.func,
|
|
424
|
-
/**
|
|
425
|
-
* Cb for when the `dragover` event occurs
|
|
426
|
-
*
|
|
427
|
-
* @param {DragEvent} event
|
|
428
|
-
*/
|
|
429
|
-
onDragOver: prop_types.default.func,
|
|
430
|
-
/**
|
|
431
|
-
* Cb for when the `drop` event occurs.
|
|
432
|
-
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
|
|
433
|
-
*
|
|
434
|
-
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
|
|
435
|
-
* `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.
|
|
436
|
-
* If `multiple` is set to false and additional files are dropped,
|
|
437
|
-
* all files besides the first will be rejected.
|
|
438
|
-
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
|
|
439
|
-
*
|
|
440
|
-
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
|
|
441
|
-
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
|
|
442
|
-
*
|
|
443
|
-
* `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.
|
|
444
|
-
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
|
|
445
|
-
*
|
|
446
|
-
* ```js
|
|
447
|
-
* function onDrop(acceptedFiles) {
|
|
448
|
-
* const req = request.post('/upload')
|
|
449
|
-
* acceptedFiles.forEach(file => {
|
|
450
|
-
* req.attach(file.name, file)
|
|
451
|
-
* })
|
|
452
|
-
* req.end(callback)
|
|
453
|
-
* }
|
|
454
|
-
* ```
|
|
455
|
-
*
|
|
456
|
-
* @param {File[]} acceptedFiles
|
|
457
|
-
* @param {FileRejection[]} fileRejections
|
|
458
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
459
|
-
*/
|
|
460
|
-
onDrop: prop_types.default.func,
|
|
461
|
-
/**
|
|
462
|
-
* Cb for when the `drop` event occurs.
|
|
463
|
-
* Note that if no files are accepted, this callback is not invoked.
|
|
464
|
-
*
|
|
465
|
-
* @param {File[]} files
|
|
466
|
-
* @param {(DragEvent|Event)} event
|
|
467
|
-
*/
|
|
468
|
-
onDropAccepted: prop_types.default.func,
|
|
469
|
-
/**
|
|
470
|
-
* Cb for when the `drop` event occurs.
|
|
471
|
-
* Note that if no files are rejected, this callback is not invoked.
|
|
472
|
-
*
|
|
473
|
-
* @param {FileRejection[]} fileRejections
|
|
474
|
-
* @param {(DragEvent|Event)} event
|
|
475
|
-
*/
|
|
476
|
-
onDropRejected: prop_types.default.func,
|
|
477
|
-
/**
|
|
478
|
-
* Cb for when there's some error from any of the promises.
|
|
479
|
-
*
|
|
480
|
-
* @param {Error} error
|
|
481
|
-
*/
|
|
482
|
-
onError: prop_types.default.func,
|
|
483
|
-
/**
|
|
484
|
-
* Custom validation function. It must return null if there's no errors.
|
|
485
|
-
* @param {File} file
|
|
486
|
-
* @returns {FileError|FileError[]|null}
|
|
487
|
-
*/
|
|
488
|
-
validator: prop_types.default.func
|
|
489
|
-
};
|
|
490
|
-
/**
|
|
491
|
-
* A function that is invoked for the `dragenter`,
|
|
492
|
-
* `dragover` and `dragleave` events.
|
|
493
|
-
* It is not invoked if the items are not files (such as link, text, etc.).
|
|
494
|
-
*
|
|
495
|
-
* @callback dragCb
|
|
496
|
-
* @param {DragEvent} event
|
|
497
|
-
*/
|
|
498
|
-
/**
|
|
499
|
-
* A function that is invoked for the `drop` or input change event.
|
|
500
|
-
* It is not invoked if the items are not files (such as link, text, etc.).
|
|
501
|
-
*
|
|
502
|
-
* @callback dropCb
|
|
503
|
-
* @param {File[]} acceptedFiles List of accepted files
|
|
504
|
-
* @param {FileRejection[]} fileRejections List of rejected files and why they were rejected. This is the authoritative source for post-drop file rejections.
|
|
505
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
506
|
-
*/
|
|
507
|
-
/**
|
|
508
|
-
* A function that is invoked for the `drop` or input change event.
|
|
509
|
-
* It is not invoked if the items are files (such as link, text, etc.).
|
|
510
|
-
*
|
|
511
|
-
* @callback dropAcceptedCb
|
|
512
|
-
* @param {File[]} files List of accepted files that meet the given criteria
|
|
513
|
-
* (`accept`, `multiple`, `minSize`, `maxSize`)
|
|
514
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
515
|
-
*/
|
|
516
|
-
/**
|
|
517
|
-
* A function that is invoked for the `drop` or input change event.
|
|
518
|
-
*
|
|
519
|
-
* @callback dropRejectedCb
|
|
520
|
-
* @param {File[]} files List of rejected files that do not meet the given criteria
|
|
521
|
-
* (`accept`, `multiple`, `minSize`, `maxSize`)
|
|
522
|
-
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
|
|
523
|
-
*/
|
|
524
|
-
/**
|
|
525
|
-
* A function that is used aggregate files,
|
|
526
|
-
* in a asynchronous fashion, from drag or input change events.
|
|
527
|
-
*
|
|
528
|
-
* @callback getFilesFromEvent
|
|
529
|
-
* @param {(DragEvent|Event|Array<FileSystemFileHandle>)} event A drag event or input change event (if files were selected via the file dialog)
|
|
530
|
-
* @returns {(File[]|Promise<File[]>)}
|
|
531
|
-
*/
|
|
532
|
-
/**
|
|
533
|
-
* An object with the current dropzone state.
|
|
534
|
-
*
|
|
535
|
-
* @typedef {object} DropzoneState
|
|
536
|
-
* @property {boolean} isFocused Dropzone area is in focus
|
|
537
|
-
* @property {boolean} isFileDialogActive File dialog is opened
|
|
538
|
-
* @property {boolean} isDragActive Active drag is in progress
|
|
539
|
-
* @property {boolean} isDragAccept Dragged files are accepted
|
|
540
|
-
* @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.
|
|
541
|
-
* @property {boolean} isDragGlobal Files are being dragged anywhere on the document
|
|
542
|
-
* @property {File[]} acceptedFiles Accepted files
|
|
543
|
-
* @property {FileRejection[]} fileRejections Rejected files and why they were rejected. This persists after drop and is the source of truth for post-drop rejections.
|
|
544
|
-
*/
|
|
545
|
-
/**
|
|
546
|
-
* An object with the dropzone methods.
|
|
547
|
-
*
|
|
548
|
-
* @typedef {object} DropzoneMethods
|
|
549
|
-
* @property {Function} getRootProps Returns the props you should apply to the root drop container you render
|
|
550
|
-
* @property {Function} getInputProps Returns the props you should apply to hidden file input you render
|
|
551
|
-
* @property {Function} open Open the native file selection dialog
|
|
552
|
-
*/
|
|
553
235
|
const initialState = {
|
|
554
236
|
isFocused: false,
|
|
555
237
|
isFileDialogActive: false,
|
|
@@ -578,81 +260,13 @@ const initialState = {
|
|
|
578
260
|
* )
|
|
579
261
|
* }
|
|
580
262
|
* ```
|
|
581
|
-
*
|
|
582
|
-
* @function useDropzone
|
|
583
|
-
*
|
|
584
|
-
* @param {object} props
|
|
585
|
-
* @param {import("./utils").AcceptProp} [props.accept] Set accepted file types.
|
|
586
|
-
* Checkout https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker types option for more information.
|
|
587
|
-
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
|
|
588
|
-
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
|
|
589
|
-
* Windows. In some cases there might not be a mime type set at all (https://github.com/react-dropzone/react-dropzone/issues/276).
|
|
590
|
-
* @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
|
|
591
|
-
* @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
|
|
592
|
-
* @param {boolean} [props.noClick=false] If true, disables click to open the native file selection dialog
|
|
593
|
-
* @param {boolean} [props.noKeyboard=false] If true, disables SPACE/ENTER to open the native file selection dialog.
|
|
594
|
-
* Note that it also stops tracking the focus state.
|
|
595
|
-
* @param {boolean} [props.noDrag=false] If true, disables drag 'n' drop
|
|
596
|
-
* @param {boolean} [props.noDragEventsBubbling=false] If true, stops drag event propagation to parents
|
|
597
|
-
* @param {number} [props.minSize=0] Minimum file size (in bytes)
|
|
598
|
-
* @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
|
|
599
|
-
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
|
|
600
|
-
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
|
|
601
|
-
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
|
|
602
|
-
* @param {boolean} [props.useFsAccessApi] Set to true to use the https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
|
|
603
|
-
* to open the file picker instead of using an `<input type="file">` click event.
|
|
604
|
-
* @param {boolean} autoFocus Set to true to auto focus the root element.
|
|
605
|
-
* @param {Function} [props.onFileDialogOpen] Cb for when opening the file dialog
|
|
606
|
-
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
|
|
607
|
-
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
|
|
608
|
-
* @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
|
|
609
|
-
* @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
|
|
610
|
-
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
|
|
611
|
-
*
|
|
612
|
-
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
|
|
613
|
-
* `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).
|
|
614
|
-
* If `multiple` is set to false and additional files are dropped,
|
|
615
|
-
* all files besides the first will be rejected.
|
|
616
|
-
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
|
|
617
|
-
*
|
|
618
|
-
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
|
|
619
|
-
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
|
|
620
|
-
*
|
|
621
|
-
* The second parameter (fileRejections) is the authoritative list of rejected files after a drop.
|
|
622
|
-
* Use this parameter or the fileRejections state property to handle post-drop file rejections,
|
|
623
|
-
* as isDragReject only indicates rejection state during active drag operations.
|
|
624
|
-
*
|
|
625
|
-
* `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.
|
|
626
|
-
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
|
|
627
|
-
*
|
|
628
|
-
* ```js
|
|
629
|
-
* function onDrop(acceptedFiles) {
|
|
630
|
-
* const req = request.post('/upload')
|
|
631
|
-
* acceptedFiles.forEach(file => {
|
|
632
|
-
* req.attach(file.name, file)
|
|
633
|
-
* })
|
|
634
|
-
* req.end(callback)
|
|
635
|
-
* }
|
|
636
|
-
* ```
|
|
637
|
-
* @param {dropAcceptedCb} [props.onDropAccepted]
|
|
638
|
-
* @param {dropRejectedCb} [props.onDropRejected]
|
|
639
|
-
* @param {(error: Error) => void} [props.onError]
|
|
640
|
-
*
|
|
641
|
-
* @returns {DropzoneState & DropzoneMethods}
|
|
642
263
|
*/
|
|
643
264
|
function useDropzone(props = {}) {
|
|
644
|
-
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 } =
|
|
645
|
-
...defaultProps,
|
|
646
|
-
...props
|
|
647
|
-
};
|
|
265
|
+
const { accept, disabled = false, getFilesFromEvent = file_selector.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;
|
|
648
266
|
const acceptAttr = (0, react.useMemo)(() => acceptPropAsAcceptAttr(accept), [accept]);
|
|
649
267
|
const pickerTypes = (0, react.useMemo)(() => pickerOptionsFromAccept(accept), [accept]);
|
|
650
268
|
const onFileDialogOpenCb = (0, react.useMemo)(() => typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop, [onFileDialogOpen]);
|
|
651
269
|
const onFileDialogCancelCb = (0, react.useMemo)(() => typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop, [onFileDialogCancel]);
|
|
652
|
-
/**
|
|
653
|
-
* @constant
|
|
654
|
-
* @type {React.MutableRefObject<HTMLElement>}
|
|
655
|
-
*/
|
|
656
270
|
const rootRef = (0, react.useRef)(null);
|
|
657
271
|
const inputRef = (0, react.useRef)(null);
|
|
658
272
|
const [state, dispatch] = (0, react.useReducer)(reducer, initialState);
|
|
@@ -662,7 +276,7 @@ function useDropzone(props = {}) {
|
|
|
662
276
|
if (!fsAccessApiWorksRef.current && isFileDialogActive) setTimeout(() => {
|
|
663
277
|
if (inputRef.current) {
|
|
664
278
|
const { files } = inputRef.current;
|
|
665
|
-
if (!files
|
|
279
|
+
if (!files?.length) {
|
|
666
280
|
dispatch({ type: "closeDialog" });
|
|
667
281
|
onFileDialogCancelCb();
|
|
668
282
|
}
|
|
@@ -683,7 +297,7 @@ function useDropzone(props = {}) {
|
|
|
683
297
|
const dragTargetsRef = (0, react.useRef)([]);
|
|
684
298
|
const globalDragTargetsRef = (0, react.useRef)([]);
|
|
685
299
|
const onDocumentDrop = (event) => {
|
|
686
|
-
if (rootRef.current && rootRef.current.contains(event.target)) return;
|
|
300
|
+
if (rootRef.current && event.target && rootRef.current.contains(event.target)) return;
|
|
687
301
|
event.preventDefault();
|
|
688
302
|
dragTargetsRef.current = [];
|
|
689
303
|
};
|
|
@@ -701,7 +315,7 @@ function useDropzone(props = {}) {
|
|
|
701
315
|
}, [rootRef, preventDropOnDocument]);
|
|
702
316
|
(0, react.useEffect)(() => {
|
|
703
317
|
const onDocumentDragEnter = (event) => {
|
|
704
|
-
globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
318
|
+
if (event.target) globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
705
319
|
if (isEvtWithFiles(event)) dispatch({
|
|
706
320
|
isDragGlobal: true,
|
|
707
321
|
type: "setDragGlobal"
|
|
@@ -754,7 +368,7 @@ function useDropzone(props = {}) {
|
|
|
754
368
|
}, [onError]);
|
|
755
369
|
const onDragEnterCb = (0, react.useCallback)((event) => {
|
|
756
370
|
event.preventDefault();
|
|
757
|
-
event.persist();
|
|
371
|
+
event.persist?.();
|
|
758
372
|
stopPropagation(event);
|
|
759
373
|
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
760
374
|
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
@@ -791,7 +405,7 @@ function useDropzone(props = {}) {
|
|
|
791
405
|
]);
|
|
792
406
|
const onDragOverCb = (0, react.useCallback)((event) => {
|
|
793
407
|
event.preventDefault();
|
|
794
|
-
event.persist();
|
|
408
|
+
event.persist?.();
|
|
795
409
|
stopPropagation(event);
|
|
796
410
|
const hasFiles = isEvtWithFiles(event);
|
|
797
411
|
if (hasFiles && event.dataTransfer) try {
|
|
@@ -802,9 +416,9 @@ function useDropzone(props = {}) {
|
|
|
802
416
|
}, [onDragOver, noDragEventsBubbling]);
|
|
803
417
|
const onDragLeaveCb = (0, react.useCallback)((event) => {
|
|
804
418
|
event.preventDefault();
|
|
805
|
-
event.persist();
|
|
419
|
+
event.persist?.();
|
|
806
420
|
stopPropagation(event);
|
|
807
|
-
const targets = dragTargetsRef.current.filter((target) => rootRef.current
|
|
421
|
+
const targets = dragTargetsRef.current.filter((target) => rootRef.current?.contains(target));
|
|
808
422
|
const targetIdx = targets.indexOf(event.target);
|
|
809
423
|
if (targetIdx !== -1) targets.splice(targetIdx, 1);
|
|
810
424
|
dragTargetsRef.current = targets;
|
|
@@ -834,7 +448,7 @@ function useDropzone(props = {}) {
|
|
|
834
448
|
if (customErrors) errors = errors.concat(customErrors);
|
|
835
449
|
fileRejections.push({
|
|
836
450
|
file,
|
|
837
|
-
errors: errors.filter((e) => e)
|
|
451
|
+
errors: errors.filter((e) => e != null)
|
|
838
452
|
});
|
|
839
453
|
}
|
|
840
454
|
});
|
|
@@ -869,7 +483,7 @@ function useDropzone(props = {}) {
|
|
|
869
483
|
]);
|
|
870
484
|
const onDropCb = (0, react.useCallback)((event) => {
|
|
871
485
|
event.preventDefault();
|
|
872
|
-
event.persist();
|
|
486
|
+
event.persist?.();
|
|
873
487
|
stopPropagation(event);
|
|
874
488
|
dragTargetsRef.current = [];
|
|
875
489
|
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
@@ -901,7 +515,7 @@ function useDropzone(props = {}) {
|
|
|
901
515
|
} else if (isSecurityError(e)) {
|
|
902
516
|
fsAccessApiWorksRef.current = false;
|
|
903
517
|
if (inputRef.current) {
|
|
904
|
-
inputRef.current.value =
|
|
518
|
+
inputRef.current.value = "";
|
|
905
519
|
inputRef.current.click();
|
|
906
520
|
} 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."));
|
|
907
521
|
} else onErrCb(e);
|
|
@@ -911,7 +525,7 @@ function useDropzone(props = {}) {
|
|
|
911
525
|
if (inputRef.current) {
|
|
912
526
|
dispatch({ type: "openDialog" });
|
|
913
527
|
onFileDialogOpenCb();
|
|
914
|
-
inputRef.current.value =
|
|
528
|
+
inputRef.current.value = "";
|
|
915
529
|
inputRef.current.click();
|
|
916
530
|
}
|
|
917
531
|
}, [
|
|
@@ -925,7 +539,7 @@ function useDropzone(props = {}) {
|
|
|
925
539
|
multiple
|
|
926
540
|
]);
|
|
927
541
|
const onKeyDownCb = (0, react.useCallback)((event) => {
|
|
928
|
-
if (!rootRef.current
|
|
542
|
+
if (!rootRef.current?.isEqualNode(event.target)) return;
|
|
929
543
|
if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
|
|
930
544
|
event.preventDefault();
|
|
931
545
|
openFileDialog();
|
|
@@ -1024,13 +638,7 @@ function useDropzone(props = {}) {
|
|
|
1024
638
|
open: composeHandler(openFileDialog)
|
|
1025
639
|
};
|
|
1026
640
|
}
|
|
1027
|
-
/**
|
|
1028
|
-
* @param {DropzoneState} state
|
|
1029
|
-
* @param {{type: string} & DropzoneState} action
|
|
1030
|
-
* @returns {DropzoneState}
|
|
1031
|
-
*/
|
|
1032
641
|
function reducer(state, action) {
|
|
1033
|
-
/* istanbul ignore next */
|
|
1034
642
|
switch (action.type) {
|
|
1035
643
|
case "focus": return {
|
|
1036
644
|
...state,
|