'How to handle multidirectional drag'n' drop list re-order with react framer motion?

I have forked React framer motion's drag'n'drop list re-order example. It works like a charm with a vertical list. I have also made it work in a horizontal way by simply passing the delta.x.translate value of the dragged div to the main function. But I'm stuck at handling multidirectional re-ordering.

Here is a sandbox: https://codesandbox.io/s/framer-motion-dragn-list-horizontal-3g9he?file=/src/App.js

The code looks like this:

export default function App() {
  const [items, setItems] = useState([
    "eat",
    "work",
    "sleep",
    "repeat",
    "cook",
    "doStuff"
  ]);
  return <Container items={items} onChangeOrder={setItems} />;
}

function Container({ items, onChangeOrder }) {
  const [updatePosition, updateOrder] = usePositionReorder(
    items,
    onChangeOrder
  );
  return (
    <ul>
      {items.map((item, i) => (
        <Item
          key={item}
          item={item}
          index={i}
          updatePosition={updatePosition}
          updateOrder={updateOrder}
        />
      ))}
    </ul>
  );
}

function Item({ index, item, updatePosition, updateOrder }) {
  const [isDragging, setDragging] = useState(false);
  const ref = useMeasurePosition((pos) => updatePosition(index, pos));

  return (
    <li>
      <motion.div
        ref={ref}
        layout
        initial={false}
        drag={true}
        onDragStart={() => setDragging(true)}
        onDragEnd={() => setDragging(false)}
        onViewportBoxUpdate={(_viewportBox, delta) => {
          isDragging &&
            updateOrder(index, delta.y.translate, delta.x.translate);
        }}
      >
        {item}
      </motion.div>
    </li>
  );
}

The main function is:

import { useRef } from "react";
import { distance } from "popmotion";

const arrayMoveMutate = (array, from, to) => {
  const startIndex = from < 0 ? array.length + from : from;
  if (startIndex >= 0 && startIndex < array.length) {
    const endIndex = to < 0 ? array.length + to : to;
    const [item] = array.splice(from, 1);
    array.splice(endIndex, 0, item);
  }
};

const move = (array, from, to) => {
  array = [...array];
  arrayMoveMutate(array, from, to);
  return array;
};

const clamp = (min, max, v) => Math.min(Math.max(v, min), max);

export function usePositionReorder(order, setOrder) {
  const positions = useRef([]).current;
  const updatePosition = (i, offset) => (positions[i] = offset);
  const updateOrder = (i, dragYOffset, dragxOffset) => {
    const targetIndex = findIndex(i, dragYOffset, dragxOffset, positions);
    if (targetIndex !== i) setOrder(move(order, i, targetIndex));
  };
  return [updatePosition, updateOrder];
}

export const findIndex = (i, yOffset, xOffset, positions) => {
  const buffer = 30;
  let target = i;
  const { top, height } = positions[i];
  const bottom = top + height;
  // If moving down
  if (yOffset > 0) {
    const nextItem = positions[i + 1];
    if (nextItem === undefined) return i;
    const swapOffset =
      distance(bottom, nextItem.top + nextItem.height / 2) + buffer;
    if (yOffset > swapOffset) return (target = i + 1);
  }
  // If moving left
     //do stuff
  //if moving right
     //do stuff
  // If moving up
  else if (yOffset < 0) {
    const prevItem = positions[i - 1];
    if (prevItem === undefined) return i;
    const prevBottom = prevItem.top + prevItem.height;
    const swapOffset = distance(top, prevBottom - prevItem.height / 2) + buffer;
    if (yOffset < -swapOffset) target = i - 1;
  }
  return clamp(0, positions.length, target);
};

The function should properly sort the list in both vertical and horizontal way. It should also work if I make a diagonal movement (from bottom right to somewhere around top-left for exemple).



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source