Skip to content

feat(dnd): useDrag: custom drag preview offset #8445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

reidbarber
Copy link
Member

@reidbarber reidbarber commented Jun 25, 2025

Closes #5409

Allows providing a getPreviewOffset to specify the x and y offset of the drag preview.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

Try the new story and adjust the controls.

🧢 Your Project:

@reidbarber reidbarber changed the title useDrag: custom drag preview offset feat(dnd): useDrag: custom drag preview offset Jun 25, 2025
snowystinger
snowystinger previously approved these changes Jun 25, 2025
@snowystinger
Copy link
Member

I've no idea why it approved twice...

@reidbarber reidbarber enabled auto-merge June 25, 2025 23:45
Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work well in the story and the code seems straight forward enough, but should setting this also be supported in useDragAndDrop as well? The original issue also mentions a use case with multiple drag previews but at the moment this is for useDrag only which only for one draggable element if I remember correctly

@reidbarber
Copy link
Member Author

@LFDanLu Good idea, just added.

@rspbot
Copy link

rspbot commented Jul 7, 2025

snowystinger
snowystinger previously approved these changes Jul 7, 2025
@@ -65,7 +82,8 @@ export function useDraggableCollectionState(props: DraggableCollectionStateOptio
onDragMove,
onDragEnd,
preview,
getAllowedDropOperations
getAllowedDropOperations,
getPreviewOffset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just gets passed through, but can you add a story or a test just making sure that providing this to useDragAndDrop works?

@rspbot
Copy link

rspbot commented Jul 8, 2025

@rspbot
Copy link

rspbot commented Jul 8, 2025

@rspbot
Copy link

rspbot commented Jul 8, 2025

## API Changes

react-aria-components

/react-aria-components:DragAndDropOptions

 DragAndDropOptions {
   acceptedDragTypes?: 'all' | Array<string | symbol> = 'all'
   dropTargetDelegate?: DropTargetDelegate
   getAllowedDropOperations?: () => Array<DropOperation>
   getDropOperation?: (DropTarget, DragTypes, Array<DropOperation>) => DropOperation
   getItems?: (Set<Key>) => Array<DragItem> = () => []
+  getPreviewOffset?: ({
+    previewRect: DOMRect
+  sourceRect: DOMRect
+  pointerPosition: {
+      x: number
+    y: number
+}
+  defaultOffset: {
+      x: number
+    y: number
+}
+}) => {
+    x: number
+  y: number
+}
   isDisabled?: boolean
   onDragEnd?: (DraggableCollectionEndEvent) => void
   onDragMove?: (DraggableCollectionMoveEvent) => void
   onDragStart?: (DraggableCollectionStartEvent) => void
   onDropActivate?: (DroppableCollectionActivateEvent) => void
   onDropEnter?: (DroppableCollectionEnterEvent) => void
   onDropExit?: (DroppableCollectionExitEvent) => void
   onInsert?: (DroppableCollectionInsertDropEvent) => void
   onItemDrop?: (DroppableCollectionOnItemDropEvent) => void
   onMove?: (DroppableCollectionReorderEvent) => void
   onReorder?: (DroppableCollectionReorderEvent) => void
   onRootDrop?: (DroppableCollectionRootDropEvent) => void
   renderDragPreview?: (Array<DragItem>) => JSX.Element
   renderDropIndicator?: (DropTarget, Set<Key>, Key) => JSX.Element
   shouldAcceptItemDrop?: (ItemDropTarget, DragTypes) => boolean
 }

@react-aria/dnd

/@react-aria/dnd:DragOptions

 DragOptions {
   getAllowedDropOperations?: () => Array<DropOperation>
   getItems: () => Array<DragItem>
+  getPreviewOffset?: ({
+    previewRect: DOMRect
+  sourceRect: DOMRect
+  pointerPosition: {
+      x: number
+    y: number
+}
+  defaultOffset: {
+      x: number
+    y: number
+}
+}) => {
+    x: number
+  y: number
+}
   hasDragButton?: boolean
   isDisabled?: boolean
   onDragEnd?: (DragEndEvent) => void
   onDragMove?: (DragMoveEvent) => void
   preview?: RefObject<DragPreviewRenderer | null>
 }

@react-spectrum/dnd

/@react-spectrum/dnd:DragAndDropOptions

 DragAndDropOptions {
   acceptedDragTypes?: 'all' | Array<string | symbol> = 'all'
   getAllowedDropOperations?: () => Array<DropOperation>
   getDropOperation?: (DropTarget, DragTypes, Array<DropOperation>) => DropOperation
   getItems?: (Set<Key>) => Array<DragItem> = () => []
+  getPreviewOffset?: ({
+    previewRect: DOMRect
+  sourceRect: DOMRect
+  pointerPosition: {
+      x: number
+    y: number
+}
+  defaultOffset: {
+      x: number
+    y: number
+}
+}) => {
+    x: number
+  y: number
+}
   onDragEnd?: (DraggableCollectionEndEvent) => void
   onDragMove?: (DraggableCollectionMoveEvent) => void
   onDragStart?: (DraggableCollectionStartEvent) => void
   onDrop?: (DroppableCollectionDropEvent) => void
   onDropEnter?: (DroppableCollectionEnterEvent) => void
   onDropExit?: (DroppableCollectionExitEvent) => void
   onInsert?: (DroppableCollectionInsertDropEvent) => void
   onItemDrop?: (DroppableCollectionOnItemDropEvent) => void
   onReorder?: (DroppableCollectionReorderEvent) => void
   onRootDrop?: (DroppableCollectionRootDropEvent) => void
   renderPreview?: (Set<Key>, Key) => JSX.Element
   shouldAcceptItemDrop?: (ItemDropTarget, DragTypes) => boolean
 }

@react-stately/dnd

/@react-stately/dnd:DraggableCollectionStateOptions

 DraggableCollectionStateOptions {
   collection: Collection<Node<unknown>>
   getAllowedDropOperations?: () => Array<DropOperation>
   getItems: (Set<Key>) => Array<DragItem>
+  getPreviewOffset?: ({
+    previewRect: DOMRect
+  sourceRect: DOMRect
+  pointerPosition: {
+      x: number
+    y: number
+}
+  defaultOffset: {
+      x: number
+    y: number
+}
+}) => {
+    x: number
+  y: number
+}
   isDisabled?: boolean
   onDragEnd?: (DraggableCollectionEndEvent) => void
   onDragMove?: (DraggableCollectionMoveEvent) => void
   onDragStart?: (DraggableCollectionStartEvent) => void
   selectionManager: MultipleSelectionManager
 }

/@react-stately/dnd:DraggableCollectionState

 DraggableCollectionState {
   collection: Collection<Node<unknown>>
   draggedKey: Key | null
   draggingKeys: Set<Key>
   endDrag: (DraggableCollectionEndEvent) => void
   getAllowedDropOperations?: () => Array<DropOperation>
   getItems: (Key) => Array<DragItem>
   getKeysForDrag: (Key) => Set<Key>
+  getPreviewOffset?: ({
+    previewRect: DOMRect
+  sourceRect: DOMRect
+  pointerPosition: {
+      x: number
+    y: number
+}
+  defaultOffset: {
+      x: number
+    y: number
+}
+}) => {
+    x: number
+  y: number
+}
   isDisabled?: boolean
   isDragging: (Key) => boolean
   moveDrag: (DragMoveEvent) => void
   preview?: RefObject<DragPreviewRenderer | null>
   startDrag: (Key, DragStartEvent) => void
 }

@devongovett
Copy link
Member

What do you think about the API proposed here #5409 (comment)? Instead of a separate callback, you'd return an object with both the preview element and the position from renderDragPreview.

@reidbarber
Copy link
Member Author

@devongovett I think that would be good so we could support different drag previews based on the data type. Any opinions about keeping the options that were passed into getPreviewOffset, and passing them into renderDragPreview instead? I think those could be useful, but it's also a lot of info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

useDrag set x/y offset for setDragImage
5 participants