react-beautiful-dnd-next
Version:
Beautiful and accessible drag and drop for lists with React
144 lines (129 loc) • 3.96 kB
JavaScript
// @flow
import type { Position } from 'css-box-model';
import invariant from 'tiny-invariant';
import type {
DragImpact,
DraggableDimension,
DraggableDimensionMap,
DroppableDimension,
Displacement,
Viewport,
DisplacedBy,
OnLift,
} from '../../../types';
import getDisplacedBy from '../../get-displaced-by';
import getDisplacement from '../../get-displacement';
import getDisplacementMap from '../../get-displacement-map';
import { noMovement } 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 removeDraggableFromList from '../../remove-draggable-from-list';
import isHomeOf from '../../droppable/is-home-of';
type Args = {|
previousPageBorderBoxCenter: Position,
moveRelativeTo: ?DraggableDimension,
insideDestination: DraggableDimension[],
draggable: DraggableDimension,
draggables: DraggableDimensionMap,
destination: DroppableDimension,
previousImpact: DragImpact,
viewport: Viewport,
onLift: OnLift,
|};
export default ({
previousPageBorderBoxCenter,
moveRelativeTo,
insideDestination,
draggable,
draggables,
destination,
previousImpact,
viewport,
onLift,
}: Args): ?DragImpact => {
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 = {
movement: noMovement,
destination: {
droppableId: destination.descriptor.id,
index: 0,
},
merge: null,
};
const proposedPageBorderBoxCenter: Position = getPageBorderBoxCenter({
impact: proposed,
draggable,
droppable: destination,
draggables,
onLift,
});
// 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 = Boolean(
previousPageBorderBoxCenter[destination.axis.line] <
moveRelativeTo.page.borderBox.center[destination.axis.line],
);
// Moving to a populated list
const targetIndex: number = insideDestination.indexOf(moveRelativeTo);
invariant(targetIndex !== -1, 'Cannot find target in list');
const proposedIndex: number = (() => {
// TODO: is this logic correct?
if (moveRelativeTo.descriptor.id === draggable.descriptor.id) {
return targetIndex;
}
if (isGoingBeforeTarget) {
return targetIndex;
}
return targetIndex + 1;
})();
const displaced: Displacement[] = removeDraggableFromList(
draggable,
insideDestination,
)
.slice(proposedIndex)
.map((dimension: DraggableDimension): Displacement =>
getDisplacement({
draggable: dimension,
destination,
viewport: viewport.frame,
previousImpact,
onLift,
}),
);
const displacedBy: DisplacedBy = getDisplacedBy(
destination.axis,
draggable.displaceBy,
);
const impact: DragImpact = {
movement: {
displacedBy,
displaced,
map: getDisplacementMap(displaced),
},
destination: {
droppableId: destination.descriptor.id,
index: proposedIndex,
},
merge: null,
};
return impact;
};