import * as React from 'react'; type ClickProps = { onClick?: React.HTMLAttributes['onClick']; }; type Props = ClickProps & { children: React.ReactElement; }; type State = { startCoords?: [number, number]; }; /** * Does not fire the onlick event if the mouse has moved outside of the * original click location upon release. * * * * */ class StrictClick extends React.PureComponent, State> { static MAX_DELTA_X = 10; static MAX_DELTA_Y = 10; handleMouseDown = ({screenX, screenY}: React.MouseEvent) => this.setState({startCoords: [screenX, screenY]}); handleMouseClick = (evt: React.MouseEvent) => { if (!this.props.onClick) { return; } // Click happens if mouse down/up in same element - click will not fire if // either initial mouse down OR final mouse up occurs in different element const [x, y] = this.state.startCoords!; const deltaX = Math.abs(evt.screenX - x); const deltaY = Math.abs(evt.screenY - y); // If mouse hasn't moved more than 10 pixels in either Y or X direction, // fire onClick if (deltaX < StrictClick.MAX_DELTA_X && deltaY < StrictClick.MAX_DELTA_Y) { this.props.onClick(evt); } this.setState({startCoords: undefined}); }; render() { // Bail out early if there is no onClick handler if (!this.props.onClick) { return this.props.children; } return React.cloneElement(this.props.children, { onMouseDown: this.handleMouseDown, onClick: this.handleMouseClick, }); } } export default StrictClick;