Skip to content

Commit

Permalink
Fix targetTouches/changedTouches mismatch and multitouch bugs.
Browse files Browse the repository at this point in the history
Fixes #159, #118
  • Loading branch information
STRML committed May 19, 2016
1 parent 91dd49f commit 96dac99
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 22 deletions.
34 changes: 18 additions & 16 deletions lib/DraggableCore.es6
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import React, {PropTypes} from 'react';
import {matchesSelector, addEvent, removeEvent, addUserSelectStyles,
import {matchesSelector, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
removeUserSelectStyles, styleHacks} from './utils/domFns';
import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';
import {dontSetMe} from './utils/shims';
Expand Down Expand Up @@ -29,7 +29,7 @@ type CoreState = {
dragging: boolean,
lastX: number,
lastY: number,
touchIdentifier: number
touchIdentifier: ?number
};

//
Expand Down Expand Up @@ -164,7 +164,7 @@ export default class DraggableCore extends React.Component {
dragging: false,
// Used while dragging to determine deltas.
lastX: NaN, lastY: NaN,
touchIdentifier: NaN
touchIdentifier: null
};

componentWillUnmount() {
Expand Down Expand Up @@ -195,12 +195,13 @@ export default class DraggableCore extends React.Component {
// Set touch identifier in component state if this is a touch event. This allows us to
// distinguish between individual touches on multitouch screens by identifying which
// touchpoint was set to this element.
if (e.targetTouches){
this.setState({touchIdentifier: e.targetTouches[0].identifier});
}
const touchIdentifier = getTouchIdentifier(e);
this.setState({touchIdentifier});

// Get the current drag point from the event. This is used as the offset.
const {x, y} = getControlPosition(e, this);
const position = getControlPosition(e, touchIdentifier, this);
if (position == null) return; // not possible but satisfies flow
const {x, y} = position;

// Create an event object with all the data parents need to make a decision here.
const coreEvent = createCoreData(this, x, y);
Expand Down Expand Up @@ -234,12 +235,15 @@ export default class DraggableCore extends React.Component {
};

handleDrag: EventHandler<MouseEvent> = (e) => {
// Return if this is a touch event, but not the correct one for this element
if (e.targetTouches && (e.targetTouches[0].identifier !== this.state.touchIdentifier)) return;

let {x, y} = getControlPosition(e, this);
// Get the current drag point from the event. This is used as the offset.
const position = getControlPosition(e, this.state.touchIdentifier, this);
if (position == null) return;
let {x, y} = position;

// Snap to grid if prop has been provided
if (x !== x) debugger;

if (Array.isArray(this.props.grid)) {
let deltaX = x - this.state.lastX, deltaY = y - this.state.lastY;
[deltaX, deltaY] = snapToGrid(this.props.grid, deltaX, deltaY);
Expand Down Expand Up @@ -267,16 +271,14 @@ export default class DraggableCore extends React.Component {
handleDragStop: EventHandler<MouseEvent> = (e) => {
if (!this.state.dragging) return;

// Short circuit if this is not the correct touch event. `changedTouches` contains all
// touch points that have been removed from the surface.
if (e.changedTouches && (e.changedTouches[0].identifier !== this.state.touchIdentifier)) return;
const position = getControlPosition(e, this.state.touchIdentifier, this);
if (position == null) return;
const {x, y} = position;
const coreEvent = createCoreData(this, x, y);

// Remove user-select hack
if (this.props.enableUserSelectHack) removeUserSelectStyles();

const {x, y} = getControlPosition(e, this);
const coreEvent = createCoreData(this, x, y);

log('DraggableCore: handleDragStop: %j', coreEvent);

// Reset the el.
Expand Down
14 changes: 11 additions & 3 deletions lib/utils/domFns.es6
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ export function innerWidth(node: HTMLElement): number {
}

// Get from offsetParent
export function offsetXYFromParentOf(e: MouseEvent, node: HTMLElement & {offsetParent: HTMLElement}): ControlPosition {
const evt = e.targetTouches ? e.targetTouches[0] : e;

export function offsetXYFromParentOf(evt: {clientX: number, clientY: number}, node: HTMLElement & {offsetParent: HTMLElement}): ControlPosition {
const offsetParent = node.offsetParent || document.body;
const offsetParentRect = node.offsetParent === document.body ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();

Expand All @@ -104,6 +102,16 @@ export function createSVGTransform({x, y}: {x: number, y: number}): string {
return 'translate(' + x + ',' + y + ')';
}

export function getTouch(e: MouseEvent, identifier: number): ?{clientX: number, clientY: number} {
return (e.targetTouches && findInArray(e.targetTouches, t => identifier === t.identifier)) ||
(e.changedTouches && findInArray(e.changedTouches, t => identifier === t.identifier));
}

export function getTouchIdentifier(e: MouseEvent): ?number {
if (e.targetTouches && e.targetTouches[0]) return e.targetTouches[0].identifier;
if (e.changedTouches && e.changedTouches[0]) return e.changedTouches[0].identifier;
}

// User-select Hacks:
//
// Useful for preventing blue highlights all over everything when dragging.
Expand Down
8 changes: 5 additions & 3 deletions lib/utils/positionFns.es6
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import {isNum, int} from './shims';
import ReactDOM from 'react-dom';
import {innerWidth, innerHeight, offsetXYFromParentOf, outerWidth, outerHeight} from './domFns';
import {getTouch, innerWidth, innerHeight, offsetXYFromParentOf, outerWidth, outerHeight} from './domFns';

import type Draggable from '../Draggable';
import type {Bounds, ControlPosition, DraggableData} from './types';
Expand Down Expand Up @@ -63,8 +63,10 @@ export function canDragY(draggable: Draggable): boolean {
}

// Get {x, y} positions from event.
export function getControlPosition(e: MouseEvent, draggableCore: DraggableCore): ControlPosition {
return offsetXYFromParentOf(e, ReactDOM.findDOMNode(draggableCore));
export function getControlPosition(e: MouseEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition {
const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null;
if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch
return offsetXYFromParentOf(touchObj || e, ReactDOM.findDOMNode(draggableCore));
}

// Create an data object exposed by <DraggableCore>'s events
Expand Down

0 comments on commit 96dac99

Please sign in to comment.