react-beautiful-dnd
Version:
Beautiful, accessible drag and drop for lists with React.js
95 lines (80 loc) • 2.58 kB
JavaScript
// @flow
import invariant from 'tiny-invariant';
import type { Position } from 'css-box-model';
import type { State, Viewport, DraggableLocation } from '../../types';
import type { Action, MiddlewareStore, Dispatch } from '../store-types';
import getMaxScroll from '../get-max-scroll';
import { isEqual } from '../position';
import { updateViewportMaxScroll } from '../action-creators';
import isMovementAllowed from '../is-movement-allowed';
const shouldCheckOnAction = (action: Action): boolean =>
action.type === 'MOVE' ||
action.type === 'MOVE_UP' ||
action.type === 'MOVE_RIGHT' ||
action.type === 'MOVE_DOWN' ||
action.type === 'MOVE_LEFT' ||
action.type === 'MOVE_BY_WINDOW_SCROLL';
// optimisation: body size can only change when the destination has changed
const hasDroppableOverChanged = (
previous: ?DraggableLocation,
current: ?DraggableLocation,
): boolean => {
// no previous - if there is a next return true
if (!previous) {
return Boolean(current);
}
// no current - if there is a previous return true
if (!current) {
return Boolean(previous);
}
return previous.droppableId !== current.droppableId;
};
const getNewMaxScroll = (
previous: State,
current: State,
action: Action,
): ?Position => {
if (!shouldCheckOnAction(action)) {
return null;
}
if (!isMovementAllowed(previous) || !isMovementAllowed(current)) {
return null;
}
if (
!hasDroppableOverChanged(
previous.impact.destination,
current.impact.destination,
)
) {
return null;
}
// check to see if the viewport max scroll has changed
const viewport: Viewport = current.viewport;
const doc: ?HTMLElement = document.documentElement;
invariant(doc, 'Could not find document.documentElement');
const maxScroll: Position = getMaxScroll({
scrollHeight: doc.scrollHeight,
scrollWidth: doc.scrollWidth,
// these cannot change during a drag
// a resize event will cancel a drag
width: viewport.frame.width,
height: viewport.frame.height,
});
// No change from current max scroll
if (isEqual(maxScroll, viewport.scroll.max)) {
return null;
}
return maxScroll;
};
export default (store: MiddlewareStore) => (next: Dispatch) => (
action: Action,
): any => {
const previous: State = store.getState();
next(action);
const current: State = store.getState();
const maxScroll: ?Position = getNewMaxScroll(previous, current, action);
// max scroll has changed - updating before action
if (maxScroll) {
next(updateViewportMaxScroll(maxScroll));
}
};