UNPKG

react-beautiful-dnd-next

Version:

Beautiful and accessible drag and drop for lists with React

200 lines (168 loc) 5.73 kB
// @flow import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; import type { Axis, DroppableDimensionMap, DraggableId, DroppableDimension, DraggableDimension, DraggableDimensionMap, Viewport, } from '../../../types'; import { toDraggableMap, toDroppableList } from '../../dimension-structures'; import { patch, add, negate } from '../../position'; import getDraggablesInsideDroppable from '../../get-draggables-inside-droppable'; import offsetDraggable from './offset-draggable'; type Args = {| droppables: DroppableDimensionMap, existing: DraggableDimensionMap, additions: DraggableDimension[], removals: DraggableId[], viewport: Viewport, |}; type Shift = {| indexChange: number, offset: Position, |}; type ShiftMap = { [id: DraggableId]: Shift, }; export default ({ existing, droppables, additions: addedDraggables, removals: removedDraggables, viewport, }: Args): DraggableDimensionMap => { const shifted: DraggableDimensionMap = {}; toDroppableList(droppables).forEach((droppable: DroppableDimension) => { const axis: Axis = droppable.axis; const original: DraggableDimension[] = getDraggablesInsideDroppable( droppable.descriptor.id, existing, ); const toShift: ShiftMap = {}; const addShift = (id: DraggableId, shift: Shift) => { const previous: ?Shift = toShift[id]; if (!previous) { toShift[id] = shift; return; } toShift[id] = { indexChange: previous.indexChange + shift.indexChange, offset: add(previous.offset, shift.offset), }; }; // phase 1: removals const removals: DraggableDimensionMap = toDraggableMap( removedDraggables .map((id: DraggableId): DraggableDimension => { const item: ?DraggableDimension = existing[id]; invariant(item, `Could not find removed draggable "${id}"`); return item; }) // only care about the ones inside of this droppable .filter( (draggable: DraggableDimension): boolean => draggable.descriptor.droppableId === droppable.descriptor.id, ), ); const withRemovals: DraggableDimension[] = original.filter( (item: DraggableDimension, index: number): boolean => { const isBeingRemoved: boolean = Boolean(removals[item.descriptor.id]); // Item is not being removed - no need to shift anything if (!isBeingRemoved) { return true; } // moving backwards by displacement const offset: Position = negate( patch(axis.line, item.displaceBy[axis.line]), ); original.slice(index).forEach((sibling: DraggableDimension) => { // no point shifting this as it is about to be removed if (removals[sibling.descriptor.id]) { return; } addShift(sibling.descriptor.id, { // item is being moved backwards one index indexChange: -1, offset, }); }); // We can now remove the draggable as its shift has been recorded return false; }, ); // Phase 2: additions // We do this on the withRemovals array as the new index coming in already account for removals const additions: DraggableDimension[] = addedDraggables.filter( (draggable: DraggableDimension): boolean => draggable.descriptor.droppableId === droppable.descriptor.id, ); // Insert additions into the correct positions // We can do this because the additions are correctly ordered const withAdditions: DraggableDimension[] = withRemovals.slice(0); additions.forEach((item: DraggableDimension) => { withAdditions.splice(item.descriptor.index, 0, item); }); const additionMap: DraggableDimensionMap = toDraggableMap(additions); // Calculate the offset to be applied to shifted items withAdditions.forEach((item: DraggableDimension, index: number) => { const wasAdded: boolean = Boolean(additionMap[item.descriptor.id]); // no shifting required when added if (!wasAdded) { return; } // need to shift everything after the addition // moving forward by size const offset: Position = patch( axis.line, item.client.marginBox[axis.size], ); withAdditions.slice(index).forEach((sibling: DraggableDimension) => { // no shifting required for newly added items // - they are already captured in the right spot if (additionMap[sibling.descriptor.id]) { return; } addShift(sibling.descriptor.id, { // item is being moved forwards one index indexChange: 1, offset, }); }); }); // Phase 3: shift dimensions withAdditions.forEach((item: DraggableDimension) => { if (additionMap[item.descriptor.id]) { return; } const shift: Shift = toShift[item.descriptor.id]; if (!shift) { return; } const moved: DraggableDimension = offsetDraggable({ draggable: item, offset: shift.offset, initialWindowScroll: viewport.scroll.initial, }); const index: number = item.descriptor.index + shift.indexChange; const updated: DraggableDimension = { ...moved, descriptor: { ...item.descriptor, index, }, }; // Add to big cache shifted[moved.descriptor.id] = updated; }); }); const map: DraggableDimensionMap = { ...existing, // will overwrite existing draggables with shifted values if required ...shifted, }; return map; };