Skip to content

Commit 3250c57

Browse files
committed
fix(sidebar): disarm the spring-open timer when a drag ends
1 parent 8b28504 commit 3250c57

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,43 @@ describe('useDragDrop spring-open revert', () => {
327327
expect(expandedFolders.has('folder-1')).toBe(true)
328328
})
329329

330+
/**
331+
* The spring-open timer is armed for 400ms, so a drag ending just before it fires leaves it
332+
* pending. Relying on the effect cleanup to cancel it would let it land after the drag-end
333+
* collapse had already emptied the set — re-adding the folder for the *next* drag to close, by
334+
* which point the user had opened it themselves.
335+
*/
336+
it('does not spring-open a folder when the drag ends before the timer fires', () => {
337+
act(() => {
338+
latest.handleDragStart(null)
339+
})
340+
act(() => {
341+
latest
342+
.createFolderDragHandlers('folder-1', null)
343+
.onDragOver(fakeFolderDragOverEvent() as never)
344+
})
345+
346+
/**
347+
* Deliberately outside `act`: the race only exists while React has scheduled the drag-end state
348+
* changes but not yet committed them, so the effect cleanup has not run and the timer is still
349+
* armed. Wrapping this in `act` would flush the commit first and cancel the timer via the
350+
* cleanup, hiding the very gap under test.
351+
*/
352+
latest.handleDragEnd()
353+
act(() => {
354+
vi.advanceTimersByTime(500)
355+
})
356+
357+
expect(mockSetExpanded).not.toHaveBeenCalledWith('folder-1', true)
358+
expect(expandedFolders.has('folder-1')).toBe(false)
359+
360+
// Nothing was left behind for a later drag to collapse.
361+
act(() => {
362+
latest.handleDragEnd()
363+
})
364+
expect(mockSetExpanded).not.toHaveBeenCalledWith('folder-1', false)
365+
})
366+
330367
it('never closes a folder the user had already opened themselves', () => {
331368
expandedFolders.add('folder-1')
332369

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,16 @@ export function useDragDrop(options: UseDragDropOptions = {}) {
732732
setDropIndicator(null)
733733
draggedSourceFolderRef.current = null
734734
setHoverFolderId(null)
735+
/**
736+
* Disarmed here rather than left to the effect cleanup, which only runs once React commits the
737+
* state changes above. A timer due within that gap would otherwise fire after the collapse
738+
* below, spring-opening a folder for a drag that already ended and leaving it in the set for
739+
* the next drag to close — a folder the user, by then, opened themselves.
740+
*/
741+
if (hoverExpandTimerRef.current) {
742+
clearTimeout(hoverExpandTimerRef.current)
743+
hoverExpandTimerRef.current = null
744+
}
735745
collapseAutoExpandedFolders()
736746
}, [collapseAutoExpandedFolders])
737747

0 commit comments

Comments
 (0)