Skip to content

Commit 2743d81

Browse files
committed
fix: prevent onClick firing on mobile devices when dropping pieces
1 parent 8b110cc commit 2743d81

File tree

2 files changed

+54
-5
lines changed

2 files changed

+54
-5
lines changed

src/Square.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
defaultSquareStyle,
1313
} from './defaults.js';
1414
import { SquareDataType } from './types.js';
15-
import { columnIndexToChessColumn } from './utils.js';
15+
import { columnIndexToChessColumn, isTouchEndWithinSquare } from './utils.js';
1616

1717
type SquareProps = {
1818
children?: React.ReactNode;
@@ -29,6 +29,8 @@ export const Square = memo(function Square({
2929
}: SquareProps) {
3030
// track if we are drawing an arrow so that onSquareRightClick is not fired when we finish drawing an arrow
3131
const [isDrawingArrow, setIsDrawingArrow] = useState(false);
32+
const [isClickingOnMobile, setIsClickingOnMobile] = useState(false);
33+
3234
const {
3335
id,
3436
allowDrawingArrows,
@@ -81,6 +83,13 @@ export const Square = memo(function Square({
8183
prevIsOverRef.current = isOver;
8284
}, [currentPosition, draggingPiece, isOver, squareId]);
8385

86+
// if we are dragging a piece, we are no longer clicking on mobile as that is a drag operation, so reset the clicking on mobile state
87+
useEffect(() => {
88+
if (draggingPiece && isClickingOnMobile) {
89+
setIsClickingOnMobile(false);
90+
}
91+
}, [draggingPiece, isClickingOnMobile]);
92+
8493
const column = squareId.match(/^[a-z]+/)?.[0];
8594
const row = squareId.match(/\d+$/)?.[0];
8695

@@ -106,13 +115,24 @@ export const Square = memo(function Square({
106115
});
107116
}
108117
}}
118+
onTouchStart={() => {
119+
// in order to prevent onTouchEnd from firing if we are dragging within the same square, we need to store if a touch has started so that we can reset it if we drag, and if it isn't reset by the time the touch ends, we know it was a tap and can fire the click event
120+
setIsClickingOnMobile(true);
121+
}}
109122
onTouchEnd={(e) => {
110123
// Prevent default to avoid double-firing with onClick on some devices
111124
e.preventDefault();
112-
onSquareClick?.({
113-
piece: currentPosition[squareId] ?? null,
114-
square: squareId,
115-
});
125+
126+
// we only want to fire onSquareClick if the touch event ended within the same square as it started
127+
const within = isTouchEndWithinSquare(id, squareId, e);
128+
129+
if (within && isClickingOnMobile) {
130+
onSquareClick?.({
131+
piece: currentPosition[squareId] ?? null,
132+
square: squareId,
133+
});
134+
}
135+
setIsClickingOnMobile(false);
116136
}}
117137
onContextMenu={(e) => {
118138
e.preventDefault();

src/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,32 @@ export function getRelativeCoords(
418418
squareWidth / 2;
419419
return { x, y };
420420
}
421+
422+
export function isTouchEndWithinSquare(
423+
id: string,
424+
squareId: string,
425+
e: React.TouchEvent<HTMLDivElement>,
426+
) {
427+
const rect = document
428+
.getElementById(`${id}-square-${squareId}`)!
429+
.getBoundingClientRect();
430+
const touches = e.changedTouches;
431+
432+
let within = false;
433+
for (let i = 0; i < touches.length; i++) {
434+
const x = touches[i].clientX;
435+
const y = touches[i].clientY;
436+
437+
if (
438+
x >= rect.left &&
439+
x <= rect.right &&
440+
y >= rect.top &&
441+
y <= rect.bottom
442+
) {
443+
within = true;
444+
break;
445+
}
446+
}
447+
448+
return within;
449+
}

0 commit comments

Comments
 (0)