Closed jeserodz closed 2 years ago
Hey @jeserodz, seems like a legit bug, thanks for the report!
For the time being, you can use the closestCenter
or closestCorners
collision detection algorithms as a workaround, as this is a bug that will take a bit of time to fix.
For the time being, you can use the
closestCenter
orclosestCorners
collision detection algorithms as a workaround, as this is a bug that will take a bit of time to fix.
In my issue (#73 you closed for duplicate), FYI, I do reproduce the bug even with others collisions algorithm. closestCorners / closestCenter are doing the same.
is the https://github.com/clauderic/dnd-kit/pull/54 usable ? i have the same kind of bug after scrolling a long list
Not sure if this bug is the cause for the following as well: https://codesandbox.io/s/confident-pike-ofooi
onDragOver
gets called repeatedly when the above occursNote: this doesn't use @dnd-kit/sortable, as it's not necessary for my particular use case.
https://user-images.githubusercontent.com/9291779/117454064-4ac08780-af78-11eb-8ce6-3ba1735d5222.mp4
First of all thanks for building such a developer friendly DnD library @clauderic
We are building a kanban board and our first choice naturally went towards using react-beautiful-dnd and after trying out that, we end up facing this issue https://github.com/atlassian/react-beautiful-dnd/issues/131 and then we have to remove the package as it is not gonna be solved anytime soon.
After searching for other libraries, we found dnd-kit
to be promising. Especially after seeing this example. So we replaced react-beautiful-dnd
with dnd-kit
and things were going great until we hit this issue https://github.com/clauderic/dnd-kit/issues/73
I saw you are working on https://github.com/clauderic/dnd-kit/pull/54 Will this solve this issue?
I am open to discuss this and support it further with your help and guidance.
Hey @Shajansheriff , that example only works because height is fixed right?
+1 Having a huge pain trying to find a workaround for this bug. Here's the video example, sorta duplicating the initial record, but you may find a couple of additional things.
As a solution, (or at least a temporary solution), it'd be just awesome to be able to remove certain scrollable ancestors from position calculation. Obviously, intersection is calculated using a concatenation of body scrollTop and the left panel scrollable block scrollTop. I don't need left scrollable block to be scrolled at all here, would be great to just remove it from either calculations, and scrollable items as well
+1 @clauderic Thank you for this incredible library!
I'm facing this exact bug today with the virtualized scrollable container using react-window library. Just informing this bug also occurs in the virtualized containers.
For now, the workaround I could think of is locating the x
and y
of my Droppable
and act accordingly when I pull the DragOverlay
element. Not really an efficient workaround, but will update if it works 🤞🏼
@clauderic like others have said, thank you for making this amazing dnd library!
We're running into this issue, are there any updates on PR 54 or any accepted workarounds in the meantime? Thank you!
Thanks for your great Lib, I have the same problem and this is strange for me how this example Link is working.
Thanks for your great Lib, I have the same problem and this is strange for me how this example Link is working.
Probably cause it's not using the default collision detection https://github.com/clauderic/dnd-kit/issues/43#issuecomment-757338784.
Hi I wonder if there's an update on this ticket? The branch at #54 seems to have gone stale. I have a workaround with a different collision detector which uses the current active item rather than the collisionRect
which seems to work for the meantime. Obviously not something we want to keep in our codebase however. Thanks.
import {
Active,
CollisionDetection,
LayoutRect,
UniqueIdentifier,
} from "@dnd-kit/core";
/**
* Returns the intersecting rectangle area between two rectangles
*/
function getIntersectionRatio(entry: LayoutRect, active: Active): number {
const {
top: currentTop = 0,
left: currentLeft = 0,
width: currentWidth = 0,
height: currentHeight = 0,
} = active.rect.current.translated ?? {};
const top = Math.max(currentTop, entry.offsetTop);
const left = Math.max(currentLeft, entry.offsetLeft);
const right = Math.min(
currentLeft + currentWidth,
entry.offsetLeft + entry.width
);
const bottom = Math.min(
currentTop + currentHeight,
entry.offsetTop + entry.height
);
const width = right - left;
const height = bottom - top;
if (left < right && top < bottom) {
const targetArea = currentWidth * currentHeight;
const entryArea = entry.width * entry.height;
const intersectionArea = width * height;
const intersectionRatio =
intersectionArea / (targetArea + entryArea - intersectionArea);
return Number(intersectionRatio.toFixed(4));
}
// Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap)
return 0;
}
/**
* Returns the rectangle that has the greatest intersection area with a given
* rectangle in an array of rectangles.
*/
export const activeRectIntersection: CollisionDetection = ({
active,
droppableContainers,
}) => {
let maxIntersectionRatio = 0;
let maxIntersectingDroppableContainer: UniqueIdentifier | null = null;
for (const droppableContainer of droppableContainers) {
const {
rect: { current: rect },
} = droppableContainer;
if (rect) {
const intersectionRatio = getIntersectionRatio(rect, active);
if (intersectionRatio > maxIntersectionRatio) {
maxIntersectionRatio = intersectionRatio;
maxIntersectingDroppableContainer = droppableContainer.id;
}
}
}
return maxIntersectingDroppableContainer;
};
Hi all,
We are building an application with two lists and are wrapping each of them with a virtual list.
I had had the same issue and tried to use closestCenter
or closestCorners
collision detection algorithms but got the same results.
@robstarbuck's comment helped me to find a workaround that works perfectly for my use case:
<DragOverlay>
).Wrap the closestCenter
or closestCorners
with a callback, e.g.:
(entries: RectEntry[], target: ViewRect) => {
// Use the default dnd-kit callback when ref doesn't exist.
if (!draggableElement?.current) return closestCenter(entries, target);
// Use all values from `getBoundingClientRect` and pass them into a new object as type `ViewRect`.
const { width, height, left, right, top, bottom, x, y } = draggableElement?.current?.getBoundingClientRect();
const domRectTarget = { width, height, left, right, top, bottom, offsetLeft: x, offsetTop: y };
// Use new collision detection algrorithm.
return rectIntersection(entries, domRectTarget);
}
rectIntersection.ts
and modified to accommodate our implementation):
import type { LayoutRect, UniqueIdentifier, RectEntry, ViewRect } from '@dnd-kit/core';
/**
Returns the intersecting rectangle area between two rectangles */ function getIntersectionRatio(entry: LayoutRect, target: ViewRect): number { const top = Math.max(target.top, entry.offsetTop); const left = Math.max(target.left, entry.offsetLeft); const right = Math.min(target.left + target.width, entry.offsetLeft + entry.width); const bottom = Math.min(target.top + target.height, entry.offsetTop + entry.height); const width = right - left; const height = bottom - top;
if (left < right && top < bottom) { const targetArea = target.width target.height; const entryArea = entry.width entry.height; const intersectionArea = width * height; const intersectionRatio = intersectionArea / (targetArea + entryArea - intersectionArea);
return Number(intersectionRatio.toFixed(4)); }
// Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap) return 0; }
/**
rectangle in an array of rectangles. */ export const rectIntersection = (droppableContainers: RectEntry[], collisionRect: ViewRect) => { let maxIntersectionRatio = 0; let maxIntersectingDroppableContainer: UniqueIdentifier | null = null;
for (const droppableContainer of droppableContainers) { const [id, rect] = droppableContainer;
if (rect) { const intersectionRatio = getIntersectionRatio(rect, collisionRect);
if (intersectionRatio > maxIntersectionRatio) { maxIntersectionRatio = intersectionRatio; maxIntersectingDroppableContainer = id; } } }
return maxIntersectingDroppableContainer; };
Hi I wonder if there's an update on this ticket? The branch at #54 seems to have gone stale. I have a workaround with a different collision detector which uses the current active item rather than the
collisionRect
which seems to work for the meantime. Obviously not something we want to keep in our codebase however. Thanks.import { Active, CollisionDetection, LayoutRect, UniqueIdentifier, } from "@dnd-kit/core"; /** * Returns the intersecting rectangle area between two rectangles */ function getIntersectionRatio(entry: LayoutRect, active: Active): number { const { top: currentTop = 0, left: currentLeft = 0, width: currentWidth = 0, height: currentHeight = 0, } = active.rect.current.translated ?? {}; const top = Math.max(currentTop, entry.offsetTop); const left = Math.max(currentLeft, entry.offsetLeft); const right = Math.min( currentLeft + currentWidth, entry.offsetLeft + entry.width ); const bottom = Math.min( currentTop + currentHeight, entry.offsetTop + entry.height ); const width = right - left; const height = bottom - top; if (left < right && top < bottom) { const targetArea = currentWidth * currentHeight; const entryArea = entry.width * entry.height; const intersectionArea = width * height; const intersectionRatio = intersectionArea / (targetArea + entryArea - intersectionArea); return Number(intersectionRatio.toFixed(4)); } // Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap) return 0; } /** * Returns the rectangle that has the greatest intersection area with a given * rectangle in an array of rectangles. */ export const activeRectIntersection: CollisionDetection = ({ active, droppableContainers, }) => { let maxIntersectionRatio = 0; let maxIntersectingDroppableContainer: UniqueIdentifier | null = null; for (const droppableContainer of droppableContainers) { const { rect: { current: rect }, } = droppableContainer; if (rect) { const intersectionRatio = getIntersectionRatio(rect, active); if (intersectionRatio > maxIntersectionRatio) { maxIntersectionRatio = intersectionRatio; maxIntersectingDroppableContainer = droppableContainer.id; } } } return maxIntersectingDroppableContainer; };
Thanks!
Hi. I used @robstarbuck's algorithm but my active lost its offset in a long list while scrolling and then I did this :
const activeRectIntersection = ({
active,
collisionRect,
droppableContainers,
}, scrollableContainersKeys) => {
let maxIntersectionRatio = 0;
let maxIntersectingDroppableContainer = null;
for (const droppableContainer of droppableContainers) {
const {
rect: { current: rect },
} = droppableContainer;
if (rect) {
let intersectionRatio = 0
if(scrollableContainersKeys.includes(droppableContainer.id)) intersectionRatio = getIntersectionRatio(rect, active?.rect?.current?.translated || {})
else intersectionRatio = getIntersectionRatio(rect, collisionRect);
if (intersectionRatio > maxIntersectionRatio) {
maxIntersectionRatio = intersectionRatio;
maxIntersectingDroppableContainer = droppableContainer.id;
}
}
}
return maxIntersectingDroppableContainer;
};
I use active to calculate the intersection ratio with the scrollable container and the collisionRect with the other elements. This works for my use case. I hope it helps
I'm having the same issue. how can I fix drag issue on scrollable container?
I made it work with document.elementsFromPoint
. It gets all nodes located on the mouse position and works within scrollable containers as well. Not sure if it's the best solution for anyone performance-wise, but worked for me.
I've added a common className
and the id
to each droppable zone to be able to find it in the results array.
import {
// ...
rectIntersection,
CollisionDetection,
} from '@dnd-kit/core';
// ...
const MainComponent = () => {
const mousePosition = useRef({x: 0, y: 0});
useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
mousePosition.current = {x: event.clientX, y: event.clientY};
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
const collisionDetection: CollisionDetection = (args) => {
const nodesAtPosition = document.elementsFromPoint(
mousePosition.current.x,
mousePosition.current.y,
);
const droppableId = nodesAtPosition.find((node) =>
node.classList.contains('droppable-zone'),
)?.id;
if (droppableId) {
return droppableId;
}
// fallback to default collision detection algorithm
return rectIntersection(args);
};
return (
<DndContext
// ...
collisionDetection={collisionDetection}
>
{/* ... */}
</DndContext>
)
};
The draggable items that are scrolled are not calculating the
intersectionRatio
correctly.Please, see this code sandbox for an example: https://codesandbox.io/s/react-dnd-grid-0ylzl?file=/src/App.js
NOTE: Try dragging the last draggable item into one droppable area.