Skip to content

Commit

Permalink
Fixed an issue with collision detection using stale rects
Browse files Browse the repository at this point in the history
The `rect.current` ref stored on DroppableContainers can be stale if measuring is scheduled but has not completed yet. Collision detection algorithms should use the `droppableRects` map instead to get the latest, most up-to-date measurement of a droppable container in order to avoid computing collisions against stale rects.
  • Loading branch information
clauderic committed Mar 15, 2022
1 parent 0f570f6 commit c1b3b5a
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 18 deletions.
16 changes: 16 additions & 0 deletions .changeset/stale-collision-rects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@dnd-kit/core': minor
---

Fixed an issue with collision detection using stale rects. The `droppableRects` property has been added to the `CollisionDetection` interface.

All built-in collision detection algorithms have been updated to get the rect for a given droppable container from `droppableRects` rather than from the `rect.current` ref:

```diff
- const rect = droppableContainers.get(id).rect.current;
+ const rect = droppableRects.get(id);
```

The `rect.current` ref stored on DroppableContainers can be stale if measuring is scheduled but has not completed yet. Collision detection algorithms should use the `droppableRects` map instead to get the latest, most up-to-date measurement of a droppable container in order to avoid computing collisions against stale rects.

This is not a breaking change. Hoever, if you've forked any of the built-in collision detection algorithms or you've authored custom ones, we highly recommend you update your use-cases to avoid possibly computing collisions against stale rects.
1 change: 1 addition & 0 deletions packages/core/src/components/DndContext/DndContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export const DndContext = memo(function DndContext({
? collisionDetection({
active,
collisionRect,
droppableRects,
droppableContainers: enabledDroppableContainers,
pointerCoordinates,
})
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/utilities/algorithms/closestCenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function centerOfRectangle(
*/
export const closestCenter: CollisionDetection = ({
collisionRect,
droppableRects,
droppableContainers,
}) => {
const centerRect = centerOfRectangle(
Expand All @@ -34,10 +35,8 @@ export const closestCenter: CollisionDetection = ({
const collisions: CollisionDescriptor[] = [];

for (const droppableContainer of droppableContainers) {
const {
id,
rect: {current: rect},
} = droppableContainer;
const {id} = droppableContainer;
const rect = droppableRects.get(id);

if (rect) {
const distBetween = distanceBetween(centerOfRectangle(rect), centerRect);
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/utilities/algorithms/closestCorners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ import {cornersOfRectangle, sortCollisionsAsc} from './helpers';
*/
export const closestCorners: CollisionDetection = ({
collisionRect,
droppableRects,
droppableContainers,
}) => {
const corners = cornersOfRectangle(collisionRect);
const collisions: CollisionDescriptor[] = [];

for (const droppableContainer of droppableContainers) {
const {
id,
rect: {current: rect},
} = droppableContainer;
const {id} = droppableContainer;
const rect = droppableRects.get(id);

if (rect) {
const rectCorners = cornersOfRectangle(rect);
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/utilities/algorithms/pointerWithin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function isPointWithinRect(point: Coordinates, rect: ClientRect): boolean {
*/
export const pointerWithin: CollisionDetection = ({
droppableContainers,
droppableRects,
pointerCoordinates,
}) => {
if (!pointerCoordinates) {
Expand All @@ -29,10 +30,8 @@ export const pointerWithin: CollisionDetection = ({
const collisions: CollisionDescriptor[] = [];

for (const droppableContainer of droppableContainers) {
const {
id,
rect: {current: rect},
} = droppableContainer;
const {id} = droppableContainer;
const rect = droppableRects.get(id);

if (rect && isPointWithinRect(pointerCoordinates, rect)) {
/* There may be more than a single rectangle intersecting
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/utilities/algorithms/rectIntersection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ export function getIntersectionRatio(
*/
export const rectIntersection: CollisionDetection = ({
collisionRect,
droppableRects,
droppableContainers,
}) => {
const collisions: CollisionDescriptor[] = [];

for (const droppableContainer of droppableContainers) {
const {
id,
rect: {current: rect},
} = droppableContainer;
const {id} = droppableContainer;
const rect = droppableRects.get(id);

if (rect) {
const intersectionRatio = getIntersectionRatio(rect, collisionRect);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/utilities/algorithms/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Active, Data, DroppableContainer} from '../../store';
import type {Active, Data, DroppableContainer, RectMap} from '../../store';
import type {Coordinates, ClientRect, UniqueIdentifier} from '../../types';

export interface Collision {
Expand All @@ -17,6 +17,7 @@ export interface CollisionDescriptor extends Collision {
export type CollisionDetection = (args: {
active: Active;
collisionRect: ClientRect;
droppableRects: RectMap;
droppableContainers: DroppableContainer[];
pointerCoordinates: Coordinates | null;
}) => Collision[];
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ const directions: string[] = [

export const sortableKeyboardCoordinates: KeyboardCoordinateGetter = (
event,
{context: {active, droppableContainers, collisionRect, scrollableAncestors}}
{
context: {
active,
droppableRects,
droppableContainers,
collisionRect,
scrollableAncestors,
},
}
) => {
if (directions.includes(event.code)) {
event.preventDefault();
Expand Down Expand Up @@ -65,6 +73,7 @@ export const sortableKeyboardCoordinates: KeyboardCoordinateGetter = (
const collisions = closestCorners({
active,
collisionRect: collisionRect,
droppableRects,
droppableContainers: filteredContainers,
pointerCoordinates: null,
});
Expand Down

0 comments on commit c1b3b5a

Please sign in to comment.