import { expand, getRect } from 'css-box-model'; import type { Rect, Spacing } from 'css-box-model'; import type { DraggableId, Displacement, DraggableDimension, DroppableDimension, DisplacementGroups, DisplacedBy, } from '../types'; import { isPartiallyVisible } from './visibility/is-visible'; interface Args { afterDragging: DraggableDimension[]; destination: DroppableDimension; displacedBy: DisplacedBy; last: DisplacementGroups | null; viewport: Rect; forceShouldAnimate?: boolean; } const getShouldAnimate = ( id: DraggableId, last?: DisplacementGroups | null, forceShouldAnimate?: boolean | null, ) => { // Use a forced value if provided if (typeof forceShouldAnimate === 'boolean') { return forceShouldAnimate; } // nothing to gauge animation from if (!last) { return true; } const { invisible, visible } = last; // it was previously invisible - no animation if (invisible[id]) { return false; } const previous: Displacement | null = visible[id]; return previous ? previous.shouldAnimate : true; }; // Note: it is also an optimisation to not render the displacement on // items when they are not longer visible. // This prevents a lot of .render() calls when leaving / entering a list function getTarget( draggable: DraggableDimension, displacedBy: DisplacedBy, ): Rect { const marginBox: Rect = draggable.page.marginBox; // ## Visibility overscanning // We are expanding rather than offsetting the marginBox. // In some cases we want // - the target based on the starting position (such as when dropping outside of any list) // - the target based on the items position without starting displacement (such as when moving inside a list) // To keep things simple we just expand the whole area for this check // The worst case is some minor redundant offscreen movements const expandBy: Spacing = { // pull backwards into viewport top: displacedBy.point.y, right: 0, bottom: 0, // pull backwards into viewport left: displacedBy.point.x, }; return getRect(expand(marginBox, expandBy)); } export default function getDisplacementGroups({ afterDragging, destination, displacedBy, viewport, forceShouldAnimate, last, }: Args): DisplacementGroups { return afterDragging.reduce( function process( groups: DisplacementGroups, draggable: DraggableDimension, ): DisplacementGroups { const target: Rect = getTarget(draggable, displacedBy); const id: DraggableId = draggable.descriptor.id; groups.all.push(id); const isVisible: boolean = isPartiallyVisible({ target, destination, viewport, withDroppableDisplacement: true, }); if (!isVisible) { groups.invisible[draggable.descriptor.id] = true; return groups; } // item is visible const shouldAnimate: boolean = getShouldAnimate( id, last, forceShouldAnimate, ); const displacement: Displacement = { draggableId: id, shouldAnimate, }; groups.visible[id] = displacement; return groups; }, { all: [], visible: {}, invisible: {}, }, ); }