in superset-frontend/src/dashboard/util/getDropPosition.js [42:158]
export default function getDropPosition(monitor, Component) {
const {
depth: componentDepth,
parentComponent,
component,
orientation,
isDraggingOverShallow,
} = Component.props;
const draggingItem = monitor.getItem();
// if dropped self on self, do nothing
if (!draggingItem || draggingItem.id === component.id) {
return null;
}
const validChild = isValidChild({
parentType: component.type,
parentDepth: componentDepth,
childType: draggingItem.type,
});
const parentType = parentComponent && parentComponent.type;
const parentDepth = // see isValidChild.js for why tabs don't increment child depth
componentDepth +
(parentType === TAB_TYPE || parentType === TABS_TYPE ? 0 : -1);
const validSibling = isValidChild({
parentType,
parentDepth,
childType: draggingItem.type,
});
if (!validChild && !validSibling) {
return DROP_FORBIDDEN;
}
const hasChildren = (component.children || []).length > 0;
const childDropOrientation =
orientation === 'row' ? 'vertical' : 'horizontal';
const siblingDropOrientation =
orientation === 'row' ? 'horizontal' : 'vertical';
if (validChild && !validSibling) {
// easiest case, insert as child
if (childDropOrientation === 'vertical') {
return hasChildren ? DROP_RIGHT : DROP_LEFT;
}
return hasChildren ? DROP_BOTTOM : DROP_TOP;
}
const refBoundingRect = Component.ref.getBoundingClientRect();
const clientOffset =
monitor.getClientOffset() || CACHED_CLIENT_OFFSET[component.id];
if (!clientOffset || !refBoundingRect) {
return null;
}
CACHED_CLIENT_OFFSET[component.id] = clientOffset;
const deltaTop = Math.abs(clientOffset.y - refBoundingRect.top);
const deltaBottom = Math.abs(clientOffset.y - refBoundingRect.bottom);
const deltaLeft = Math.abs(clientOffset.x - refBoundingRect.left);
const deltaRight = Math.abs(clientOffset.x - refBoundingRect.right);
// Most of the time we only want a drop indicator for shallow (top-level, non-nested) drop targets
// However there are some cases where considering only shallow targets would result in NO drop
// indicators which is a bad UX.
// e.g.,
// when dragging row-a over a chart that's in another row-b, the chart is the shallow droptarget
// but row-a is not a valid child or sibling. in this case we want to show a sibling drop
// indicator for row-b, which is NOT a shallow drop target.
// BUT if we ALWAYS consider non-shallow drop targets we may get multiple indicators shown at the
// same time, which is also a bad UX. to prevent this we can enforce a threshold proximity of the
// mouse to the edge of a non-shallow target
if (
!isDraggingOverShallow &&
[deltaTop, deltaBottom, deltaLeft, deltaRight].every(
delta => delta > NON_SHALLOW_DROP_THRESHOLD,
)
) {
return null;
}
// Drop based on mouse position relative to component center
if (validSibling && !validChild) {
if (siblingDropOrientation === 'vertical') {
const refMiddleX =
refBoundingRect.left +
(refBoundingRect.right - refBoundingRect.left) / 2;
return clientOffset.x < refMiddleX ? DROP_LEFT : DROP_RIGHT;
}
const refMiddleY =
refBoundingRect.top + (refBoundingRect.bottom - refBoundingRect.top) / 2;
return clientOffset.y < refMiddleY ? DROP_TOP : DROP_BOTTOM;
}
// either is valid, so choose location based on boundary deltas
if (validSibling && validChild) {
// if near enough to a sibling boundary, drop there
if (siblingDropOrientation === 'vertical') {
if (deltaLeft < SIBLING_DROP_THRESHOLD) return DROP_LEFT;
if (deltaRight < SIBLING_DROP_THRESHOLD) return DROP_RIGHT;
} else {
if (deltaTop < SIBLING_DROP_THRESHOLD) return DROP_TOP;
if (deltaBottom < SIBLING_DROP_THRESHOLD) return DROP_BOTTOM;
}
// drop as child
if (childDropOrientation === 'vertical') {
return hasChildren ? DROP_RIGHT : DROP_LEFT;
}
return hasChildren ? DROP_BOTTOM : DROP_TOP;
}
return null;
}