@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
275 lines (268 loc) • 8.28 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { createPortal } from 'react-dom';
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { getHiddenTextElementId } from '../drag-drop-context/hooks/use-hidden-text-element';
import { useDragDropContext } from '../drag-drop-context/internal-context';
import { useMonitorForLifecycle } from '../drag-drop-context/lifecycle-context';
import { rbdInvariant } from '../drag-drop-context/rbd-invariant';
import { isDraggableData } from '../draggable/data';
import { getDraggableProvidedStyle } from '../draggable/get-draggable-provided-style';
import { idleState, reducer } from '../draggable/state';
import { useDraggableStateSnapshot } from '../draggable/use-draggable-state-snapshot';
import { useDraggableDimensions } from '../hooks/use-captured-dimensions';
import { attributes } from '../utils/attributes';
import { findDragHandle } from '../utils/find-drag-handle';
import { findDropIndicator } from '../utils/find-drop-indicator';
import { findPlaceholder } from '../utils/find-placeholder';
function getBody() {
return document.body;
}
/**
* Calls the `renderClone` function.
*
* Only rendered during drags.
*/
function DraggableCloneInner({
children,
droppableId,
type,
draggableId,
index,
draggingOver,
style,
/**
* Defaults to `document.body`
*/
getContainerForClone = getBody,
mode
}) {
const {
contextId
} = useDragDropContext();
/**
* The handle should maintain focus during a drag,
* if it had focus before the drag started.
*/
const focusDragHandle = useCallback(element => {
if (!element) {
return;
}
const dragHandle = findDragHandle({
contextId,
draggableId
});
dragHandle === null || dragHandle === void 0 ? void 0 : dragHandle.focus();
}, [contextId, draggableId]);
const provided = useMemo(() => {
return {
innerRef: focusDragHandle,
draggableProps: {
[attributes.draggable.contextId]: contextId,
[attributes.draggable.id]: draggableId,
style
},
dragHandleProps: {
role: 'button',
'aria-describedby': getHiddenTextElementId(contextId),
[attributes.dragHandle.contextId]: contextId,
[attributes.dragHandle.draggableId]: draggableId,
tabIndex: 0,
/**
* This must be `false` for drags to trigger on the draggable `element`,
* which may be a parent, and not on the `dragHandle`.
*
* If the drag triggers on the `dragHandle` it won't be handled by the
* library.
*/
draggable: false,
onDragStart: () => {}
}
};
}, [contextId, draggableId, focusDragHandle, style]);
const snapshot = useDraggableStateSnapshot({
draggingOver,
isClone: true,
isDragging: true,
mode
});
const rubric = useMemo(() => {
return {
draggableId,
type,
source: {
droppableId,
index
}
};
}, [draggableId, droppableId, index, type]);
return /*#__PURE__*/createPortal(children(provided, snapshot, rubric), getContainerForClone());
}
/**
* Wrapper that is always rendered if there is a `renderClone` function.
*
* It sets up a monitor, and needs to observe the entire lifecycle.
*/
export function DraggableClone({
children,
droppableId,
type,
getContainerForClone
}) {
const {
contextId,
getDragState
} = useDragDropContext();
const draggableDimensions = useDraggableDimensions();
const [state, dispatch] = useReducer(reducer, idleState);
const monitorForLifecycle = useMonitorForLifecycle();
useEffect(() => {
return combine(monitorForLifecycle({
onPendingDragStart({
start,
droppable
}) {
if (droppableId !== start.source.droppableId) {
return;
}
if (start.mode === 'FLUID') {
return dispatch({
type: 'START_POINTER_DRAG',
payload: {
start
}
});
}
if (start.mode === 'SNAP') {
const dragState = getDragState();
rbdInvariant(dragState.isDragging && dragState.draggableDimensions);
return dispatch({
type: 'START_KEYBOARD_DRAG',
payload: {
start,
draggableDimensions: dragState.draggableDimensions,
droppable
}
});
}
},
onPendingDragUpdate({
update,
droppable
}) {
if (state.type !== 'dragging') {
return;
}
if (state.draggableId !== update.draggableId) {
return;
}
dispatch({
type: 'UPDATE_DRAG',
payload: {
update
}
});
if (update.mode === 'SNAP') {
/**
* Updating the position in a microtask to resolve timing issues.
*
* When doing cross-axis dragging, the drop indicator in the new
* droppable will mount and update in a `onPendingDragUpdate` too.
*
* The microtask ensures that the indicator will have updated by
* the time this runs, so the preview will have the correct
* location of the indicator.
*/
queueMicrotask(() => {
/**
* Because this update occurs in a microtask, we need to check
* that the drag is still happening.
*
* If it has ended we should not try to update the preview.
*/
const dragState = getDragState();
if (!dragState.isDragging) {
return;
}
/**
* The placeholder might not exist if its associated
* draggable unmounts in a virtual list.
*/
const placeholder = findPlaceholder(contextId);
const placeholderRect = placeholder ? placeholder.getBoundingClientRect() : null;
/**
* The drop indicator might not exist if the current target
* is null
*/
const dropIndicator = findDropIndicator();
const dropIndicatorRect = dropIndicator ? dropIndicator.getBoundingClientRect() : null;
dispatch({
type: 'UPDATE_KEYBOARD_PREVIEW',
payload: {
update,
draggableDimensions,
droppable,
placeholderRect,
dropIndicatorRect
}
});
});
}
},
onBeforeDragEnd({
draggableId
}) {
if (state.type !== 'dragging') {
return;
}
if (draggableId !== state.draggableId) {
return;
}
dispatch({
type: 'DROP'
});
}
}), monitorForElements({
canMonitor({
source
}) {
if (!isDraggableData(source.data)) {
// not dragging something from the migration layer
// we should not monitor it
return false;
}
return source.data.contextId === contextId && source.data.droppableId === droppableId;
},
onDrag({
location
}) {
dispatch({
type: 'UPDATE_POINTER_PREVIEW',
payload: {
pointerLocation: location
}
});
}
}));
}, [droppableId, contextId, monitorForLifecycle, state, draggableDimensions, getDragState]);
if (state.type !== 'dragging') {
return null;
}
const style = getDraggableProvidedStyle({
draggableDimensions,
draggableState: state
});
return /*#__PURE__*/React.createElement(DraggableCloneInner, {
droppableId: droppableId,
type: type,
draggableId: state.draggableId,
index: state.start.index,
draggingOver: state.draggingOver,
mode: state.mode
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
,
style: style,
getContainerForClone: getContainerForClone
}, children);
}