UNPKG

@atlaskit/pragmatic-drag-and-drop-hitbox

Version:

An optional package for Pragmatic drag and drop that enables the attaching of interaction information to a drop target

148 lines (143 loc) • 3.87 kB
import { isShallowEqual } from './internal/isShallowEqual'; import { stable } from './internal/stable'; import { uniqueKey } from './tree-item'; function getCenter(rect) { return { x: (rect.right + rect.left) / 2, y: (rect.bottom + rect.top) / 2 }; } function standardHitbox({ client, borderBox }) { const quarterOfHeight = borderBox.height / 4; // In the top 1/4: reorder-above // On the line = in the top 1/4 to give this zone a bit more space if (client.y <= borderBox.top + quarterOfHeight) { return 'reorder-above'; } // In the bottom 1/4: reorder-below // On the line = in the bottom 1/4 to give this zone a bit more space if (client.y >= borderBox.bottom - quarterOfHeight) { return 'reorder-below'; } return 'make-child'; } function getInstruction({ element, input, currentLevel, indentPerLevel, mode }) { const client = { x: input.clientX, y: input.clientY }; const borderBox = element.getBoundingClientRect(); if (mode === 'standard') { const type = standardHitbox({ borderBox, client }); return { type, indentPerLevel, currentLevel }; } const center = getCenter(borderBox); if (mode === 'expanded') { // leveraging "standard" hitbox to ensure that the 'reorder-above' hit zone is // exactly the same for "standard" and "expanded" items const type = standardHitbox({ borderBox, client }); return { // Use the "standard" hitbox for "reorder above", // The rest of the item is "make-child" type: type === 'reorder-above' ? type : 'make-child', indentPerLevel, currentLevel }; } // `mode` is "last-in-group" const visibleInset = indentPerLevel * currentLevel; // Before the left edge of the visible item if (client.x < borderBox.left + visibleInset) { // Above the center: `reorder-above` if (client.y < center.y) { return { type: 'reorder-above', indentPerLevel, currentLevel }; } // On or below the center: `reparent` // On the center = `reparent` as we are giving a slightly bigger hitbox to this // action as it is the only place a user can do it const rawLevel = (client.x - borderBox.left) / indentPerLevel; // We can get sub pixel negative numbers as getBoundingClientRect gives sub-pixel accuracy, // where as clientX is rounded to the nearest pixel. // Using Math.max() ensures we can never get a negative level const desiredLevel = Math.max(Math.floor(rawLevel), 0); return { type: 'reparent', desiredLevel, indentPerLevel, currentLevel }; } // On the visible item return { type: standardHitbox({ borderBox, client }), indentPerLevel, currentLevel }; } function areInstructionsEqual(a, b) { // Shortcut if (a.type !== b.type) { return false; } if (a.type === 'instruction-blocked' && b.type === 'instruction-blocked') { return areInstructionsEqual(a.desired, b.desired); } return isShallowEqual(a, b); } // Note: not using `memoize-one` as all we need is a cached value. // We do not need to avoid executing an expensive function. const memoizeInstruction = stable(areInstructionsEqual); function applyInstructionBlock({ desired, block }) { if (block !== null && block !== void 0 && block.includes(desired.type) && desired.type !== 'instruction-blocked') { const blocked = { type: 'instruction-blocked', desired }; return blocked; } return desired; } export function attachInstruction(userData, { block, ...rest }) { const desired = getInstruction(rest); const withBlock = applyInstructionBlock({ desired, block }); const memoized = memoizeInstruction(withBlock); return { ...userData, [uniqueKey]: memoized }; }