All files / platform/ui-next/src/components/Dialog useDraggable.ts

36.11% Statements 13/36
27.27% Branches 3/11
40% Functions 2/5
36.11% Lines 13/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                          1x 1x 1x   1x   1x                                   1x                 1x                                           1x   7x 7x   7x             1x       1x                
import * as React from 'react';
 
interface Offset {
  x: number;
  y: number;
}
 
interface DragState {
  startX: number;
  startY: number;
  initialOffset: Offset;
}
 
interface UseDraggableProps {
  enabled?: boolean;
}
 
interface UseDraggableReturn {
  offset: React.RefObject<Offset>;
  internalRef: React.RefObject<HTMLDivElement>;
  handlePointerDown: (e: React.PointerEvent<HTMLDivElement>) => void;
  setRefs: (node: HTMLDivElement) => void;
  initialTransform: string | undefined;
}
 
export function useDraggable(
  props: UseDraggableProps,
  ref?: React.ForwardedRef<HTMLDivElement>
): UseDraggableReturn {
  const { enabled = false } = props;
  const offsetRef = React.useRef<Offset>({ x: 0, y: 0 });
  const internalRef = React.useRef<HTMLDivElement>(null);
 
  const dragState = React.useRef<DragState | null>(null);
 
  const handlePointerMove = React.useCallback((e: PointerEvent) => {
    Iif (!dragState.current) {
      return;
    }
 
    const deltaX = e.clientX - dragState.current.startX;
    const deltaY = e.clientY - dragState.current.startY;
    const newOffset = {
      x: dragState.current.initialOffset.x + deltaX,
      y: dragState.current.initialOffset.y + deltaY,
    };
 
    offsetRef.current = newOffset;
    Iif (internalRef.current) {
      internalRef.current.style.transform = `translate(-50%, -50%) translate(${newOffset.x}px, ${newOffset.y}px)`;
    }
  }, []);
 
  const handlePointerUp = React.useCallback(() => {
    Iif (internalRef.current) {
      internalRef.current.style.transition = '';
    }
    window.removeEventListener('pointermove', handlePointerMove);
    window.removeEventListener('pointerup', handlePointerUp);
    dragState.current = null;
  }, [handlePointerMove]);
 
  const handlePointerDown = React.useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      const target = e.target as HTMLElement;
      Iif (!target.closest('.drag-handle')) {
        return;
      }
 
      Iif (internalRef.current) {
        internalRef.current.style.transition = 'none';
      }
      dragState.current = {
        startX: e.clientX,
        startY: e.clientY,
        initialOffset: { ...offsetRef.current },
      };
 
      window.addEventListener('pointermove', handlePointerMove);
      window.addEventListener('pointerup', handlePointerUp);
    },
    [handlePointerMove, handlePointerUp]
  );
 
  const setRefs = React.useCallback(
    (node: HTMLDivElement) => {
      internalRef.current = node;
      Iif (typeof ref === 'function') {
        ref(node);
      } else Iif (ref) {
        (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
      }
    },
    [ref]
  );
 
  const initialTransform = enabled
    ? `translate(-50%, -50%) translate(${offsetRef.current.x}px, ${offsetRef.current.y}px)`
    : undefined;
 
  return {
    offset: offsetRef,
    internalRef,
    handlePointerDown,
    setRefs,
    initialTransform,
  };
}