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'];
|
||||
|
||||
let isDragging = $state(false);
|
||||
let windowDragging = $state(false);
|
||||
let dragCounter = 0;
|
||||
let isUploading = $state(false);
|
||||
let uploadMessages = $state<{ text: string; isError: boolean }[]>([]);
|
||||
let fileInput: HTMLInputElement;
|
||||
@@ -48,6 +50,8 @@ function handleDragLeave() {
|
||||
async function handleDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
isDragging = false;
|
||||
windowDragging = false;
|
||||
dragCounter = 0;
|
||||
const files = Array.from(e.dataTransfer?.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.
|
||||
// Guard q: skip overwrite while the user is actively typing in the search field.
|
||||
$effect(() => {
|
||||
@@ -297,9 +335,11 @@ $effect(() => {
|
||||
<div
|
||||
role="button"
|
||||
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
|
||||
? 'border-primary bg-accent-bg text-primary'
|
||||
: 'border-ink/20 text-ink-3 hover:border-primary hover:text-primary'}"
|
||||
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 py-10 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}
|
||||
ondragleave={handleDragLeave}
|
||||
ondrop={handleDrop}
|
||||
|
||||
Reference in New Issue
Block a user