Skip to content

Commit 7d3ff11

Browse files
author
Roland Groza
committed
feat: add support for reading files from FileSystemFileHandle
1 parent d828791 commit 7d3ff11

File tree

4 files changed

+90
-19
lines changed

4 files changed

+90
-19
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ document.addEventListener('drop', async evt => {
4646
---------
4747

4848
#### ES6
49-
Convert a `DragEvent` to File objects:
49+
Convert a [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent) to File objects:
5050
```ts
5151
import {fromEvent} from 'file-selector';
5252
document.addEventListener('drop', async evt => {
@@ -55,7 +55,7 @@ document.addEventListener('drop', async evt => {
5555
});
5656
```
5757

58-
Convert a file input to File objects:
58+
Convert a [change event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) for an input type file to File objects:
5959
```ts
6060
import {fromEvent} from 'file-selector';
6161
const input = document.getElementById('myInput');
@@ -65,6 +65,18 @@ input.addEventListener('change', async evt => {
6565
});
6666
```
6767

68+
Convert [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle) items to File objects:
69+
```ts
70+
import {fromEvent} from 'file-selector';
71+
72+
// Open file picker
73+
const handles = await window.showOpenFilePicker({multiple: true});
74+
// Get the files
75+
const files = await fromEvent(handles);
76+
console.log(files);
77+
```
78+
**NOTE** The above is experimental and subject to change.
79+
6880
#### CommonJS
6981
Convert a `DragEvent` to File objects:
7082
```ts

src/file-selector.spec.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ it('returns a Promise', async () => {
77
expect(fromEvent(evt)).toBeInstanceOf(Promise);
88
});
99

10-
it('should return an empty array if the passed event is not a DragEvent', async () => {
11-
const evt = new Event('test');
12-
const files = await fromEvent(evt);
10+
it('should return an empty array if the passed arg is not what we expect', async () => {
11+
const files = await fromEvent({});
1312
expect(files).toHaveLength(0);
1413
});
1514

@@ -33,14 +32,19 @@ it('should return the evt {target} {files} if the passed event is an input evt',
3332
expect(file.path).toBe(name);
3433
});
3534

36-
it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11)', async () => {
35+
it('should return an empty array if the evt {target} has no {files} prop', async () => {
36+
const evt = inputEvtFromFiles();
37+
const files = await fromEvent(evt);
38+
expect(files).toHaveLength(0);
39+
});
40+
41+
it('should return files if the arg is a list of FileSystemFileHandle', async () => {
3742
const name = 'test.json';
38-
const mockFile = createFile(name, {ping: true}, {
43+
const [mockFile, mockHandle] = createFileSystemFileHandle(name, {ping: true}, {
3944
type: 'application/json'
4045
});
41-
const evt = dragEvt([mockFile]);
4246

43-
const files = await fromEvent(evt);
47+
const files = await fromEvent([mockHandle]);
4448
expect(files).toHaveLength(1);
4549
expect(files.every(file => file instanceof File)).toBe(true);
4650

@@ -53,12 +57,32 @@ it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11
5357
expect(file.path).toBe(name);
5458
});
5559

56-
it('should return an empty array if the evt {target} has no {files} prop', async () => {
57-
const evt = inputEvtFromFiles();
60+
it('should return an empty array if the passed event is not a DragEvent', async () => {
61+
const evt = new Event('test');
5862
const files = await fromEvent(evt);
5963
expect(files).toHaveLength(0);
6064
});
6165

66+
it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11)', async () => {
67+
const name = 'test.json';
68+
const mockFile = createFile(name, {ping: true}, {
69+
type: 'application/json'
70+
});
71+
const evt = dragEvt([mockFile]);
72+
73+
const files = await fromEvent(evt);
74+
expect(files).toHaveLength(1);
75+
expect(files.every(file => file instanceof File)).toBe(true);
76+
77+
const [file] = files as FileWithPath[];
78+
79+
expect(file.name).toBe(mockFile.name);
80+
expect(file.type).toBe(mockFile.type);
81+
expect(file.size).toBe(mockFile.size);
82+
expect(file.lastModified).toBe(mockFile.lastModified);
83+
expect(file.path).toBe(name);
84+
});
85+
6286
it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => {
6387
const name = 'test.json';
6488
const mockFile = createFile(name, {ping: true}, {
@@ -373,9 +397,14 @@ function inputEvtFromFiles(...files: File[]): Event {
373397
value: files
374398
});
375399
}
376-
return {
377-
target: input
378-
} as any;
400+
return new Proxy(new CustomEvent('input'), {
401+
get(t, p, rcvr) {
402+
if (p === 'target') {
403+
return input;
404+
}
405+
return t[p];
406+
}
407+
});
379408
}
380409

381410
function createFile<T>(name: string, data: T, options?: FilePropertyBag) {
@@ -384,12 +413,26 @@ function createFile<T>(name: string, data: T, options?: FilePropertyBag) {
384413
return file;
385414
}
386415

416+
function createFileSystemFileHandle<T>(name: string, data: T, options?: FilePropertyBag): [File, FileSystemFileHandle] {
417+
const json = JSON.stringify(data);
418+
const file = new File([json], name, options);
419+
return [file, {
420+
getFile() {
421+
return Promise.resolve(file);
422+
}
423+
}];
424+
}
425+
387426
function sortFiles<T extends File>(files: T[]) {
388427
return files.slice(0)
389428
.sort((a, b) => a.name.localeCompare(b.name));
390429
}
391430

392431

432+
interface FileSystemFileHandle {
433+
getFile(): Promise<File>;
434+
}
435+
393436
type FileOrDirEntry = FileEntry | DirEntry
394437

395438
interface FileEntry extends Entry {

src/file-selector.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,21 @@ const FILES_TO_IGNORE = [
1212
* Convert a DragEvent's DataTrasfer object to a list of File objects
1313
* NOTE: If some of the items are folders,
1414
* everything will be flattened and placed in the same list but the paths will be kept as a {path} property.
15+
*
16+
* EXPERIMENTAL: A list of https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle objects can also be passed as an arg
17+
* and a list of File objects will be returned.
18+
*
1519
* @param evt
1620
*/
17-
export async function fromEvent(evt: Event): Promise<(FileWithPath | DataTransferItem)[]> {
18-
return isDragEvt(evt) && evt.dataTransfer
19-
? getDataTransferFiles(evt.dataTransfer, evt.type)
20-
: getInputFiles(evt);
21+
export async function fromEvent(evt: Event | any): Promise<(FileWithPath | DataTransferItem)[]> {
22+
if (isDragEvt(evt) && evt.dataTransfer) {
23+
return getDataTransferFiles(evt.dataTransfer, evt.type);
24+
} else if (evt instanceof Event) {
25+
return getInputFiles(evt);
26+
} else if (Array.isArray(evt) && evt.every(item => 'getFile' in item && typeof item.getFile === 'function')) {
27+
return getFsHandleFiles(evt)
28+
}
29+
return [];
2130
}
2231

2332
function isDragEvt(value: any): value is DragEvent {
@@ -33,6 +42,12 @@ function getInputFiles(evt: Event) {
3342
return files.map(file => toFileWithPath(file));
3443
}
3544

45+
// Ee expect each handle to be https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle
46+
async function getFsHandleFiles(handles: any[]) {
47+
const files = await Promise.all(handles.map(h => h.getFile()));
48+
return files.map(file => toFileWithPath(file));
49+
}
50+
3651
function isInput(value: EventTarget | null): value is HTMLInputElement {
3752
return value !== null;
3853
}

tsconfig.spec.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"extends": "./tsconfig.json",
33
"compilerOptions": {
44
"module": "commonjs",
5-
"target": "es5"
5+
"target": "es5",
6+
"suppressImplicitAnyIndexErrors": true
67
},
78
"include": [
89
"src/**/*.ts"

0 commit comments

Comments
 (0)