{"version":3,"file":"use-radial-move.cjs","names":["clamp"],"sources":["../../src/use-radial-move/use-radial-move.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { clamp } from '../utils';\n\nfunction radiansToDegrees(radians: number) {\n  return radians * (180 / Math.PI);\n}\n\nfunction getElementCenter(element: HTMLElement) {\n  const rect = element.getBoundingClientRect();\n  return [rect.left + rect.width / 2, rect.top + rect.height / 2];\n}\n\nfunction getAngle(coordinates: [number, number], element: HTMLElement) {\n  const center = getElementCenter(element);\n  const x = coordinates[0] - center[0];\n  const y = coordinates[1] - center[1];\n  const deg = radiansToDegrees(Math.atan2(x, y)) + 180;\n  return 360 - deg;\n}\n\nfunction toFixed(value: number, digits: number) {\n  return parseFloat(value.toFixed(digits));\n}\n\nfunction getDigitsAfterDot(value: number) {\n  return value.toString().split('.')[1]?.length || 0;\n}\n\nexport function normalizeRadialValue(degree: number, step: number) {\n  const clamped = clamp(degree, 0, 360);\n  const high = Math.ceil(clamped / step);\n  const low = Math.round(clamped / step);\n  return toFixed(\n    high >= clamped / step ? (high * step === 360 ? 0 : high * step) : low * step,\n    getDigitsAfterDot(step)\n  );\n}\n\nexport interface UseRadialMoveOptions {\n  /** Number by which value is incremented/decremented with mouse and touch events, `0.01` by default */\n  step?: number;\n\n  /** Called in `onMouseUp` and `onTouchEnd` events with the current value */\n  onChangeEnd?: (value: number) => void;\n\n  /** Called in `onMouseDown` and `onTouchStart` events */\n  onScrubStart?: () => void;\n\n  /** Called in `onMouseUp` and `onTouchEnd` events */\n  onScrubEnd?: () => void;\n}\n\nexport interface UseRadialMoveReturnValue<T extends HTMLElement = any> {\n  /** Ref to be passed to the element that should be used for radial move */\n  ref: React.RefCallback<T | null>;\n\n  /** Indicates whether the radial move is active */\n  active: boolean;\n}\n\nexport function useRadialMove<T extends HTMLElement = any>(\n  onChange: (value: number) => void,\n  { step = 0.01, onChangeEnd, onScrubStart, onScrubEnd }: UseRadialMoveOptions = {}\n): UseRadialMoveReturnValue<T> {\n  const [active, setActive] = useState(false);\n  const cleanupRef = useRef<(() => void) | null>(null);\n\n  useEffect(() => {\n    return () => {\n      cleanupRef.current?.();\n    };\n  }, []);\n\n  const refCallback: React.RefCallback<T | null> = useCallback(\n    (node) => {\n      const update = (event: MouseEvent, done = false) => {\n        if (node) {\n          node.style.userSelect = 'none';\n          const deg = getAngle([event.clientX, event.clientY], node);\n          const newValue = normalizeRadialValue(deg, step || 1);\n\n          onChange(newValue);\n          done && onChangeEnd?.(newValue);\n        }\n      };\n\n      const beginTracking = () => {\n        onScrubStart?.();\n        setActive(true);\n        document.addEventListener('mousemove', handleMouseMove, false);\n        document.addEventListener('mouseup', handleMouseUp, false);\n        document.addEventListener('touchmove', handleTouchMove, { passive: false });\n        document.addEventListener('touchend', handleTouchEnd, false);\n      };\n\n      const endTracking = () => {\n        onScrubEnd?.();\n        setActive(false);\n        document.removeEventListener('mousemove', handleMouseMove, false);\n        document.removeEventListener('mouseup', handleMouseUp, false);\n        document.removeEventListener('touchmove', handleTouchMove, false);\n        document.removeEventListener('touchend', handleTouchEnd, false);\n      };\n\n      const onMouseDown = (event: MouseEvent) => {\n        beginTracking();\n        update(event);\n      };\n\n      const handleMouseMove = (event: MouseEvent) => {\n        update(event);\n      };\n\n      const handleMouseUp = (event: MouseEvent) => {\n        update(event, true);\n        endTracking();\n      };\n\n      const handleTouchMove = (event: TouchEvent) => {\n        event.preventDefault();\n        update(event.touches[0] as any);\n      };\n\n      const handleTouchEnd = (event: TouchEvent) => {\n        update(event.changedTouches[0] as any, true);\n        endTracking();\n      };\n\n      const handleTouchStart = (event: TouchEvent) => {\n        event.preventDefault();\n        beginTracking();\n        update(event.touches[0] as any);\n      };\n\n      node?.addEventListener('mousedown', onMouseDown);\n      node?.addEventListener('touchstart', handleTouchStart, { passive: false });\n\n      cleanupRef.current = () => {\n        document.removeEventListener('mousemove', handleMouseMove, false);\n        document.removeEventListener('mouseup', handleMouseUp, false);\n        document.removeEventListener('touchmove', handleTouchMove, false);\n        document.removeEventListener('touchend', handleTouchEnd, false);\n      };\n\n      return () => {\n        if (node) {\n          node.removeEventListener('mousedown', onMouseDown);\n          node.removeEventListener('touchstart', handleTouchStart);\n        }\n      };\n    },\n    [onChange]\n  );\n\n  return { ref: refCallback, active };\n}\n\nexport namespace useRadialMove {\n  export type Options = UseRadialMoveOptions;\n  export type ReturnValue<T extends HTMLElement> = UseRadialMoveReturnValue<T>;\n}\n"],"mappings":"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"}