react-dropzone 12.0.6 → 14.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 +9 -19
- package/dist/es/index.js +106 -74
- package/dist/es/utils/index.js +88 -20
- package/dist/index.js +3 -3
- package/examples/accept/README.md +19 -16
- package/examples/pintura/README.md +3 -1
- package/examples/previews/README.md +3 -1
- package/examples/styling/README.md +2 -2
- package/package.json +8 -8
- package/src/index.js +116 -72
- package/src/index.spec.js +599 -616
- package/src/utils/index.js +82 -26
- package/src/utils/index.spec.js +124 -58
- package/typings/react-dropzone.d.ts +5 -2
- package/typings/tests/accept.tsx +3 -37
- package/typings/tests/all.tsx +3 -1
package/src/utils/index.js
CHANGED
|
@@ -175,33 +175,60 @@ export function canUseFileSystemAccessAPI() {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* @param {
|
|
178
|
+
* Convert the `{accept}` dropzone prop to the
|
|
179
|
+
* `{types}` option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
|
|
180
|
+
*
|
|
181
|
+
* @param {AcceptProp} accept
|
|
182
|
+
* @returns {{accept: string[]}[]}
|
|
182
183
|
*/
|
|
183
|
-
export function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
184
|
+
export function pickerOptionsFromAccept(accept) {
|
|
185
|
+
if (isDefined(accept)) {
|
|
186
|
+
return Object.entries(accept)
|
|
187
|
+
.filter(([mimeType, ext]) => {
|
|
188
|
+
let ok = true;
|
|
189
|
+
|
|
190
|
+
if (!isMIMEType(mimeType)) {
|
|
191
|
+
console.warn(
|
|
192
|
+
`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.`
|
|
193
|
+
);
|
|
194
|
+
ok = false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!Array.isArray(ext) || !ext.every(isExt)) {
|
|
198
|
+
console.warn(
|
|
199
|
+
`Skipped "${mimeType}" because an invalid file extension was provided.`
|
|
200
|
+
);
|
|
201
|
+
ok = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return ok;
|
|
205
|
+
})
|
|
206
|
+
.map(([mimeType, ext]) => ({
|
|
207
|
+
accept: {
|
|
208
|
+
[mimeType]: ext,
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
return accept;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
|
|
217
|
+
* @param {AcceptProp} accept
|
|
218
|
+
* @returns {string}
|
|
219
|
+
*/
|
|
220
|
+
export function acceptPropAsAcceptAttr(accept) {
|
|
221
|
+
if (isDefined(accept)) {
|
|
222
|
+
return (
|
|
223
|
+
Object.entries(accept)
|
|
224
|
+
.reduce((a, [mimeType, ext]) => [...a, mimeType, ...ext], [])
|
|
225
|
+
// Silently discard invalid entries as pickerOptionsFromAccept warns about these
|
|
226
|
+
.filter((v) => isMIMEType(v) || isExt(v))
|
|
227
|
+
.join(",")
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return undefined;
|
|
205
232
|
}
|
|
206
233
|
|
|
207
234
|
/**
|
|
@@ -231,3 +258,32 @@ export function isSecurityError(v) {
|
|
|
231
258
|
(v.name === "SecurityError" || v.code === v.SECURITY_ERR)
|
|
232
259
|
);
|
|
233
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if v is a MIME type string.
|
|
264
|
+
*
|
|
265
|
+
* See accepted format: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} v
|
|
268
|
+
*/
|
|
269
|
+
export function isMIMEType(v) {
|
|
270
|
+
return (
|
|
271
|
+
v === "audio/*" ||
|
|
272
|
+
v === "video/*" ||
|
|
273
|
+
v === "image/*" ||
|
|
274
|
+
v === "text/*" ||
|
|
275
|
+
/\w+\/[-+.\w]+/g.test(v)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if v is a file extension.
|
|
281
|
+
* @param {string} v
|
|
282
|
+
*/
|
|
283
|
+
export function isExt(v) {
|
|
284
|
+
return /^.*\.[\w]+$/.test(v);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @typedef {Object.<string, string[]>} AcceptProp
|
|
289
|
+
*/
|
package/src/utils/index.spec.js
CHANGED
|
@@ -3,6 +3,10 @@ beforeEach(() => {
|
|
|
3
3
|
});
|
|
4
4
|
|
|
5
5
|
describe("fileMatchSize()", () => {
|
|
6
|
+
/**
|
|
7
|
+
* @constant
|
|
8
|
+
* @type {import('./index')}
|
|
9
|
+
*/
|
|
6
10
|
let utils;
|
|
7
11
|
beforeEach(async () => {
|
|
8
12
|
utils = await import("./index");
|
|
@@ -63,6 +67,10 @@ describe("fileMatchSize()", () => {
|
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
describe("isIeOrEdge", () => {
|
|
70
|
+
/**
|
|
71
|
+
* @constant
|
|
72
|
+
* @type {import('./index')}
|
|
73
|
+
*/
|
|
66
74
|
let utils;
|
|
67
75
|
beforeEach(async () => {
|
|
68
76
|
utils = await import("./index");
|
|
@@ -98,6 +106,10 @@ describe("isIeOrEdge", () => {
|
|
|
98
106
|
|
|
99
107
|
describe("isKindFile()", () => {
|
|
100
108
|
it('should return true for DataTransferItem of kind "file"', async () => {
|
|
109
|
+
/**
|
|
110
|
+
* @constant
|
|
111
|
+
* @type {import('./index')}
|
|
112
|
+
*/
|
|
101
113
|
const utils = await import("./index");
|
|
102
114
|
expect(utils.isKindFile({ kind: "file" })).toBe(true);
|
|
103
115
|
expect(utils.isKindFile({ kind: "text/html" })).toBe(false);
|
|
@@ -109,6 +121,10 @@ describe("isKindFile()", () => {
|
|
|
109
121
|
describe("isPropagationStopped()", () => {
|
|
110
122
|
const trueFn = jest.fn(() => true);
|
|
111
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @constant
|
|
126
|
+
* @type {import('./index')}
|
|
127
|
+
*/
|
|
112
128
|
let utils;
|
|
113
129
|
beforeEach(async () => {
|
|
114
130
|
utils = await import("./index");
|
|
@@ -130,6 +146,10 @@ describe("isPropagationStopped()", () => {
|
|
|
130
146
|
});
|
|
131
147
|
|
|
132
148
|
describe("isEvtWithFiles()", () => {
|
|
149
|
+
/**
|
|
150
|
+
* @constant
|
|
151
|
+
* @type {import('./index')}
|
|
152
|
+
*/
|
|
133
153
|
let utils;
|
|
134
154
|
beforeEach(async () => {
|
|
135
155
|
utils = await import("./index");
|
|
@@ -176,7 +196,11 @@ describe("isEvtWithFiles()", () => {
|
|
|
176
196
|
});
|
|
177
197
|
});
|
|
178
198
|
|
|
179
|
-
describe("composeEventHandlers", () => {
|
|
199
|
+
describe("composeEventHandlers()", () => {
|
|
200
|
+
/**
|
|
201
|
+
* @constant
|
|
202
|
+
* @type {import('./index')}
|
|
203
|
+
*/
|
|
180
204
|
let utils;
|
|
181
205
|
beforeEach(async () => {
|
|
182
206
|
utils = await import("./index");
|
|
@@ -223,7 +247,11 @@ describe("composeEventHandlers", () => {
|
|
|
223
247
|
});
|
|
224
248
|
});
|
|
225
249
|
|
|
226
|
-
describe("fileAccepted", () => {
|
|
250
|
+
describe("fileAccepted()", () => {
|
|
251
|
+
/**
|
|
252
|
+
* @constant
|
|
253
|
+
* @type {import('./index')}
|
|
254
|
+
*/
|
|
227
255
|
let utils;
|
|
228
256
|
beforeEach(async () => {
|
|
229
257
|
utils = await import("./index");
|
|
@@ -273,6 +301,10 @@ describe("fileAccepted", () => {
|
|
|
273
301
|
});
|
|
274
302
|
|
|
275
303
|
describe("allFilesAccepted()", () => {
|
|
304
|
+
/**
|
|
305
|
+
* @constant
|
|
306
|
+
* @type {import('./index')}
|
|
307
|
+
*/
|
|
276
308
|
let utils;
|
|
277
309
|
beforeEach(async () => {
|
|
278
310
|
utils = await import("./index");
|
|
@@ -316,6 +348,10 @@ describe("allFilesAccepted()", () => {
|
|
|
316
348
|
});
|
|
317
349
|
|
|
318
350
|
describe("ErrorCode", () => {
|
|
351
|
+
/**
|
|
352
|
+
* @constant
|
|
353
|
+
* @type {import('./index')}
|
|
354
|
+
*/
|
|
319
355
|
let utils;
|
|
320
356
|
beforeEach(async () => {
|
|
321
357
|
utils = await import("./index");
|
|
@@ -330,6 +366,10 @@ describe("ErrorCode", () => {
|
|
|
330
366
|
});
|
|
331
367
|
|
|
332
368
|
describe("canUseFileSystemAccessAPI()", () => {
|
|
369
|
+
/**
|
|
370
|
+
* @constant
|
|
371
|
+
* @type {import('./index')}
|
|
372
|
+
*/
|
|
333
373
|
let utils;
|
|
334
374
|
beforeEach(async () => {
|
|
335
375
|
utils = await import("./index");
|
|
@@ -346,87 +386,109 @@ describe("canUseFileSystemAccessAPI()", () => {
|
|
|
346
386
|
});
|
|
347
387
|
});
|
|
348
388
|
|
|
349
|
-
describe("
|
|
389
|
+
describe("pickerOptionsFromAccept()", () => {
|
|
390
|
+
/**
|
|
391
|
+
* @constant
|
|
392
|
+
* @type {import('./index')}
|
|
393
|
+
*/
|
|
350
394
|
let utils;
|
|
351
395
|
beforeEach(async () => {
|
|
352
396
|
utils = await import("./index");
|
|
353
397
|
});
|
|
354
398
|
|
|
355
|
-
it("
|
|
356
|
-
expect(utils.filePickerOptionsTypes("application/zip")).toEqual([
|
|
357
|
-
{
|
|
358
|
-
description: "everything",
|
|
359
|
-
accept: { "application/zip": [] },
|
|
360
|
-
},
|
|
361
|
-
]);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("should return proper types when the arg is an array of MIME types", () => {
|
|
399
|
+
it("converts the {accept} prop to file picker options", () => {
|
|
365
400
|
expect(
|
|
366
|
-
utils.
|
|
401
|
+
utils.pickerOptionsFromAccept({
|
|
402
|
+
"image/*": [".png", ".jpg"], // ok
|
|
403
|
+
"text/*": [".txt", ".pdf"], // ok
|
|
404
|
+
"audio/*": ["mp3"], // not ok
|
|
405
|
+
"*": [".p12"], // not ok
|
|
406
|
+
})
|
|
367
407
|
).toEqual([
|
|
368
408
|
{
|
|
369
|
-
description: "everything",
|
|
370
409
|
accept: {
|
|
371
|
-
"
|
|
372
|
-
"application/json": [],
|
|
410
|
+
"image/*": [".png", ".jpg"],
|
|
373
411
|
},
|
|
374
412
|
},
|
|
375
|
-
]);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it("should exclude anything that's not a MIME type", () => {
|
|
379
|
-
expect(
|
|
380
|
-
utils.filePickerOptionsTypes([
|
|
381
|
-
"audio/*",
|
|
382
|
-
"video/*",
|
|
383
|
-
"image/*",
|
|
384
|
-
".txt",
|
|
385
|
-
"text/*",
|
|
386
|
-
])
|
|
387
|
-
).toEqual([
|
|
388
413
|
{
|
|
389
|
-
description: "everything",
|
|
390
414
|
accept: {
|
|
391
|
-
"
|
|
392
|
-
"video/*": [],
|
|
393
|
-
"image/*": [],
|
|
394
|
-
"text/*": [],
|
|
415
|
+
"text/*": [".txt", ".pdf"],
|
|
395
416
|
},
|
|
396
417
|
},
|
|
397
418
|
]);
|
|
398
419
|
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe("acceptPropAsAcceptAttr()", () => {
|
|
423
|
+
/**
|
|
424
|
+
* @constant
|
|
425
|
+
* @type {import('./index')}
|
|
426
|
+
*/
|
|
427
|
+
let utils;
|
|
428
|
+
beforeEach(async () => {
|
|
429
|
+
utils = await import("./index");
|
|
430
|
+
});
|
|
399
431
|
|
|
400
|
-
it("
|
|
432
|
+
it("converts {accept} to an array of strings", () => {
|
|
401
433
|
expect(
|
|
402
|
-
utils.
|
|
403
|
-
"
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"audio/*": [],
|
|
410
|
-
"video/*": [],
|
|
411
|
-
"image/*": [],
|
|
412
|
-
"text/*": [],
|
|
413
|
-
"application/zip": [],
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
]);
|
|
434
|
+
utils.acceptPropAsAcceptAttr({
|
|
435
|
+
"image/*": [".png", ".jpg"],
|
|
436
|
+
"text/*": [".txt", ".pdf"],
|
|
437
|
+
"audio/*": ["mp3"], // `mp3` not ok
|
|
438
|
+
"*": [".p12"], // `*` not ok
|
|
439
|
+
})
|
|
440
|
+
).toEqual("image/*,.png,.jpg,text/*,.txt,.pdf,audio/*,.p12");
|
|
417
441
|
});
|
|
442
|
+
});
|
|
418
443
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
444
|
+
describe("isMIMEType()", () => {
|
|
445
|
+
/**
|
|
446
|
+
* @constant
|
|
447
|
+
* @type {import('./index')}
|
|
448
|
+
*/
|
|
449
|
+
let utils;
|
|
450
|
+
beforeEach(async () => {
|
|
451
|
+
utils = await import("./index");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("checks that the value is a valid MIME type string", () => {
|
|
455
|
+
expect(utils.isMIMEType("text/html")).toBe(true);
|
|
456
|
+
expect(utils.isMIMEType("text/*")).toBe(true);
|
|
457
|
+
expect(utils.isMIMEType("image/*")).toBe(true);
|
|
458
|
+
expect(utils.isMIMEType("video/*")).toBe(true);
|
|
459
|
+
expect(utils.isMIMEType("audio/*")).toBe(true);
|
|
460
|
+
expect(utils.isMIMEType("test/*")).toBe(false);
|
|
461
|
+
expect(utils.isMIMEType("text")).toBe(false);
|
|
462
|
+
expect(utils.isMIMEType("")).toBe(false);
|
|
463
|
+
expect(utils.isMIMEType(undefined)).toBe(false);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe("isExt()", () => {
|
|
468
|
+
/**
|
|
469
|
+
* @constant
|
|
470
|
+
* @type {import('./index')}
|
|
471
|
+
*/
|
|
472
|
+
let utils;
|
|
473
|
+
beforeEach(async () => {
|
|
474
|
+
utils = await import("./index");
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("checks that the value is a valid file extension", () => {
|
|
478
|
+
expect(utils.isExt(".jpg")).toBe(true);
|
|
479
|
+
expect(utils.isExt("me.jpg")).toBe(true);
|
|
480
|
+
expect(utils.isExt("me.prev.png")).toBe(true);
|
|
481
|
+
expect(utils.isExt("")).toBe(false);
|
|
482
|
+
expect(utils.isExt("text")).toBe(false);
|
|
483
|
+
expect(utils.isExt(undefined)).toBe(false);
|
|
426
484
|
});
|
|
427
485
|
});
|
|
428
486
|
|
|
429
487
|
describe("isAbort()", () => {
|
|
488
|
+
/**
|
|
489
|
+
* @constant
|
|
490
|
+
* @type {import('./index')}
|
|
491
|
+
*/
|
|
430
492
|
let utils;
|
|
431
493
|
beforeEach(async () => {
|
|
432
494
|
utils = await import("./index");
|
|
@@ -453,6 +515,10 @@ describe("isAbort()", () => {
|
|
|
453
515
|
});
|
|
454
516
|
|
|
455
517
|
describe("isSecurityError()", () => {
|
|
518
|
+
/**
|
|
519
|
+
* @constant
|
|
520
|
+
* @type {import('./index')}
|
|
521
|
+
*/
|
|
456
522
|
let utils;
|
|
457
523
|
beforeEach(async () => {
|
|
458
524
|
utils = await import("./index");
|
|
@@ -28,7 +28,7 @@ export interface FileRejection {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
|
|
31
|
-
accept?:
|
|
31
|
+
accept?: Accept;
|
|
32
32
|
minSize?: number;
|
|
33
33
|
maxSize?: number;
|
|
34
34
|
maxFiles?: number;
|
|
@@ -66,7 +66,6 @@ export type DropzoneState = DropzoneRef & {
|
|
|
66
66
|
isDragAccept: boolean;
|
|
67
67
|
isDragReject: boolean;
|
|
68
68
|
isFileDialogActive: boolean;
|
|
69
|
-
draggedFiles: File[];
|
|
70
69
|
acceptedFiles: File[];
|
|
71
70
|
fileRejections: FileRejection[];
|
|
72
71
|
rootRef: React.RefObject<HTMLElement>;
|
|
@@ -90,3 +89,7 @@ export interface DropzoneInputProps
|
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
type PropTypes = "multiple" | "onDragEnter" | "onDragOver" | "onDragLeave";
|
|
92
|
+
|
|
93
|
+
export interface Accept {
|
|
94
|
+
[key: string]: string[];
|
|
95
|
+
}
|
package/typings/tests/accept.tsx
CHANGED
|
@@ -12,7 +12,9 @@ export default class Accept extends React.Component {
|
|
|
12
12
|
<section>
|
|
13
13
|
<div className="dropzone">
|
|
14
14
|
<Dropzone
|
|
15
|
-
accept=
|
|
15
|
+
accept={{
|
|
16
|
+
"image/*": [".jpeg", ".png"],
|
|
17
|
+
}}
|
|
16
18
|
onDrop={(accepted, rejected) => {
|
|
17
19
|
this.setState({ accepted, rejected });
|
|
18
20
|
}}
|
|
@@ -50,39 +52,3 @@ export default class Accept extends React.Component {
|
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
export const acceptExt = (
|
|
55
|
-
<Dropzone accept=".jpeg,.png">
|
|
56
|
-
{({ getRootProps, isDragActive, isDragAccept, isDragReject }) => (
|
|
57
|
-
<div {...getRootProps()}>
|
|
58
|
-
{isDragAccept && "All files will be accepted"}
|
|
59
|
-
{isDragReject && "Some files will be rejected"}
|
|
60
|
-
{isDragActive && "Drop some files here ..."}
|
|
61
|
-
</div>
|
|
62
|
-
)}
|
|
63
|
-
</Dropzone>
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
export const acceptMime = (
|
|
67
|
-
<Dropzone accept="image/jpeg, image/png">
|
|
68
|
-
{({ getRootProps, isDragActive, isDragAccept, isDragReject }) => (
|
|
69
|
-
<div {...getRootProps()}>
|
|
70
|
-
{isDragAccept && "All files will be accepted"}
|
|
71
|
-
{isDragReject && "Some files will be rejected"}
|
|
72
|
-
{isDragActive && "Drop some files here ..."}
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
</Dropzone>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
export const acceptArray = (
|
|
79
|
-
<Dropzone accept={["image/jpeg", "image/png"]}>
|
|
80
|
-
{({ getRootProps, isDragActive, isDragAccept, isDragReject }) => (
|
|
81
|
-
<div {...getRootProps()}>
|
|
82
|
-
{isDragAccept && "All files will be accepted"}
|
|
83
|
-
{isDragReject && "Some files will be rejected"}
|
|
84
|
-
{isDragActive && "Drop some files here ..."}
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
</Dropzone>
|
|
88
|
-
);
|
package/typings/tests/all.tsx
CHANGED