react-dropzone 16.0.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ import attrAccept from "attr-accept";
2
+
3
+ // attr-accept ships as a CommonJS module (`module.exports = { __esModule: true, default: fn }`).
4
+ // Bundler interop surfaces its default export inconsistently — as the function under Node/Vitest,
5
+ // but as `{ default: fn }` in some browser bundles. Normalize to the function.
6
+ const accepts =
7
+ typeof attrAccept === "function" ? attrAccept : (attrAccept as unknown as {default: typeof attrAccept}).default;
8
+
9
+ /**
10
+ * A map of accepted MIME types to file extensions, as passed to the `accept` prop.
11
+ */
12
+ export interface Accept {
13
+ [key: string]: readonly string[];
14
+ }
15
+
16
+ /**
17
+ * A file rejection error.
18
+ */
19
+ export interface FileError {
20
+ message: string;
21
+ code: ErrorCode | string;
22
+ }
23
+
24
+ // Error codes
25
+ export const FILE_INVALID_TYPE = "file-invalid-type";
26
+ export const FILE_TOO_LARGE = "file-too-large";
27
+ export const FILE_TOO_SMALL = "file-too-small";
28
+ export const TOO_MANY_FILES = "too-many-files";
29
+
30
+ export enum ErrorCode {
31
+ FileInvalidType = "file-invalid-type",
32
+ FileTooLarge = "file-too-large",
33
+ FileTooSmall = "file-too-small",
34
+ TooManyFiles = "too-many-files"
35
+ }
36
+
37
+ export function getInvalidTypeRejectionErr(accept: string = ""): FileError {
38
+ const acceptArr = accept.split(",");
39
+ const msg = acceptArr.length > 1 ? `one of ${acceptArr.join(", ")}` : acceptArr[0];
40
+
41
+ return {
42
+ code: FILE_INVALID_TYPE,
43
+ message: `File type must be ${msg}`
44
+ };
45
+ }
46
+
47
+ export function getTooLargeRejectionErr(maxSize: number): FileError {
48
+ return {
49
+ code: FILE_TOO_LARGE,
50
+ message: `File is larger than ${maxSize} ${maxSize === 1 ? "byte" : "bytes"}`
51
+ };
52
+ }
53
+
54
+ export function getTooSmallRejectionErr(minSize: number): FileError {
55
+ return {
56
+ code: FILE_TOO_SMALL,
57
+ message: `File is smaller than ${minSize} ${minSize === 1 ? "byte" : "bytes"}`
58
+ };
59
+ }
60
+
61
+ export const TOO_MANY_FILES_REJECTION: FileError = {
62
+ code: TOO_MANY_FILES,
63
+ message: "Too many files"
64
+ };
65
+
66
+ /**
67
+ * Check if the given file is a DataTransferItem with an empty type.
68
+ *
69
+ * During drag events, browsers may return DataTransferItem objects instead of File objects.
70
+ * Some browsers (e.g., Chrome) return an empty MIME type for certain file types (like .md files)
71
+ * on DataTransferItem during drag events, even though the type is correctly set during drop.
72
+ */
73
+ export function isDataTransferItemWithEmptyType(file: File | DataTransferItem): boolean {
74
+ return file.type === "" && typeof (file as DataTransferItem).getAsFile === "function";
75
+ }
76
+
77
+ /**
78
+ * Check if file is accepted.
79
+ *
80
+ * Firefox versions prior to 53 return a bogus MIME type for every file drag,
81
+ * so dragovers with that MIME type will always be accepted.
82
+ *
83
+ * Chrome/other browsers may return an empty MIME type for files during drag events,
84
+ * so we accept those as well (we'll validate properly on drop).
85
+ */
86
+ export function fileAccepted(file: File, accept?: string): [boolean, FileError | null] {
87
+ const isAcceptable =
88
+ file.type === "application/x-moz-file" || accepts(file, accept ?? "") || isDataTransferItemWithEmptyType(file);
89
+ return [isAcceptable, isAcceptable ? null : getInvalidTypeRejectionErr(accept)];
90
+ }
91
+
92
+ export function fileMatchSize(
93
+ file: {size?: number | null},
94
+ minSize?: number,
95
+ maxSize?: number
96
+ ): [boolean, FileError | null] {
97
+ if (isDefined(file.size)) {
98
+ if (isDefined(minSize) && isDefined(maxSize)) {
99
+ if (file.size > maxSize) return [false, getTooLargeRejectionErr(maxSize)];
100
+ if (file.size < minSize) return [false, getTooSmallRejectionErr(minSize)];
101
+ } else if (isDefined(minSize) && file.size < minSize) {
102
+ return [false, getTooSmallRejectionErr(minSize)];
103
+ } else if (isDefined(maxSize) && file.size > maxSize) {
104
+ return [false, getTooLargeRejectionErr(maxSize)];
105
+ }
106
+ }
107
+ return [true, null];
108
+ }
109
+
110
+ function isDefined<T>(value: T): value is NonNullable<T> {
111
+ return value !== undefined && value !== null;
112
+ }
113
+
114
+ export function allFilesAccepted({
115
+ files,
116
+ accept,
117
+ minSize,
118
+ maxSize,
119
+ multiple,
120
+ maxFiles = 0,
121
+ validator
122
+ }: {
123
+ files: File[];
124
+ accept?: string;
125
+ minSize?: number;
126
+ maxSize?: number;
127
+ multiple?: boolean;
128
+ maxFiles?: number;
129
+ validator?: (file: File) => FileError | readonly FileError[] | null;
130
+ }): boolean {
131
+ if ((!multiple && files.length > 1) || (multiple && maxFiles >= 1 && files.length > maxFiles)) {
132
+ return false;
133
+ }
134
+
135
+ return files.every(file => {
136
+ const [accepted] = fileAccepted(file, accept);
137
+ const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
138
+ const customErrors = validator ? validator(file) : null;
139
+ return accepted && sizeMatch && !customErrors;
140
+ });
141
+ }
142
+
143
+ // React's synthetic events has event.isPropagationStopped,
144
+ // but to remain compatibility with other libs (Preact) fall back
145
+ // to check event.cancelBubble
146
+ export function isPropagationStopped(event: any): boolean {
147
+ if (typeof event.isPropagationStopped === "function") {
148
+ return event.isPropagationStopped();
149
+ } else if (typeof event.cancelBubble !== "undefined") {
150
+ return event.cancelBubble;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ export function isEvtWithFiles(event: any): boolean {
156
+ if (!event.dataTransfer) {
157
+ return !!event.target && !!event.target.files;
158
+ }
159
+ // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
160
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
161
+ return Array.prototype.some.call(
162
+ event.dataTransfer.types,
163
+ (type: string) => type === "Files" || type === "application/x-moz-file"
164
+ );
165
+ }
166
+
167
+ export function isKindFile(item: any): boolean {
168
+ return typeof item === "object" && item !== null && item.kind === "file";
169
+ }
170
+
171
+ // allow the entire document to be a drag target
172
+ export function onDocumentDragOver(event: Event): void {
173
+ event.preventDefault();
174
+ }
175
+
176
+ function isIe(userAgent: string): boolean {
177
+ return userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1;
178
+ }
179
+
180
+ function isEdge(userAgent: string): boolean {
181
+ return userAgent.indexOf("Edge/") !== -1;
182
+ }
183
+
184
+ export function isIeOrEdge(userAgent: string = window.navigator.userAgent): boolean {
185
+ return isIe(userAgent) || isEdge(userAgent);
186
+ }
187
+
188
+ /**
189
+ * This is intended to be used to compose event handlers.
190
+ * They are executed in order until one of them calls `event.isPropagationStopped()`.
191
+ * Note that the check is done on the first invoke too,
192
+ * meaning that if propagation was stopped before invoking the fns,
193
+ * no handlers will be executed.
194
+ */
195
+ export function composeEventHandlers(
196
+ ...fns: Array<((event: any, ...args: any[]) => void) | null | undefined>
197
+ ): (event: any, ...args: any[]) => boolean {
198
+ return (event: any, ...args: any[]) =>
199
+ fns.some(fn => {
200
+ if (!isPropagationStopped(event) && fn) {
201
+ fn(event, ...args);
202
+ }
203
+ return isPropagationStopped(event);
204
+ });
205
+ }
206
+
207
+ /**
208
+ * canUseFileSystemAccessAPI checks if the File System Access API is supported by the browser.
209
+ */
210
+ export function canUseFileSystemAccessAPI(): boolean {
211
+ return "showOpenFilePicker" in window;
212
+ }
213
+
214
+ /**
215
+ * Convert the `{accept}` dropzone prop to the `{types}` option for showOpenFilePicker.
216
+ */
217
+ export function pickerOptionsFromAccept(accept?: Accept): Array<{description: string; accept: Accept}> | undefined {
218
+ if (isDefined(accept)) {
219
+ const acceptForPicker = Object.entries(accept)
220
+ .filter(([mimeType, ext]) => {
221
+ let ok = true;
222
+
223
+ if (!isMIMEType(mimeType)) {
224
+ console.warn(
225
+ `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.`
226
+ );
227
+ ok = false;
228
+ }
229
+
230
+ if (!Array.isArray(ext) || !ext.every(isExt)) {
231
+ console.warn(`Skipped "${mimeType}" because an invalid file extension was provided.`);
232
+ ok = false;
233
+ }
234
+
235
+ return ok;
236
+ })
237
+ .reduce<Accept>((agg, [mimeType, ext]) => {
238
+ agg[mimeType] = ext;
239
+ return agg;
240
+ }, {});
241
+ return [
242
+ {
243
+ // description is required due to https://crbug.com/1264708
244
+ description: "Files",
245
+ accept: acceptForPicker
246
+ }
247
+ ];
248
+ }
249
+ return undefined;
250
+ }
251
+
252
+ /**
253
+ * Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
254
+ */
255
+ export function acceptPropAsAcceptAttr(accept?: Accept): string | undefined {
256
+ if (isDefined(accept)) {
257
+ return (
258
+ Object.entries(accept)
259
+ .reduce<string[]>((a, [mimeType, ext]) => {
260
+ a.push(mimeType, ...ext);
261
+ return a;
262
+ }, [])
263
+ // Silently discard invalid entries as pickerOptionsFromAccept warns about these
264
+ .filter(v => isMIMEType(v) || isExt(v))
265
+ .join(",")
266
+ );
267
+ }
268
+
269
+ return undefined;
270
+ }
271
+
272
+ /**
273
+ * Check if v is an exception caused by aborting a request (e.g window.showOpenFilePicker()).
274
+ */
275
+ export function isAbort(v: any): boolean {
276
+ return v instanceof DOMException && (v.name === "AbortError" || v.code === v.ABORT_ERR);
277
+ }
278
+
279
+ /**
280
+ * Check if v is a security error.
281
+ */
282
+ export function isSecurityError(v: any): boolean {
283
+ return v instanceof DOMException && (v.name === "SecurityError" || v.code === v.SECURITY_ERR);
284
+ }
285
+
286
+ /**
287
+ * Check if v is a MIME type string.
288
+ */
289
+ export function isMIMEType(v: string): boolean {
290
+ return (
291
+ v === "audio/*" ||
292
+ v === "video/*" ||
293
+ v === "image/*" ||
294
+ v === "text/*" ||
295
+ v === "application/*" ||
296
+ /\w+\/[-+.\w]+/g.test(v)
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Check if v is a file extension.
302
+ */
303
+ export function isExt(v: string): boolean {
304
+ return /^.*\.[\w]+$/.test(v);
305
+ }