Created
April 5, 2016 11:43
-
-
Save hakunin/ff66c1f4af4801d8a8ee73dc0af32e8e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2015, Yahoo Inc. | |
* Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. | |
*/ | |
'use strict'; | |
import invariant from 'invariant'; | |
var ignore = false | |
function getEventClientTouchOffset (e) { | |
if (e.targetTouches.length === 1) { | |
return getEventClientOffset(e.targetTouches[0]); | |
} | |
} | |
function getEventClientOffset (e) { | |
if (e.targetTouches) { | |
return getEventClientTouchOffset(e); | |
} else { | |
return { | |
x: e.clientX, | |
y: e.clientY | |
}; | |
} | |
} | |
const ELEMENT_NODE = 1; | |
function getNodeClientOffset (node) { | |
const el = node.nodeType === ELEMENT_NODE | |
? node | |
: node.parentElement; | |
if (!el) { | |
return null; | |
} | |
const { top, left } = el.getBoundingClientRect(); | |
return { x: left, y: top }; | |
} | |
const eventNames = { | |
mouse: { | |
start: 'mousedown', | |
move: 'mousemove', | |
end: 'mouseup' | |
}, | |
touch: { | |
start: 'touchstart', | |
move: 'touchmove', | |
end: 'touchend' | |
} | |
}; | |
export class TouchBackend { | |
constructor (manager, options = {}) { | |
options = { | |
enableTouchEvents: true, | |
enableMouseEvents: false, | |
delay: 0, | |
...options | |
}; | |
this.actions = manager.getActions(); | |
this.monitor = manager.getMonitor(); | |
this.registry = manager.getRegistry(); | |
this.delay = options.delay; | |
this.sourceNodes = {}; | |
this.sourceNodeOptions = {}; | |
this.sourcePreviewNodes = {}; | |
this.sourcePreviewNodeOptions = {}; | |
this.targetNodes = {}; | |
this.targetNodeOptions = {}; | |
this.listenerTypes = []; | |
this._mouseClientOffset = {}; | |
if (options.enableMouseEvents) { | |
this.listenerTypes.push('mouse'); | |
} | |
if (options.enableTouchEvents) { | |
this.listenerTypes.push('touch'); | |
} | |
this.getSourceClientOffset = this.getSourceClientOffset.bind(this); | |
this.handleTopMoveStart = this.handleTopMoveStart.bind(this); | |
this.handleTopMoveStartDelay = this.handleTopMoveStartDelay.bind(this); | |
this.handleTopMoveStartCapture = this.handleTopMoveStartCapture.bind(this); | |
this.handleTopMoveCapture = this.handleTopMoveCapture.bind(this); | |
this.handleTopMoveEndCapture = this.handleTopMoveEndCapture.bind(this); | |
} | |
setup () { | |
if (typeof window === 'undefined') { | |
return; | |
} | |
invariant(!this.constructor.isSetUp, 'Cannot have two Touch backends at the same time.'); | |
this.constructor.isSetUp = true; | |
var startHandler = this.delay | |
? this.handleTopMoveStartDelay | |
: this.handleTopMoveStart; | |
this.addEventListener(window, 'start', startHandler); | |
this.addEventListener(window, 'start', this.handleTopMoveStartCapture, true); | |
this.addEventListener(window, 'move', this.handleTopMoveCapture, true); | |
this.addEventListener(window, 'end', this.handleTopMoveEndCapture, true); | |
window.addEventListener('click', (e) => { | |
if (window.__dnd_dragging) { | |
e.preventDefault() | |
e.stopPropagation() | |
} | |
}, true); | |
} | |
teardown () { | |
if (typeof window === 'undefined') { | |
return; | |
} | |
this.constructor.isSetUp = false; | |
this._mouseClientOffset = {}; | |
this.removeEventListener(window, 'start', this.handleTopMoveStartCapture, true); | |
this.removeEventListener(window, 'start', this.handleTopMoveStart); | |
this.removeEventListener(window, 'move', this.handleTopMoveCapture, true); | |
this.removeEventListener(window, 'end', this.handleTopMoveEndCapture, true); | |
this.uninstallSourceNodeRemovalObserver(); | |
} | |
addEventListener (subject, event, handler, capture) { | |
this.listenerTypes.forEach(function (listenerType) { | |
subject.addEventListener(eventNames[listenerType][event], handler, capture); | |
}); | |
} | |
removeEventListener (subject, event, handler, capture) { | |
this.listenerTypes.forEach(function (listenerType) { | |
subject.removeEventListener(eventNames[listenerType][event], handler, capture); | |
}); | |
} | |
connectDragSource (sourceId, node, options) { | |
const handleMoveStart = this.handleMoveStart.bind(this, sourceId); | |
this.sourceNodes[sourceId] = node; | |
this.addEventListener(node, 'start', handleMoveStart); | |
return () => { | |
delete this.sourceNodes[sourceId]; | |
this.removeEventListener(node, 'start', handleMoveStart); | |
}; | |
} | |
connectDragPreview (sourceId, node, options) { | |
this.sourcePreviewNodeOptions[sourceId] = options; | |
this.sourcePreviewNodes[sourceId] = node; | |
return () => { | |
delete this.sourcePreviewNodes[sourceId]; | |
delete this.sourcePreviewNodeOptions[sourceId]; | |
}; | |
} | |
connectDropTarget (targetId, node) { | |
this.targetNodes[targetId] = node; | |
return () => { | |
delete this.targetNodes[targetId]; | |
}; | |
} | |
getSourceClientOffset (sourceId) { | |
return getNodeClientOffset(this.sourceNodes[sourceId]); | |
} | |
handleTopMoveStartCapture (e) { | |
this.moveStartSourceIds = []; | |
} | |
handleMoveStart (sourceId) { | |
this.moveStartSourceIds.unshift(sourceId); | |
} | |
handleTopMoveStart (e) { | |
// Don't prematurely preventDefault() here since it might: | |
// 1. Mess up scrolling | |
// 2. Mess up long tap (which brings up context menu) | |
// 3. If there's an anchor link as a child, tap won't be triggered on link | |
// only left mosue click can drag | |
if (e.type == 'mousedown') { | |
if (e.which != 1) { | |
ignore = true | |
} | |
if ( | |
e.target.tagName == 'TEXTAREA' || | |
e.target.tagName == 'INPUT' | |
) { | |
ignore = true | |
} | |
} | |
const clientOffset = getEventClientOffset(e); | |
if (clientOffset) { | |
this._mouseClientOffset = clientOffset; | |
} | |
} | |
handleTopMoveStartDelay (e) { | |
this.timeout = setTimeout(this.handleTopMoveStart.bind(this, e), this.delay); | |
} | |
handleTopMoveCapture (e) { | |
clearTimeout(this.timeout); | |
if (ignore) { | |
return | |
} | |
const { moveStartSourceIds } = this; | |
const clientOffset = getEventClientOffset(e); | |
if (!clientOffset) { | |
return; | |
} | |
var difference = Math.abs(this._mouseClientOffset.x - clientOffset.x) + | |
Math.abs(this._mouseClientOffset.y - clientOffset.y) | |
// If we're not dragging and we've moved a little, that counts as a drag start | |
if ( | |
!this.monitor.isDragging() && | |
this._mouseClientOffset.hasOwnProperty('x') && | |
moveStartSourceIds && | |
difference > 10 | |
) { | |
window.__dnd_dragging = true | |
this.moveStartSourceIds = null; | |
this.actions.beginDrag(moveStartSourceIds, { | |
clientOffset: this._mouseClientOffset, | |
getSourceClientOffset: this.getSourceClientOffset, | |
publishSource: false | |
}); | |
} | |
if (!this.monitor.isDragging()) { | |
return; | |
} | |
const sourceNode = this.sourceNodes[this.monitor.getSourceId()]; | |
this.installSourceNodeRemovalObserver(sourceNode); | |
this.actions.publishDragSource(); | |
const matchingTargetIds = Object.keys(this.targetNodes) | |
.filter((targetId) => { | |
const boundingRect = this.targetNodes[targetId].getBoundingClientRect(); | |
return clientOffset.x >= boundingRect.left && | |
clientOffset.x <= boundingRect.right && | |
clientOffset.y >= boundingRect.top && | |
clientOffset.y <= boundingRect.bottom; | |
}); | |
this.actions.hover(matchingTargetIds, { | |
clientOffset: clientOffset | |
}); | |
} | |
handleTopMoveEndCapture (e) { | |
// reset ignore flag | |
ignore = false | |
setTimeout(() => { | |
window.__dnd_dragging = false | |
}, 10) | |
if (!this.monitor.isDragging() || this.monitor.didDrop()) { | |
this.moveStartSourceIds = null; | |
return; | |
} | |
this._mouseClientOffset = {}; | |
this.uninstallSourceNodeRemovalObserver(); | |
this.actions.drop(); | |
this.actions.endDrag(); | |
} | |
installSourceNodeRemovalObserver (node) { | |
this.uninstallSourceNodeRemovalObserver(); | |
this.draggedSourceNode = node; | |
this.draggedSourceNodeRemovalObserver = new window.MutationObserver(() => { | |
if (!node.parentElement) { | |
this.resurrectSourceNode(); | |
this.uninstallSourceNodeRemovalObserver(); | |
} | |
}); | |
if (!node || !node.parentElement) { | |
return; | |
} | |
this.draggedSourceNodeRemovalObserver.observe( | |
node.parentElement, | |
{ childList: true } | |
); | |
} | |
resurrectSourceNode () { | |
this.draggedSourceNode.style.display = 'none'; | |
this.draggedSourceNode.removeAttribute('data-reactid'); | |
document.body.appendChild(this.draggedSourceNode); | |
} | |
uninstallSourceNodeRemovalObserver () { | |
if (this.draggedSourceNodeRemovalObserver) { | |
this.draggedSourceNodeRemovalObserver.disconnect(); | |
} | |
this.draggedSourceNodeRemovalObserver = null; | |
this.draggedSourceNode = null; | |
} | |
} | |
export default function createTouchBackend (optionsOrManager = {}) { | |
const touchBackendFactory = function (manager) { | |
return new TouchBackend(manager, optionsOrManager); | |
}; | |
if (optionsOrManager.getMonitor) { | |
return touchBackendFactory(optionsOrManager); | |
} else { | |
return touchBackendFactory; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment