@hello-pangea/dnd
Version:
Beautiful and accessible drag and drop for lists with React
121 lines (107 loc) • 3.45 kB
text/typescript
import type { Position } from 'css-box-model';
import type {
DragImpact,
DraggableDimension,
DraggableDimensionMap,
DroppableDimension,
Viewport,
DisplacedBy,
LiftEffect,
} from '../../../types';
import getDisplacedBy from '../../get-displaced-by';
import { emptyGroups, noDisplacedBy } from '../../no-impact';
import getPageBorderBoxCenter from '../../get-center-from-impact/get-page-border-box-center';
import isTotallyVisibleInNewLocation from '../move-to-next-place/is-totally-visible-in-new-location';
import { addPlaceholder } from '../../droppable/with-placeholder';
import isHomeOf from '../../droppable/is-home-of';
import calculateReorderImpact from '../../calculate-drag-impact/calculate-reorder-impact';
interface Args {
previousPageBorderBoxCenter: Position;
moveRelativeTo: DraggableDimension | null;
insideDestination: DraggableDimension[];
draggable: DraggableDimension;
draggables: DraggableDimensionMap;
destination: DroppableDimension;
viewport: Viewport;
afterCritical: LiftEffect;
}
export default ({
previousPageBorderBoxCenter,
moveRelativeTo,
insideDestination,
draggable,
draggables,
destination,
viewport,
afterCritical,
}: Args): DragImpact | null => {
if (!moveRelativeTo) {
// Draggables available, but none are candidates for movement
if (insideDestination.length) {
return null;
}
// Try move to top of empty list if it is visible
const proposed: DragImpact = {
displaced: emptyGroups,
displacedBy: noDisplacedBy,
at: {
type: 'REORDER',
destination: {
droppableId: destination.descriptor.id,
index: 0,
},
},
};
const proposedPageBorderBoxCenter: Position = getPageBorderBoxCenter({
impact: proposed,
draggable,
droppable: destination,
draggables,
afterCritical,
});
// need to add room for a placeholder in a foreign list
const withPlaceholder: DroppableDimension = isHomeOf(draggable, destination)
? destination
: addPlaceholder(destination, draggable, draggables);
const isVisibleInNewLocation: boolean = isTotallyVisibleInNewLocation({
draggable,
destination: withPlaceholder,
newPageBorderBoxCenter: proposedPageBorderBoxCenter,
viewport: viewport.frame,
// already taken into account by getPageBorderBoxCenter
withDroppableDisplacement: false,
onlyOnMainAxis: true,
});
return isVisibleInNewLocation ? proposed : null;
}
const isGoingBeforeTarget = Boolean(
// Using <= as we optimise slightly for moving before items in a new list
// This is nicer in lists with fixed height items
previousPageBorderBoxCenter[destination.axis.line] <=
moveRelativeTo.page.borderBox.center[destination.axis.line],
);
const proposedIndex: number = (() => {
const relativeTo: number = moveRelativeTo.descriptor.index;
if (moveRelativeTo.descriptor.id === draggable.descriptor.id) {
return relativeTo;
}
if (isGoingBeforeTarget) {
return relativeTo;
}
return relativeTo + 1;
})();
const displacedBy: DisplacedBy = getDisplacedBy(
destination.axis,
draggable.displaceBy,
);
return calculateReorderImpact({
draggable,
insideDestination,
destination,
viewport,
displacedBy,
// last groups won't be relevant
last: emptyGroups,
index: proposedIndex,
});
};