react-dropzone 14.4.1 → 16.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 +3 -3
- package/dist/index.cjs +1077 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1046 -2
- package/dist/index.js.map +1 -0
- package/package.json +62 -161
- package/src/{index.js → index.jsx} +10 -7
- package/typings/tests/tsconfig.json +12 -11
- package/.babelrc.js +0 -28
- package/.codeclimate.yml +0 -15
- package/.editorconfig +0 -13
- package/.eslintignore +0 -3
- package/.eslintrc +0 -37
- package/.gitpod.yml +0 -6
- package/.husky/commit-msg +0 -5
- package/.husky/pre-commit +0 -5
- package/.nvmrc +0 -1
- package/.releaserc.json +0 -27
- package/CHANGELOG.md +0 -6
- package/commitlint.config.js +0 -1
- package/dist/es/index.js +0 -1048
- package/dist/es/package.json +0 -1
- package/dist/es/utils/index.js +0 -366
- package/examples/.eslintrc +0 -5
- package/examples/accept/README.md +0 -144
- package/examples/basic/README.md +0 -70
- package/examples/class-component/README.md +0 -45
- package/examples/drag-overlay/README.md +0 -132
- package/examples/events/README.md +0 -164
- package/examples/file-dialog/README.md +0 -90
- package/examples/forms/README.md +0 -69
- package/examples/maxFiles/README.md +0 -58
- package/examples/no-jsx/README.md +0 -32
- package/examples/pintura/README.md +0 -146
- package/examples/plugins/README.md +0 -55
- package/examples/previews/README.md +0 -86
- package/examples/styling/README.md +0 -126
- package/examples/theme.css +0 -37
- package/examples/validator/README.md +0 -68
- package/rollup.config.js +0 -33
- package/src/.eslintrc +0 -37
- package/src/__snapshots__/index.spec.js.snap +0 -3
- package/src/index.spec.js +0 -3777
- package/src/utils/index.spec.js +0 -625
- package/styleguide.config.js +0 -107
- package/testSetup.js +0 -8
- package/typings/.eslintrc +0 -28
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
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
|
+
let file_selector = require("file-selector");
|
|
32
|
+
let attr_accept = require("attr-accept");
|
|
33
|
+
attr_accept = __toESM(attr_accept, 1);
|
|
34
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
+
//#region src/utils/index.js
|
|
36
|
+
const accepts = typeof attr_accept.default === "function" ? attr_accept.default : attr_accept.default.default;
|
|
37
|
+
const FILE_INVALID_TYPE = "file-invalid-type";
|
|
38
|
+
const FILE_TOO_LARGE = "file-too-large";
|
|
39
|
+
const FILE_TOO_SMALL = "file-too-small";
|
|
40
|
+
const TOO_MANY_FILES = "too-many-files";
|
|
41
|
+
const ErrorCode = {
|
|
42
|
+
FileInvalidType: FILE_INVALID_TYPE,
|
|
43
|
+
FileTooLarge: FILE_TOO_LARGE,
|
|
44
|
+
FileTooSmall: FILE_TOO_SMALL,
|
|
45
|
+
TooManyFiles: TOO_MANY_FILES
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param {string} accept
|
|
50
|
+
*/
|
|
51
|
+
const getInvalidTypeRejectionErr = (accept = "") => {
|
|
52
|
+
const acceptArr = accept.split(",");
|
|
53
|
+
const msg = acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
|
|
54
|
+
return {
|
|
55
|
+
code: FILE_INVALID_TYPE,
|
|
56
|
+
message: `File type must be ${msg}`
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const getTooLargeRejectionErr = (maxSize) => {
|
|
60
|
+
return {
|
|
61
|
+
code: FILE_TOO_LARGE,
|
|
62
|
+
message: `File is larger than ${maxSize} ${maxSize === 1 ? "byte" : "bytes"}`
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const getTooSmallRejectionErr = (minSize) => {
|
|
66
|
+
return {
|
|
67
|
+
code: FILE_TOO_SMALL,
|
|
68
|
+
message: `File is smaller than ${minSize} ${minSize === 1 ? "byte" : "bytes"}`
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const TOO_MANY_FILES_REJECTION = {
|
|
72
|
+
code: TOO_MANY_FILES,
|
|
73
|
+
message: "Too many files"
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Check if the given file is a DataTransferItem with an empty type.
|
|
77
|
+
*
|
|
78
|
+
* During drag events, browsers may return DataTransferItem objects instead of File objects.
|
|
79
|
+
* Some browsers (e.g., Chrome) return an empty MIME type for certain file types (like .md files)
|
|
80
|
+
* 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
|
+
*/
|
|
92
|
+
function isDataTransferItemWithEmptyType(file) {
|
|
93
|
+
return file.type === "" && typeof file.getAsFile === "function";
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if file is accepted.
|
|
97
|
+
*
|
|
98
|
+
* Firefox versions prior to 53 return a bogus MIME type for every file drag,
|
|
99
|
+
* so dragovers with that MIME type will always be accepted.
|
|
100
|
+
*
|
|
101
|
+
* Chrome/other browsers may return an empty MIME type for files during drag events,
|
|
102
|
+
* so we accept those as well (we'll validate properly on drop).
|
|
103
|
+
*
|
|
104
|
+
* @param {File} file
|
|
105
|
+
* @param {string} accept
|
|
106
|
+
* @returns
|
|
107
|
+
*/
|
|
108
|
+
function fileAccepted(file, accept) {
|
|
109
|
+
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept) || isDataTransferItemWithEmptyType(file);
|
|
110
|
+
return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
|
|
111
|
+
}
|
|
112
|
+
function fileMatchSize(file, minSize, maxSize) {
|
|
113
|
+
if (isDefined(file.size)) {
|
|
114
|
+
if (isDefined(minSize) && isDefined(maxSize)) {
|
|
115
|
+
if (file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
|
|
116
|
+
if (file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
|
|
117
|
+
} else if (isDefined(minSize) && file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
|
|
118
|
+
else if (isDefined(maxSize) && file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
|
|
119
|
+
}
|
|
120
|
+
return [true, null];
|
|
121
|
+
}
|
|
122
|
+
function isDefined(value) {
|
|
123
|
+
return value !== void 0 && value !== null;
|
|
124
|
+
}
|
|
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 }) {
|
|
138
|
+
if (!multiple && files.length > 1 || multiple && maxFiles >= 1 && files.length > maxFiles) return false;
|
|
139
|
+
return files.every((file) => {
|
|
140
|
+
const [accepted] = fileAccepted(file, accept);
|
|
141
|
+
const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
|
|
142
|
+
const customErrors = validator ? validator(file) : null;
|
|
143
|
+
return accepted && sizeMatch && !customErrors;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function isPropagationStopped(event) {
|
|
147
|
+
if (typeof event.isPropagationStopped === "function") return event.isPropagationStopped();
|
|
148
|
+
else if (typeof event.cancelBubble !== "undefined") return event.cancelBubble;
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
function isEvtWithFiles(event) {
|
|
152
|
+
if (!event.dataTransfer) return !!event.target && !!event.target.files;
|
|
153
|
+
return Array.prototype.some.call(event.dataTransfer.types, (type) => type === "Files" || type === "application/x-moz-file");
|
|
154
|
+
}
|
|
155
|
+
function onDocumentDragOver(event) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
}
|
|
158
|
+
function isIe(userAgent) {
|
|
159
|
+
return userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1;
|
|
160
|
+
}
|
|
161
|
+
function isEdge(userAgent) {
|
|
162
|
+
return userAgent.indexOf("Edge/") !== -1;
|
|
163
|
+
}
|
|
164
|
+
function isIeOrEdge(userAgent = window.navigator.userAgent) {
|
|
165
|
+
return isIe(userAgent) || isEdge(userAgent);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* This is intended to be used to compose event handlers
|
|
169
|
+
* They are executed in order until one of them calls `event.isPropagationStopped()`.
|
|
170
|
+
* Note that the check is done on the first invoke too,
|
|
171
|
+
* meaning that if propagation was stopped before invoking the fns,
|
|
172
|
+
* 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
|
+
*/
|
|
177
|
+
function composeEventHandlers(...fns) {
|
|
178
|
+
return (event, ...args) => fns.some((fn) => {
|
|
179
|
+
if (!isPropagationStopped(event) && fn) fn(event, ...args);
|
|
180
|
+
return isPropagationStopped(event);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* canUseFileSystemAccessAPI checks if the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
|
|
185
|
+
* is supported by the browser.
|
|
186
|
+
* @returns {boolean}
|
|
187
|
+
*/
|
|
188
|
+
function canUseFileSystemAccessAPI() {
|
|
189
|
+
return "showOpenFilePicker" in window;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
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[]}[]}
|
|
197
|
+
*/
|
|
198
|
+
function pickerOptionsFromAccept(accept) {
|
|
199
|
+
if (isDefined(accept)) return [{
|
|
200
|
+
description: "Files",
|
|
201
|
+
accept: Object.entries(accept).filter(([mimeType, ext]) => {
|
|
202
|
+
let ok = true;
|
|
203
|
+
if (!isMIMEType(mimeType)) {
|
|
204
|
+
console.warn(`Skipped "${mimeType}" because it is not a valid MIME type. Check https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types for a list of valid MIME types.`);
|
|
205
|
+
ok = false;
|
|
206
|
+
}
|
|
207
|
+
if (!Array.isArray(ext) || !ext.every(isExt)) {
|
|
208
|
+
console.warn(`Skipped "${mimeType}" because an invalid file extension was provided.`);
|
|
209
|
+
ok = false;
|
|
210
|
+
}
|
|
211
|
+
return ok;
|
|
212
|
+
}).reduce((agg, [mimeType, ext]) => ({
|
|
213
|
+
...agg,
|
|
214
|
+
[mimeType]: ext
|
|
215
|
+
}), {})
|
|
216
|
+
}];
|
|
217
|
+
return accept;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
|
|
221
|
+
* @param {AcceptProp} accept
|
|
222
|
+
* @returns {string}
|
|
223
|
+
*/
|
|
224
|
+
function acceptPropAsAcceptAttr(accept) {
|
|
225
|
+
if (isDefined(accept)) return Object.entries(accept).reduce((a, [mimeType, ext]) => [
|
|
226
|
+
...a,
|
|
227
|
+
mimeType,
|
|
228
|
+
...ext
|
|
229
|
+
], []).filter((v) => isMIMEType(v) || isExt(v)).join(",");
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 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
|
+
*/
|
|
238
|
+
function isAbort(v) {
|
|
239
|
+
return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 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
|
+
*/
|
|
248
|
+
function isSecurityError(v) {
|
|
249
|
+
return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 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
|
+
*/
|
|
258
|
+
function isMIMEType(v) {
|
|
259
|
+
return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || v === "application/*" || /\w+\/[-+.\w]+/g.test(v);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if v is a file extension.
|
|
263
|
+
* @param {string} v
|
|
264
|
+
*/
|
|
265
|
+
function isExt(v) {
|
|
266
|
+
return /^.*\.[\w]+$/.test(v);
|
|
267
|
+
}
|
|
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
|
+
//#endregion
|
|
280
|
+
//#region src/index.jsx
|
|
281
|
+
/**
|
|
282
|
+
* Convenience wrapper component for the `useDropzone` hook
|
|
283
|
+
*
|
|
284
|
+
* ```jsx
|
|
285
|
+
* <Dropzone>
|
|
286
|
+
* {({getRootProps, getInputProps}) => (
|
|
287
|
+
* <div {...getRootProps()}>
|
|
288
|
+
* <input {...getInputProps()} />
|
|
289
|
+
* <p>Drag 'n' drop some files here, or click to select files</p>
|
|
290
|
+
* </div>
|
|
291
|
+
* )}
|
|
292
|
+
* </Dropzone>
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
const Dropzone = (0, react.forwardRef)(({ children, ...params }, ref) => {
|
|
296
|
+
const { open, ...props } = useDropzone(params);
|
|
297
|
+
(0, react.useImperativeHandle)(ref, () => ({ open }), [open]);
|
|
298
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: children({
|
|
299
|
+
...props,
|
|
300
|
+
open
|
|
301
|
+
}) });
|
|
302
|
+
});
|
|
303
|
+
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
|
+
const initialState = {
|
|
554
|
+
isFocused: false,
|
|
555
|
+
isFileDialogActive: false,
|
|
556
|
+
isDragActive: false,
|
|
557
|
+
isDragAccept: false,
|
|
558
|
+
isDragReject: false,
|
|
559
|
+
isDragGlobal: false,
|
|
560
|
+
acceptedFiles: [],
|
|
561
|
+
fileRejections: []
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* A React hook that creates a drag 'n' drop area.
|
|
565
|
+
*
|
|
566
|
+
* ```jsx
|
|
567
|
+
* function MyDropzone(props) {
|
|
568
|
+
* const {getRootProps, getInputProps} = useDropzone({
|
|
569
|
+
* onDrop: acceptedFiles => {
|
|
570
|
+
* // do something with the File objects, e.g. upload to some server
|
|
571
|
+
* }
|
|
572
|
+
* });
|
|
573
|
+
* return (
|
|
574
|
+
* <div {...getRootProps()}>
|
|
575
|
+
* <input {...getInputProps()} />
|
|
576
|
+
* <p>Drag and drop some files here, or click to select files</p>
|
|
577
|
+
* </div>
|
|
578
|
+
* )
|
|
579
|
+
* }
|
|
580
|
+
* ```
|
|
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
|
+
*/
|
|
643
|
+
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
|
+
};
|
|
648
|
+
const acceptAttr = (0, react.useMemo)(() => acceptPropAsAcceptAttr(accept), [accept]);
|
|
649
|
+
const pickerTypes = (0, react.useMemo)(() => pickerOptionsFromAccept(accept), [accept]);
|
|
650
|
+
const onFileDialogOpenCb = (0, react.useMemo)(() => typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop, [onFileDialogOpen]);
|
|
651
|
+
const onFileDialogCancelCb = (0, react.useMemo)(() => typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop, [onFileDialogCancel]);
|
|
652
|
+
/**
|
|
653
|
+
* @constant
|
|
654
|
+
* @type {React.MutableRefObject<HTMLElement>}
|
|
655
|
+
*/
|
|
656
|
+
const rootRef = (0, react.useRef)(null);
|
|
657
|
+
const inputRef = (0, react.useRef)(null);
|
|
658
|
+
const [state, dispatch] = (0, react.useReducer)(reducer, initialState);
|
|
659
|
+
const { isFocused, isFileDialogActive } = state;
|
|
660
|
+
const fsAccessApiWorksRef = (0, react.useRef)(typeof window !== "undefined" && window.isSecureContext && useFsAccessApi && canUseFileSystemAccessAPI());
|
|
661
|
+
const onWindowFocus = () => {
|
|
662
|
+
if (!fsAccessApiWorksRef.current && isFileDialogActive) setTimeout(() => {
|
|
663
|
+
if (inputRef.current) {
|
|
664
|
+
const { files } = inputRef.current;
|
|
665
|
+
if (!files.length) {
|
|
666
|
+
dispatch({ type: "closeDialog" });
|
|
667
|
+
onFileDialogCancelCb();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}, 300);
|
|
671
|
+
};
|
|
672
|
+
(0, react.useEffect)(() => {
|
|
673
|
+
window.addEventListener("focus", onWindowFocus, false);
|
|
674
|
+
return () => {
|
|
675
|
+
window.removeEventListener("focus", onWindowFocus, false);
|
|
676
|
+
};
|
|
677
|
+
}, [
|
|
678
|
+
inputRef,
|
|
679
|
+
isFileDialogActive,
|
|
680
|
+
onFileDialogCancelCb,
|
|
681
|
+
fsAccessApiWorksRef
|
|
682
|
+
]);
|
|
683
|
+
const dragTargetsRef = (0, react.useRef)([]);
|
|
684
|
+
const globalDragTargetsRef = (0, react.useRef)([]);
|
|
685
|
+
const onDocumentDrop = (event) => {
|
|
686
|
+
if (rootRef.current && rootRef.current.contains(event.target)) return;
|
|
687
|
+
event.preventDefault();
|
|
688
|
+
dragTargetsRef.current = [];
|
|
689
|
+
};
|
|
690
|
+
(0, react.useEffect)(() => {
|
|
691
|
+
if (preventDropOnDocument) {
|
|
692
|
+
document.addEventListener("dragover", onDocumentDragOver, false);
|
|
693
|
+
document.addEventListener("drop", onDocumentDrop, false);
|
|
694
|
+
}
|
|
695
|
+
return () => {
|
|
696
|
+
if (preventDropOnDocument) {
|
|
697
|
+
document.removeEventListener("dragover", onDocumentDragOver);
|
|
698
|
+
document.removeEventListener("drop", onDocumentDrop);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}, [rootRef, preventDropOnDocument]);
|
|
702
|
+
(0, react.useEffect)(() => {
|
|
703
|
+
const onDocumentDragEnter = (event) => {
|
|
704
|
+
globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
705
|
+
if (isEvtWithFiles(event)) dispatch({
|
|
706
|
+
isDragGlobal: true,
|
|
707
|
+
type: "setDragGlobal"
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
const onDocumentDragLeave = (event) => {
|
|
711
|
+
globalDragTargetsRef.current = globalDragTargetsRef.current.filter((el) => el !== event.target && el !== null);
|
|
712
|
+
if (globalDragTargetsRef.current.length > 0) return;
|
|
713
|
+
dispatch({
|
|
714
|
+
isDragGlobal: false,
|
|
715
|
+
type: "setDragGlobal"
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
const onDocumentDragEnd = () => {
|
|
719
|
+
globalDragTargetsRef.current = [];
|
|
720
|
+
dispatch({
|
|
721
|
+
isDragGlobal: false,
|
|
722
|
+
type: "setDragGlobal"
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
const onDocumentDropGlobal = () => {
|
|
726
|
+
globalDragTargetsRef.current = [];
|
|
727
|
+
dispatch({
|
|
728
|
+
isDragGlobal: false,
|
|
729
|
+
type: "setDragGlobal"
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
document.addEventListener("dragenter", onDocumentDragEnter, false);
|
|
733
|
+
document.addEventListener("dragleave", onDocumentDragLeave, false);
|
|
734
|
+
document.addEventListener("dragend", onDocumentDragEnd, false);
|
|
735
|
+
document.addEventListener("drop", onDocumentDropGlobal, false);
|
|
736
|
+
return () => {
|
|
737
|
+
document.removeEventListener("dragenter", onDocumentDragEnter);
|
|
738
|
+
document.removeEventListener("dragleave", onDocumentDragLeave);
|
|
739
|
+
document.removeEventListener("dragend", onDocumentDragEnd);
|
|
740
|
+
document.removeEventListener("drop", onDocumentDropGlobal);
|
|
741
|
+
};
|
|
742
|
+
}, [rootRef]);
|
|
743
|
+
(0, react.useEffect)(() => {
|
|
744
|
+
if (!disabled && autoFocus && rootRef.current) rootRef.current.focus();
|
|
745
|
+
return () => {};
|
|
746
|
+
}, [
|
|
747
|
+
rootRef,
|
|
748
|
+
autoFocus,
|
|
749
|
+
disabled
|
|
750
|
+
]);
|
|
751
|
+
const onErrCb = (0, react.useCallback)((e) => {
|
|
752
|
+
if (onError) onError(e);
|
|
753
|
+
else console.error(e);
|
|
754
|
+
}, [onError]);
|
|
755
|
+
const onDragEnterCb = (0, react.useCallback)((event) => {
|
|
756
|
+
event.preventDefault();
|
|
757
|
+
event.persist();
|
|
758
|
+
stopPropagation(event);
|
|
759
|
+
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
760
|
+
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
761
|
+
if (isPropagationStopped(event) && !noDragEventsBubbling) return;
|
|
762
|
+
const fileCount = files.length;
|
|
763
|
+
const isDragAccept = fileCount > 0 && allFilesAccepted({
|
|
764
|
+
files,
|
|
765
|
+
accept: acceptAttr,
|
|
766
|
+
minSize,
|
|
767
|
+
maxSize,
|
|
768
|
+
multiple,
|
|
769
|
+
maxFiles,
|
|
770
|
+
validator
|
|
771
|
+
});
|
|
772
|
+
dispatch({
|
|
773
|
+
isDragAccept,
|
|
774
|
+
isDragReject: fileCount > 0 && !isDragAccept,
|
|
775
|
+
isDragActive: true,
|
|
776
|
+
type: "setDraggedFiles"
|
|
777
|
+
});
|
|
778
|
+
if (onDragEnter) onDragEnter(event);
|
|
779
|
+
}).catch((e) => onErrCb(e));
|
|
780
|
+
}, [
|
|
781
|
+
getFilesFromEvent,
|
|
782
|
+
onDragEnter,
|
|
783
|
+
onErrCb,
|
|
784
|
+
noDragEventsBubbling,
|
|
785
|
+
acceptAttr,
|
|
786
|
+
minSize,
|
|
787
|
+
maxSize,
|
|
788
|
+
multiple,
|
|
789
|
+
maxFiles,
|
|
790
|
+
validator
|
|
791
|
+
]);
|
|
792
|
+
const onDragOverCb = (0, react.useCallback)((event) => {
|
|
793
|
+
event.preventDefault();
|
|
794
|
+
event.persist();
|
|
795
|
+
stopPropagation(event);
|
|
796
|
+
const hasFiles = isEvtWithFiles(event);
|
|
797
|
+
if (hasFiles && event.dataTransfer) try {
|
|
798
|
+
event.dataTransfer.dropEffect = "copy";
|
|
799
|
+
} catch {}
|
|
800
|
+
if (hasFiles && onDragOver) onDragOver(event);
|
|
801
|
+
return false;
|
|
802
|
+
}, [onDragOver, noDragEventsBubbling]);
|
|
803
|
+
const onDragLeaveCb = (0, react.useCallback)((event) => {
|
|
804
|
+
event.preventDefault();
|
|
805
|
+
event.persist();
|
|
806
|
+
stopPropagation(event);
|
|
807
|
+
const targets = dragTargetsRef.current.filter((target) => rootRef.current && rootRef.current.contains(target));
|
|
808
|
+
const targetIdx = targets.indexOf(event.target);
|
|
809
|
+
if (targetIdx !== -1) targets.splice(targetIdx, 1);
|
|
810
|
+
dragTargetsRef.current = targets;
|
|
811
|
+
if (targets.length > 0) return;
|
|
812
|
+
dispatch({
|
|
813
|
+
type: "setDraggedFiles",
|
|
814
|
+
isDragActive: false,
|
|
815
|
+
isDragAccept: false,
|
|
816
|
+
isDragReject: false
|
|
817
|
+
});
|
|
818
|
+
if (isEvtWithFiles(event) && onDragLeave) onDragLeave(event);
|
|
819
|
+
}, [
|
|
820
|
+
rootRef,
|
|
821
|
+
onDragLeave,
|
|
822
|
+
noDragEventsBubbling
|
|
823
|
+
]);
|
|
824
|
+
const setFiles = (0, react.useCallback)((files, event) => {
|
|
825
|
+
const acceptedFiles = [];
|
|
826
|
+
const fileRejections = [];
|
|
827
|
+
files.forEach((file) => {
|
|
828
|
+
const [accepted, acceptError] = fileAccepted(file, acceptAttr);
|
|
829
|
+
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
|
|
830
|
+
const customErrors = validator ? validator(file) : null;
|
|
831
|
+
if (accepted && sizeMatch && !customErrors) acceptedFiles.push(file);
|
|
832
|
+
else {
|
|
833
|
+
let errors = [acceptError, sizeError];
|
|
834
|
+
if (customErrors) errors = errors.concat(customErrors);
|
|
835
|
+
fileRejections.push({
|
|
836
|
+
file,
|
|
837
|
+
errors: errors.filter((e) => e)
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
if (!multiple && acceptedFiles.length > 1 || multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles) {
|
|
842
|
+
acceptedFiles.forEach((file) => {
|
|
843
|
+
fileRejections.push({
|
|
844
|
+
file,
|
|
845
|
+
errors: [TOO_MANY_FILES_REJECTION]
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
acceptedFiles.splice(0);
|
|
849
|
+
}
|
|
850
|
+
dispatch({
|
|
851
|
+
acceptedFiles,
|
|
852
|
+
fileRejections,
|
|
853
|
+
type: "setFiles"
|
|
854
|
+
});
|
|
855
|
+
if (onDrop) onDrop(acceptedFiles, fileRejections, event);
|
|
856
|
+
if (fileRejections.length > 0 && onDropRejected) onDropRejected(fileRejections, event);
|
|
857
|
+
if (acceptedFiles.length > 0 && onDropAccepted) onDropAccepted(acceptedFiles, event);
|
|
858
|
+
}, [
|
|
859
|
+
dispatch,
|
|
860
|
+
multiple,
|
|
861
|
+
acceptAttr,
|
|
862
|
+
minSize,
|
|
863
|
+
maxSize,
|
|
864
|
+
maxFiles,
|
|
865
|
+
onDrop,
|
|
866
|
+
onDropAccepted,
|
|
867
|
+
onDropRejected,
|
|
868
|
+
validator
|
|
869
|
+
]);
|
|
870
|
+
const onDropCb = (0, react.useCallback)((event) => {
|
|
871
|
+
event.preventDefault();
|
|
872
|
+
event.persist();
|
|
873
|
+
stopPropagation(event);
|
|
874
|
+
dragTargetsRef.current = [];
|
|
875
|
+
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
876
|
+
if (isPropagationStopped(event) && !noDragEventsBubbling) return;
|
|
877
|
+
setFiles(files, event);
|
|
878
|
+
}).catch((e) => onErrCb(e));
|
|
879
|
+
dispatch({ type: "reset" });
|
|
880
|
+
}, [
|
|
881
|
+
getFilesFromEvent,
|
|
882
|
+
setFiles,
|
|
883
|
+
onErrCb,
|
|
884
|
+
noDragEventsBubbling
|
|
885
|
+
]);
|
|
886
|
+
const openFileDialog = (0, react.useCallback)(() => {
|
|
887
|
+
if (fsAccessApiWorksRef.current) {
|
|
888
|
+
dispatch({ type: "openDialog" });
|
|
889
|
+
onFileDialogOpenCb();
|
|
890
|
+
const opts = {
|
|
891
|
+
multiple,
|
|
892
|
+
types: pickerTypes
|
|
893
|
+
};
|
|
894
|
+
window.showOpenFilePicker(opts).then((handles) => getFilesFromEvent(handles)).then((files) => {
|
|
895
|
+
setFiles(files, null);
|
|
896
|
+
dispatch({ type: "closeDialog" });
|
|
897
|
+
}).catch((e) => {
|
|
898
|
+
if (isAbort(e)) {
|
|
899
|
+
onFileDialogCancelCb(e);
|
|
900
|
+
dispatch({ type: "closeDialog" });
|
|
901
|
+
} else if (isSecurityError(e)) {
|
|
902
|
+
fsAccessApiWorksRef.current = false;
|
|
903
|
+
if (inputRef.current) {
|
|
904
|
+
inputRef.current.value = null;
|
|
905
|
+
inputRef.current.click();
|
|
906
|
+
} 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
|
+
} else onErrCb(e);
|
|
908
|
+
});
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (inputRef.current) {
|
|
912
|
+
dispatch({ type: "openDialog" });
|
|
913
|
+
onFileDialogOpenCb();
|
|
914
|
+
inputRef.current.value = null;
|
|
915
|
+
inputRef.current.click();
|
|
916
|
+
}
|
|
917
|
+
}, [
|
|
918
|
+
dispatch,
|
|
919
|
+
onFileDialogOpenCb,
|
|
920
|
+
onFileDialogCancelCb,
|
|
921
|
+
useFsAccessApi,
|
|
922
|
+
setFiles,
|
|
923
|
+
onErrCb,
|
|
924
|
+
pickerTypes,
|
|
925
|
+
multiple
|
|
926
|
+
]);
|
|
927
|
+
const onKeyDownCb = (0, react.useCallback)((event) => {
|
|
928
|
+
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) return;
|
|
929
|
+
if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
|
|
930
|
+
event.preventDefault();
|
|
931
|
+
openFileDialog();
|
|
932
|
+
}
|
|
933
|
+
}, [rootRef, openFileDialog]);
|
|
934
|
+
const onFocusCb = (0, react.useCallback)(() => {
|
|
935
|
+
dispatch({ type: "focus" });
|
|
936
|
+
}, []);
|
|
937
|
+
const onBlurCb = (0, react.useCallback)(() => {
|
|
938
|
+
dispatch({ type: "blur" });
|
|
939
|
+
}, []);
|
|
940
|
+
const onClickCb = (0, react.useCallback)(() => {
|
|
941
|
+
if (noClick) return;
|
|
942
|
+
if (isIeOrEdge()) setTimeout(openFileDialog, 0);
|
|
943
|
+
else openFileDialog();
|
|
944
|
+
}, [noClick, openFileDialog]);
|
|
945
|
+
const composeHandler = (fn) => {
|
|
946
|
+
return disabled ? null : fn;
|
|
947
|
+
};
|
|
948
|
+
const composeKeyboardHandler = (fn) => {
|
|
949
|
+
return noKeyboard ? null : composeHandler(fn);
|
|
950
|
+
};
|
|
951
|
+
const composeDragHandler = (fn) => {
|
|
952
|
+
return noDrag ? null : composeHandler(fn);
|
|
953
|
+
};
|
|
954
|
+
const stopPropagation = (event) => {
|
|
955
|
+
if (noDragEventsBubbling) event.stopPropagation();
|
|
956
|
+
};
|
|
957
|
+
const getRootProps = (0, react.useMemo)(() => ({ refKey = "ref", role, onKeyDown, onFocus, onBlur, onClick, onDragEnter, onDragOver, onDragLeave, onDrop, ...rest } = {}) => ({
|
|
958
|
+
onKeyDown: composeKeyboardHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
|
|
959
|
+
onFocus: composeKeyboardHandler(composeEventHandlers(onFocus, onFocusCb)),
|
|
960
|
+
onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
|
|
961
|
+
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
|
|
962
|
+
onDragEnter: composeDragHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
|
|
963
|
+
onDragOver: composeDragHandler(composeEventHandlers(onDragOver, onDragOverCb)),
|
|
964
|
+
onDragLeave: composeDragHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
|
|
965
|
+
onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
|
|
966
|
+
role: typeof role === "string" && role !== "" ? role : "presentation",
|
|
967
|
+
[refKey]: rootRef,
|
|
968
|
+
...!disabled && !noKeyboard ? { tabIndex: 0 } : {},
|
|
969
|
+
...rest
|
|
970
|
+
}), [
|
|
971
|
+
rootRef,
|
|
972
|
+
onKeyDownCb,
|
|
973
|
+
onFocusCb,
|
|
974
|
+
onBlurCb,
|
|
975
|
+
onClickCb,
|
|
976
|
+
onDragEnterCb,
|
|
977
|
+
onDragOverCb,
|
|
978
|
+
onDragLeaveCb,
|
|
979
|
+
onDropCb,
|
|
980
|
+
noKeyboard,
|
|
981
|
+
noDrag,
|
|
982
|
+
disabled
|
|
983
|
+
]);
|
|
984
|
+
const onInputElementClick = (0, react.useCallback)((event) => {
|
|
985
|
+
event.stopPropagation();
|
|
986
|
+
}, []);
|
|
987
|
+
const getInputProps = (0, react.useMemo)(() => ({ refKey = "ref", onChange, onClick, ...rest } = {}) => {
|
|
988
|
+
return {
|
|
989
|
+
accept: acceptAttr,
|
|
990
|
+
multiple,
|
|
991
|
+
type: "file",
|
|
992
|
+
style: {
|
|
993
|
+
border: 0,
|
|
994
|
+
clip: "rect(0, 0, 0, 0)",
|
|
995
|
+
clipPath: "inset(50%)",
|
|
996
|
+
height: "1px",
|
|
997
|
+
margin: "0 -1px -1px 0",
|
|
998
|
+
overflow: "hidden",
|
|
999
|
+
padding: 0,
|
|
1000
|
+
position: "absolute",
|
|
1001
|
+
width: "1px",
|
|
1002
|
+
whiteSpace: "nowrap"
|
|
1003
|
+
},
|
|
1004
|
+
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
|
|
1005
|
+
onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
|
|
1006
|
+
tabIndex: -1,
|
|
1007
|
+
[refKey]: inputRef,
|
|
1008
|
+
...rest
|
|
1009
|
+
};
|
|
1010
|
+
}, [
|
|
1011
|
+
inputRef,
|
|
1012
|
+
accept,
|
|
1013
|
+
multiple,
|
|
1014
|
+
onDropCb,
|
|
1015
|
+
disabled
|
|
1016
|
+
]);
|
|
1017
|
+
return {
|
|
1018
|
+
...state,
|
|
1019
|
+
isFocused: isFocused && !disabled,
|
|
1020
|
+
getRootProps,
|
|
1021
|
+
getInputProps,
|
|
1022
|
+
rootRef,
|
|
1023
|
+
inputRef,
|
|
1024
|
+
open: composeHandler(openFileDialog)
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* @param {DropzoneState} state
|
|
1029
|
+
* @param {{type: string} & DropzoneState} action
|
|
1030
|
+
* @returns {DropzoneState}
|
|
1031
|
+
*/
|
|
1032
|
+
function reducer(state, action) {
|
|
1033
|
+
/* istanbul ignore next */
|
|
1034
|
+
switch (action.type) {
|
|
1035
|
+
case "focus": return {
|
|
1036
|
+
...state,
|
|
1037
|
+
isFocused: true
|
|
1038
|
+
};
|
|
1039
|
+
case "blur": return {
|
|
1040
|
+
...state,
|
|
1041
|
+
isFocused: false
|
|
1042
|
+
};
|
|
1043
|
+
case "openDialog": return {
|
|
1044
|
+
...initialState,
|
|
1045
|
+
isFileDialogActive: true
|
|
1046
|
+
};
|
|
1047
|
+
case "closeDialog": return {
|
|
1048
|
+
...state,
|
|
1049
|
+
isFileDialogActive: false
|
|
1050
|
+
};
|
|
1051
|
+
case "setDraggedFiles": return {
|
|
1052
|
+
...state,
|
|
1053
|
+
isDragActive: action.isDragActive,
|
|
1054
|
+
isDragAccept: action.isDragAccept,
|
|
1055
|
+
isDragReject: action.isDragReject
|
|
1056
|
+
};
|
|
1057
|
+
case "setFiles": return {
|
|
1058
|
+
...state,
|
|
1059
|
+
acceptedFiles: action.acceptedFiles,
|
|
1060
|
+
fileRejections: action.fileRejections,
|
|
1061
|
+
isDragReject: false
|
|
1062
|
+
};
|
|
1063
|
+
case "setDragGlobal": return {
|
|
1064
|
+
...state,
|
|
1065
|
+
isDragGlobal: action.isDragGlobal
|
|
1066
|
+
};
|
|
1067
|
+
case "reset": return { ...initialState };
|
|
1068
|
+
default: return state;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function noop() {}
|
|
1072
|
+
//#endregion
|
|
1073
|
+
exports.ErrorCode = ErrorCode;
|
|
1074
|
+
exports.default = Dropzone;
|
|
1075
|
+
exports.useDropzone = useDropzone;
|
|
1076
|
+
|
|
1077
|
+
//# sourceMappingURL=index.cjs.map
|