Skip to content

Commit 035ecf9

Browse files
committed
finish library typing, and fix some safty issues
1 parent 4191bb3 commit 035ecf9

File tree

4 files changed

+115
-66
lines changed

4 files changed

+115
-66
lines changed

src/lib/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// This file exists for Stories
21
import DropZone from "./internal/components/Dropzone.svelte";
32

43
export default DropZone;

src/lib/internal/components/Dropzone.svelte

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script lang="ts">
2-
import { fromEvent } from "file-selector";
2+
import { fromEvent, type FileWithPath } from "file-selector";
33
import {
44
fileAccepted,
55
fileMatchSize,
66
isEvtWithFiles,
77
isIeOrEdge,
88
isPropagationStopped,
9-
TOO_MANY_FILES_REJECTION
9+
TOO_MANY_FILES_REJECTION,
10+
type GenericFileItem,
11+
type ErrorDescription
1012
} from "../utils/index";
1113
import { onDestroy, createEventDispatcher } from "svelte";
1214
@@ -17,7 +19,7 @@
1719
*/
1820
export let accept: string | string[];
1921
export let disabled = false;
20-
export let getFilesFromEvent = fromEvent;
22+
export let getFilesFromEvent = fromEvent as (evt: Event) => Promise<(GenericFileItem)[]>;
2123
export let maxSize = Infinity;
2224
export let minSize = 0;
2325
export let multiple = true;
@@ -30,7 +32,34 @@
3032
export let containerStyles = "";
3133
export let disableDefaultStyles = false;
3234
export let name = "";
33-
const dispatch = createEventDispatcher();
35+
36+
export type { GenericFileItem };
37+
38+
interface GenericEventDetails {
39+
event: Event;
40+
}
41+
42+
interface DragEventDetails {
43+
dragEvent: DragEvent
44+
};
45+
46+
const dispatch = createEventDispatcher<{
47+
dragenter: DragEventDetails;
48+
dragover: DragEventDetails;
49+
dragleave: DragEventDetails;
50+
filedropped: GenericEventDetails;
51+
drop: {
52+
acceptedFiles: File[];
53+
fileRejections: { file: File, errors: ErrorDescription[] }[];
54+
} & GenericEventDetails;
55+
droprejected: {
56+
fileRejections: { file: File, errors: ErrorDescription[] }[];
57+
} & GenericEventDetails;
58+
dropaccepted: {
59+
acceptedFiles: File[];
60+
} & GenericEventDetails;
61+
filedialogcancel: undefined;
62+
}>();
3463
3564
//state
3665
@@ -40,13 +69,13 @@
4069
isDragActive: false,
4170
isDragAccept: false,
4271
isDragReject: false,
43-
draggedFiles: [],
44-
acceptedFiles: [],
45-
fileRejections: []
72+
draggedFiles: [] as (FileWithPath | DataTransferItem)[],
73+
acceptedFiles: [] as File[],
74+
fileRejections: [] as { file: File, errors: ErrorDescription[] }[]
4675
};
4776
48-
let rootRef;
49-
let inputRef;
77+
let rootRef: HTMLDivElement;
78+
let inputRef: HTMLInputElement | null;
5079
5180
function resetState() {
5281
state.isFileDialogActive = false;
@@ -59,19 +88,21 @@
5988
// Fn for opening the file dialog programmatically
6089
function openFileDialog() {
6190
if (inputRef) {
62-
inputRef.value = null; // TODO check if null needs to be set
91+
inputRef.value = ""; // TODO check if empty needs to be set
6392
state.isFileDialogActive = true;
6493
inputRef.click();
6594
}
6695
}
6796
6897
// Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
69-
function onKeyDownCb(event) {
98+
function onKeyDownCb(event: KeyboardEvent) {
7099
// Ignore keyboard events bubbling up the DOM tree
71-
if (!rootRef || !rootRef.isEqualNode(event.target)) {
100+
// TODO should we just check `rootRef === event.target`?
101+
if (!rootRef || !(event.target instanceof Node) || !rootRef.isEqualNode(event.target)) {
72102
return;
73103
}
74104
105+
// @ts-ignore: can't fix this deprecation, we need this version for legacy support
75106
if (event.keyCode === 32 || event.keyCode === 13) {
76107
event.preventDefault();
77108
openFileDialog();
@@ -102,11 +133,13 @@
102133
}
103134
}
104135
105-
function onDragEnterCb(event) {
136+
function onDragEnterCb(event: DragEvent) {
106137
event.preventDefault();
107138
stopPropagation(event);
108139
109-
dragTargetsRef = [...dragTargetsRef, event.target];
140+
if (event.target != null) {
141+
dragTargetsRef = [...dragTargetsRef, event.target];
142+
}
110143
111144
if (isEvtWithFiles(event)) {
112145
Promise.resolve(getFilesFromEvent(event)).then(draggedFiles => {
@@ -124,7 +157,7 @@
124157
}
125158
}
126159
127-
function onDragOverCb(event) {
160+
function onDragOverCb(event: DragEvent) {
128161
event.preventDefault();
129162
stopPropagation(event);
130163
@@ -143,17 +176,17 @@
143176
return false;
144177
}
145178
146-
function onDragLeaveCb(event) {
179+
function onDragLeaveCb(event: DragEvent) {
147180
event.preventDefault();
148181
stopPropagation(event);
149182
150183
// Only deactivate once the dropzone and all children have been left
151184
const targets = dragTargetsRef.filter(
152-
target => rootRef && rootRef.contains(target)
185+
target => rootRef && target instanceof Node && rootRef.contains(target)
153186
);
154187
// Make sure to remove a target present multiple times only once
155188
// (Firefox may fire dragenter/dragleave multiple times on the same element)
156-
const targetIdx = targets.indexOf(event.target);
189+
const targetIdx = (targets as (EventTarget | null)[]).indexOf(event.target);
157190
if (targetIdx !== -1) {
158191
targets.splice(targetIdx, 1);
159192
}
@@ -172,7 +205,7 @@
172205
}
173206
}
174207
175-
function onDropCb(event) {
208+
function onDropCb(event: Event) {
176209
event.preventDefault();
177210
stopPropagation(event);
178211
@@ -187,16 +220,18 @@
187220
return;
188221
}
189222
190-
const acceptedFiles = [];
191-
const fileRejections = [];
223+
const acceptedFiles: File[] = [];
224+
const fileRejections: { file: File, errors: ErrorDescription[] }[] = [];
225+
226+
files.forEach(fileGeneric => {
227+
const file = fileGeneric instanceof DataTransferItem ? (fileGeneric.getAsFile()!) : fileGeneric;
192228
193-
files.forEach(file => {
194-
const [accepted, acceptError] = fileAccepted(file, accept);
195-
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize);
229+
const { accepted, acceptError } = fileAccepted(file, accept);
230+
const { sizeMatch, sizeError } = fileMatchSize(file, minSize, maxSize);
196231
if (accepted && sizeMatch) {
197232
acceptedFiles.push(file);
198233
} else {
199-
const errors = [acceptError, sizeError].filter(e => e);
234+
const errors = [acceptError, sizeError].filter(e => e) as ErrorDescription[];
200235
fileRejections.push({ file, errors });
201236
}
202237
});
@@ -236,37 +271,37 @@
236271
resetState();
237272
}
238273
239-
function composeHandler(fn) {
274+
function composeHandler<Fn>(fn: Fn) {
240275
return disabled ? null : fn;
241276
}
242277
243-
function composeKeyboardHandler(fn) {
278+
function composeKeyboardHandler<Fn>(fn: Fn) {
244279
return noKeyboard ? null : composeHandler(fn);
245280
}
246281
247-
function composeDragHandler(fn) {
282+
function composeDragHandler<Fn>(fn: Fn) {
248283
return noDrag ? null : composeHandler(fn);
249284
}
250285
251-
function stopPropagation(event) {
286+
function stopPropagation(event: Event) {
252287
if (noDragEventsBubbling) {
253288
event.stopPropagation();
254289
}
255290
}
256291
257292
// allow the entire document to be a drag target
258-
function onDocumentDragOver(event) {
293+
function onDocumentDragOver(event: DragEvent) {
259294
if (preventDropOnDocument) {
260295
event.preventDefault();
261296
}
262297
}
263298
264-
let dragTargetsRef = [];
265-
function onDocumentDrop(event) {
299+
let dragTargetsRef: EventTarget[] = [];
300+
function onDocumentDrop(event: DragEvent) {
266301
if (!preventDropOnDocument) {
267302
return;
268303
}
269-
if (rootRef && rootRef.contains(event.target)) {
304+
if (rootRef && event.target instanceof Node && rootRef.contains(event.target)) {
270305
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
271306
return;
272307
}
@@ -282,7 +317,7 @@
282317
if (inputRef) {
283318
const { files } = inputRef;
284319
285-
if (!files.length) {
320+
if (!files?.length) {
286321
state.isFileDialogActive = false;
287322
dispatch("filedialogcancel");
288323
}
@@ -296,7 +331,7 @@
296331
inputRef = null;
297332
});
298333
299-
function onInputElementClick(event) {
334+
function onInputElementClick(event: MouseEvent) {
300335
event.stopPropagation();
301336
}
302337
</script>
@@ -339,7 +374,7 @@
339374
on:dragleave={composeDragHandler(onDragLeaveCb)}
340375
on:drop={composeDragHandler(onDropCb)}>
341376
<input
342-
{accept}
377+
accept={Array.isArray(accept) ? accept.join(',') : accept}
343378
{multiple}
344379
type="file"
345380
name={name}

src/lib/internal/utils/attr-accept.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @param acceptedFiles
99
* @returns {boolean}
1010
*/
11-
export default function(file: File, acceptedFiles: string): boolean {
11+
export default function(file: File, acceptedFiles: string | string[]): boolean {
1212
if (file && acceptedFiles) {
1313
const acceptedFilesArray = Array.isArray(acceptedFiles)
1414
? acceptedFiles

0 commit comments

Comments
 (0)