Skip to content

Commit 2eb4eac

Browse files
committed
fix: include file extensions in accept attribute for better cross-browser compatibility
Populate the extension arrays in generateClientDropzoneAccept() using the existing @uploadthing/mime-types extension data. This ensures the HTML <input accept> attribute includes both MIME types and their corresponding file extensions (e.g. .jar, .war, .ear), which fixes file picker filtering on Windows/Chrome for types like application/java-archive. Closes #1157
1 parent d20c5ba commit 2eb4eac

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

packages/shared/src/component-utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { RenderFunction, StyleValue } from "vue";
77
* We don't need all the types, and `/application`
88
* entrypoint is ~7k gzip which we can shave off
99
*/
10+
import { getExtensions } from "@uploadthing/mime-types";
1011
import { audio } from "@uploadthing/mime-types/audio";
1112
import { image } from "@uploadthing/mime-types/image";
1213
import { text } from "@uploadthing/mime-types/text";
@@ -51,7 +52,21 @@ export const generateMimeTypes = (
5152

5253
export const generateClientDropzoneAccept = (fileTypes: string[]) => {
5354
const mimeTypes = generateMimeTypes(fileTypes);
54-
return Object.fromEntries(mimeTypes.map((type) => [type, []]));
55+
const extensionsMap = getExtensions();
56+
57+
return Object.fromEntries(
58+
mimeTypes.map((type) => {
59+
// Generic types produce comma-joined strings: "image/*, image/png, ..."
60+
const subTypes = type.split(",").map((t) => t.trim());
61+
const exts = subTypes.flatMap((mime) => {
62+
const mimeExts =
63+
extensionsMap[mime as keyof typeof extensionsMap] ?? [];
64+
return mimeExts.map((ext) => `.${ext}`);
65+
});
66+
67+
return [type, exts];
68+
}),
69+
);
5570
};
5671

5772
export function getFilesFromClipboardEvent(event: ClipboardEvent) {

packages/shared/test/component-utils.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import * as E from "effect/Effect";
22
import { describe, expect, it } from "vitest";
33

4-
import { generateMimeTypes } from "../src/component-utils";
4+
import {
5+
generateClientDropzoneAccept,
6+
generateMimeTypes,
7+
} from "../src/component-utils";
58
import { fillInputRouteConfig } from "../src/utils";
69

710
describe("generateMimeTypes", () => {
@@ -38,3 +41,56 @@ describe("generateMimeTypes", () => {
3841
expect(videoMimes).toContain("video/webm");
3942
});
4043
});
44+
45+
describe("generateClientDropzoneAccept", () => {
46+
it("includes file extensions for specific MIME types", () => {
47+
const result = generateClientDropzoneAccept(["application/java-archive"]);
48+
expect(result).toEqual({
49+
"application/java-archive": [".jar", ".war", ".ear"],
50+
});
51+
});
52+
53+
it("includes file extensions for application/pdf via 'pdf' shorthand", () => {
54+
const result = generateClientDropzoneAccept(["pdf"]);
55+
expect(result).toEqual({
56+
"application/pdf": [".pdf"],
57+
});
58+
});
59+
60+
it("returns empty object for blob type", () => {
61+
const result = generateClientDropzoneAccept(["blob"]);
62+
expect(result).toEqual({});
63+
});
64+
65+
it("includes extensions for generic types like 'image'", () => {
66+
const result = generateClientDropzoneAccept(["image"]);
67+
const keys = Object.keys(result);
68+
expect(keys).toHaveLength(1);
69+
70+
const extensions = Object.values(result)[0]!;
71+
expect(extensions).toContain(".png");
72+
expect(extensions).toContain(".jpg");
73+
expect(extensions).toContain(".gif");
74+
expect(extensions).toContain(".webp");
75+
});
76+
77+
it("handles MIME types with no known extensions gracefully", () => {
78+
const result = generateClientDropzoneAccept(["application/x-unknown-type"]);
79+
expect(result).toEqual({
80+
"application/x-unknown-type": [],
81+
});
82+
});
83+
84+
it("handles multiple file types", () => {
85+
const result = generateClientDropzoneAccept([
86+
"application/java-archive",
87+
"pdf",
88+
]);
89+
expect(result["application/java-archive"]).toEqual([
90+
".jar",
91+
".war",
92+
".ear",
93+
]);
94+
expect(result["application/pdf"]).toEqual([".pdf"]);
95+
});
96+
});

0 commit comments

Comments
 (0)