@@ -15,6 +15,7 @@ import supportedPageVisibilityEventName from '../util/supported-page-visibility-
1515import createPostDragEventPreventer , {
1616 type EventPreventer ,
1717} from '../util/create-post-drag-event-preventer' ;
18+ import useLayoutEffect from '../../use-isomorphic-layout-effect' ;
1819
1920export type Args = { |
2021 callbacks : Callbacks ,
@@ -36,75 +37,13 @@ type TouchWithForce = Touch & {
3637 force : number ,
3738} ;
3839
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 ;
4543export const forcePressThreshold : number = 0.15 ;
4644const touchStartMarshal : EventMarshal = createEventMarshal ( ) ;
4745const noop = ( ) : void => { } ;
4846
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-
10847export default function useTouchSensor ( args : Args ) : OnTouchStart {
10948 const {
11049 callbacks,
@@ -143,7 +82,6 @@ export default function useTouchSensor(args: Args): OnTouchStart {
14382 schedule . cancel ( ) ;
14483 unbindWindowEventsRef . current ( ) ;
14584 touchStartMarshal . reset ( ) ;
146- webkitHack . releaseTouchMove ( ) ;
14785 hasMovedRef . current = false ;
14886 onCaptureEnd ( ) ;
14987
@@ -196,11 +134,15 @@ export default function useTouchSensor(args: Args): OnTouchStart {
196134 hasMovedRef . current = true ;
197135 }
198136
199- const { clientX, clientY } = event . touches [ 0 ] ;
137+ const touch : ?Touch = event . touches [ 0 ] ;
138+
139+ if ( ! touch ) {
140+ return ;
141+ }
200142
201143 const point : Position = {
202- x : clientX ,
203- y : clientY ,
144+ x : touch . clientX ,
145+ y : touch . clientY ,
204146 } ;
205147
206148 // We need to prevent the default event in order to block native scrolling
@@ -308,32 +250,44 @@ export default function useTouchSensor(args: Args): OnTouchStart {
308250 // Need to opt out of dragging if the user is a force press
309251 // Only for webkit which has decided to introduce its own custom way of doing things
310252 // https://developer.apple.com/library/content/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html
253+ // NOTE: this function is back-ported from the `virtual` branch
311254 {
312255 eventName : 'touchforcechange' ,
313256 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 ) {
317261 return ;
318262 }
319263
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
325273 return ;
326274 }
327275
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
331277
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
335286 cancel ( ) ;
287+ return ;
336288 }
289+ // not respecting during a drag
290+ event . preventDefault ( ) ;
337291 } ,
338292 } ,
339293 // Cancel on page visibility change
@@ -425,11 +379,25 @@ export default function useTouchSensor(args: Args): OnTouchStart {
425379 // browser interactions as possible.
426380 // This includes navigation on anchors which we want to preserve
427381 touchStartMarshal . handle ( ) ;
428-
429- // A webkit only hack to prevent touch move events
430- webkitHack . preventTouchMove ( ) ;
431382 startPendingDrag ( event ) ;
432383 } ;
433384
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+
434402 return onTouchStart ;
435403}
0 commit comments