clauderic / dnd-kit

The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
http://dndkit.com
MIT License
13.13k stars 651 forks source link

Droppable - Wrong Droppable area with transform style #205

Closed a1ooha closed 2 years ago

a1ooha commented 3 years ago

Wrong Droppable area when using transfrom style on Droppable container, how can i fix it?

demo: https://codesandbox.io/s/stoic-river-iwsl3?file=/src/App.js

a1ooha commented 3 years ago

when using transfrom style, node.offsetLeft and node.offsetTop are incorrect.

function getEdgeOffset(
  node: HTMLElement | null,
  parent: (Node & ParentNode) | null,
  offset = defaultCoordinates
): Coordinates {
  if (!node || !(node instanceof HTMLElement)) {
    return offset;
  }

  const nodeOffset = {
    x: offset.x + node.offsetLeft,
    y: offset.y + node.offsetTop,
  };

  if (node.offsetParent === parent) {
    return nodeOffset;
  }

  return getEdgeOffset(node.offsetParent as HTMLElement, parent, nodeOffset);
}

so, getEdgeOffset return incorrect nodeOffset. i try to fix it with replacing const {x: offsetLeft, y: offsetTop} = getEdgeOffset(element, null); with const {left: offsetLeft, top: offsetTop} = element.getBoundingClientRect();, it performs well in my application.

export function getElementLayout(element: HTMLElement): LayoutRect {
  const {offsetWidth: width, offsetHeight: height} = element;
  const {left: offsetLeft, top: offsetTop} = element.getBoundingClientRect();

  return {
    width,
    height,
    offsetTop,
    offsetLeft,
  };
}
ranbena commented 3 years ago

I have another use-case in which a transformed SortableContext yields an offset drag overlay. Here's the sandbox demo https://codesandbox.io/s/dnd-kit-offset-b2744

@a1ooha can you test to see if your PR resolves this case as well?

https://user-images.githubusercontent.com/486954/116719759-1da62f00-a9e4-11eb-8e9b-08d079c2e643.mov

a1ooha commented 3 years ago

I have another use-case in which a transformed SortableContext yields an offset drag overlay. Here's the sandbox demo https://codesandbox.io/s/dnd-kit-offset-b2744

@a1ooha can you test to see if your PR resolves this case as well?

Screen.Recording.2021-04-30.at.18.40.50.mov

no, it can't resolve your case.

haftav commented 3 years ago

Hi, just wanted to follow up on this thread as I’m also running into issues related to CSS transforms. I need to support a use case where I can drag an item from a sortable list onto a node in a directed graph to perform a copy action. The graph rendering library (React Flow) uses transforms to position the nodes on the graph, but it doesn’t appear that dnd-kit is taking those transforms into account when computing whether a draggable element is over a droppable element. You can see an example in this gif - when I hover directly over a droppable node on the graph, nothing happens, but when I hover over the top left corner of the graph (where the elements would exist without any transforms applied) the isOver variable flips to true for the droppable element.

dnd-toolkit + react-flow

Here’s a codesandbox link to the example: https://codesandbox.io/s/dndkit-react-flow-example-e71bc?file=/src/App.js

I noticed there may be a measure config option introduced in a future release - would that provide a way to get around this issue?

Thanks for your help and work on this library! Apart from this issue, everything’s worked great and has been pretty intuitive to use.

haftav commented 3 years ago

Following up on the comment above, I updated the @dnd-kit packages to the @next tag and was able to get it working using the measuring prop with this configuration:

{
  droppable: {
    measure: getViewportLayoutRect,
  }
}
abrman commented 2 years ago

Think this one is closed since Refactor measuring and collision detection #518

nelsieborja commented 2 years ago

Hi, just wanted to follow up on this thread as I’m also running into issues related to CSS transforms. I need to support a use case where I can drag an item from a sortable list onto a node in a directed graph to perform a copy action. The graph rendering library (React Flow) uses transforms to position the nodes on the graph, but it doesn’t appear that dnd-kit is taking those transforms into account when computing whether a draggable element is over a droppable element. You can see an example in this gif - when I hover directly over a droppable node on the graph, nothing happens, but when I hover over the top left corner of the graph (where the elements would exist without any transforms applied) the isOver variable flips to true for the droppable element.

dnd-toolkit + react-flow dnd-toolkit + react-flow

Here’s a codesandbox link to the example: https://codesandbox.io/s/dndkit-react-flow-example-e71bc?file=/src/App.js

I noticed there may be a measure config option introduced in a future release - would that provide a way to get around this issue?

Thanks for your help and work on this library! Apart from this issue, everything’s worked great and has been pretty intuitive to use.

@haftav , were you able to fix this issue without using portal to render DragOverlay ?

naotake51 commented 2 weeks ago

Hi, just wanted to follow up on this thread as I’m also running into issues related to CSS transforms. I need to support a use case where I can drag an item from a sortable list onto a node in a directed graph to perform a copy action. The graph rendering library (React Flow) uses transforms to position the nodes on the graph, but it doesn’t appear that dnd-kit is taking those transforms into account when computing whether a draggable element is over a droppable element. You can see an example in this gif - when I hover directly over a droppable node on the graph, nothing happens, but when I hover over the top left corner of the graph (where the elements would exist without any transforms applied) the isOver variable flips to true for the droppable element.↳

dnd-toolkit + react-flow dnd-toolkit + react-flow

Here’s a codesandbox link to the example: https://codesandbox.io/s/dndkit-react-flow-example-e71bc?file=/src/App.js

I noticed there may be a measure config option introduced in a future release - would that provide a way to get around this issue?

Thanks for your help and work on this library! Apart from this issue, everything’s worked great and has been pretty intuitive to use.

I'm using transform: translate(x, y) to adjust the position of the Droppable component, but it seems that the default collision detection logic doesn't take transformations into account, as it relies on the ClientRect properties—top, bottom, left, and right—which are unaffected by transform.

スクリーンショット 2024-11-01 16 41 39

https://github.com/clauderic/dnd-kit/blob/e2a1776d0de657669192d3cfd1558e91905b5fad/packages/core/src/utilities/algorithms/rectIntersection.ts#L9-L32

I know that the collision detection logic can be customized in DndContext through the collisionDetection prop, but I'm not sure how to implement it.

https://docs.dndkit.com/api-documentation/context-provider#props

naotake51 commented 2 weeks ago

https://github.com/clauderic/dnd-kit/blob/e2a1776d0de657669192d3cfd1558e91905b5fad/packages/core/src/utilities/rect/getRect.ts#L21-L28

This code might be helpful

naotake51 commented 2 weeks ago

Think this one is closed since Refactor measuring and collision detection #518

Thank you. I finally understood it and was able to achieve the intended behavior.

I'll post the code I used to check the operation.

import {
  useDraggable,
  useDroppable,
  DndContext,
  getClientRect,
} from '@dnd-kit/core'
import { CSS } from '@dnd-kit/utilities'

export function Test() {
  const measuring = {
    droppable: {
      measure: getClientRect, // ⭐️ By default, getTransformAgnosticClientRect is used.
    },
  }
  return (
    <DndContext measuring={measuring}>
      <div>
        <Draggable />
        <Droppable />
      </div>
    </DndContext>
  )
}

function Droppable() {
  const { setNodeRef, isOver } = useDroppable({
    id: 'droppable',
  })
  const style: React.CSSProperties = {
    transform: 'translateX(55px) translateY(165px)', // ⭐️ Move it with transform and check if isOver fires at the intended location
    backgroundColor: isOver ? 'red' : 'gray',
  }

  return (
    <div ref={setNodeRef} style={style}>
      Droppable
    </div>
  )
}

function Draggable() {
  const { setNodeRef, transform, listeners } = useDraggable({
    id: 'draggable',
  })

  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    backgroundColor: 'gray',
    cursor: 'pointer',
  }

  return (
    <div ref={setNodeRef} style={style} {...listeners}>
      Draggable
    </div>
  )
}