feat(upload): expand drop zone when dragging file over browser window
Adds window-level dragenter/dragleave/drop listeners that detect when the user drags any file into the browser. The drop zone expands from py-3 to py-10 with a softened highlight, giving a clear visual cue that dropping is possible anywhere on the page. Uses a drag-counter to correctly handle the dragenter/dragleave storm that fires as the pointer moves across child elements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ let tagNames = $state<string[]>(untrack(() => data.filters?.tags || []));
|
|||||||
const ACCEPTED_TYPES = ['application/pdf', 'image/jpeg', 'image/png', 'image/tiff'];
|
const ACCEPTED_TYPES = ['application/pdf', 'image/jpeg', 'image/png', 'image/tiff'];
|
||||||
|
|
||||||
let isDragging = $state(false);
|
let isDragging = $state(false);
|
||||||
|
let windowDragging = $state(false);
|
||||||
|
let dragCounter = 0;
|
||||||
let isUploading = $state(false);
|
let isUploading = $state(false);
|
||||||
let uploadMessages = $state<{ text: string; isError: boolean }[]>([]);
|
let uploadMessages = $state<{ text: string; isError: boolean }[]>([]);
|
||||||
let fileInput: HTMLInputElement;
|
let fileInput: HTMLInputElement;
|
||||||
@@ -48,6 +50,8 @@ function handleDragLeave() {
|
|||||||
async function handleDrop(e: DragEvent) {
|
async function handleDrop(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
|
windowDragging = false;
|
||||||
|
dragCounter = 0;
|
||||||
const files = Array.from(e.dataTransfer?.files ?? []);
|
const files = Array.from(e.dataTransfer?.files ?? []);
|
||||||
await uploadFiles(files);
|
await uploadFiles(files);
|
||||||
}
|
}
|
||||||
@@ -144,6 +148,40 @@ $effect(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Expand drop zone whenever a file is dragged anywhere over the browser window
|
||||||
|
$effect(() => {
|
||||||
|
if (!data.canWrite) return;
|
||||||
|
|
||||||
|
function onWindowDragEnter(e: DragEvent) {
|
||||||
|
if (!e.dataTransfer?.types.includes('Files')) return;
|
||||||
|
dragCounter++;
|
||||||
|
windowDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowDragLeave() {
|
||||||
|
dragCounter--;
|
||||||
|
if (dragCounter <= 0) {
|
||||||
|
dragCounter = 0;
|
||||||
|
windowDragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowDrop() {
|
||||||
|
dragCounter = 0;
|
||||||
|
windowDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('dragenter', onWindowDragEnter);
|
||||||
|
window.addEventListener('dragleave', onWindowDragLeave);
|
||||||
|
window.addEventListener('drop', onWindowDrop);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('dragenter', onWindowDragEnter);
|
||||||
|
window.removeEventListener('dragleave', onWindowDragLeave);
|
||||||
|
window.removeEventListener('drop', onWindowDrop);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Sync local state with server data after navigation.
|
// Sync local state with server data after navigation.
|
||||||
// Guard q: skip overwrite while the user is actively typing in the search field.
|
// Guard q: skip overwrite while the user is actively typing in the search field.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -297,9 +335,11 @@ $effect(() => {
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="mb-4 flex cursor-pointer items-center justify-center gap-3 border border-dashed px-6 py-3 text-sm transition-colors duration-150 {isDragging
|
class="mb-4 flex cursor-pointer items-center justify-center gap-3 border border-dashed px-6 text-sm transition-all duration-200 {isDragging
|
||||||
? 'border-primary bg-accent-bg text-primary'
|
? 'border-primary bg-accent-bg py-10 text-primary'
|
||||||
: 'border-ink/20 text-ink-3 hover:border-primary hover:text-primary'}"
|
: windowDragging
|
||||||
|
? 'border-primary/60 bg-accent-bg/50 py-10 text-primary/80'
|
||||||
|
: 'border-ink/20 py-3 text-ink-3 hover:border-primary hover:text-primary'}"
|
||||||
ondragover={handleDragOver}
|
ondragover={handleDragOver}
|
||||||
ondragleave={handleDragLeave}
|
ondragleave={handleDragLeave}
|
||||||
ondrop={handleDrop}
|
ondrop={handleDrop}
|
||||||
|
|||||||
Reference in New Issue
Block a user