72 lines
1.6 KiB
Svelte
72 lines
1.6 KiB
Svelte
<script lang="ts">
|
|
const { onfiles = () => {}, accept = '*', multiple = false, children } = $props();
|
|
|
|
let dragging = $state(false);
|
|
let fileInputEl: HTMLInputElement; // Reference to the hidden file input
|
|
|
|
function handleDrop(e: DragEvent) {
|
|
e.preventDefault();
|
|
dragging = false;
|
|
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
if (files.length) {
|
|
onfiles(files);
|
|
}
|
|
}
|
|
|
|
function handleDragOver(e: DragEvent) {
|
|
e.preventDefault();
|
|
dragging = true;
|
|
}
|
|
|
|
function handleDragLeave(e: DragEvent) {
|
|
e.preventDefault();
|
|
dragging = false;
|
|
}
|
|
|
|
/** Handles file selection from the native dialog */
|
|
function handleFileSelect(e: Event) {
|
|
const input = e.target as HTMLInputElement;
|
|
const files = Array.from(input.files ?? []);
|
|
if (files.length) {
|
|
onfiles(files);
|
|
}
|
|
// IMPORTANT: Reset the input value so the same file can be selected again
|
|
input.value = '';
|
|
}
|
|
|
|
/** Programmatically clicks the hidden file input */
|
|
function openFileSelect() {
|
|
fileInputEl.click();
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
role="button"
|
|
aria-label="Drop files or click to upload"
|
|
tabindex="0"
|
|
ondragover={handleDragOver}
|
|
ondragleave={handleDragLeave}
|
|
ondrop={handleDrop}
|
|
onclick={openFileSelect}
|
|
onkeydown={(e) => {
|
|
// Allow keyboard users to trigger the click with Enter or Space
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
openFileSelect();
|
|
}
|
|
}}
|
|
class="rounded-lg border-2 border-dashed p-6 text-center transition-colors
|
|
{dragging ? 'border-primary bg-primary/5' : 'border-gray-300'}"
|
|
>
|
|
<input
|
|
type="file"
|
|
bind:this={fileInputEl}
|
|
{accept}
|
|
{multiple}
|
|
onchange={handleFileSelect}
|
|
class="sr-only"
|
|
/>
|
|
|
|
{@render children?.()}
|
|
</div>
|