Skip to main content
react-md

useDraggable

function useDraggable<E extends HTMLElement>(
  options: DraggableOptions<E>
): DraggableImplementation<E>;

This is not a drag and drop hook.

The useDraggable hook is used to drag elements through mouse, touch, and keyboard events and was created for to support the WindowSplitter and Slider components.

Example Usage

Here's a quick example using one of the useDraggable tests.

function Example() {
  const [value, setValue] = useState(20);
  const [dragging, setDragging] = useState(false);

  const {
    draggableRef,
    keyboardEventHandlers,
    minimum,
    maximum,
    increment,
    decrement,
  } = useDraggable({
    min: 0,
    max: 100,
    dragging,
    value,
    setValue,
    setDragging,
  });

  const percentage = getPercentage({ min: 0, max: 100, value });

  return (
    <>
      <Button
        aria-valuenow={Math.ceil(percentage * 100)}
        ref={draggableRef}
        {...keyboardEventHandlers}
        className={cnb(dragging && "dragging")}
      >
        Button
      </Button>
      <Button onClick={minimum}>Minimum</Button>
      <Button onClick={maximum}>Maximum</Button>
      <Button onClick={increment}>Increment</Button>
      <Button onClick={decrement}>Decrement</Button>
    </>
  );
}

Parameters

export type DraggableOptions<E extends HTMLElement = HTMLElement> =
  BaseDraggableOptions<E> & DraggableStateOptions;

export interface BaseDraggableOptions<E extends HTMLElement>
  extends DraggableEventHandlers<E>,
    ControllableDraggableStateOptions {
  /**
   * An optional ref to merge with the returned
   * {@link DraggableImplementation.draggableRef}.
   */
  ref?: Ref<E>;

  /**
   * The minimum number of pixels allowed for the draggable element. This must
   * be a number greater than or equal to 0.
   *
   * When {@link withinOffsetParent} is set to `true`, this is the minimum value
   * allowed instead of pixels.
   */
  min: number;

  /**
   * The maximum number of pixels allowed for the draggable element. This must
   * be a number greater than the {@link min} and usually a number less than the
   * viewport size.
   *
   * When {@link withinOffsetParent} is set to `true`, this is the maximum value
   * allowed instead of pixels.
   */
  max: number;

  /**
   * The amount to increment or decrement the value with arrow keys.
   *
   * @defaultValue `1`
   */
  step?: number;

  /**
   * This was added to support range sliders where there are two (or more)
   * draggable elements within the same container element and their values
   * cannot pass each other. Without these overrides, the range would keep
   * changing as the other values change, so the drag percentage would be
   * incorrect.
   *
   * @example Range Slider
   * ```ts
   * const min = 0;
   * const max = 100;
   * const minValue = 3;
   * const maxValue = 80;
   *
   * const minValueDraggable = useDraggable({
   *   min,
   *   max,
   *   rangeMax: maxValue,
   * });
   * const maxValueDraggable = useDraggable({
   *   min,
   *   max,
   *   rangeMin: minValue,
   * });
   * ```
   *
   * @defaultValue `min`
   */
  rangeMin?: number;

  /**
   * @see {@link rangeMin} for an explanation of this option.
   * @defaultValue `max`
   */
  rangeMax?: number;

  /**
   * Set this to `true` to enable dragging vertically instead of horizontally.
   *
   * @defaultValue `false`
   */
  vertical?: boolean;

  /**
   * The default drag behavior is to increase the value when:
   *
   * - dragging `"right"` and the writing direction is `"ltr"`
   * - dragging `"left"` and the writing direction is `"rtl"`
   * - dragging `"upwards"`
   *
   * When this is set to `true`, the value when increase when:
   *
   * - dragging `"left"` and the writing direction is `"ltr"`
   * - dragging `"right"` and the writing direction is `"rtl"`
   * - dragging `"downwards"`
   *
   * @defaultValue `false`
   */
  reversed?: boolean;

  /**
   * Set this to `true` to disable all drag behavior. This will still call any
   * of the provided {@link DraggableEventHandlers}.
   *
   * @defaultValue `false`
   */
  disabled?: boolean;

  /**
   * Set this to `true` if the dragging calculations should be to the
   * `draggableRef.current.offsetParent` instead of the entire window. The main
   * use case for this is sliders.
   *
   * @defaultValue `false`
   */
  withinOffsetParent?: boolean;

  /**
   * Set this to `true` to prevent the `document.documentElement` from gaining
   * the `.rmd-dragging` class names while dragging.
   *
   * This should normally remain as `false` to improve performance and prevent
   * other mouse events from firing while dragging.
   *
   * @defaultValue `false`
   */
  disableDraggingClassName?: boolean;

  /**
   * Set this to `true` to prevent the vertical or horizontal cursor from
   * appearing while dragging.
   *
   * @defaultValue `false`
   */
  disableDraggingCursorClassName?: boolean;
}

Returns

export interface DraggableImplementation<E extends HTMLElement = HTMLElement>
  extends Required<DraggableEventHandlers<E>> {
  mouseEventHandlers: Required<DraggableMouseEventHandlers<E>>;
  touchEventHandlers: Required<DraggableTouchEventHandlers<E>>;
  keyboardEventHandlers: Required<DraggableKeyboardEventHandlers<E>>;

  /**
   * Set the {@link value} to {@link DraggableOptions.min}.
   */
  minimum: () => void;

  /**
   * Set the {@link value} to {@link DraggableOptions.max}.
   */
  maximum: () => void;

  /**
   * Increment the {@link value} by {@link DraggableOptions.step}.
   */
  increment: () => void;

  /**
   * Decrement the {@link value} by {@link DraggableOptions.step}.
   */
  decrement: () => void;

  /**
   * The current percentage the `value` is within the range.
   *
   * ```ts
   * const percentage =
   *   dragging && withinOffsetParent
   *     ? : dragPercentage
   *     : getPercentage({ min, max, value });
   * ```
   */
  percentage: number;

  /**
   * A ref that **Must** be passed to the element that should be draggable.
   */
  draggableRef: RefCallback<E>;

  /**
   * This value will only update while dragging with a mouse or touch and should
   * be used for the positioning styles while dragging.
   *
   * Note: The {@link percentage} will use this value while dragging.
   */
  dragPercentage: number;

  /**
   * Flag to determine if the user has dragged at least once. Used internally
   * for manually persisting the value into local storage once the user has
   * stopped dragging.
   */
  draggedOnce: NonNullRef<boolean>;

  value: number;
  setValue: UseStateSetter<number>;
  dragging: boolean;
  setDragging: UseStateSetter<boolean>;
}