strictClick.tsx 1.7 KB

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