@@ -23,6 +23,24 @@ let waiter = buildWaiter('yapp-scroll-view:scrolling');
23
23
const FIELD_REGEXP = / i n p u t | t e x t a r e a | s e l e c t / i;
24
24
const MEASUREMENT_INTERVAL = 250 ;
25
25
const MEASUREMENT_INTERVAL_WHILE_SCROLLING_OR_OFFSCREEN = 1000 ;
26
+ const LONG_PRESS_DELAY = 500 ;
27
+ const GHOST_CLICK_DELAY = 100 ;
28
+
29
+ const isIPhone = / i P h o n e | i P o d | i P a d / i. test ( navigator . appVersion ) ;
30
+
31
+ let timeoutID = 0 ;
32
+ function addCaptureClick ( domElement ) {
33
+ if ( timeoutID ) {
34
+ clearTimeout ( timeoutID ) ;
35
+ } else {
36
+ domElement . addEventListener ( 'click' , captureClick , true ) ;
37
+ }
38
+ let cancelCaptureClick = ( ) => {
39
+ timeoutID = 0 ;
40
+ domElement . removeEventListener ( 'click' , captureClick , true ) ;
41
+ }
42
+ timeoutID = setTimeout ( cancelCaptureClick , GHOST_CLICK_DELAY )
43
+ }
26
44
27
45
function debounce ( func , wait , immediate ) {
28
46
var timeout ;
@@ -93,6 +111,7 @@ class ScrollView extends Component {
93
111
_appliedScrollTop ;
94
112
_shouldMeasureContent = undefined ;
95
113
_isScrolling = false ;
114
+ _touchStartTimeStamp = null ;
96
115
_lastIsScrolling = false ;
97
116
98
117
@service ( 'scroll-position-memory' )
@@ -279,6 +298,7 @@ class ScrollView extends Component {
279
298
280
299
doTouchStart ( touches , timeStamp ) {
281
300
this . _wasScrollingAtTouchStart = this . _isScrolling ;
301
+ this . _touchStartTimeStamp = timeStamp ;
282
302
this . scroller . doTouchStart ( touches , timeStamp ) ;
283
303
}
284
304
@@ -287,37 +307,46 @@ class ScrollView extends Component {
287
307
}
288
308
289
309
doTouchEnd ( _touches , timeStamp , event ) {
290
- let preventClick = this . needsPreventClick ( ) ;
310
+ let preventClick = this . needsPreventClick ( timeStamp - this . _touchStartTimeStamp ) ;
291
311
292
312
if ( preventClick ) {
293
313
// A touchend event can prevent a follow-on click event by calling preventDefault.
294
- // However, a mouseup event cannot do this so we need to capture the upcoming click instead.
295
- if ( event instanceof MouseEvent ) {
296
- this . scrollViewElement . addEventListener ( 'click' , captureClick , true ) ;
297
- } else {
298
- event . preventDefault ( ) ;
299
- event . stopPropagation ( ) ;
314
+ // On Android, it works well.
315
+ // On iOS, we see a click event being triggered after a touchend event,
316
+ // even when `preventDefault` and `stopPropagation` were called. However, phantom clicks
317
+ // are not triggered consistently. In order to avoid capturing legit click events,
318
+ // we only try to capture phantom clicks if they happen less than 100ms after a touchend event.
319
+ // On desktop browsers, a mouseup event cannot do this so we need to capture the upcoming click instead.
320
+
321
+ if ( isIPhone || ( event instanceof MouseEvent ) ) {
322
+ addCaptureClick ( this . scrollViewElement ) ;
300
323
}
324
+ event . preventDefault ( ) ;
325
+ event . stopPropagation ( ) ;
301
326
}
302
327
328
+ this . _touchStartTimeStamp = null ;
303
329
this . scroller . doTouchEnd ( timeStamp ) ;
304
330
}
305
331
306
- needsPreventClick ( ) {
307
- // There are two cases where we want to prevent the click that normally follows a mouseup/touchend.
332
+ needsPreventClick ( touchDuration ) {
333
+ // There are three cases where we want to prevent the click that normally follows a mouseup/touchend.
308
334
//
309
335
// 1) when the user is just finishing a purposeful scroll (i.e. dragging scroll view beyond a threshold)
336
+ // This is only true on a desktop.
310
337
// 2) when animating with "momentum", a tap should stop the movement rather than
311
338
// trigger an interactive element that may be under the tap. Zynga scroller
312
339
// takes care of stopping the movement, but we need to capture the click
313
340
// and stop propagation.
341
+ // 3) when the user does a long press (> 500 ms)
314
342
//
315
343
// This method determines whether either of these cases apply.
316
344
let isFinishingDragging = this . scroller . __isDragging ;
317
345
let wasAnimatingWithMomentum =
318
346
this . _wasScrollingAtTouchStart &&
319
347
Math . abs ( this . _decelerationVelocityY ) > 2 ;
320
- return isFinishingDragging || wasAnimatingWithMomentum ;
348
+ let isLongPress = touchDuration > LONG_PRESS_DELAY ;
349
+ return isFinishingDragging || wasAnimatingWithMomentum || isLongPress ;
321
350
}
322
351
323
352
handleWheel ( e ) {
0 commit comments