@@ -15,6 +15,7 @@ import supportedPageVisibilityEventName from '../util/supported-page-visibility-
15
15
import createPostDragEventPreventer , {
16
16
type EventPreventer ,
17
17
} from '../util/create-post-drag-event-preventer' ;
18
+ import useLayoutEffect from '../../use-isomorphic-layout-effect' ;
18
19
19
20
export type Args = { |
20
21
callbacks : Callbacks ,
@@ -36,75 +37,13 @@ type TouchWithForce = Touch & {
36
37
force : number ,
37
38
} ;
38
39
39
- type WebkitHack = { |
40
- preventTouchMove : ( ) => void ,
41
- releaseTouchMove : ( ) => void ,
42
- | } ;
43
-
44
- export const timeForLongPress : number = 150 ;
40
+ // Decreased from 150 as a work around for an issue for forcepress on iOS
41
+ // https://github.com/atlassian/react-beautiful-dnd/issues/1401
42
+ export const timeForLongPress : number = 120 ;
45
43
export const forcePressThreshold : number = 0.15 ;
46
44
const touchStartMarshal : EventMarshal = createEventMarshal ( ) ;
47
45
const noop = ( ) : void => { } ;
48
46
49
- // Webkit does not allow event.preventDefault() in dynamically added handlers
50
- // So we add an always listening event handler to get around this :(
51
- // webkit bug: https://bugs.webkit.org/show_bug.cgi?id=184250
52
- const webkitHack : WebkitHack = ( ( ) => {
53
- const stub : WebkitHack = {
54
- preventTouchMove : noop ,
55
- releaseTouchMove : noop ,
56
- } ;
57
-
58
- // Do nothing when server side rendering
59
- if ( typeof window === 'undefined' ) {
60
- return stub ;
61
- }
62
-
63
- // Device has no touch support - no point adding the touch listener
64
- if ( ! ( 'ontouchstart' in window ) ) {
65
- return stub ;
66
- }
67
-
68
- // Not adding any user agent testing as everything pretends to be webkit
69
-
70
- let isBlocking : boolean = false ;
71
-
72
- // Adding a persistent event handler
73
- window . addEventListener (
74
- 'touchmove' ,
75
- ( event : TouchEvent ) => {
76
- // We let the event go through as normal as nothing
77
- // is blocking the touchmove
78
- if ( ! isBlocking ) {
79
- return ;
80
- }
81
-
82
- // Our event handler would have worked correctly if the browser
83
- // was not webkit based, or an older version of webkit.
84
- if ( event . defaultPrevented ) {
85
- return ;
86
- }
87
-
88
- // Okay, now we need to step in and fix things
89
- event . preventDefault ( ) ;
90
-
91
- // Forcing this to be non-passive so we can get every touchmove
92
- // Not activating in the capture phase like the dynamic touchmove we add.
93
- // Technically it would not matter if we did this in the capture phase
94
- } ,
95
- { passive : false , capture : false } ,
96
- ) ;
97
-
98
- const preventTouchMove = ( ) => {
99
- isBlocking = true ;
100
- } ;
101
- const releaseTouchMove = ( ) => {
102
- isBlocking = false ;
103
- } ;
104
-
105
- return { preventTouchMove, releaseTouchMove } ;
106
- } ) ( ) ;
107
-
108
47
export default function useTouchSensor ( args : Args ) : OnTouchStart {
109
48
const {
110
49
callbacks,
@@ -143,7 +82,6 @@ export default function useTouchSensor(args: Args): OnTouchStart {
143
82
schedule . cancel ( ) ;
144
83
unbindWindowEventsRef . current ( ) ;
145
84
touchStartMarshal . reset ( ) ;
146
- webkitHack . releaseTouchMove ( ) ;
147
85
hasMovedRef . current = false ;
148
86
onCaptureEnd ( ) ;
149
87
@@ -196,11 +134,15 @@ export default function useTouchSensor(args: Args): OnTouchStart {
196
134
hasMovedRef . current = true ;
197
135
}
198
136
199
- const { clientX, clientY } = event . touches [ 0 ] ;
137
+ const touch : ?Touch = event . touches [ 0 ] ;
138
+
139
+ if ( ! touch ) {
140
+ return ;
141
+ }
200
142
201
143
const point : Position = {
202
- x : clientX ,
203
- y : clientY ,
144
+ x : touch . clientX ,
145
+ y : touch . clientY ,
204
146
} ;
205
147
206
148
// We need to prevent the default event in order to block native scrolling
@@ -308,32 +250,44 @@ export default function useTouchSensor(args: Args): OnTouchStart {
308
250
// Need to opt out of dragging if the user is a force press
309
251
// Only for webkit which has decided to introduce its own custom way of doing things
310
252
// https://developer.apple.com/library/content/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html
253
+ // NOTE: this function is back-ported from the `virtual` branch
311
254
{
312
255
eventName : 'touchforcechange' ,
313
256
fn : ( event : TouchEvent ) => {
314
- // Not respecting force touches - prevent the event
315
- if ( ! getShouldRespectForcePress ( ) ) {
316
- event . preventDefault ( ) ;
257
+ const touch : TouchWithForce = ( event . touches [ 0 ] : any ) ;
258
+ const isForcePress : boolean = touch . force >= forcePressThreshold ;
259
+
260
+ if ( ! isForcePress ) {
317
261
return ;
318
262
}
319
263
320
- // A force push action will no longer fire after a touchmove
321
- if ( hasMovedRef . current ) {
322
- // This is being super safe. While this situation should not occur we
323
- // are still expressing that we want to opt out of force pressing
324
- event . preventDefault ( ) ;
264
+ const shouldRespect : boolean = getShouldRespectForcePress ( ) ;
265
+
266
+ if ( pendingRef . current ) {
267
+ if ( shouldRespect ) {
268
+ cancel ( ) ;
269
+ }
270
+ // If not respecting we just let the event go through
271
+ // It will not have an impact on the browser until
272
+ // there has been a sufficient time ellapsed
325
273
return ;
326
274
}
327
275
328
- // A drag could be pending or has already started but no movement has occurred
329
-
330
- const touch : TouchWithForce = ( event . touches [ 0 ] : any ) ;
276
+ // DRAGGING
331
277
332
- if ( touch . force >= forcePressThreshold ) {
333
- // this is an indirect cancel so we do not preventDefault
334
- // we also want to allow the force press to occur
278
+ if ( shouldRespect ) {
279
+ if ( hasMovedRef . current ) {
280
+ // After the user has moved we do not allow the dragging item to be force pressed
281
+ // This prevents strange behaviour such as a link preview opening mid drag
282
+ event . preventDefault ( ) ;
283
+ return ;
284
+ }
285
+ // indirect cancel
335
286
cancel ( ) ;
287
+ return ;
336
288
}
289
+ // not respecting during a drag
290
+ event . preventDefault ( ) ;
337
291
} ,
338
292
} ,
339
293
// Cancel on page visibility change
@@ -425,11 +379,25 @@ export default function useTouchSensor(args: Args): OnTouchStart {
425
379
// browser interactions as possible.
426
380
// This includes navigation on anchors which we want to preserve
427
381
touchStartMarshal . handle ( ) ;
428
-
429
- // A webkit only hack to prevent touch move events
430
- webkitHack . preventTouchMove ( ) ;
431
382
startPendingDrag ( event ) ;
432
383
} ;
433
384
385
+ // This is needed for safari
386
+ // Simply adding a non capture, non passive 'touchmove' listener.
387
+ // This forces event.preventDefault() in dynamically added
388
+ // touchmove event handlers to actually work
389
+ // https://github.com/atlassian/react-beautiful-dnd/issues/1374
390
+ useLayoutEffect ( function webkitHack ( ) {
391
+ const unbind = bindEvents ( window , [
392
+ {
393
+ eventName : 'touchmove' ,
394
+ fn : noop ,
395
+ options : { capture : false , passive : false } ,
396
+ } ,
397
+ ] ) ;
398
+
399
+ return unbind ;
400
+ } , [ ] ) ;
401
+
434
402
return onTouchStart ;
435
403
}
0 commit comments