Skip to content

Commit

Permalink
iOS 11.3 fix (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexreardon authored Apr 3, 2018
1 parent bbec4c9 commit f25bee2
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/guides/how-we-use-dom-events.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# How we use DOM events

> This page details how we use DOM input events, what we do with them, and how you can build things on top of our usage. **Generally you will not need to know this information** but it can be helpful if you are also binding your own event handlers to the window or to a *drag handle*.
> ⚠️ Note: due to a [bug in webkit](https://bugs.webkit.org/show_bug.cgi?id=184250), particular events such as `mousemove` will not correctly set `event.defaultPrevented` to `true` when `event.preventDefault()` is called. You can follow progress on this issue [here](https://github.com/atlassian/react-beautiful-dnd/issues/413).
## Prior knowledge

Expand Down
74 changes: 70 additions & 4 deletions src/view/drag-handle/sensor/create-touch-sensor.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,71 @@ type TouchWithForce = Touch & {
force: number
}

type WebkitHack = {|
preventTouchMove: () => void,
releaseTouchMove: () => void,
|}

export const timeForLongPress: number = 150;
export const forcePressThreshold: number = 0.15;
const touchStartMarshal: EventMarshal = createEventMarshal();

const noop = (): void => { };

// Webkit does not allow event.preventDefault() in dynamically added handlers
// So we add an always listening event handler to get around this :(
// webkit bug: https://bugs.webkit.org/show_bug.cgi?id=184250
const webkitHack: WebkitHack = (() => {
const stub: WebkitHack = {
preventTouchMove: noop,
releaseTouchMove: noop,
};

// Do nothing when server side rendering
if (typeof window === 'undefined') {
return stub;
}

// Device has no touch support - no point adding the touch listener
if (!('ontouchstart' in window)) {
return stub;
}

// Not adding any user agent testing as everything pretends to be webkit

let isBlocking: boolean = false;

// Adding a persistent event handler
window.addEventListener('touchmove', (event: TouchEvent) => {
// We let the event go through as normal as nothing
// is blocking the touchmove
if (!isBlocking) {
return;
}

// Our event handler would have worked correctly if the browser
// was not webkit based, or an older version of webkit.
if (event.defaultPrevented) {
return;
}

// Okay, now we need to step in and fix things
event.preventDefault();

// Forcing this to be non-passive so we can get every touchmove
// Not activating in the capture phase like the dynamic touchmove we add.
// Technically it would not matter if we did this in the capture phase
}, { passive: false, capture: false });

const preventTouchMove = () => {
isBlocking = true;
};
const releaseTouchMove = () => {
isBlocking = false;
};

return { preventTouchMove, releaseTouchMove };
})();

const initial: State = {
isDragging: false,
pending: null,
Expand Down Expand Up @@ -82,6 +141,7 @@ export default ({
const stopDragging = (fn?: Function = noop) => {
schedule.cancel();
touchStartMarshal.reset();
webkitHack.releaseTouchMove();
unbindWindowEvents();
postDragEventPreventer.preventNext();
setState(initial);
Expand Down Expand Up @@ -113,6 +173,7 @@ export default ({
}
schedule.cancel();
touchStartMarshal.reset();
webkitHack.releaseTouchMove();
unbindWindowEvents();

setState(initial);
Expand All @@ -138,7 +199,7 @@ export default ({
const windowBindings: EventBinding[] = [
{
eventName: 'touchmove',
// opting out of passive touchmove (default) so as to prevent scrolling while moving
// Opting out of passive touchmove (default) so as to prevent scrolling while moving
// Not worried about performance as effect of move is throttled in requestAnimationFrame
options: { passive: false },
fn: (event: TouchEvent) => {
Expand All @@ -163,7 +224,9 @@ export default ({
y: clientY,
};

// already dragging
// We need to prevent the default event in order to block native scrolling
// Also because we are using it as part of a drag we prevent the default action
// as a sign that we are using the event
event.preventDefault();
schedule.move(point);
},
Expand Down Expand Up @@ -258,7 +321,7 @@ export default ({
},
},
// Need to opt out of dragging if the user is a force press
// Only for safari which has decided to introduce its own custom way of doing things
// Only for webkit which has decided to introduce its own custom way of doing things
// https://developer.apple.com/library/content/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html
{
eventName: 'touchforcechange',
Expand Down Expand Up @@ -311,8 +374,11 @@ export default ({
// We need to stop parents from responding to this event - which may cause a double lift
// We also need to NOT call event.preventDefault() so as to maintain as much standard
// browser interactions as possible.
// This includes navigation on anchors which we want to preserve
touchStartMarshal.handle();

// A webkit only hack to prevent touch move events
webkitHack.preventTouchMove();
startPendingDrag(event);
};

Expand Down

0 comments on commit f25bee2

Please sign in to comment.