@hello-pangea/dnd
Version:
Beautiful and accessible drag and drop for lists with React
96 lines (83 loc) • 2.58 kB
text/typescript
import rafSchd from 'raf-schd';
import type { Position } from 'css-box-model';
import type { DraggingState, DroppableId } from '../../../types';
import scroll from './scroll';
import { invariant } from '../../../invariant';
import * as timings from '../../../debug/timings';
import { AutoScrollerOptions } from './auto-scroller-options-types';
import { defaultAutoScrollerOptions } from './config';
export interface PublicArgs {
scrollWindow: (change: Position) => void;
scrollDroppable: (id: DroppableId, change: Position) => void;
getAutoScrollerOptions?: () => AutoScrollerOptions;
}
export interface FluidScroller {
scroll: (state: DraggingState) => void;
start: (state: DraggingState) => void;
stop: () => void;
}
interface WhileDragging {
dragStartTime: number;
shouldUseTimeDampening: boolean;
}
export default ({
scrollWindow,
scrollDroppable,
getAutoScrollerOptions = () => defaultAutoScrollerOptions,
}: PublicArgs): FluidScroller => {
const scheduleWindowScroll = rafSchd(scrollWindow);
const scheduleDroppableScroll = rafSchd(scrollDroppable);
let dragging: WhileDragging | null = null;
const tryScroll = (state: DraggingState): void => {
invariant(dragging, 'Cannot fluid scroll if not dragging');
const { shouldUseTimeDampening, dragStartTime } = dragging;
scroll({
state,
scrollWindow: scheduleWindowScroll,
scrollDroppable: scheduleDroppableScroll,
dragStartTime,
shouldUseTimeDampening,
getAutoScrollerOptions,
});
};
const start = (state: DraggingState) => {
timings.start('starting fluid scroller');
invariant(!dragging, 'Cannot start auto scrolling when already started');
const dragStartTime: number = Date.now();
let wasScrollNeeded = false;
const fakeScrollCallback = () => {
wasScrollNeeded = true;
};
scroll({
state,
dragStartTime: 0,
shouldUseTimeDampening: false,
scrollWindow: fakeScrollCallback,
scrollDroppable: fakeScrollCallback,
getAutoScrollerOptions,
});
dragging = {
dragStartTime,
shouldUseTimeDampening: wasScrollNeeded,
};
timings.finish('starting fluid scroller');
// we know an auto scroll is needed - let's do it!
if (wasScrollNeeded) {
tryScroll(state);
}
};
const stop = () => {
// can be called defensively
if (!dragging) {
return;
}
scheduleWindowScroll.cancel();
scheduleDroppableScroll.cancel();
dragging = null;
};
return {
start,
stop,
scroll: tryScroll,
};
};