# All files / utils useDragEvent.ts

20% Statements 10/50
16.67% Branches 2/12
20% Functions 1/5
25% Lines 10/40

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
    110
    1x
    1x

    1x

    1x
    5x
    5x

    5x

    5x

    5x

    5x

     import * as React from 'react'
    import {useRef, useCallback} from 'react'
    import {useForceUpdate} from './useForceUpdate'

    // Minimum number of pixels a user must drag before we decide whether the
    // action is a vertical or horizontal drag
    const MIN_DRAG_DELTA = 3

    export interface DragEvent {
      type: 'drag' | 'dragend'
      initialX: number
      initialY: number
      direction: 'x' | 'y' | null
      x: number
      y: number
    }

    interface UseDragEventProps {
      onMouseDown: (e: React.MouseEvent<Element, MouseEvent>) => any
    }

    export const useDragEvent = (): [DragEvent | null, UseDragEventProps] => {
      const dragEventRef = useRef<DragEvent | null>(null)
      const forceUpdate = useForceUpdate()

      const onMouseDown = useCallback(
        (mouseDownEvent: React.MouseEvent<Element, MouseEvent>) => {
          mouseDownEvent.stopPropagation()

          const el = mouseDownEvent.currentTarget

          const getXYCoords = baseEvent => {
            const {left, top} = el.getBoundingClientRect()

            return [baseEvent.pageX - left, baseEvent.pageY - top]
          }

          const onMouseMove = mouseMoveEvent => {
            const [x, y] = getXYCoords(mouseMoveEvent)

            const {initialX, initialY} = dragEventRef.current
            let {direction} = dragEventRef.current

            if (!direction) {
              const dx = Math.abs(x - initialX)
              const dy = Math.abs(y - initialY)

              if (dx >= dy && dx > MIN_DRAG_DELTA) {
                direction = 'x'
              } else if (dy > MIN_DRAG_DELTA) {
                direction = 'y'
              }
            }

            dragEventRef.current = {
              ...dragEventRef.current,
              type: 'drag',
              direction,
              x,
              y,
            }

            forceUpdate()
          }

          const onMouseUp = mouseUpEvent => {
            document.removeEventListener('mousemove', onMouseMove)
            document.removeEventListener('mousemove', onMouseUp)

            const [x, y] = getXYCoords(mouseUpEvent)

            dragEventRef.current = {
              ...dragEventRef.current,
              type: 'dragend',
              x,
              y,
            }

            forceUpdate()
          }

          document.addEventListener('mousemove', onMouseMove)
          document.addEventListener('mouseup', onMouseUp)

          const [x, y] = getXYCoords(mouseDownEvent)

          dragEventRef.current = {
            type: 'drag',
            initialX: x,
            initialY: y,
            x,
            y,
            direction: null,
          }

          forceUpdate()
        },
        []
      )

      const {current: dragEvent} = dragEventRef

      Iif (dragEvent && dragEvent.type === 'dragend') {
        // 'dragEnd' events should be emitted exactly once at the end of a drag
        dragEventRef.current = null
      }

      return [dragEvent, {onMouseDown}]
    }