import type { Rect } from 'css-box-model'; import type { DraggableId, DraggableDimension, DroppableDimension, DragImpact, Axis, DisplacementGroups, Viewport, DisplacedBy, LiftEffect, } from '../../types'; import getDisplacedBy from '../get-displaced-by'; import removeDraggableFromList from '../remove-draggable-from-list'; import isHomeOf from '../droppable/is-home-of'; import getDidStartAfterCritical from '../did-start-after-critical'; import calculateReorderImpact from '../calculate-drag-impact/calculate-reorder-impact'; import getIsDisplaced from '../get-is-displaced'; interface Args { pageBorderBoxWithDroppableScroll: Rect; draggable: DraggableDimension; destination: DroppableDimension; insideDestination: DraggableDimension[]; last: DisplacementGroups; viewport: Viewport; afterCritical: LiftEffect; } interface AtIndexArgs { draggable: DraggableDimension; closest: DraggableDimension | null; inHomeList: boolean; } function atIndex({ draggable, closest, inHomeList, }: AtIndexArgs): number | null { if (!closest) { return null; } if (!inHomeList) { return closest.descriptor.index; } if (closest.descriptor.index > draggable.descriptor.index) { return closest.descriptor.index - 1; } return closest.descriptor.index; } export default ({ pageBorderBoxWithDroppableScroll: targetRect, draggable, destination, insideDestination, last, viewport, afterCritical, }: Args): DragImpact => { 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 closest = withoutDragging.find((child): boolean => { const id: DraggableId = child.descriptor.id; const childCenter: number = child.page.borderBox.center[axis.line]; const didStartAfterCritical: boolean = getDidStartAfterCritical( id, afterCritical, ); const isDisplaced: boolean = getIsDisplaced({ displaced: last, id }); /* Note: we change things when moving *past* the child center - not when it hits the center If we make it when we *hit* the child center then there can be a hit on the next update causing a flicker. - Update 1: targetBottom hits center => displace backwards - Update 2: targetStart is now hitting the displaced center => displace forwards - Update 3: goto 1 (boom) */ if (didStartAfterCritical) { // Continue to displace while targetEnd before the childCenter // Move once we *move forward past* the childCenter if (isDisplaced) { return targetEnd <= childCenter; } // Has been moved backwards from where it started // Displace forwards when targetStart *moves backwards past* the displaced childCenter return targetStart < childCenter - displacement; } // Item has been shifted forward. // Remove displacement when targetEnd moves forward past the displaced center if (isDisplaced) { return targetEnd <= childCenter + displacement; } // Item is behind the dragging item // We want to displace it if the targetStart goes *backwards past* the childCenter return targetStart < childCenter; }) || null; const newIndex: number | null = atIndex({ draggable, closest, inHomeList: isHomeOf(draggable, destination), }); // TODO: index cannot be null? // otherwise return null from there and return empty impact // that was calculate reorder impact does not need to account for a null index return calculateReorderImpact({ draggable, insideDestination, destination, viewport, last, displacedBy, index: newIndex, }); };