clickable-element-enhancer.tsx 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { useRef, useEffect } from 'react'
  2. import PolymorphicComponent, {
  3. PolymorphicComponentProps,
  4. } from '@/shared/components/polymorphic-component'
  5. import { MergeAndOverride } from '../../../../types/utils'
  6. // Performs a click event on elements that has been clicked,
  7. // but when releasing the mouse button are no longer hovered
  8. // by the cursor (which by default cancels the event).
  9. type ClickableElementEnhancerOwnProps = {
  10. onClick: () => void
  11. onMouseDown?: (e: React.MouseEvent) => void
  12. offset?: number
  13. }
  14. type ClickableElementEnhancerProps<E extends React.ElementType> =
  15. MergeAndOverride<
  16. PolymorphicComponentProps<E>,
  17. ClickableElementEnhancerOwnProps
  18. >
  19. function ClickableElementEnhancer<E extends React.ElementType>({
  20. onClick,
  21. onMouseDown,
  22. offset = 50, // the offset around the clicked element which should still trigger the click
  23. ...rest
  24. }: ClickableElementEnhancerProps<E>) {
  25. const isClickedRef = useRef(false)
  26. const elRectRef = useRef<DOMRect>()
  27. const restProps = rest as PolymorphicComponentProps<E>
  28. const handleMouseDown = (e: React.MouseEvent) => {
  29. isClickedRef.current = true
  30. elRectRef.current = (e.target as HTMLElement).getBoundingClientRect()
  31. onMouseDown?.(e)
  32. }
  33. useEffect(() => {
  34. const handleMouseUp = (e: MouseEvent) => {
  35. if (isClickedRef.current) {
  36. isClickedRef.current = false
  37. if (!elRectRef.current) {
  38. return
  39. }
  40. const halfWidth = elRectRef.current.width / 2
  41. const halfHeight = elRectRef.current.height / 2
  42. const centerX = elRectRef.current.x + halfWidth
  43. const centerY = elRectRef.current.y + halfHeight
  44. const deltaX = Math.abs(e.clientX - centerX)
  45. const deltaY = Math.abs(e.clientY - centerY)
  46. // Check if the mouse has moved significantly from the element position
  47. if (deltaX < halfWidth + offset && deltaY < halfHeight + offset) {
  48. // If the mouse hasn't moved much, consider it a click
  49. onClick()
  50. }
  51. }
  52. }
  53. document.addEventListener('mouseup', handleMouseUp)
  54. return () => {
  55. document.removeEventListener('mouseup', handleMouseUp)
  56. }
  57. }, [onClick, offset])
  58. return <PolymorphicComponent onMouseDown={handleMouseDown} {...restProps} />
  59. }
  60. export default ClickableElementEnhancer