@hello-pangea/dnd
Version:
Beautiful and accessible drag and drop for lists with React
126 lines (110 loc) • 3.39 kB
text/typescript
import type { Rect } from 'css-box-model';
import type {
DraggableId,
Axis,
DraggableDimension,
DroppableDimension,
DragImpact,
LiftEffect,
DisplacedBy,
} from '../../types';
import getDidStartAfterCritical from '../did-start-after-critical';
import getDisplacedBy from '../get-displaced-by';
import getIsDisplaced from '../get-is-displaced';
import removeDraggableFromList from '../remove-draggable-from-list';
interface Args {
draggable: DraggableDimension;
pageBorderBoxWithDroppableScroll: Rect;
previousImpact: DragImpact;
destination: DroppableDimension;
insideDestination: DraggableDimension[];
afterCritical: LiftEffect;
}
// exported for testing
export const combineThresholdDivisor = 4;
export default ({
draggable,
pageBorderBoxWithDroppableScroll: targetRect,
previousImpact,
destination,
insideDestination,
afterCritical,
}: Args): DragImpact | null => {
if (!destination.isCombineEnabled) {
return null;
}
const axis: Axis = destination.axis;
const displacedBy: DisplacedBy = getDisplacedBy(
destination.axis,
draggable.displaceBy,
);
const displacement: number = displacedBy.value;
const targetStart: number = targetRect[axis.start];
const targetEnd: number = targetRect[axis.end];
const withoutDragging: DraggableDimension[] = removeDraggableFromList(
draggable,
insideDestination,
);
const combineWith = withoutDragging.find((child): boolean => {
const id: DraggableId = child.descriptor.id;
const childRect: Rect = child.page.borderBox;
const childSize: number = childRect[axis.size];
const threshold: number = childSize / combineThresholdDivisor;
const didStartAfterCritical: boolean = getDidStartAfterCritical(
id,
afterCritical,
);
const isDisplaced: boolean = getIsDisplaced({
displaced: previousImpact.displaced,
id,
});
/*
Only combining when in the combine region
As soon as a boundary is hit then no longer combining
*/
if (didStartAfterCritical) {
// In original position
// Will combine with item when inside a band
if (isDisplaced) {
return (
targetEnd > childRect[axis.start] + threshold &&
targetEnd < childRect[axis.end] - threshold
);
}
// child is now 'displaced' backwards from where it started
// want to combine when we move backwards onto it
return (
targetStart > childRect[axis.start] - displacement + threshold &&
targetStart < childRect[axis.end] - displacement - threshold
);
}
// item has moved forwards
if (isDisplaced) {
return (
targetEnd > childRect[axis.start] + displacement + threshold &&
targetEnd < childRect[axis.end] + displacement - threshold
);
}
// is in resting position - being moved backwards on to
return (
targetStart > childRect[axis.start] + threshold &&
targetStart < childRect[axis.end] - threshold
);
});
if (!combineWith) {
return null;
}
const impact: DragImpact = {
// no change to displacement when combining
displacedBy,
displaced: previousImpact.displaced,
at: {
type: 'COMBINE',
combine: {
draggableId: combineWith.descriptor.id,
droppableId: destination.descriptor.id,
},
},
};
return impact;
};