@marciocamello/react-sortable-tree
Version:
Drag-and-drop sortable component for nested data and hierarchies
289 lines (249 loc) • 7.14 kB
text/typescript
// @ts-nocheck
import { DragSource as dragSource, DropTarget as dropTarget } from 'react-dnd'
import { memoizedInsertNode } from './memoized-tree-data-utils'
import { getDepth } from './tree-data-utils'
let rafId = 0
const nodeDragSourcePropInjection = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
didDrop: monitor.didDrop(),
})
export const wrapSource = (el, startDrag, endDrag, dndType) => {
const nodeDragSource = {
beginDrag: (props) => {
startDrag(props)
return {
node: props.node,
parentNode: props.parentNode,
path: props.path,
treeIndex: props.treeIndex,
treeId: props.treeId,
}
},
endDrag: (props, monitor) => {
endDrag(monitor.getDropResult())
},
isDragging: (props, monitor) => {
const dropTargetNode = monitor.getItem().node
const draggedNode = props.node
return draggedNode === dropTargetNode
},
}
return dragSource(dndType, nodeDragSource, nodeDragSourcePropInjection)(el)
}
const propInjection = (connect, monitor) => {
const dragged = monitor.getItem()
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedNode: dragged ? dragged.node : undefined,
}
}
export const wrapPlaceholder = (el, treeId, drop, dndType) => {
const placeholderDropTarget = {
drop: (dropTargetProps, monitor) => {
const { node, path, treeIndex } = monitor.getItem()
const result = {
node,
path,
treeIndex,
treeId,
minimumTreeIndex: 0,
depth: 0,
}
drop(result)
return result
},
}
return dropTarget(dndType, placeholderDropTarget, propInjection)(el)
}
const getTargetDepth = (
dropTargetProps,
monitor,
component,
canNodeHaveChildren,
treeId,
maxDepth
) => {
let dropTargetDepth = 0
const rowAbove = dropTargetProps.getPrevRow()
if (rowAbove) {
const { node } = rowAbove
let { path } = rowAbove
const aboveNodeCannotHaveChildren = !canNodeHaveChildren(node)
if (aboveNodeCannotHaveChildren) {
path = path.slice(0, -1)
}
// Limit the length of the path to the deepest possible
dropTargetDepth = Math.min(path.length, dropTargetProps.path.length)
}
let blocksOffset
let dragSourceInitialDepth = (monitor.getItem().path || []).length
// When adding node from external source
if (monitor.getItem().treeId === treeId) {
// handle row direction support
const direction = dropTargetProps.rowDirection === 'rtl' ? -1 : 1
blocksOffset = Math.round(
(direction * monitor.getDifferenceFromInitialOffset().x) /
dropTargetProps.scaffoldBlockPxWidth
)
} else {
// Ignore the tree depth of the source, if it had any to begin with
dragSourceInitialDepth = 0
if (component) {
const relativePosition = component.node.getBoundingClientRect()
const leftShift =
monitor.getSourceClientOffset().x - relativePosition.left
blocksOffset = Math.round(
leftShift / dropTargetProps.scaffoldBlockPxWidth
)
} else {
blocksOffset = dropTargetProps.path.length
}
}
let targetDepth = Math.min(
dropTargetDepth,
Math.max(0, dragSourceInitialDepth + blocksOffset - 1)
)
// If a maxDepth is defined, constrain the target depth
if (maxDepth !== undefined && maxDepth !== undefined) {
const draggedNode = monitor.getItem().node
const draggedChildDepth = getDepth(draggedNode)
targetDepth = Math.max(
0,
Math.min(targetDepth, maxDepth - draggedChildDepth - 1)
)
}
return targetDepth
}
const canDrop = (
dropTargetProps,
monitor,
canNodeHaveChildren,
treeId,
maxDepth,
treeRefcanDrop,
draggingTreeData,
treeReftreeData,
getNodeKey
) => {
if (!monitor.isOver()) {
return false
}
const rowAbove = dropTargetProps.getPrevRow()
const abovePath = rowAbove ? rowAbove.path : []
const aboveNode = rowAbove ? rowAbove.node : {}
const targetDepth = getTargetDepth(
dropTargetProps,
monitor,
undefined,
canNodeHaveChildren,
treeId,
maxDepth
)
// Cannot drop if we're adding to the children of the row above and
// the row above is a function
if (
targetDepth >= abovePath.length &&
typeof aboveNode.children === 'function'
) {
return false
}
if (typeof treeRefcanDrop === 'function') {
const { node } = monitor.getItem()
return treeRefcanDrop({
node,
prevPath: monitor.getItem().path,
prevParent: monitor.getItem().parentNode,
prevTreeIndex: monitor.getItem().treeIndex, // Equals -1 when dragged from external tree
nextPath: dropTargetProps.children.props.path,
nextParent: dropTargetProps.children.props.parentNode,
nextTreeIndex: dropTargetProps.children.props.treeIndex,
})
}
return true
}
export const wrapTarget = (
el,
canNodeHaveChildren,
treeId,
maxDepth,
treeRefcanDrop,
drop,
dragHover,
dndType,
draggingTreeData,
treeReftreeData,
getNodeKey
) => {
const nodeDropTarget = {
drop: (dropTargetProps, monitor, component) => {
const result = {
node: monitor.getItem().node,
path: monitor.getItem().path,
treeIndex: monitor.getItem().treeIndex,
treeId,
minimumTreeIndex: dropTargetProps.treeIndex,
depth: getTargetDepth(
dropTargetProps,
monitor,
component,
canNodeHaveChildren,
treeId,
maxDepth
),
}
drop(result)
return result
},
hover: (dropTargetProps, monitor, component) => {
const targetDepth = getTargetDepth(
dropTargetProps,
monitor,
component,
canNodeHaveChildren,
treeId,
maxDepth
)
const draggedNode = monitor.getItem().node
const needsRedraw =
// Redraw if hovered above different nodes
dropTargetProps.node !== draggedNode ||
// Or hovered above the same node but at a different depth
targetDepth !== dropTargetProps.path.length - 1
if (!needsRedraw) {
return
}
// throttle `dragHover` work to available animation frames
cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(() => {
const item = monitor.getItem()
// skip if drag already ended before the animation frame
if (!item || !monitor.isOver()) {
return
}
dragHover({
node: draggedNode,
path: item.path,
minimumTreeIndex: dropTargetProps.listIndex,
depth: targetDepth,
})
})
},
canDrop: (dropTargetProps, monitor) =>
canDrop(
dropTargetProps,
monitor,
canNodeHaveChildren,
treeId,
maxDepth,
treeRefcanDrop,
draggingTreeData,
treeReftreeData,
getNodeKey
),
}
return dropTarget(dndType, nodeDropTarget, propInjection)(el)
}