react-dropzone 15.0.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -114
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +656 -2
- package/dist/index.js.map +1 -0
- package/package.json +76 -177
- package/src/index.tsx +774 -0
- package/src/utils/index.ts +305 -0
- 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 -9
- package/commitlint.config.js +0 -1
- package/dist/es/index.js +0 -1051
- 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.js +0 -1103
- package/src/index.spec.js +0 -3838
- package/src/utils/index.js +0 -362
- 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/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 -22
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
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 file_selector = require("file-selector");
|
|
28
|
+
let react = require("react");
|
|
29
|
+
let attr_accept = require("attr-accept");
|
|
30
|
+
attr_accept = __toESM(attr_accept, 1);
|
|
31
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
//#region src/utils/index.ts
|
|
33
|
+
const accepts = typeof attr_accept.default === "function" ? attr_accept.default : attr_accept.default.default;
|
|
34
|
+
const FILE_INVALID_TYPE = "file-invalid-type";
|
|
35
|
+
const FILE_TOO_LARGE = "file-too-large";
|
|
36
|
+
const FILE_TOO_SMALL = "file-too-small";
|
|
37
|
+
const TOO_MANY_FILES = "too-many-files";
|
|
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 = "") {
|
|
46
|
+
const acceptArr = accept.split(",");
|
|
47
|
+
const msg = acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
|
|
48
|
+
return {
|
|
49
|
+
code: FILE_INVALID_TYPE,
|
|
50
|
+
message: `File type must be ${msg}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getTooLargeRejectionErr(maxSize) {
|
|
54
|
+
return {
|
|
55
|
+
code: FILE_TOO_LARGE,
|
|
56
|
+
message: `File is larger than ${maxSize} ${maxSize === 1 ? "byte" : "bytes"}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getTooSmallRejectionErr(minSize) {
|
|
60
|
+
return {
|
|
61
|
+
code: FILE_TOO_SMALL,
|
|
62
|
+
message: `File is smaller than ${minSize} ${minSize === 1 ? "byte" : "bytes"}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const TOO_MANY_FILES_REJECTION = {
|
|
66
|
+
code: TOO_MANY_FILES,
|
|
67
|
+
message: "Too many files"
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Check if the given file is a DataTransferItem with an empty type.
|
|
71
|
+
*
|
|
72
|
+
* During drag events, browsers may return DataTransferItem objects instead of File objects.
|
|
73
|
+
* Some browsers (e.g., Chrome) return an empty MIME type for certain file types (like .md files)
|
|
74
|
+
* on DataTransferItem during drag events, even though the type is correctly set during drop.
|
|
75
|
+
*/
|
|
76
|
+
function isDataTransferItemWithEmptyType(file) {
|
|
77
|
+
return file.type === "" && typeof file.getAsFile === "function";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if file is accepted.
|
|
81
|
+
*
|
|
82
|
+
* Firefox versions prior to 53 return a bogus MIME type for every file drag,
|
|
83
|
+
* so dragovers with that MIME type will always be accepted.
|
|
84
|
+
*
|
|
85
|
+
* Chrome/other browsers may return an empty MIME type for files during drag events,
|
|
86
|
+
* so we accept those as well (we'll validate properly on drop).
|
|
87
|
+
*/
|
|
88
|
+
function fileAccepted(file, accept) {
|
|
89
|
+
const isAcceptable = file.type === "application/x-moz-file" || accepts(file, accept ?? "") || isDataTransferItemWithEmptyType(file);
|
|
90
|
+
return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
|
|
91
|
+
}
|
|
92
|
+
function fileMatchSize(file, minSize, maxSize) {
|
|
93
|
+
if (isDefined(file.size)) {
|
|
94
|
+
if (isDefined(minSize) && isDefined(maxSize)) {
|
|
95
|
+
if (file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
|
|
96
|
+
if (file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
|
|
97
|
+
} else if (isDefined(minSize) && file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
|
|
98
|
+
else if (isDefined(maxSize) && file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
|
|
99
|
+
}
|
|
100
|
+
return [true, null];
|
|
101
|
+
}
|
|
102
|
+
function isDefined(value) {
|
|
103
|
+
return value !== void 0 && value !== null;
|
|
104
|
+
}
|
|
105
|
+
function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles = 0, validator }) {
|
|
106
|
+
if (!multiple && files.length > 1 || multiple && maxFiles >= 1 && files.length > maxFiles) return false;
|
|
107
|
+
return files.every((file) => {
|
|
108
|
+
const [accepted] = fileAccepted(file, accept);
|
|
109
|
+
const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
|
|
110
|
+
const customErrors = validator ? validator(file) : null;
|
|
111
|
+
return accepted && sizeMatch && !customErrors;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function isPropagationStopped(event) {
|
|
115
|
+
if (typeof event.isPropagationStopped === "function") return event.isPropagationStopped();
|
|
116
|
+
else if (typeof event.cancelBubble !== "undefined") return event.cancelBubble;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function isEvtWithFiles(event) {
|
|
120
|
+
if (!event.dataTransfer) return !!event.target && !!event.target.files;
|
|
121
|
+
return Array.prototype.some.call(event.dataTransfer.types, (type) => type === "Files" || type === "application/x-moz-file");
|
|
122
|
+
}
|
|
123
|
+
function onDocumentDragOver(event) {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
}
|
|
126
|
+
function isIe(userAgent) {
|
|
127
|
+
return userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1;
|
|
128
|
+
}
|
|
129
|
+
function isEdge(userAgent) {
|
|
130
|
+
return userAgent.indexOf("Edge/") !== -1;
|
|
131
|
+
}
|
|
132
|
+
function isIeOrEdge(userAgent = window.navigator.userAgent) {
|
|
133
|
+
return isIe(userAgent) || isEdge(userAgent);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* This is intended to be used to compose event handlers.
|
|
137
|
+
* They are executed in order until one of them calls `event.isPropagationStopped()`.
|
|
138
|
+
* Note that the check is done on the first invoke too,
|
|
139
|
+
* meaning that if propagation was stopped before invoking the fns,
|
|
140
|
+
* no handlers will be executed.
|
|
141
|
+
*/
|
|
142
|
+
function composeEventHandlers(...fns) {
|
|
143
|
+
return (event, ...args) => fns.some((fn) => {
|
|
144
|
+
if (!isPropagationStopped(event) && fn) fn(event, ...args);
|
|
145
|
+
return isPropagationStopped(event);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* canUseFileSystemAccessAPI checks if the File System Access API is supported by the browser.
|
|
150
|
+
*/
|
|
151
|
+
function canUseFileSystemAccessAPI() {
|
|
152
|
+
return "showOpenFilePicker" in window;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Convert the `{accept}` dropzone prop to the `{types}` option for showOpenFilePicker.
|
|
156
|
+
*/
|
|
157
|
+
function pickerOptionsFromAccept(accept) {
|
|
158
|
+
if (isDefined(accept)) return [{
|
|
159
|
+
description: "Files",
|
|
160
|
+
accept: Object.entries(accept).filter(([mimeType, ext]) => {
|
|
161
|
+
let ok = true;
|
|
162
|
+
if (!isMIMEType(mimeType)) {
|
|
163
|
+
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.`);
|
|
164
|
+
ok = false;
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(ext) || !ext.every(isExt)) {
|
|
167
|
+
console.warn(`Skipped "${mimeType}" because an invalid file extension was provided.`);
|
|
168
|
+
ok = false;
|
|
169
|
+
}
|
|
170
|
+
return ok;
|
|
171
|
+
}).reduce((agg, [mimeType, ext]) => {
|
|
172
|
+
agg[mimeType] = ext;
|
|
173
|
+
return agg;
|
|
174
|
+
}, {})
|
|
175
|
+
}];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
|
|
179
|
+
*/
|
|
180
|
+
function acceptPropAsAcceptAttr(accept) {
|
|
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(",");
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
|
|
188
|
+
*/
|
|
189
|
+
function isAbort(v) {
|
|
190
|
+
return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if v is a security error.
|
|
194
|
+
*/
|
|
195
|
+
function isSecurityError(v) {
|
|
196
|
+
return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check if v is a MIME type string.
|
|
200
|
+
*/
|
|
201
|
+
function isMIMEType(v) {
|
|
202
|
+
return v === "audio/*" || v === "video/*" || v === "image/*" || v === "text/*" || v === "application/*" || /\w+\/[-+.\w]+/g.test(v);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if v is a file extension.
|
|
206
|
+
*/
|
|
207
|
+
function isExt(v) {
|
|
208
|
+
return /^.*\.[\w]+$/.test(v);
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/index.tsx
|
|
212
|
+
/**
|
|
213
|
+
* Convenience wrapper component for the `useDropzone` hook
|
|
214
|
+
*
|
|
215
|
+
* ```jsx
|
|
216
|
+
* <Dropzone>
|
|
217
|
+
* {({getRootProps, getInputProps}) => (
|
|
218
|
+
* <div {...getRootProps()}>
|
|
219
|
+
* <input {...getInputProps()} />
|
|
220
|
+
* <p>Drag 'n' drop some files here, or click to select files</p>
|
|
221
|
+
* </div>
|
|
222
|
+
* )}
|
|
223
|
+
* </Dropzone>
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
const Dropzone = (0, react.forwardRef)(({ children, ...params }, ref) => {
|
|
227
|
+
const { open, ...props } = useDropzone(params);
|
|
228
|
+
(0, react.useImperativeHandle)(ref, () => ({ open }), [open]);
|
|
229
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: children?.({
|
|
230
|
+
...props,
|
|
231
|
+
open
|
|
232
|
+
}) });
|
|
233
|
+
});
|
|
234
|
+
Dropzone.displayName = "Dropzone";
|
|
235
|
+
const initialState = {
|
|
236
|
+
isFocused: false,
|
|
237
|
+
isFileDialogActive: false,
|
|
238
|
+
isDragActive: false,
|
|
239
|
+
isDragAccept: false,
|
|
240
|
+
isDragReject: false,
|
|
241
|
+
isDragGlobal: false,
|
|
242
|
+
acceptedFiles: [],
|
|
243
|
+
fileRejections: []
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* A React hook that creates a drag 'n' drop area.
|
|
247
|
+
*
|
|
248
|
+
* ```jsx
|
|
249
|
+
* function MyDropzone(props) {
|
|
250
|
+
* const {getRootProps, getInputProps} = useDropzone({
|
|
251
|
+
* onDrop: acceptedFiles => {
|
|
252
|
+
* // do something with the File objects, e.g. upload to some server
|
|
253
|
+
* }
|
|
254
|
+
* });
|
|
255
|
+
* return (
|
|
256
|
+
* <div {...getRootProps()}>
|
|
257
|
+
* <input {...getInputProps()} />
|
|
258
|
+
* <p>Drag and drop some files here, or click to select files</p>
|
|
259
|
+
* </div>
|
|
260
|
+
* )
|
|
261
|
+
* }
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
function useDropzone(props = {}) {
|
|
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;
|
|
266
|
+
const acceptAttr = (0, react.useMemo)(() => acceptPropAsAcceptAttr(accept), [accept]);
|
|
267
|
+
const pickerTypes = (0, react.useMemo)(() => pickerOptionsFromAccept(accept), [accept]);
|
|
268
|
+
const onFileDialogOpenCb = (0, react.useMemo)(() => typeof onFileDialogOpen === "function" ? onFileDialogOpen : noop, [onFileDialogOpen]);
|
|
269
|
+
const onFileDialogCancelCb = (0, react.useMemo)(() => typeof onFileDialogCancel === "function" ? onFileDialogCancel : noop, [onFileDialogCancel]);
|
|
270
|
+
const rootRef = (0, react.useRef)(null);
|
|
271
|
+
const inputRef = (0, react.useRef)(null);
|
|
272
|
+
const [state, dispatch] = (0, react.useReducer)(reducer, initialState);
|
|
273
|
+
const { isFocused, isFileDialogActive } = state;
|
|
274
|
+
const fsAccessApiWorksRef = (0, react.useRef)(typeof window !== "undefined" && window.isSecureContext && useFsAccessApi && canUseFileSystemAccessAPI());
|
|
275
|
+
const onWindowFocus = () => {
|
|
276
|
+
if (!fsAccessApiWorksRef.current && isFileDialogActive) setTimeout(() => {
|
|
277
|
+
if (inputRef.current) {
|
|
278
|
+
const { files } = inputRef.current;
|
|
279
|
+
if (!files?.length) {
|
|
280
|
+
dispatch({ type: "closeDialog" });
|
|
281
|
+
onFileDialogCancelCb();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}, 300);
|
|
285
|
+
};
|
|
286
|
+
(0, react.useEffect)(() => {
|
|
287
|
+
window.addEventListener("focus", onWindowFocus, false);
|
|
288
|
+
return () => {
|
|
289
|
+
window.removeEventListener("focus", onWindowFocus, false);
|
|
290
|
+
};
|
|
291
|
+
}, [
|
|
292
|
+
inputRef,
|
|
293
|
+
isFileDialogActive,
|
|
294
|
+
onFileDialogCancelCb,
|
|
295
|
+
fsAccessApiWorksRef
|
|
296
|
+
]);
|
|
297
|
+
const dragTargetsRef = (0, react.useRef)([]);
|
|
298
|
+
const globalDragTargetsRef = (0, react.useRef)([]);
|
|
299
|
+
const onDocumentDrop = (event) => {
|
|
300
|
+
if (rootRef.current && event.target && rootRef.current.contains(event.target)) return;
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
dragTargetsRef.current = [];
|
|
303
|
+
};
|
|
304
|
+
(0, react.useEffect)(() => {
|
|
305
|
+
if (preventDropOnDocument) {
|
|
306
|
+
document.addEventListener("dragover", onDocumentDragOver, false);
|
|
307
|
+
document.addEventListener("drop", onDocumentDrop, false);
|
|
308
|
+
}
|
|
309
|
+
return () => {
|
|
310
|
+
if (preventDropOnDocument) {
|
|
311
|
+
document.removeEventListener("dragover", onDocumentDragOver);
|
|
312
|
+
document.removeEventListener("drop", onDocumentDrop);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}, [rootRef, preventDropOnDocument]);
|
|
316
|
+
(0, react.useEffect)(() => {
|
|
317
|
+
const onDocumentDragEnter = (event) => {
|
|
318
|
+
if (event.target) globalDragTargetsRef.current = [...globalDragTargetsRef.current, event.target];
|
|
319
|
+
if (isEvtWithFiles(event)) dispatch({
|
|
320
|
+
isDragGlobal: true,
|
|
321
|
+
type: "setDragGlobal"
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
const onDocumentDragLeave = (event) => {
|
|
325
|
+
globalDragTargetsRef.current = globalDragTargetsRef.current.filter((el) => el !== event.target && el !== null);
|
|
326
|
+
if (globalDragTargetsRef.current.length > 0) return;
|
|
327
|
+
dispatch({
|
|
328
|
+
isDragGlobal: false,
|
|
329
|
+
type: "setDragGlobal"
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const onDocumentDragEnd = () => {
|
|
333
|
+
globalDragTargetsRef.current = [];
|
|
334
|
+
dispatch({
|
|
335
|
+
isDragGlobal: false,
|
|
336
|
+
type: "setDragGlobal"
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
const onDocumentDropGlobal = () => {
|
|
340
|
+
globalDragTargetsRef.current = [];
|
|
341
|
+
dispatch({
|
|
342
|
+
isDragGlobal: false,
|
|
343
|
+
type: "setDragGlobal"
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
document.addEventListener("dragenter", onDocumentDragEnter, false);
|
|
347
|
+
document.addEventListener("dragleave", onDocumentDragLeave, false);
|
|
348
|
+
document.addEventListener("dragend", onDocumentDragEnd, false);
|
|
349
|
+
document.addEventListener("drop", onDocumentDropGlobal, false);
|
|
350
|
+
return () => {
|
|
351
|
+
document.removeEventListener("dragenter", onDocumentDragEnter);
|
|
352
|
+
document.removeEventListener("dragleave", onDocumentDragLeave);
|
|
353
|
+
document.removeEventListener("dragend", onDocumentDragEnd);
|
|
354
|
+
document.removeEventListener("drop", onDocumentDropGlobal);
|
|
355
|
+
};
|
|
356
|
+
}, [rootRef]);
|
|
357
|
+
(0, react.useEffect)(() => {
|
|
358
|
+
if (!disabled && autoFocus && rootRef.current) rootRef.current.focus();
|
|
359
|
+
return () => {};
|
|
360
|
+
}, [
|
|
361
|
+
rootRef,
|
|
362
|
+
autoFocus,
|
|
363
|
+
disabled
|
|
364
|
+
]);
|
|
365
|
+
const onErrCb = (0, react.useCallback)((e) => {
|
|
366
|
+
if (onError) onError(e);
|
|
367
|
+
else console.error(e);
|
|
368
|
+
}, [onError]);
|
|
369
|
+
const onDragEnterCb = (0, react.useCallback)((event) => {
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
event.persist?.();
|
|
372
|
+
stopPropagation(event);
|
|
373
|
+
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
374
|
+
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
375
|
+
if (isPropagationStopped(event) && !noDragEventsBubbling) return;
|
|
376
|
+
const fileCount = files.length;
|
|
377
|
+
const isDragAccept = fileCount > 0 && allFilesAccepted({
|
|
378
|
+
files,
|
|
379
|
+
accept: acceptAttr,
|
|
380
|
+
minSize,
|
|
381
|
+
maxSize,
|
|
382
|
+
multiple,
|
|
383
|
+
maxFiles,
|
|
384
|
+
validator
|
|
385
|
+
});
|
|
386
|
+
dispatch({
|
|
387
|
+
isDragAccept,
|
|
388
|
+
isDragReject: fileCount > 0 && !isDragAccept,
|
|
389
|
+
isDragActive: true,
|
|
390
|
+
type: "setDraggedFiles"
|
|
391
|
+
});
|
|
392
|
+
if (onDragEnter) onDragEnter(event);
|
|
393
|
+
}).catch((e) => onErrCb(e));
|
|
394
|
+
}, [
|
|
395
|
+
getFilesFromEvent,
|
|
396
|
+
onDragEnter,
|
|
397
|
+
onErrCb,
|
|
398
|
+
noDragEventsBubbling,
|
|
399
|
+
acceptAttr,
|
|
400
|
+
minSize,
|
|
401
|
+
maxSize,
|
|
402
|
+
multiple,
|
|
403
|
+
maxFiles,
|
|
404
|
+
validator
|
|
405
|
+
]);
|
|
406
|
+
const onDragOverCb = (0, react.useCallback)((event) => {
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
event.persist?.();
|
|
409
|
+
stopPropagation(event);
|
|
410
|
+
const hasFiles = isEvtWithFiles(event);
|
|
411
|
+
if (hasFiles && event.dataTransfer) try {
|
|
412
|
+
event.dataTransfer.dropEffect = "copy";
|
|
413
|
+
} catch {}
|
|
414
|
+
if (hasFiles && onDragOver) onDragOver(event);
|
|
415
|
+
return false;
|
|
416
|
+
}, [onDragOver, noDragEventsBubbling]);
|
|
417
|
+
const onDragLeaveCb = (0, react.useCallback)((event) => {
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
event.persist?.();
|
|
420
|
+
stopPropagation(event);
|
|
421
|
+
const targets = dragTargetsRef.current.filter((target) => rootRef.current?.contains(target));
|
|
422
|
+
const targetIdx = targets.indexOf(event.target);
|
|
423
|
+
if (targetIdx !== -1) targets.splice(targetIdx, 1);
|
|
424
|
+
dragTargetsRef.current = targets;
|
|
425
|
+
if (targets.length > 0) return;
|
|
426
|
+
dispatch({
|
|
427
|
+
type: "setDraggedFiles",
|
|
428
|
+
isDragActive: false,
|
|
429
|
+
isDragAccept: false,
|
|
430
|
+
isDragReject: false
|
|
431
|
+
});
|
|
432
|
+
if (isEvtWithFiles(event) && onDragLeave) onDragLeave(event);
|
|
433
|
+
}, [
|
|
434
|
+
rootRef,
|
|
435
|
+
onDragLeave,
|
|
436
|
+
noDragEventsBubbling
|
|
437
|
+
]);
|
|
438
|
+
const setFiles = (0, react.useCallback)((files, event) => {
|
|
439
|
+
const acceptedFiles = [];
|
|
440
|
+
const fileRejections = [];
|
|
441
|
+
files.forEach((file) => {
|
|
442
|
+
const [accepted, acceptError] = fileAccepted(file, acceptAttr);
|
|
443
|
+
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
|
|
444
|
+
const customErrors = validator ? validator(file) : null;
|
|
445
|
+
if (accepted && sizeMatch && !customErrors) acceptedFiles.push(file);
|
|
446
|
+
else {
|
|
447
|
+
let errors = [acceptError, sizeError];
|
|
448
|
+
if (customErrors) errors = errors.concat(customErrors);
|
|
449
|
+
fileRejections.push({
|
|
450
|
+
file,
|
|
451
|
+
errors: errors.filter((e) => e != null)
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
if (!multiple && acceptedFiles.length > 1 || multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles) {
|
|
456
|
+
acceptedFiles.forEach((file) => {
|
|
457
|
+
fileRejections.push({
|
|
458
|
+
file,
|
|
459
|
+
errors: [TOO_MANY_FILES_REJECTION]
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
acceptedFiles.splice(0);
|
|
463
|
+
}
|
|
464
|
+
dispatch({
|
|
465
|
+
acceptedFiles,
|
|
466
|
+
fileRejections,
|
|
467
|
+
type: "setFiles"
|
|
468
|
+
});
|
|
469
|
+
if (onDrop) onDrop(acceptedFiles, fileRejections, event);
|
|
470
|
+
if (fileRejections.length > 0 && onDropRejected) onDropRejected(fileRejections, event);
|
|
471
|
+
if (acceptedFiles.length > 0 && onDropAccepted) onDropAccepted(acceptedFiles, event);
|
|
472
|
+
}, [
|
|
473
|
+
dispatch,
|
|
474
|
+
multiple,
|
|
475
|
+
acceptAttr,
|
|
476
|
+
minSize,
|
|
477
|
+
maxSize,
|
|
478
|
+
maxFiles,
|
|
479
|
+
onDrop,
|
|
480
|
+
onDropAccepted,
|
|
481
|
+
onDropRejected,
|
|
482
|
+
validator
|
|
483
|
+
]);
|
|
484
|
+
const onDropCb = (0, react.useCallback)((event) => {
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
event.persist?.();
|
|
487
|
+
stopPropagation(event);
|
|
488
|
+
dragTargetsRef.current = [];
|
|
489
|
+
if (isEvtWithFiles(event)) Promise.resolve(getFilesFromEvent(event)).then((files) => {
|
|
490
|
+
if (isPropagationStopped(event) && !noDragEventsBubbling) return;
|
|
491
|
+
setFiles(files, event);
|
|
492
|
+
}).catch((e) => onErrCb(e));
|
|
493
|
+
dispatch({ type: "reset" });
|
|
494
|
+
}, [
|
|
495
|
+
getFilesFromEvent,
|
|
496
|
+
setFiles,
|
|
497
|
+
onErrCb,
|
|
498
|
+
noDragEventsBubbling
|
|
499
|
+
]);
|
|
500
|
+
const openFileDialog = (0, react.useCallback)(() => {
|
|
501
|
+
if (fsAccessApiWorksRef.current) {
|
|
502
|
+
dispatch({ type: "openDialog" });
|
|
503
|
+
onFileDialogOpenCb();
|
|
504
|
+
const opts = {
|
|
505
|
+
multiple,
|
|
506
|
+
types: pickerTypes
|
|
507
|
+
};
|
|
508
|
+
window.showOpenFilePicker(opts).then((handles) => getFilesFromEvent(handles)).then((files) => {
|
|
509
|
+
setFiles(files, null);
|
|
510
|
+
dispatch({ type: "closeDialog" });
|
|
511
|
+
}).catch((e) => {
|
|
512
|
+
if (isAbort(e)) {
|
|
513
|
+
onFileDialogCancelCb(e);
|
|
514
|
+
dispatch({ type: "closeDialog" });
|
|
515
|
+
} else if (isSecurityError(e)) {
|
|
516
|
+
fsAccessApiWorksRef.current = false;
|
|
517
|
+
if (inputRef.current) {
|
|
518
|
+
inputRef.current.value = "";
|
|
519
|
+
inputRef.current.click();
|
|
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."));
|
|
521
|
+
} else onErrCb(e);
|
|
522
|
+
});
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (inputRef.current) {
|
|
526
|
+
dispatch({ type: "openDialog" });
|
|
527
|
+
onFileDialogOpenCb();
|
|
528
|
+
inputRef.current.value = "";
|
|
529
|
+
inputRef.current.click();
|
|
530
|
+
}
|
|
531
|
+
}, [
|
|
532
|
+
dispatch,
|
|
533
|
+
onFileDialogOpenCb,
|
|
534
|
+
onFileDialogCancelCb,
|
|
535
|
+
useFsAccessApi,
|
|
536
|
+
setFiles,
|
|
537
|
+
onErrCb,
|
|
538
|
+
pickerTypes,
|
|
539
|
+
multiple
|
|
540
|
+
]);
|
|
541
|
+
const onKeyDownCb = (0, react.useCallback)((event) => {
|
|
542
|
+
if (!rootRef.current?.isEqualNode(event.target)) return;
|
|
543
|
+
if (event.key === " " || event.key === "Enter" || event.keyCode === 32 || event.keyCode === 13) {
|
|
544
|
+
event.preventDefault();
|
|
545
|
+
openFileDialog();
|
|
546
|
+
}
|
|
547
|
+
}, [rootRef, openFileDialog]);
|
|
548
|
+
const onFocusCb = (0, react.useCallback)(() => {
|
|
549
|
+
dispatch({ type: "focus" });
|
|
550
|
+
}, []);
|
|
551
|
+
const onBlurCb = (0, react.useCallback)(() => {
|
|
552
|
+
dispatch({ type: "blur" });
|
|
553
|
+
}, []);
|
|
554
|
+
const onClickCb = (0, react.useCallback)(() => {
|
|
555
|
+
if (noClick) return;
|
|
556
|
+
if (isIeOrEdge()) setTimeout(openFileDialog, 0);
|
|
557
|
+
else openFileDialog();
|
|
558
|
+
}, [noClick, openFileDialog]);
|
|
559
|
+
const composeHandler = (fn) => {
|
|
560
|
+
return disabled ? null : fn;
|
|
561
|
+
};
|
|
562
|
+
const composeKeyboardHandler = (fn) => {
|
|
563
|
+
return noKeyboard ? null : composeHandler(fn);
|
|
564
|
+
};
|
|
565
|
+
const composeDragHandler = (fn) => {
|
|
566
|
+
return noDrag ? null : composeHandler(fn);
|
|
567
|
+
};
|
|
568
|
+
const stopPropagation = (event) => {
|
|
569
|
+
if (noDragEventsBubbling) event.stopPropagation();
|
|
570
|
+
};
|
|
571
|
+
const getRootProps = (0, react.useMemo)(() => ({ refKey = "ref", role, onKeyDown, onFocus, onBlur, onClick, onDragEnter, onDragOver, onDragLeave, onDrop, ...rest } = {}) => ({
|
|
572
|
+
onKeyDown: composeKeyboardHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
|
|
573
|
+
onFocus: composeKeyboardHandler(composeEventHandlers(onFocus, onFocusCb)),
|
|
574
|
+
onBlur: composeKeyboardHandler(composeEventHandlers(onBlur, onBlurCb)),
|
|
575
|
+
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
|
|
576
|
+
onDragEnter: composeDragHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
|
|
577
|
+
onDragOver: composeDragHandler(composeEventHandlers(onDragOver, onDragOverCb)),
|
|
578
|
+
onDragLeave: composeDragHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
|
|
579
|
+
onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
|
|
580
|
+
role: typeof role === "string" && role !== "" ? role : "presentation",
|
|
581
|
+
[refKey]: rootRef,
|
|
582
|
+
...!disabled && !noKeyboard ? { tabIndex: 0 } : {},
|
|
583
|
+
...rest
|
|
584
|
+
}), [
|
|
585
|
+
rootRef,
|
|
586
|
+
onKeyDownCb,
|
|
587
|
+
onFocusCb,
|
|
588
|
+
onBlurCb,
|
|
589
|
+
onClickCb,
|
|
590
|
+
onDragEnterCb,
|
|
591
|
+
onDragOverCb,
|
|
592
|
+
onDragLeaveCb,
|
|
593
|
+
onDropCb,
|
|
594
|
+
noKeyboard,
|
|
595
|
+
noDrag,
|
|
596
|
+
disabled
|
|
597
|
+
]);
|
|
598
|
+
const onInputElementClick = (0, react.useCallback)((event) => {
|
|
599
|
+
event.stopPropagation();
|
|
600
|
+
}, []);
|
|
601
|
+
const getInputProps = (0, react.useMemo)(() => ({ refKey = "ref", onChange, onClick, ...rest } = {}) => {
|
|
602
|
+
return {
|
|
603
|
+
accept: acceptAttr,
|
|
604
|
+
multiple,
|
|
605
|
+
type: "file",
|
|
606
|
+
style: {
|
|
607
|
+
border: 0,
|
|
608
|
+
clip: "rect(0, 0, 0, 0)",
|
|
609
|
+
clipPath: "inset(50%)",
|
|
610
|
+
height: "1px",
|
|
611
|
+
margin: "0 -1px -1px 0",
|
|
612
|
+
overflow: "hidden",
|
|
613
|
+
padding: 0,
|
|
614
|
+
position: "absolute",
|
|
615
|
+
width: "1px",
|
|
616
|
+
whiteSpace: "nowrap"
|
|
617
|
+
},
|
|
618
|
+
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
|
|
619
|
+
onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
|
|
620
|
+
tabIndex: -1,
|
|
621
|
+
[refKey]: inputRef,
|
|
622
|
+
...rest
|
|
623
|
+
};
|
|
624
|
+
}, [
|
|
625
|
+
inputRef,
|
|
626
|
+
accept,
|
|
627
|
+
multiple,
|
|
628
|
+
onDropCb,
|
|
629
|
+
disabled
|
|
630
|
+
]);
|
|
631
|
+
return {
|
|
632
|
+
...state,
|
|
633
|
+
isFocused: isFocused && !disabled,
|
|
634
|
+
getRootProps,
|
|
635
|
+
getInputProps,
|
|
636
|
+
rootRef,
|
|
637
|
+
inputRef,
|
|
638
|
+
open: composeHandler(openFileDialog)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function reducer(state, action) {
|
|
642
|
+
switch (action.type) {
|
|
643
|
+
case "focus": return {
|
|
644
|
+
...state,
|
|
645
|
+
isFocused: true
|
|
646
|
+
};
|
|
647
|
+
case "blur": return {
|
|
648
|
+
...state,
|
|
649
|
+
isFocused: false
|
|
650
|
+
};
|
|
651
|
+
case "openDialog": return {
|
|
652
|
+
...initialState,
|
|
653
|
+
isFileDialogActive: true
|
|
654
|
+
};
|
|
655
|
+
case "closeDialog": return {
|
|
656
|
+
...state,
|
|
657
|
+
isFileDialogActive: false
|
|
658
|
+
};
|
|
659
|
+
case "setDraggedFiles": return {
|
|
660
|
+
...state,
|
|
661
|
+
isDragActive: action.isDragActive,
|
|
662
|
+
isDragAccept: action.isDragAccept,
|
|
663
|
+
isDragReject: action.isDragReject
|
|
664
|
+
};
|
|
665
|
+
case "setFiles": return {
|
|
666
|
+
...state,
|
|
667
|
+
acceptedFiles: action.acceptedFiles,
|
|
668
|
+
fileRejections: action.fileRejections,
|
|
669
|
+
isDragReject: false
|
|
670
|
+
};
|
|
671
|
+
case "setDragGlobal": return {
|
|
672
|
+
...state,
|
|
673
|
+
isDragGlobal: action.isDragGlobal
|
|
674
|
+
};
|
|
675
|
+
case "reset": return { ...initialState };
|
|
676
|
+
default: return state;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function noop() {}
|
|
680
|
+
//#endregion
|
|
681
|
+
exports.ErrorCode = ErrorCode;
|
|
682
|
+
exports.default = Dropzone;
|
|
683
|
+
exports.useDropzone = useDropzone;
|
|
684
|
+
|
|
685
|
+
//# sourceMappingURL=index.cjs.map
|