diff --git a/README.md b/README.md index 0c3cf17..6e86f43 100644 --- a/README.md +++ b/README.md @@ -95,11 +95,11 @@ document.addEventListener("drop", async (event) => { Convert a [change event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) for an input type file to File objects: ```js -import { fromEvent } from "file-selector"; +import { fromChangeEvent } from "file-selector"; const input = document.getElementById("myInput"); -input.addEventListener("change", async (event) => { - const files = await fromEvent(event); +input.addEventListener("change", (event) => { + const files = fromChangeEvent(event); console.log(files); }); ``` diff --git a/src/file-selector.spec.ts b/src/file-selector.spec.ts index 7b9b81f..1e85149 100644 --- a/src/file-selector.spec.ts +++ b/src/file-selector.spec.ts @@ -1,5 +1,42 @@ import { FileWithPath } from "./file.js"; -import { fromEvent, fromFileHandles } from "./file-selector.js"; +import { + fromChangeEvent, + fromEvent, + fromFileHandles, +} from "./file-selector.js"; + +describe("fromChangeEvent", () => { + it("returns the files from the event", () => { + const name = "test.json"; + const mockFile = createFile( + name, + { ping: true }, + { + type: "application/json", + }, + ); + const evt = inputEvtFromFiles(mockFile); + + const files = fromChangeEvent(evt); + expect(files).toHaveLength(1); + expect(files.every((file) => file instanceof File)).toBe(true); + + const [file] = files as FileWithPath[]; + + expect(file.name).toBe(mockFile.name); + expect(file.type).toBe(mockFile.type); + expect(file.size).toBe(mockFile.size); + expect(file.lastModified).toBe(mockFile.lastModified); + expect(file.path).toBe(`./${name}`); + }); + + it("throws if the event is not associated with a file input", () => { + const event = new Event("change"); + expect(() => fromChangeEvent(event)).toThrow( + "Event is not associated with a valid file input element.", + ); + }); +}); it("returns a Promise", async () => { const evt = new Event("test"); @@ -16,30 +53,6 @@ it("should return an empty array if drag event", async () => { expect(files).toHaveLength(0); }); -it("should return the evt {target} {files} if the passed event is an input evt", async () => { - const name = "test.json"; - const mockFile = createFile( - name, - { ping: true }, - { - type: "application/json", - }, - ); - const evt = inputEvtFromFiles(mockFile); - - const files = await fromEvent(evt); - expect(files).toHaveLength(1); - expect(files.every((file) => file instanceof File)).toBe(true); - - const [file] = files as FileWithPath[]; - - expect(file.name).toBe(mockFile.name); - expect(file.type).toBe(mockFile.type); - expect(file.size).toBe(mockFile.size); - expect(file.lastModified).toBe(mockFile.lastModified); - expect(file.path).toBe(`./${name}`); -}); - it("should return an empty array if the evt {target} has no {files} prop", async () => { const evt = inputEvtFromFiles(); const files = await fromEvent(evt); diff --git a/src/file-selector.ts b/src/file-selector.ts index 8538af4..06fc203 100644 --- a/src/file-selector.ts +++ b/src/file-selector.ts @@ -18,27 +18,32 @@ export async function fromEvent( ): Promise<(FileWithPath | DataTransferItem)[]> { if (isObject(evt) && isDataTransfer(evt.dataTransfer)) { return getDataTransferFiles(evt.dataTransfer, evt.type); - } else if (isChangeEvt(evt)) { - return getInputFiles(evt); } return []; } -function isDataTransfer(value: any): value is DataTransfer { - return isObject(value); -} - -function isChangeEvt(value: any): value is Event { - return isObject(value) && isObject(value.target); -} - -function isObject(v: any): v is T { - return typeof v === "object" && v !== null; -} - -function getInputFiles(event: Event): FileWithPath[] { +/** + * Retrieves files from the `change` event of a file input element. + * + * @param event The `change` event of a file input element to retrieve files from. + * @throws If the event is not associated with a valid file input element. + * @returns An array of file objects retrieved from the event. + * + * @example + * ```js + * const input = document.getElementById("myInput"); + * input.addEventListener("change", (event) => { + * const files = fromChangeEvent(event); + * console.log(files); + * }); + * ``` + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file|MDN - ``} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event|MDN - HTMLElement: `change` event} + */ +export function fromChangeEvent(event: Event): FileWithPath[] { if (!(event.target instanceof HTMLInputElement) || !event.target.files) { - return []; + throw new Error("Event is not associated with a valid file input element."); } return Array.from(event.target.files).map((file) => toFileWithPath(file)); @@ -72,6 +77,14 @@ async function fromFileHandle( return toFileWithPath(file); } +function isDataTransfer(value: any): value is DataTransfer { + return isObject(value); +} + +function isObject(v: any): v is T { + return typeof v === "object" && v !== null; +} + async function getDataTransferFiles(dataTransfer: DataTransfer, type: string) { const items = Array.from(dataTransfer.items).filter( (item) => item.kind === "file", diff --git a/src/index.ts b/src/index.ts index 7dd1b46..ed08baf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,6 @@ -export { fromEvent, fromFileHandles } from "./file-selector.js"; +export { + fromChangeEvent, + fromEvent, + fromFileHandles, +} from "./file-selector.js"; export { FileWithPath } from "./file.js";