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

275 lines (268 loc) 8.28 kB
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); }