logo@krutoo/utils

useDragAndDrop

This block is draggable

Hook for adding ability to drag and drop some element.

Usage

Here is an example of simple draggable element

import { type CSSProperties, useRef, useState } from 'react';
import { useDragAndDrop } from '@krutoo/utils/react';

function App() {
  const ref = useRef<HTMLDivElement>(null);

  const [grabbed, setGrabbed] = useState(false);
  const [offset, setOffset] = useState({ x: 0, y: 0 });

  useDragAndDrop(ref, {
    onGrab(event) {
      setGrabbed(true);
      setOffset(event.offset);
    },
    onMove(event) {
      setOffset(event.offset);
    },
    onDrop() {
      setGrabbed(false);
    },
  });

  const style: CSSProperties = grabbed
    ? {
        position: 'absolute',
        top: `${offset.y}px`,
        left: `${offset.x}px`,
      }
    : {};

  return <div ref={ref}>This block is draggable</div>;
}

This example uses state for style but you can set position to style directly to element in event callbacks using ref or event.target.

useDragAndDrop(ref, {
  onGrab(event) {
    event.target.style.position = 'absolute';
    event.target.style.left = `${event.offset.x}px`;
    event.target.style.top = `${event.offset.y}px`;
  },
  onMove(event) {
    event.target.style.left = `${event.offset.x}px`;
    event.target.style.top = `${event.offset.y}px`;
  },
  onDrop(event) {
    event.target.style.position = 'static';
  },
});

Controlling drag start

By default hook start dragging immediately on pointerdown event.

To disable or change this behavior you can call preventDefault on grab event.

For example you can start drag only if some child element is pressed:

const needStartDrag = event => {
  return event.nativeEvent.target.classList.contains('handle');
};

useDragAndDrop(ref, {
  onGrab(event) {
    if (needStartDrag(event)) {
      return event.preventDefault();
    }

    // ... your state logic
  },
});

Or you can implement dragging activation only after some shift threshold reached:

const threshold = 8;

const needStartDrag = event => {
  return Math.abs(event.offset.x - event.startOffset.x) >= threshold;
};

Plugins

Hook provides ability to define plugins to extend basic drag-and-drop behavior.

useDragAndDrop(ref, {
  plugins: [myPlugin, myOtherPlugin],
  // ...other options
});

Plugin can add hooks for some events:

  • init - here you can add some listeners
  • grab - calls hook right after dispatching grab event
  • move- calls hook right after dispatching move event
  • drop- calls hook right after dispatching drop event
  • destroy - here you can remove some listeners

By default hook uses some builtin plugins.

You can disable this plugins by passing array:

useDragAndDrop(ref, {
  // no plugins and no builtins
  plugins: [],
});

useDragAndDrop(ref, {
  // only your plugin and no builtins
  plugins: [myPlugin],
});

Builtin plugin cleanSelection

Plugin that cleans selection during drag.

Builtin plugin preventClick

Plugin that prevents click if target element was moved by drag-and-drop.

Builtin plugin touchScroll

Plugin that prevents page scroll and "pull-to-refresh" for draggable element.

Using builtins selectively

import { DragAndDropBuiltinPlugins, useDragAndDrop } from '@krutoo/utils/react';

useDragAndDrop(ref, {
  // Using only `touchScroll` plugin from builtins
  plugins: [DragAndDropBuiltinPlugins.touchScroll],
});

Optimization

By default observer uses same event object between callbacks, just change property values. You can disable this behavior by passing option reuseEvent: false.