react-dropzone 19.1.1 → 19.3.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 +26 -0
- package/dist/index.cjs +114 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -4
- package/dist/index.d.ts +30 -4
- package/dist/index.js +114 -14
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/index.tsx +74 -7
- package/src/utils/index.ts +159 -35
package/src/utils/index.ts
CHANGED
|
@@ -8,9 +8,28 @@ const accepts =
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* A map of accepted MIME types to file extensions, as passed to the `accept` prop.
|
|
11
|
+
*
|
|
12
|
+
* An extension value may be a single extension string or an array of them - both are
|
|
13
|
+
* accepted, mirroring the shape of `window.showOpenFilePicker`'s `accept`.
|
|
11
14
|
*/
|
|
12
15
|
export interface Accept {
|
|
13
|
-
[key: string]: readonly string[];
|
|
16
|
+
[key: string]: string | readonly string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A labeled group of accepted types, mirroring one entry of `window.showOpenFilePicker`'s
|
|
21
|
+
* `types` option. Passing the `accept` prop as an array of these lets the File System Access
|
|
22
|
+
* picker present multiple named filter rows instead of a single one (see
|
|
23
|
+
* {@link pickerOptionsFromAccept}). The optional `description` labels the row; when omitted it
|
|
24
|
+
* is derived from the group's extensions.
|
|
25
|
+
*
|
|
26
|
+
* Grouping only surfaces when the FS Access picker is actually used (`useFsAccessApi` +
|
|
27
|
+
* a secure context + browser support). The native `<input>` fallback has no concept of groups
|
|
28
|
+
* or descriptions, so every group is flattened into one accept attribute there.
|
|
29
|
+
*/
|
|
30
|
+
export interface AcceptGroup {
|
|
31
|
+
description?: string;
|
|
32
|
+
accept: Accept;
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
/**
|
|
@@ -256,7 +275,10 @@ export function isPropagationStopped(event: any): boolean {
|
|
|
256
275
|
}
|
|
257
276
|
|
|
258
277
|
export function isEvtWithFiles(event: any): boolean {
|
|
259
|
-
|
|
278
|
+
// A paste (ClipboardEvent) carries its DataTransfer under {clipboardData} rather than
|
|
279
|
+
// {dataTransfer}, so a pasted screenshot is detected the same way as a drop.
|
|
280
|
+
const dataTransfer = event.dataTransfer ?? event.clipboardData;
|
|
281
|
+
if (!dataTransfer) {
|
|
260
282
|
return !!event.target && !!event.target.files;
|
|
261
283
|
}
|
|
262
284
|
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
|
|
@@ -266,9 +288,9 @@ export function isEvtWithFiles(event: any): boolean {
|
|
|
266
288
|
// uses to extract the files. Accept either so detection stays consistent. See #1409.
|
|
267
289
|
return (
|
|
268
290
|
Array.prototype.some.call(
|
|
269
|
-
|
|
291
|
+
dataTransfer.types,
|
|
270
292
|
(type: string) => type === "Files" || type === "application/x-moz-file"
|
|
271
|
-
) || Array.prototype.some.call(
|
|
293
|
+
) || Array.prototype.some.call(dataTransfer.items ?? [], isKindFile)
|
|
272
294
|
);
|
|
273
295
|
}
|
|
274
296
|
|
|
@@ -320,41 +342,142 @@ export function canUseFileSystemAccessAPI(): boolean {
|
|
|
320
342
|
}
|
|
321
343
|
|
|
322
344
|
/**
|
|
323
|
-
*
|
|
345
|
+
* Coerce an `accept` extension value to an array. `window.showOpenFilePicker` allows a single
|
|
346
|
+
* extension string as well as an array, so we accept both and normalize to an array. Anything
|
|
347
|
+
* else (a malformed value) collapses to an empty list.
|
|
324
348
|
*/
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
);
|
|
335
|
-
ok = false;
|
|
336
|
-
}
|
|
349
|
+
function toExtensions(ext: string | readonly string[] | undefined): readonly string[] {
|
|
350
|
+
if (Array.isArray(ext)) {
|
|
351
|
+
return ext;
|
|
352
|
+
}
|
|
353
|
+
if (typeof ext === "string") {
|
|
354
|
+
return [ext];
|
|
355
|
+
}
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
337
358
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Normalize either `accept` form to the internal array of `{description, accept}` groups.
|
|
361
|
+
*
|
|
362
|
+
* - The object form (`{mime: ext}`) becomes a single group; its `description` (which the object
|
|
363
|
+
* form can't express) is derived from the extensions like any other group.
|
|
364
|
+
* - The array form is passed through; entries missing an `accept` map are dropped.
|
|
365
|
+
*
|
|
366
|
+
* In both cases a missing `description` is filled in later from the group's extensions (see
|
|
367
|
+
* {@link describeAccept}), so the picker always gets the non-empty description it requires
|
|
368
|
+
* (https://crbug.com/1264708). Returns `undefined` when no `accept` is provided.
|
|
369
|
+
*/
|
|
370
|
+
function normalizeAcceptGroups(accept?: Accept | readonly AcceptGroup[]): AcceptGroup[] | undefined {
|
|
371
|
+
if (!isDefined(accept)) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
if (Array.isArray(accept)) {
|
|
375
|
+
return (accept as readonly AcceptGroup[]).filter(group => isDefined(group) && isDefined(group.accept));
|
|
376
|
+
}
|
|
377
|
+
return [{accept: accept as Accept}];
|
|
378
|
+
}
|
|
342
379
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Build a human-readable label for a picker group from its (validated) accept map, used when the
|
|
382
|
+
* caller doesn't supply a `description`. Prefer the file extensions, fall back to the MIME types,
|
|
383
|
+
* then to a generic label - `showOpenFilePicker` requires a non-empty description (crbug 1264708).
|
|
384
|
+
*/
|
|
385
|
+
function describeAccept(accept: Accept): string {
|
|
386
|
+
const extensions: string[] = [];
|
|
387
|
+
const mimeTypes = Object.keys(accept);
|
|
388
|
+
for (const ext of Object.values(accept)) {
|
|
389
|
+
for (const e of toExtensions(ext)) {
|
|
390
|
+
if (!extensions.includes(e)) {
|
|
391
|
+
extensions.push(e);
|
|
354
392
|
}
|
|
355
|
-
|
|
393
|
+
}
|
|
356
394
|
}
|
|
357
|
-
|
|
395
|
+
if (extensions.length > 0) {
|
|
396
|
+
return extensions.join(", ");
|
|
397
|
+
}
|
|
398
|
+
if (mimeTypes.length > 0) {
|
|
399
|
+
return mimeTypes.join(", ");
|
|
400
|
+
}
|
|
401
|
+
return "Files";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Merge every `accept` group into a single MIME-type -> extensions map. Duplicate MIME keys union
|
|
406
|
+
* their extensions. This flattened map drives the native `<input accept>` attribute and the
|
|
407
|
+
* drag/drop validators, which have no concept of groups or descriptions.
|
|
408
|
+
*/
|
|
409
|
+
export function flattenAccept(accept?: Accept | readonly AcceptGroup[]): Accept | undefined {
|
|
410
|
+
const groups = normalizeAcceptGroups(accept);
|
|
411
|
+
if (!isDefined(groups)) {
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
const merged: Record<string, string[]> = {};
|
|
415
|
+
for (const group of groups) {
|
|
416
|
+
for (const [mimeType, ext] of Object.entries(group.accept)) {
|
|
417
|
+
const existing = merged[mimeType] ?? (merged[mimeType] = []);
|
|
418
|
+
for (const e of toExtensions(ext)) {
|
|
419
|
+
if (!existing.includes(e)) {
|
|
420
|
+
existing.push(e);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return merged;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Convert the `{accept}` dropzone prop to the `{types}` option for showOpenFilePicker.
|
|
430
|
+
*
|
|
431
|
+
* Each group becomes one filter row in the picker. Invalid MIME types / extensions are dropped
|
|
432
|
+
* with a warning; a group left with no valid entries is omitted entirely (showOpenFilePicker
|
|
433
|
+
* rejects an empty `accept`). Returns `undefined` when nothing valid remains.
|
|
434
|
+
*/
|
|
435
|
+
export function pickerOptionsFromAccept(
|
|
436
|
+
accept?: Accept | readonly AcceptGroup[]
|
|
437
|
+
): Array<{description: string; accept: Accept}> | undefined {
|
|
438
|
+
const groups = normalizeAcceptGroups(accept);
|
|
439
|
+
if (!isDefined(groups)) {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const options = groups
|
|
444
|
+
.map(group => {
|
|
445
|
+
const acceptForPicker = Object.entries(group.accept)
|
|
446
|
+
.filter(([mimeType, ext]) => {
|
|
447
|
+
let ok = true;
|
|
448
|
+
|
|
449
|
+
if (!isMIMEType(mimeType)) {
|
|
450
|
+
console.warn(
|
|
451
|
+
`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.`
|
|
452
|
+
);
|
|
453
|
+
ok = false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const isExtInput = Array.isArray(ext) || typeof ext === "string";
|
|
457
|
+
if (!isExtInput || !toExtensions(ext).every(isExt)) {
|
|
458
|
+
console.warn(`Skipped "${mimeType}" because an invalid file extension was provided.`);
|
|
459
|
+
ok = false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return ok;
|
|
463
|
+
})
|
|
464
|
+
.reduce<Accept>((agg, [mimeType, ext]) => {
|
|
465
|
+
agg[mimeType] = toExtensions(ext);
|
|
466
|
+
return agg;
|
|
467
|
+
}, {});
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
description:
|
|
471
|
+
isDefined(group.description) && group.description !== ""
|
|
472
|
+
? group.description
|
|
473
|
+
: describeAccept(acceptForPicker),
|
|
474
|
+
accept: acceptForPicker
|
|
475
|
+
};
|
|
476
|
+
})
|
|
477
|
+
// Drop groups with no valid entries - showOpenFilePicker rejects an empty accept map.
|
|
478
|
+
.filter(option => Object.keys(option.accept).length > 0);
|
|
479
|
+
|
|
480
|
+
return options.length > 0 ? options : undefined;
|
|
358
481
|
}
|
|
359
482
|
|
|
360
483
|
/**
|
|
@@ -376,7 +499,8 @@ export function acceptPropAsAcceptAttr(
|
|
|
376
499
|
if (isDefined(accept)) {
|
|
377
500
|
return (
|
|
378
501
|
Object.entries(accept)
|
|
379
|
-
.reduce<string[]>((a, [mimeType,
|
|
502
|
+
.reduce<string[]>((a, [mimeType, extVal]) => {
|
|
503
|
+
const ext = toExtensions(extVal);
|
|
380
504
|
if (omitWildcardMimeTypesWithExtensions && isMIMETypeWildcard(mimeType) && ext.some(isExt)) {
|
|
381
505
|
a.push(...ext);
|
|
382
506
|
} else {
|