Skip to content

Commit 5935b32

Browse files
author
Roland Groza
committed
fix: return DataTransferItem items if evt type is not 'drop'
1 parent 546ea01 commit 5935b32

File tree

2 files changed

+96
-26
lines changed

2 files changed

+96
-26
lines changed

src/file-selector.spec.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {FileWithPath} from './file';
12
import {fromEvent} from './file-selector';
23

34

@@ -21,8 +22,9 @@ it('should return the DataTransfer {files} if the passed event is a DragEvent',
2122

2223
const files = await fromEvent(evt);
2324
expect(files).toHaveLength(1);
25+
expect(files.every(file => file instanceof File)).toBe(true);
2426

25-
const [file] = files;
27+
const [file] = files as FileWithPath[];
2628

2729
expect(file.name).toBe(mockFile.name);
2830
expect(file.type).toBe(mockFile.type);
@@ -40,8 +42,9 @@ it('should return the evt {target} {files} if the passed event is an input evt',
4042

4143
const files = await fromEvent(evt);
4244
expect(files).toHaveLength(1);
45+
expect(files.every(file => file instanceof File)).toBe(true);
4346

44-
const [file] = files;
47+
const [file] = files as FileWithPath[];
4548

4649
expect(file.name).toBe(mockFile.name);
4750
expect(file.type).toBe(mockFile.type);
@@ -60,8 +63,9 @@ it('uses the DataTransfer {items} instead of {files} if it exists', async () =>
6063

6164
const files = await fromEvent(evt);
6265
expect(files).toHaveLength(1);
66+
expect(files.every(file => file instanceof File)).toBe(true);
6367

64-
const [file] = files;
68+
const [file] = files as FileWithPath[];
6569

6670
expect(file.name).toBe(mockFile.name);
6771
expect(file.type).toBe(mockFile.type);
@@ -78,9 +82,9 @@ it('uses the DataTransfer {files} if {items} is empty', async () => {
7882
const evt = dragEvtFromFilesAndItems([mockFile], []);
7983

8084
const files = await fromEvent(evt);
81-
expect(files).toHaveLength(1);
85+
expect(files.every(file => file instanceof File)).toBe(true);
8286

83-
const [file] = files;
87+
const [file] = files as FileWithPath[];
8488

8589
expect(file.name).toBe(mockFile.name);
8690
expect(file.type).toBe(mockFile.type);
@@ -90,11 +94,24 @@ it('uses the DataTransfer {files} if {items} is empty', async () => {
9094
});
9195

9296
it('skips DataTransfer {items} that are of kind "string"', async () => {
93-
const item = dataTransferItemFromStr('test');
94-
const evt = dragEvtFromItems(item);
97+
const name = 'test.json';
98+
const mockFile = createFile(name, {ping: true}, {
99+
type: 'application/json'
100+
});
101+
const f = dataTransferItemFromFile(mockFile);
102+
const str = dataTransferItemFromStr('test');
103+
const evt = dragEvtFromItems([str, f]);
95104

96105
const files = await fromEvent(evt);
97-
expect(files).toHaveLength(0);
106+
expect(files).toHaveLength(1);
107+
108+
const [file] = files as FileWithPath[];
109+
110+
expect(file.name).toBe(mockFile.name);
111+
expect(file.type).toBe(mockFile.type);
112+
expect(file.size).toBe(mockFile.size);
113+
expect(file.lastModified).toBe(mockFile.lastModified);
114+
expect(file.path).toBe(name);
98115
});
99116

100117
it('can read a tree of directories recursively and return a flat list of FileWithPath objects', async () => {
@@ -126,26 +143,74 @@ it('can read a tree of directories recursively and return a flat list of FileWit
126143
fileSystemFileEntryFromFile(f8)
127144
];
128145

129-
const evt = dragEvtFromItems(...entries.map(dataTransferItemFromEntry));
146+
const evt = dragEvtFromItems(entries.map(dataTransferItemFromEntry));
130147

131-
const files = sortFiles(await fromEvent(evt));
148+
const items = await fromEvent(evt);
149+
const files = sortFiles(items as FileWithPath[]);
132150
expect(files).toHaveLength(6);
133151
expect(files.every(file => file instanceof File)).toBe(true);
134152
expect(files.every(file => typeof file.path === 'string')).toBe(true);
135153
expect(files).toEqual(mockFiles);
136154
});
137155

156+
it('returns the DataTransfer {items} if the DragEvent {type} is not "drop"', async () => {
157+
const name = 'test.json';
158+
const mockFile = createFile(name, {ping: true}, {
159+
type: 'application/json'
160+
});
161+
const item = dataTransferItemFromFile(mockFile);
162+
const evt = dragEvtFromItems(item, 'dragenter');
163+
164+
const items = await fromEvent(evt);
165+
expect(items).toHaveLength(1);
166+
167+
const [itm] = items as DataTransferItem[];
168+
169+
expect(itm.kind).toBe(item.kind);
170+
expect(itm.kind).toBe('file');
171+
});
172+
173+
// tslint:disable-next-line: max-line-length
174+
it('filters DataTransfer {items} if the DragEvent {type} is not "drop" and DataTransferItem {kind} is "string"', async () => {
175+
const name = 'test.json';
176+
const mockFile = createFile(name, {ping: true}, {
177+
type: 'application/json'
178+
});
179+
const file = dataTransferItemFromFile(mockFile);
180+
const str = dataTransferItemFromStr('test');
181+
const evt = dragEvtFromItems([file, str], 'dragenter');
182+
183+
const items = await fromEvent(evt);
184+
expect(items).toHaveLength(1);
185+
186+
const [item] = items as DataTransferItem[];
187+
188+
expect(item.kind).toBe(file.kind);
189+
expect(item.kind).toBe('file');
190+
});
191+
138192

139-
function dragEvtFromFiles(...files: File[]): DragEvent {
140-
return {dataTransfer: {files}} as any;
193+
function dragEvtFromFiles(files: File | File[], type: string = 'drop'): DragEvent {
194+
return {
195+
type,
196+
dataTransfer: {
197+
files: Array.isArray(files) ? files : [files]
198+
}
199+
} as any;
141200
}
142201

143-
function dragEvtFromItems(...items: DataTransferItem[]): DragEvent {
144-
return {dataTransfer: {items}} as any;
202+
function dragEvtFromItems(items: DataTransferItem | DataTransferItem[], type: string = 'drop'): DragEvent {
203+
return {
204+
type,
205+
dataTransfer: {
206+
items: Array.isArray(items) ? items : [items]
207+
}
208+
} as any;
145209
}
146210

147-
function dragEvtFromFilesAndItems(files: File[], items: DataTransferItem[]): DragEvent {
211+
function dragEvtFromFilesAndItems(files: File[], items: DataTransferItem[], type: string = 'drop'): DragEvent {
148212
return {
213+
type,
149214
dataTransfer: {files, items}
150215
} as any;
151216
}
@@ -177,6 +242,7 @@ function dataTransferItemFromStr(str: string): DataTransferItem {
177242

178243
function dataTransferItemFromEntry(entry: FileEntry | DirEntry): DataTransferItem {
179244
return {
245+
kind: 'file',
180246
webkitGetAsEntry: () => {
181247
return entry;
182248
}

src/file-selector.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ const FILES_TO_IGNORE = [
1313
* everything will be flattened and placed in the same list but the paths will be kept as a {path} property.
1414
* @param evt
1515
*/
16-
export async function fromEvent(evt: Event): Promise<FileWithPath[]> {
16+
export async function fromEvent(evt: Event): Promise<Array<FileWithPath | DataTransferItem>> {
1717
if (isDragEvt(evt)) {
1818
const dt = evt.dataTransfer!;
1919
if (dt.items && dt.items.length) {
20-
return getDataTransferFiles(dt);
20+
return getDataTransferFiles(dt, evt.type);
2121
} else if (dt.files && dt.files.length) {
2222
return fromFileList(dt.files);
2323
}
@@ -32,20 +32,24 @@ function isDragEvt(value: any): value is DragEvent {
3232
return !!value.dataTransfer;
3333
}
3434

35-
async function getDataTransferFiles(dt: DataTransfer) {
36-
const items = Array.from(dt.items);
37-
const files = await Promise.all(items.map(item => toFilePromises(item)));
38-
return flatten<FileWithPath>(files)
39-
.filter(file => !FILES_TO_IGNORE.includes(file.name));
35+
async function getDataTransferFiles(dt: DataTransfer, type: string) {
36+
const items = Array.from(dt.items)
37+
.filter(item => item.kind === 'file');
38+
// According to https://html.spec.whatwg.org/multipage/dnd.html#dndevents,
39+
// only dragstart, and dragend (drop) has access to the data (source node),
40+
// hence return the DataTransferItem for other event types
41+
if (type === 'drop') {
42+
const files = await Promise.all(items.map(item => toFilePromises(item)));
43+
return flatten<FileWithPath>(files)
44+
.filter(file => !FILES_TO_IGNORE.includes(file.name));
45+
}
46+
return items;
4047
}
4148

4249
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem
4350
function toFilePromises(item: DataTransferItem) {
4451
if (typeof item.webkitGetAsEntry !== 'function') {
45-
if (item.kind === 'file') {
46-
return fromDataTransferItem(item);
47-
}
48-
return [];
52+
return fromDataTransferItem(item);
4953
}
5054

5155
const entry = item.webkitGetAsEntry();

0 commit comments

Comments
 (0)