[Svelte] DragOverlay disappears one frame before the hidden source is restored
- Dominant language
- TypeScript
- Stars
- 17.6k
- Forks
- 924
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 2
Description
When the source content is hidden using `draggable.isDragSource`, the default drop animation ends with a brief blank frame: the overlay disappears before the source becomes visible again.
The source element is already in the DOM and remains connected throughout the animation and cleanup. This reproduces with a single draggable, without Query, network requests, list updates, or application state changes.
## Environment
- `@dnd-kit/svelte`: **0.5.0**
- `@dnd-kit/dom`: **0.5.0**
- Svelte: **5.57.0**
- Chromium: **151.0.7922.34**, via Playwright **1.62.1**
- Windows
- Default drop animation; normal motion preference
## Minimal reproduction
In a Svelte/Vite project with the versions above, use these two components. The original card keeps its dimensions while its content is hidden during the drag.
**App.svelte**
```svelte
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import Card from './Card.svelte';
Drag the card away, release, and watch the end of its return animation.
```
**Card.svelte**
```svelte
import { createDraggable } from '@dnd-kit/svelte';
const draggable = createDraggable({ id: 'card' });
```
1. Open the reproduction in Chromium.
2. Set CPU throttling to **4× slowdown** to make the handoff easier to inspect.
3. Drag the card several hundred pixels away from its original position.
4. Release it and watch the end of the return animation.
The problem also reproduced without CPU throttling.
## Expected behavior
The overlay remains visible until the source is visible again. No frame during the handoff should have both hidden.
## Actual behavior
The overlay animates back correctly, disappears, and then the source becomes visible on a later frame.
A `requestAnimationFrame` observer sampled both elements' visibility until cleanup completed. The same source DOM node remained connected in every sample:
| CPU throttling | Existing timer cleanup: blank frame samples | Microtask cleanup: blank frame samples |
| --- | ---: | ---: |
| None | 1 | 0 |
| 4× slowdown | 1 | 0 |
These are observations from the isolated reproduction, not a claim that every device will always show exactly one blank frame. A DOM assertion that only checks the final settled state misses this intermediate state.
## Cause
In [Feedback.ts](https://github.com/clauderic/dnd-kit/blob/main/packages/dom/src/core/plugins/feedback/Feedback.ts#L445-L495), cleanup hides the overlay by removing its dragging attribute and resetting its styles. For an overlay, `finalize` then runs in a separate timer task.
`finalize` sets the source status to idle, allowing the drag operation to reset and Svelte to reveal the source. The task boundary permits rendering between hiding the overlay and revealing the source.
## Proposed fix
Keep finalization asynchronous, but schedule it in a microtask:
```diff
if (feedbackElement === this.overlay) {
- setTimeout(finalize, 0);
+ queueMicrotask(finalize);
} else {
finalize();
}
```
In the Svelte reproduction this lets the operation reset and the resulting DOM update finish before the next paint. The change is in the shared DOM feedback plugin; it does not require an application-level animation workaround.
## Validation
- The standalone reproduction has no Query dependency or application data layer. The existing timer produced a blank frame at both 1× and 4×; replacing only that scheduling call removed it.
- Additional browser checks in a calendar application passed for planner → todo, todo → planner, and day → day at 4× slowdown.
- Reduced-motion drops, keyboard cancellation with focus restoration, a second drag after settling, and rollback after a rejected optimistic update also passed.
The patch has been validated with the Svelte adapter in Chromium. Other adapters and browsers have not been validated here. A regression test should inspect visibility across animation frames through the final overlay-to-source handoff, rather than only checking the final DOM.
Contributor guide
Research direction
Start in packages/dom/src/core/plugins/feedback/Feedback.ts, especially the overlay cleanup and finalize flow around lines 445-495. Replace the timer scheduling only after checking how the source status reset reaches the Svelte DOM, then add a regression test that samples visibility across animation frames. Done means the overlay remains visible until the source is visible, including under throttling, without breaking reduced-motion, cancellation, or rollback behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100