@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
JavaScript
/**
* 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;
}