Background
Originally proposed in #133 (now closed).
Today, Tree always mounts DragPreviewContainer, which renders either the user's renderDragPreview or the built-in DefaultDragPreview. Inside useDragHook, we unconditionally call preview(getEmptyImage()) to suppress the browser's native drag image so our overlay can take its place.
This has two downsides:
- No way to use the browser's native drag preview. Consumers who want the plain, GPU-accelerated browser drag image (with no custom overlay) can't get it — we always hide the native image.
- Stutter on Chrome/macOS. When the custom preview moves outside the drop target, Chrome delays
drop events by ~250ms, causing a visible stutter. Using the native preview avoids this.
Additionally, consumers currently have no way to attach a drag preview element separately from the drag handle. React-dnd's useDrag exposes both a ref (drag source) and a preview connector, but only dragHandle is forwarded to NodeRenderer.
Proposed API
Two independent additions:
1. previewHandle on NodeRendererProps
Additive, non-breaking. Forwards the preview connector from useDrag so consumers can attach it to a specific DOM node (e.g. a wrapper element that excludes toolbars/badges).
type NodeRendererProps<T> = {
// ...existing fields
dragHandle?: (el: HTMLDivElement | null) => void;
previewHandle?: (el: HTMLDivElement | null) => void;
};
2. Opt-out for the custom drag preview
Let consumers say "use the browser's native preview, don't render DragPreviewContainer, don't call getEmptyImage()." Open question on the exact shape — options:
<Tree disableDragPreview /> — boolean prop, simple.
<Tree renderDragPreview={null} /> — reuse the existing prop; undefined keeps today's default, null opts out.
Either preserves backward compatibility: existing users with no renderDragPreview continue to see DefaultDragPreview.
Out of scope / follow-up
getEmptyImage() comes from react-dnd-html5-backend. Now that Tree accepts a custom dndBackend (#326), calling getEmptyImage() with a non-HTML5 backend is a latent bug. Worth fixing alongside this, but not required.
Prior art
PR #133 implemented (1) and a version of (2), but changed the default behavior (flipped the fallback from DefaultDragPreview to native), which would be a breaking visual change for every consumer who hasn't passed renderDragPreview. Closing that PR in favor of this issue.
Background
Originally proposed in #133 (now closed).
Today,
Treealways mountsDragPreviewContainer, which renders either the user'srenderDragPreviewor the built-inDefaultDragPreview. InsideuseDragHook, we unconditionally callpreview(getEmptyImage())to suppress the browser's native drag image so our overlay can take its place.This has two downsides:
dropevents by ~250ms, causing a visible stutter. Using the native preview avoids this.Additionally, consumers currently have no way to attach a drag preview element separately from the drag handle. React-dnd's
useDragexposes both aref(drag source) and apreviewconnector, but onlydragHandleis forwarded toNodeRenderer.Proposed API
Two independent additions:
1.
previewHandleonNodeRendererPropsAdditive, non-breaking. Forwards the
previewconnector fromuseDragso consumers can attach it to a specific DOM node (e.g. a wrapper element that excludes toolbars/badges).2. Opt-out for the custom drag preview
Let consumers say "use the browser's native preview, don't render
DragPreviewContainer, don't callgetEmptyImage()." Open question on the exact shape — options:<Tree disableDragPreview />— boolean prop, simple.<Tree renderDragPreview={null} />— reuse the existing prop;undefinedkeeps today's default,nullopts out.Either preserves backward compatibility: existing users with no
renderDragPreviewcontinue to seeDefaultDragPreview.Out of scope / follow-up
getEmptyImage()comes fromreact-dnd-html5-backend. Now thatTreeaccepts a customdndBackend(#326), callinggetEmptyImage()with a non-HTML5 backend is a latent bug. Worth fixing alongside this, but not required.Prior art
PR #133 implemented (1) and a version of (2), but changed the default behavior (flipped the fallback from
DefaultDragPreviewto native), which would be a breaking visual change for every consumer who hasn't passedrenderDragPreview. Closing that PR in favor of this issue.