react-beautiful-dnd-next
Version:
Beautiful and accessible drag and drop for lists with React
199 lines (175 loc) • 5.53 kB
JSX
// @flow
import { useRef } from 'react';
import { type Position } from 'css-box-model';
import invariant from 'tiny-invariant';
import { useMemo, useCallback } from 'use-memo-one';
import getStyle from './get-style';
import useDragHandle from '../use-drag-handle/use-drag-handle';
import type {
Args as DragHandleArgs,
Callbacks as DragHandleCallbacks,
DragHandleProps,
} from '../use-drag-handle/drag-handle-types';
import type { MovementMode } from '../../types';
import useDraggableDimensionPublisher, {
type Args as DimensionPublisherArgs,
} from '../use-draggable-dimension-publisher/use-draggable-dimension-publisher';
import * as timings from '../../debug/timings';
import type { Props, Provided, DraggableStyle } from './draggable-types';
import getWindowScroll from '../window/get-window-scroll';
// import throwIfRefIsInvalid from '../throw-if-invalid-inner-ref';
// import checkOwnProps from './check-own-props';
import AppContext, { type AppContextValue } from '../context/app-context';
import useRequiredContext from '../use-required-context';
import useValidation from './use-validation';
export default function Draggable(props: Props) {
// reference to DOM node
const ref = useRef<?HTMLElement>(null);
const setRef = useCallback((el: ?HTMLElement) => {
ref.current = el;
}, []);
const getRef = useCallback((): ?HTMLElement => ref.current, []);
// context
const appContext: AppContextValue = useRequiredContext(AppContext);
// Validating props and innerRef
useValidation(props, getRef);
// props
const {
// ownProps
children,
draggableId,
isDragDisabled,
shouldRespectForcePress,
disableInteractiveElementBlocking: canDragInteractiveElements,
index,
// mapProps
mapped,
// dispatchProps
moveUp: moveUpAction,
move: moveAction,
drop: dropAction,
moveDown: moveDownAction,
moveRight: moveRightAction,
moveLeft: moveLeftAction,
moveByWindowScroll: moveByWindowScrollAction,
lift: liftAction,
dropAnimationFinished: dropAnimationFinishedAction,
} = props;
// The dimension publisher: talks to the marshal
const forPublisher: DimensionPublisherArgs = useMemo(
() => ({
draggableId,
index,
getDraggableRef: getRef,
}),
[draggableId, getRef, index],
);
useDraggableDimensionPublisher(forPublisher);
// The Drag handle
const onLift = useCallback(
(options: { clientSelection: Position, movementMode: MovementMode }) => {
timings.start('LIFT');
const el: ?HTMLElement = ref.current;
invariant(el);
invariant(!isDragDisabled, 'Cannot lift a Draggable when it is disabled');
const { clientSelection, movementMode } = options;
liftAction({
id: draggableId,
clientSelection,
movementMode,
});
timings.finish('LIFT');
},
[draggableId, isDragDisabled, liftAction],
);
const getShouldRespectForcePress = useCallback(
() => shouldRespectForcePress,
[shouldRespectForcePress],
);
const callbacks: DragHandleCallbacks = useMemo(
() => ({
onLift,
onMove: (clientSelection: Position) =>
moveAction({ client: clientSelection }),
onDrop: () => dropAction({ reason: 'DROP' }),
onCancel: () => dropAction({ reason: 'CANCEL' }),
onMoveUp: moveUpAction,
onMoveDown: moveDownAction,
onMoveRight: moveRightAction,
onMoveLeft: moveLeftAction,
onWindowScroll: () =>
moveByWindowScrollAction({
newScroll: getWindowScroll(),
}),
}),
[
dropAction,
moveAction,
moveByWindowScrollAction,
moveDownAction,
moveLeftAction,
moveRightAction,
moveUpAction,
onLift,
],
);
const isDragging: boolean = mapped.type === 'DRAGGING';
const isDropAnimating: boolean =
mapped.type === 'DRAGGING' && Boolean(mapped.dropping);
const dragHandleArgs: DragHandleArgs = useMemo(
() => ({
draggableId,
isDragging,
isDropAnimating,
isEnabled: !isDragDisabled,
callbacks,
getDraggableRef: getRef,
canDragInteractiveElements,
getShouldRespectForcePress,
}),
[
callbacks,
canDragInteractiveElements,
draggableId,
getRef,
getShouldRespectForcePress,
isDragDisabled,
isDragging,
isDropAnimating,
],
);
const dragHandleProps: ?DragHandleProps = useDragHandle(dragHandleArgs);
const onMoveEnd = useCallback(
(event: TransitionEvent) => {
if (mapped.type !== 'DRAGGING') {
return;
}
if (!mapped.dropping) {
return;
}
// There might be other properties on the element that are
// being transitioned. We do not want those to end a drop animation!
if (event.propertyName !== 'transform') {
return;
}
dropAnimationFinishedAction();
},
[dropAnimationFinishedAction, mapped],
);
const provided: Provided = useMemo(() => {
const style: DraggableStyle = getStyle(mapped);
const onTransitionEnd =
mapped.type === 'DRAGGING' && mapped.dropping ? onMoveEnd : null;
const result: Provided = {
innerRef: setRef,
draggableProps: {
'data-react-beautiful-dnd-draggable': appContext.style,
style,
onTransitionEnd,
},
dragHandleProps,
};
return result;
}, [appContext.style, dragHandleProps, mapped, onMoveEnd, setRef]);
return children(provided, mapped.snapshot);
}