Skip to content

Commit 3fd58eb

Browse files
authored
Merge pull request #1407 from atlassian/fixing-force-press
back porting force press fix from virtual branch
2 parents 0bf8072 + 0218553 commit 3fd58eb

File tree

5 files changed

+75
-487
lines changed

5 files changed

+75
-487
lines changed

.size-snapshot.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"dist/react-beautiful-dnd.js": {
3-
"bundled": 393181,
4-
"minified": 147695,
5-
"gzipped": 41532
3+
"bundled": 392081,
4+
"minified": 147079,
5+
"gzipped": 41340
66
},
77
"dist/react-beautiful-dnd.min.js": {
8-
"bundled": 324803,
9-
"minified": 116622,
10-
"gzipped": 33468
8+
"bundled": 323864,
9+
"minified": 116189,
10+
"gzipped": 33372
1111
},
1212
"dist/react-beautiful-dnd.esm.js": {
13-
"bundled": 239412,
14-
"minified": 124303,
15-
"gzipped": 31620,
13+
"bundled": 238384,
14+
"minified": 123773,
15+
"gzipped": 31477,
1616
"treeshaked": {
1717
"rollup": {
18-
"code": 30396,
18+
"code": 29975,
1919
"import_statements": 793
2020
},
2121
"webpack": {
22-
"code": 34327
22+
"code": 33907
2323
}
2424
}
2525
}

src/view/use-drag-handle/sensor/use-mouse-sensor.js

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import createEventMarshal, {
1010
import type { Callbacks } from '../drag-handle-types';
1111
import { bindEvents, unbindEvents } from '../util/bind-events';
1212
import createScheduler from '../util/create-scheduler';
13-
import { warning } from '../../../dev-warning';
1413
import * as keyCodes from '../../key-codes';
1514
import supportedPageVisibilityEventName from '../util/supported-page-visibility-event-name';
1615
import createPostDragEventPreventer, {
@@ -32,10 +31,6 @@ export type Args = {|
3231
export type OnMouseDown = (event: MouseEvent) => void;
3332

3433
// Custom event format for force press inputs
35-
type MouseForceChangedEvent = MouseEvent & {
36-
webkitForce?: number,
37-
};
38-
3934
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
4035
const primaryButton: number = 0;
4136
const noop = () => {};
@@ -48,7 +43,8 @@ export default function useMouseSensor(args: Args): OnMouseDown {
4843
canStartCapturing,
4944
getWindow,
5045
callbacks,
51-
getShouldRespectForcePress,
46+
// Currently always respecting force press due to safari bug (see below)
47+
// getShouldRespectForcePress,
5248
onCaptureStart,
5349
onCaptureEnd,
5450
} = args;
@@ -248,34 +244,13 @@ export default function useMouseSensor(args: Args): OnMouseDown {
248244
// Only for safari which has decided to introduce its own custom way of doing things
249245
// https://developer.apple.com/library/content/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html
250246
{
251-
eventName: 'webkitmouseforcechanged',
252-
fn: (event: MouseForceChangedEvent) => {
253-
if (
254-
event.webkitForce == null ||
255-
(MouseEvent: any).WEBKIT_FORCE_AT_FORCE_MOUSE_DOWN == null
256-
) {
257-
warning(
258-
'handling a mouse force changed event when it is not supported',
259-
);
260-
return;
261-
}
262-
263-
const forcePressThreshold: number = (MouseEvent: any)
264-
.WEBKIT_FORCE_AT_FORCE_MOUSE_DOWN;
265-
const isForcePressing: boolean =
266-
event.webkitForce >= forcePressThreshold;
267-
268-
// New behaviour
269-
if (!getShouldRespectForcePress()) {
270-
event.preventDefault();
271-
return;
272-
}
273-
274-
if (isForcePressing) {
275-
// it is considered a indirect cancel so we do not
276-
// prevent default in any situation.
277-
cancel();
278-
}
247+
eventName: 'webkitmouseforcedown',
248+
fn: () => {
249+
// In order to opt out of force press correctly we need to call
250+
// event.preventDefault() on webkitmouseforcewillbegin
251+
// We have no way of doing this in this branch so we are always respecting force touches
252+
// There is a correct fix in the `virtual` branch
253+
cancel();
279254
},
280255
},
281256
// Cancel on page visibility change
@@ -293,7 +268,6 @@ export default function useMouseSensor(args: Args): OnMouseDown {
293268
stop,
294269
callbacks,
295270
getWindow,
296-
getShouldRespectForcePress,
297271
]);
298272

299273
const bindWindowEvents = useCallback(() => {

src/view/use-drag-handle/sensor/use-touch-sensor.js

Lines changed: 54 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import supportedPageVisibilityEventName from '../util/supported-page-visibility-
1515
import createPostDragEventPreventer, {
1616
type EventPreventer,
1717
} from '../util/create-post-drag-event-preventer';
18+
import useLayoutEffect from '../../use-isomorphic-layout-effect';
1819

1920
export 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;
4543
export const forcePressThreshold: number = 0.15;
4644
const touchStartMarshal: EventMarshal = createEventMarshal();
4745
const 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-
10847
export 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

Comments
 (0)