react-beautiful-dnd-next
Version:
Beautiful and accessible drag and drop for lists with React
163 lines (143 loc) • 4.74 kB
JavaScript
// @flow
import invariant from 'tiny-invariant';
import type {
DimensionMap,
DraggingState,
CollectingState,
DropPendingState,
Published,
Critical,
DraggableDimension,
DraggableDimensionMap,
DroppableDimensionMap,
DragImpact,
} from '../../types';
import * as timings from '../../debug/timings';
import whatIsDraggedOver from '../droppable/what-is-dragged-over';
import getDragImpact from '../get-drag-impact';
import getHomeOnLift from '../get-home-on-lift';
import getDragPositions from './get-drag-positions';
import updateDraggables from './update-draggables';
import updateDroppables from './update-droppables';
import withNoAnimatedDisplacement from './with-no-animated-displacement';
import recomputePlaceholders from '../recompute-placeholders';
type Args = {|
state: CollectingState | DropPendingState,
published: Published,
|};
const timingsKey: string = 'Processing dynamic changes';
export default ({
state,
published,
}: Args): DraggingState | DropPendingState => {
timings.start(timingsKey);
// Change the subject size and scroll of droppables
// will remove any subject.withPlaceholder
const updatedDroppables: DroppableDimensionMap = updateDroppables({
modified: published.modified,
existing: state.dimensions.droppables,
viewport: state.viewport,
});
const draggables: DraggableDimensionMap = updateDraggables({
updatedDroppables,
// will not change during a drag
criticalId: state.critical.draggable.id,
existing: state.dimensions.draggables,
additions: published.additions,
removals: published.removals,
viewport: state.viewport,
});
const critical: Critical = {
draggable: draggables[state.critical.draggable.id].descriptor,
droppable: updatedDroppables[state.critical.droppable.id].descriptor,
};
const original: DraggableDimension =
state.dimensions.draggables[critical.draggable.id];
const updated: DraggableDimension = draggables[critical.draggable.id];
// TODO: this is a bit of a chicken and egg problem, but it will use the old impact for placeholders
const droppables: DroppableDimensionMap = recomputePlaceholders({
draggable: updated,
draggables,
droppables: updatedDroppables,
previousImpact: state.impact,
impact: state.impact,
});
const dimensions: DimensionMap = {
draggables,
droppables,
};
// Get the updated drag positions to account for any
// shift to the critical draggable
const { initial, current } = getDragPositions({
initial: state.initial,
current: state.current,
oldClientBorderBoxCenter: original.client.borderBox.center,
newClientBorderBoxCenter: updated.client.borderBox.center,
viewport: state.viewport,
});
// Get the impact of all of our changes
// this could result in a strange snap placement (will be fixed on next move)
const { impact: homeImpact, onLift } = getHomeOnLift({
draggable: updated,
home: dimensions.droppables[critical.droppable.id],
draggables: dimensions.draggables,
viewport: state.viewport,
});
// now need to calculate the impact for the current pageBorderBoxCenter
const impact: DragImpact = withNoAnimatedDisplacement(
getDragImpact({
pageBorderBoxCenter: current.page.borderBoxCenter,
draggable: updated,
draggables: dimensions.draggables,
droppables: dimensions.droppables,
// starting from a fresh slate
previousImpact: homeImpact,
viewport: state.viewport,
userDirection: state.userDirection,
onLift,
}),
);
const isOrphaned: boolean = Boolean(
state.movementMode === 'SNAP' && !whatIsDraggedOver(impact),
);
// TODO: try and recover?
invariant(
!isOrphaned,
'Dragging item no longer has a valid merge/destination after a dynamic update. This is not supported',
);
// TODO: move into move visually pleasing position if using JUMP auto scrolling
timings.finish(timingsKey);
const draggingState: DraggingState = {
// appeasing flow
phase: 'DRAGGING',
...state,
// eslint-disable-next-line
phase: 'DRAGGING',
critical,
current,
initial,
impact,
dimensions,
onLift,
onLiftImpact: homeImpact,
// not animating this movement
forceShouldAnimate: false,
};
if (state.phase === 'COLLECTING') {
return draggingState;
}
// There was a DROP_PENDING
// Staying in the DROP_PENDING phase
// setting isWaiting for false
const dropPending: DropPendingState = {
// appeasing flow
phase: 'DROP_PENDING',
...draggingState,
// eslint-disable-next-line
phase: 'DROP_PENDING',
// No longer waiting
reason: state.reason,
isWaiting: false,
};
return dropPending;
};