Skip to content

Commit 05dfd81

Browse files
committed
fix(dropzone): unconditional reset on drop covers onDrop early-return paths
1 parent c70bd89 commit 05dfd81

1 file changed

Lines changed: 18 additions & 5 deletions

File tree

src/dashboard/src/hooks/useDashboardDropZone.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,19 @@ export function useDashboardDropZone(
137137
// over every tab, dimming the page and visually blocking every
138138
// interaction even though pointer-events:none lets clicks through.
139139
//
140-
// Three independent resets:
141-
// 1. `dragend` fires on the drag SOURCE when the operation ends
142-
// for any reason (drop, ESC, cancel). Always reset.
143-
// 2. window `blur` — user switched apps mid-drag. Reset so the
140+
// Four independent resets:
141+
// 1. `drop` safety pass — the main onDrop handler returns early
142+
// when payload isn't files or when a local zone owns the
143+
// drop, so this unconditional reset runs after it to guarantee
144+
// isDragging clears no matter which branch onDrop took.
145+
// 2. `dragend` fires on the drag SOURCE when an in-page drag ends
146+
// for any reason (drop, ESC, cancel). Does NOT fire for
147+
// external OS-originated file drags (where the source is the
148+
// Finder/Explorer, not the window) — those are covered by the
149+
// drop safety pass above and the blur/pointerover fallbacks.
150+
// 3. window `blur` — user switched apps mid-drag. Reset so the
144151
// overlay doesn't survive the focus loss.
145-
// 3. `pointerover` outside any drag — if the cursor is moving
152+
// 4. `pointerover` outside any drag — if the cursor is moving
146153
// around the page without an active drag, there can't be one
147154
// in progress; reset to safe state.
148155
const reset = () => {
@@ -156,6 +163,11 @@ export function useDashboardDropZone(
156163
window.addEventListener('dragover', onDragOver);
157164
window.addEventListener('dragleave', onDragLeave);
158165
window.addEventListener('drop', onDrop);
166+
// Unconditional reset runs AFTER onDrop. Both listeners fire on the
167+
// same event; React's event ordering preserves attachment order so
168+
// onDrop processes the file first, then this pass clears state
169+
// even if onDrop took an early-return path that skipped the reset.
170+
window.addEventListener('drop', reset);
159171
window.addEventListener('dragend', reset);
160172
window.addEventListener('blur', reset);
161173
window.addEventListener('pointerover', onPointerOverIdle);
@@ -164,6 +176,7 @@ export function useDashboardDropZone(
164176
window.removeEventListener('dragover', onDragOver);
165177
window.removeEventListener('dragleave', onDragLeave);
166178
window.removeEventListener('drop', onDrop);
179+
window.removeEventListener('drop', reset);
167180
window.removeEventListener('dragend', reset);
168181
window.removeEventListener('blur', reset);
169182
window.removeEventListener('pointerover', onPointerOverIdle);

0 commit comments

Comments
 (0)