Skip to content

Commit e4906a6

Browse files
dagatsoinerictraut
authored andcommitted
[FIX] Web: prevent triggering longPress when pointer is outside the button (#1083)
* fix on mobile * fix on desktop * ignore click after a longpress followed by a mouse leave * fix regression * Web: rewrite onResponderRelease/onPressOut rules
1 parent a57de2d commit e4906a6

1 file changed

Lines changed: 66 additions & 11 deletions

File tree

src/web/Button.tsx

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class Button extends ButtonBase {
6060

6161
private _mountedButton: HTMLButtonElement | null = null;
6262
private _lastMouseDownEvent: Types.SyntheticEvent | undefined;
63-
private _ignoreMouseUp = false;
63+
private _ignoreTouchEnd = false;
6464
private _ignoreClick = false;
6565
private _longPressTimer: number | undefined;
6666
private _isMouseOver = false;
@@ -104,7 +104,8 @@ export class Button extends ButtonBase {
104104
aria-checked={ ariaChecked }
105105
onClick={ this.onClick }
106106
onTouchStart={ this._onMouseDown }
107-
onTouchEnd={ this._onMouseUp }
107+
onTouchMove={ this._onTouchMove }
108+
onTouchEnd={ this._onTouchEnd }
108109
onContextMenu={ this._onContextMenu }
109110
onMouseDown={ this._onMouseDown }
110111
onMouseUp={ this._onMouseUp }
@@ -194,6 +195,7 @@ export class Button extends ButtonBase {
194195
}
195196

196197
private _onMouseDown = (e: React.SyntheticEvent<any>) => {
198+
this._isMouseOver = true;
197199
if (this.props.onPressIn) {
198200
this.props.onPressIn(e);
199201
}
@@ -211,21 +213,32 @@ export class Button extends ButtonBase {
211213
if (this.props.onLongPress) {
212214
// lastMouseDownEvent can never be undefined at this point
213215
this.props.onLongPress(this._lastMouseDownEvent!);
214-
this._ignoreMouseUp = true;
215-
this._ignoreClick = true;
216+
217+
if ('touches' in e) {
218+
this._ignoreTouchEnd = true;
219+
} else {
220+
this._ignoreClick = true;
221+
}
216222
}
217223
}, this.props.delayLongPress || _longPressTime);
218224
}
219225
}
220226

221-
private _onMouseUp = (e: Types.SyntheticEvent | Types.TouchEvent) => {
222-
if (this._ignoreMouseUp) {
223-
e.stopPropagation();
224-
// Touch event won't trigger onClick when a long press is released. So we reset the ignore flag here.
225-
if ('touches' in e) {
226-
this._ignoreClick = false;
227-
}
227+
private _onTouchMove = (e: React.SyntheticEvent<HTMLButtonElement, TouchEvent>) => {
228+
const buttonRect = (e.target as HTMLButtonElement).getBoundingClientRect();
229+
this._isMouseOver =
230+
e.nativeEvent.touches[0].clientX > buttonRect.left &&
231+
e.nativeEvent.touches[0].clientX < buttonRect.right &&
232+
e.nativeEvent.touches[0].clientY > buttonRect.top &&
233+
e.nativeEvent.touches[0].clientY < buttonRect.bottom;
234+
235+
// Touch has left the button, cancel the longpress handler.
236+
if (this._isMouseOver && this._longPressTimer) {
237+
clearTimeout(this._longPressTimer);
228238
}
239+
}
240+
241+
private _onMouseUp = (e: Types.SyntheticEvent | Types.TouchEvent) => {
229242
if (this.props.onPressOut) {
230243
this.props.onPressOut(e);
231244
}
@@ -235,6 +248,40 @@ export class Button extends ButtonBase {
235248
}
236249
}
237250

251+
/**
252+
* Case where onPressOut is not triggered and the bubbling is canceled:
253+
* 1- Long press > release
254+
*
255+
* Cases where onPressOut is triggered:
256+
* 2- Long press > leave button > release touch
257+
* 3- Tap
258+
*
259+
* Case where onPressOut is not triggered and the bubbling is NOT canceled:
260+
* 4- Press in > leave button > release touch
261+
*/
262+
private _onTouchEnd = (e: Types.SyntheticEvent | Types.TouchEvent) => {
263+
if (this._isMouseOver && this._ignoreTouchEnd) {
264+
/* 1 */
265+
e.stopPropagation();
266+
} else if (
267+
/* 2 */
268+
!this._isMouseOver && this._ignoreTouchEnd ||
269+
/* 3 */
270+
this._isMouseOver && !this._ignoreTouchEnd
271+
) {
272+
if (this.props.onPressOut) {
273+
this.props.onPressOut(e);
274+
}
275+
} else {
276+
/* 4 */
277+
this._ignoreTouchEnd = false;
278+
}
279+
280+
if (this._longPressTimer) {
281+
clearTimeout(this._longPressTimer);
282+
}
283+
}
284+
238285
private _onMouseEnter = (e: Types.SyntheticEvent) => {
239286
this._isMouseOver = true;
240287
this._onHoverStart(e);
@@ -243,6 +290,14 @@ export class Button extends ButtonBase {
243290
private _onMouseLeave = (e: Types.SyntheticEvent) => {
244291
this._isMouseOver = false;
245292
this._onHoverEnd(e);
293+
294+
// The mouse is still down. A long press may be just happened. Re-enable the next click.
295+
this._ignoreClick = false;
296+
297+
// Cancel longpress if mouse has left.
298+
if (this._longPressTimer) {
299+
clearTimeout(this._longPressTimer);
300+
}
246301
}
247302

248303
// When we get focus on an element, show the hover effect on the element.

0 commit comments

Comments
 (0)