UNPKG

@atlaskit/pragmatic-drag-and-drop-react-beautiful-dnd-migration

Version:

An optional Pragmatic drag and drop package that enables rapid migration from react-beautiful-dnd to Pragmatic drag and drop

274 lines (266 loc) 7.75 kB
/** * All state for the Draggable in one place. * * This avoids rerenders (caused by unbatched state updates), * but also keeps state logic together. */ // eslint-disable-next-line import/no-extraneous-dependencies import { isSameLocation } from '../drag-drop-context/draggable-location'; import { rbdInvariant } from '../drag-drop-context/rbd-invariant'; import { directionMapping } from '../droppable/drop-indicator/constants'; import { keyboardPreviewCrossAxisOffset } from './constants'; /** * The state of a draggable that is currently being dragged. * It does not have a clone. */ /** * The state of a draggable that is currently hiding, * because it its clone is being rendered instead. */ export const idleState = { type: 'idle', draggingOver: null }; function getHidingState(mode) { return { type: 'hiding', draggingOver: null, mode }; } const getKeyboardPreviewOffset = { initial({ direction }) { /** * The initial offset doesn't use a base offset, * as no scrolling should have ocurred yet. */ const { mainAxis, crossAxis } = directionMapping[direction]; return { /** * The drag preview should not be offset on the main axis. */ [mainAxis.name]: 0, /** * On the cross axis, the drag preview is offset by a fixed percentage * of its cross axis length. */ [crossAxis.name]: keyboardPreviewCrossAxisOffset }; }, home({ droppable: { direction }, placeholderRect, draggableDimensions }) { rbdInvariant(placeholderRect, 'the placeholder should exist if in home position'); /** * This base offset will result in the preview being over the placeholder * (same x and y coordinates). * * Consider this as `currentPosition - initialPosition` to find an offset. * * The `placeholderRect` is the **current** viewport-relative position of the * gap where the draggable originated from. * * The `draggableDimensions.rect` is the **initial** viewport-relative position * of the draggable. */ const baseOffset = { x: placeholderRect.x - draggableDimensions.rect.x, y: placeholderRect.y - draggableDimensions.rect.y }; const { mainAxis, crossAxis } = directionMapping[direction]; return { /** * The drag preview should not be visibly offset on the main axis. */ [mainAxis.name]: baseOffset[mainAxis.name], /** * On the cross axis, the drag preview is offset by a fixed percentage * of its cross axis length. * * This is to remain aligned with when it is in the away position. */ [crossAxis.name]: baseOffset[crossAxis.name] + keyboardPreviewCrossAxisOffset }; }, away({ droppable: { direction }, dropIndicatorRect, draggableDimensions }) { rbdInvariant(dropIndicatorRect, 'the drop indicator should exist if in away position'); /** * This base offset will result in the preview being over the drop indicator * (same x and y coordinates). * * Consider this as `currentPosition - initialPosition` to find an offset. * * The `dropIndicatorRect` is the **current** viewport-relative position of the * drop indicator. * * The `draggableDimensions.rect` is the **initial** viewport-relative position * of the draggable. */ const baseOffset = { x: dropIndicatorRect.x - draggableDimensions.rect.x, y: dropIndicatorRect.y - draggableDimensions.rect.y }; const { mainAxis, crossAxis } = directionMapping[direction]; return { /** * The drop indicator should bisect the preview on its main axis. * * In other words, the drop indicator should be in the middle. */ [mainAxis.name]: baseOffset[mainAxis.name] - 0.5 * draggableDimensions.rect[mainAxis.style.length], /** * On the cross axis, the drag preview is offset by a fixed percentage * of its cross axis length. * * This allows some of the drop indicator to remain visible. */ [crossAxis.name]: baseOffset[crossAxis.name] + keyboardPreviewCrossAxisOffset }; } }; /** * Determines the offset for the drag preview for keyboard drags. * * Unlike mouse drags, during which the drag preview follows the cursor, * the drag preview will follow the drop indicator for keyboard drags. */ function updateKeyboardPreview(state, { update, droppable, draggableDimensions, placeholderRect, dropIndicatorRect }) { var _update$destination; if (!droppable || !draggableDimensions) { return state; } const data = { droppable, draggableDimensions, placeholderRect, dropIndicatorRect }; const isHome = isSameLocation(update.source, (_update$destination = update.destination) !== null && _update$destination !== void 0 ? _update$destination : null); const previewOffset = isHome ? getKeyboardPreviewOffset.home(data) : getKeyboardPreviewOffset.away(data); if (!previewOffset) { return state; } return { ...state, previewOffset }; } function startDrag(state, { start, previewOffset }) { rbdInvariant(state.type === 'idle', 'The draggable is idle.'); const draggingOver = start.source.droppableId; const nextState = { type: 'dragging', draggingOver, location: null, start: start.source, draggableId: start.draggableId, mode: start.mode, previewOffset }; return nextState; } export function reducer(state, action) { if (action.type === 'START_POINTER_DRAG') { return startDrag(state, { ...action.payload, previewOffset: { x: 0, y: 0 } }); } if (action.type === 'START_KEYBOARD_DRAG') { const { draggableDimensions, droppable } = action.payload; return startDrag(state, { ...action.payload, previewOffset: getKeyboardPreviewOffset.initial({ draggableDimensions, direction: droppable.direction }) }); } if (action.type === 'UPDATE_DRAG') { rbdInvariant(state.type === 'dragging', 'The draggable is dragging.'); const { update } = action.payload; const draggingOver = update.destination ? update.destination.droppableId : null; if (draggingOver === state.draggingOver) { // Save on an unnecessary rerender return state; } const nextState = { ...state, draggingOver }; return nextState; } if (action.type === 'UPDATE_POINTER_PREVIEW') { rbdInvariant(state.type === 'dragging', 'The draggable is dragging.'); const { pointerLocation } = action.payload; const nextState = { ...state, previewOffset: { x: pointerLocation.current.input.clientX - pointerLocation.initial.input.clientX, y: pointerLocation.current.input.clientY - pointerLocation.initial.input.clientY } }; return nextState; } if (action.type === 'UPDATE_KEYBOARD_PREVIEW') { rbdInvariant(state.type === 'dragging', 'The draggable is dragging.'); if (state.type !== 'dragging') { return state; } const nextState = updateKeyboardPreview(state, action.payload); return nextState; } if (action.type === 'DROP') { rbdInvariant(state.type === 'dragging', 'The draggable is dragging.'); return idleState; } if (action.type === 'START_HIDING') { rbdInvariant(state.type === 'idle' || state.type === 'hiding'); return getHidingState(action.payload.mode); } if (action.type === 'STOP_HIDING') { rbdInvariant(state.type === 'hiding'); return idleState; } return state; }