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
options
- An object with the following definition:
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>;
}