strictClick.tsx 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. import {cloneElement, useCallback, useRef} from 'react';
  2. type ClickProps<T> = {
  3. onClick?: React.HTMLAttributes<T>['onClick'];
  4. };
  5. interface Props<T extends HTMLElement> extends ClickProps<T> {
  6. children: React.ReactElement;
  7. }
  8. /**
  9. * Does not fire the onclick event if the mouse has moved outside of the
  10. * original click location upon release.
  11. *
  12. * <StrictClick onClick={onClickHandler}>
  13. * <button>Some button</button>
  14. * </StrictClick>
  15. */
  16. const MAX_DELTA_X = 10;
  17. const MAX_DELTA_Y = 10;
  18. function StrictClick<T extends HTMLElement>({onClick, children}: Props<T>) {
  19. const mouseDownCoordinates = useRef<[number, number] | null>(null);
  20. const handleMouseDown = useCallback((event: React.MouseEvent<T>) => {
  21. mouseDownCoordinates.current = [event.screenX, event.screenY];
  22. }, []);
  23. const handleMouseClick = useCallback(
  24. (evt: React.MouseEvent<T>) => {
  25. if (!onClick) {
  26. return;
  27. }
  28. if (mouseDownCoordinates.current === null) {
  29. return;
  30. }
  31. // Click happens if mouse down/up in same element - click will not fire if
  32. // either initial mouse down OR final mouse up occurs in different element
  33. const [x, y] = mouseDownCoordinates.current;
  34. const deltaX = Math.abs(evt.screenX - x);
  35. const deltaY = Math.abs(evt.screenY - y);
  36. // If mouse hasn't moved more than 10 pixels in either Y or X direction,
  37. // fire onClick
  38. if (deltaX < MAX_DELTA_X && deltaY < MAX_DELTA_Y) {
  39. onClick(evt);
  40. }
  41. mouseDownCoordinates.current = null;
  42. },
  43. [onClick]
  44. );
  45. // Bail out early if there is no onClick handler
  46. if (!onClick) {
  47. return children;
  48. }
  49. return cloneElement(children, {
  50. onMouseDown: handleMouseDown,
  51. onClick: handleMouseClick,
  52. });
  53. }
  54. export default StrictClick;