Created
March 2, 2017 12:32
-
-
Save Ledzz/a688925e21014bc71711bfd7dc8ba24b 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
const coordinateSystemForElementFromPoint = navigator.userAgent.match(/OS [1-4](?:_\d+)+ like Mac/) ? "page" : "client"; | |
let doc = document; | |
function onEvt(el, event, handler, context) { | |
if(context) { | |
handler = handler.bind(context); | |
} | |
el.addEventListener(event, handler); | |
return { | |
off: function() { | |
return el.removeEventListener(event, handler); | |
} | |
}; | |
} | |
function once(el, event, handler, context) { | |
if(context) { | |
handler = handler.bind(context); | |
} | |
function listener(evt) { | |
handler(evt); | |
return el.removeEventListener(event,listener); | |
} | |
return el.addEventListener(event,listener); | |
} | |
function log(msg) { | |
// console.log(msg); | |
} | |
function average(arr) { | |
if (arr.length === 0) return 0; | |
return arr.reduce((function(s, v) { | |
return v + s; | |
}), 0) / arr.length; | |
} | |
function noop() { | |
} | |
function elementFromTouchEvent(el,event) { | |
var touch = event.changedTouches[0]; | |
var target = doc.elementFromPoint( | |
touch[coordinateSystemForElementFromPoint + "X"], | |
touch[coordinateSystemForElementFromPoint + "Y"] | |
); | |
return target; | |
} | |
//calculate the offset position of an element (relative to the window, not the document) | |
function getOffset(el) { | |
var rect = el.getBoundingClientRect(); | |
return { | |
"x": rect.left, | |
"y": rect.top | |
}; | |
} | |
// duplicateStyle expects dstNode to be a clone of srcNode | |
function duplicateStyle(srcNode, dstNode) { | |
// Is this node an element? | |
if (srcNode.nodeType == 1) { | |
// Remove any potential conflict attributes | |
dstNode.removeAttribute("id"); | |
dstNode.removeAttribute("class"); | |
dstNode.removeAttribute("style"); | |
dstNode.removeAttribute("draggable"); | |
// Clone the style | |
var cs = window.getComputedStyle(srcNode); | |
for (var i = 0; i < cs.length; i++) { | |
var csName = cs[i]; | |
dstNode.style.setProperty(csName, cs.getPropertyValue(csName), cs.getPropertyPriority(csName)); | |
} | |
// Pointer events as none makes the drag image transparent to document.elementFromPoint() | |
dstNode.style.pointerEvents = "none"; | |
} | |
// Do the same for the children | |
if (srcNode.hasChildNodes()) { | |
for (var j = 0; j < srcNode.childNodes.length; j++) { | |
duplicateStyle(srcNode.childNodes[j], dstNode.childNodes[j]); | |
} | |
} | |
} | |
export class DragDropShim { | |
constructor(private config, private doc) { | |
log(this.doc); | |
var div = doc.createElement('div'); | |
var dragDiv = 'draggable' in div; | |
var evts = 'ondragstart' in div && 'ondrop' in div; | |
var needsPatch = !(dragDiv || evts) || /iPad|iPhone|iPod|Android/.test(navigator.userAgent); | |
log((needsPatch ? "" : "not ") + "patching html5 drag drop"); | |
if(!needsPatch) { | |
return; | |
} | |
if(!config.enableEnterLeave) { | |
DragDrop.prototype.synthesizeEnterLeave = noop; | |
} | |
if(config.holdToDrag){ | |
doc.addEventListener("touchstart", this.touchstartDelay(config.holdToDrag).bind(this)); | |
} | |
else { | |
doc.addEventListener("touchstart", this.touchstart.bind(this)); | |
} | |
} | |
///// | |
touchstartDelay(delay) { | |
return (evt) => { | |
var el = evt.target; | |
if (el.draggable === true) { | |
var heldItem = () => { | |
end.off(); | |
cancel.off(); | |
scroll.off(); | |
this.touchstart(evt); | |
}; | |
var onReleasedItem = () => { | |
end.off(); | |
cancel.off(); | |
scroll.off(); | |
clearTimeout(timer); | |
}; | |
var timer = setTimeout(heldItem, delay); | |
var end = onEvt(el, 'touchend', onReleasedItem, this); | |
var cancel = onEvt(el, 'touchcancel', onReleasedItem, this); | |
var scroll = onEvt(window, 'scroll', onReleasedItem, this); | |
} | |
}; | |
}; | |
// event listeners | |
touchstart(evt) { | |
var el = evt.target; | |
do { | |
if (el.draggable === true) { | |
// If draggable isn't explicitly set for anchors, then simulate a click event. | |
// Otherwise plain old vanilla links will stop working. | |
// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events#Handling_clicks | |
if (!el.hasAttribute("draggable") && el.tagName.toLowerCase() == "a") { | |
var clickEvt = this.doc.createEvent("MouseEvents"); | |
clickEvt.initMouseEvent("click", true, true, el.ownerDocument.defaultView, 1, | |
evt.screenX, evt.screenY, evt.clientX, evt.clientY, | |
evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, 0, null); | |
el.dispatchEvent(clickEvt); | |
log("Simulating click to anchor"); | |
} | |
evt.preventDefault(); | |
new DragDrop(evt,el, this.doc); | |
break; | |
} | |
} while((el = el.parentNode) && el !== this.doc.body); | |
} | |
// DOM helpers | |
// general helpers | |
} | |
class DragDrop { | |
public dragData; | |
public dragDataTypes; | |
public dragImage; | |
public dragImageTransform; | |
public dragImageWebKitTransform; | |
public customDragImage; | |
public customDragImageX; | |
public customDragImageY; | |
public el; | |
public lastEnter; | |
constructor(event, el, private doc) { | |
this.dragData = {}; | |
this.dragDataTypes = []; | |
this.dragImage = null; | |
this.dragImageTransform = null; | |
this.dragImageWebKitTransform = null; | |
this.customDragImage = null; | |
this.customDragImageX = null; | |
this.customDragImageY = null; | |
this.el = el || event.target; | |
log("dragstart"); | |
if (this.dispatchDragStart()) { | |
this.createDragImage(); | |
this.listen(); | |
} | |
} | |
listen() { | |
let cleanup = () => { | |
log("cleanup"); | |
this.dragDataTypes = []; | |
if (this.dragImage !== null) { | |
this.dragImage.parentNode.removeChild(this.dragImage); | |
this.dragImage = null; | |
this.dragImageTransform = null; | |
this.dragImageWebKitTransform = null; | |
} | |
this.customDragImage = null; | |
this.customDragImageX = null; | |
this.customDragImageY = null; | |
this.el = this.dragData = null; | |
return [move, end, cancel].forEach(function(handler) { | |
return handler.off(); | |
}); | |
} | |
let ontouchend = (event) => { | |
this.dragend(event); | |
cleanup.call(this); | |
} | |
var move = onEvt(this.doc, "touchmove", this.move, this); | |
var end = onEvt(this.doc, "touchend", ontouchend, this); | |
var cancel = onEvt(this.doc, "touchcancel", cleanup, this); | |
} | |
move(event) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
var pageXs = [], pageYs = []; | |
[].forEach.call(event.changedTouches, function(touch) { | |
pageXs.push(touch.pageX); | |
pageYs.push(touch.pageY); | |
}); | |
var x = average(pageXs) - (this.customDragImageX || parseInt(this.dragImage.offsetWidth, 10) / 2); | |
var y = average(pageYs) - (this.customDragImageY || parseInt(this.dragImage.offsetHeight, 10) / 2); | |
this.translateDragImage(x, y); | |
this.synthesizeEnterLeave(event); | |
} | |
translateDragImage(x, y) { | |
var translate = "translate(" + x + "px," + y + "px) "; | |
if (this.dragImageWebKitTransform !== null) { | |
this.dragImage.style["-webkit-transform"] = translate + this.dragImageWebKitTransform; | |
} | |
if (this.dragImageTransform !== null) { | |
this.dragImage.style.transform = translate + this.dragImageTransform; | |
} | |
} | |
synthesizeEnterLeave(event) { | |
var target = elementFromTouchEvent(this.el,event) | |
if (target != this.lastEnter) { | |
if (this.lastEnter) { | |
this.dispatchLeave(event); | |
} | |
this.lastEnter = target; | |
if (this.lastEnter) { | |
this.dispatchEnter(event); | |
} | |
} | |
if (this.lastEnter) { | |
this.dispatchOver(event); | |
} | |
} | |
dragend(event) { | |
// we'll dispatch drop if there's a target, then dragEnd. | |
// drop comes first http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model | |
log("dragend"); | |
if (this.lastEnter) { | |
this.dispatchLeave(event); | |
} | |
var target = elementFromTouchEvent(this.el,event) | |
if (target) { | |
log("found drop target " + target.tagName); | |
this.dispatchDrop(target, event); | |
} else { | |
log("no drop target"); | |
} | |
var dragendEvt = this.doc.createEvent("Event"); | |
dragendEvt.initEvent("dragend", true, true); | |
this.el.dispatchEvent(dragendEvt); | |
} | |
dispatchDrop(target, event) { | |
var dropEvt = this.doc.createEvent("Event"); | |
dropEvt.initEvent("drop", true, true); | |
var touch = event.changedTouches[0]; | |
var x = touch[coordinateSystemForElementFromPoint + 'X']; | |
var y = touch[coordinateSystemForElementFromPoint + 'Y']; | |
var targetOffset = getOffset(target); | |
dropEvt.offsetX = x - targetOffset.x; | |
dropEvt.offsetY = y - targetOffset.y; | |
dropEvt.dataTransfer = { | |
types: this.dragDataTypes, | |
getData: function(type) { | |
return this.dragData[type]; | |
}.bind(this), | |
dropEffect: "move" | |
}; | |
dropEvt.preventDefault = function() { | |
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=14638 - if we don't cancel it, we'll snap back | |
}.bind(this); | |
once(this.doc, "drop", function() { | |
log("drop event not canceled"); | |
},this); | |
target.dispatchEvent(dropEvt); | |
} | |
dispatchEnter(event) { | |
var enterEvt = this.doc.createEvent("Event"); | |
enterEvt.initEvent("dragenter", true, true); | |
enterEvt.dataTransfer = { | |
types: this.dragDataTypes, | |
getData: function(type) { | |
return this.dragData[type]; | |
}.bind(this) | |
}; | |
var touch = event.changedTouches[0]; | |
enterEvt.pageX = touch.pageX; | |
enterEvt.pageY = touch.pageY; | |
enterEvt.clientX = touch.clientX; | |
enterEvt.clientY = touch.clientY; | |
this.lastEnter.dispatchEvent(enterEvt); | |
} | |
dispatchOver(event) { | |
var overEvt = this.doc.createEvent("Event"); | |
overEvt.initEvent("dragover", true, true); | |
overEvt.dataTransfer = { | |
types: this.dragDataTypes, | |
getData: function(type) { | |
return this.dragData[type]; | |
}.bind(this) | |
}; | |
var touch = event.changedTouches[0]; | |
overEvt.pageX = touch.pageX; | |
overEvt.pageY = touch.pageY; | |
overEvt.clientX = touch.clientX; | |
overEvt.clientY = touch.clientY; | |
event.stopPropagation(); | |
this.lastEnter.dispatchEvent(overEvt); | |
} | |
dispatchLeave(event) { | |
var leaveEvt = this.doc.createEvent("Event"); | |
leaveEvt.initEvent("dragleave", true, true); | |
leaveEvt.dataTransfer = { | |
types: this.dragDataTypes, | |
getData: function(type) { | |
return this.dragData[type]; | |
}.bind(this) | |
}; | |
var touch = event.changedTouches[0]; | |
leaveEvt.pageX = touch.pageX; | |
leaveEvt.pageY = touch.pageY; | |
leaveEvt.clientX = touch.clientX; | |
leaveEvt.clientY = touch.clientY; | |
this.lastEnter.dispatchEvent(leaveEvt); | |
this.lastEnter = null; | |
} | |
dispatchDragStart() { | |
var evt = this.doc.createEvent("Event"); | |
evt.initEvent("dragstart", true, true); | |
evt.dataTransfer = { | |
setData: function(type, val) { | |
this.dragData[type] = val; | |
if (this.dragDataTypes.indexOf(type) == -1) { | |
this.dragDataTypes[this.dragDataTypes.length] = type; | |
} | |
return val; | |
}.bind(this), | |
setDragImage: function(el, x, y){ | |
this.customDragImage = el; | |
this.customDragImageX = x | |
this.customDragImageY = y | |
}.bind(this), | |
dropEffect: "move" | |
}; | |
return this.el.dispatchEvent(evt); | |
} | |
createDragImage() { | |
if (this.customDragImage) { | |
this.dragImage = this.customDragImage.cloneNode(true); | |
duplicateStyle(this.customDragImage, this.dragImage); | |
} else { | |
this.dragImage = this.el.cloneNode(true); | |
duplicateStyle(this.el, this.dragImage); | |
} | |
this.dragImage.style.opacity = "0.5"; | |
this.dragImage.style.position = "absolute"; | |
this.dragImage.style.left = "0px"; | |
this.dragImage.style.top = "0px"; | |
this.dragImage.style.zIndex = "999999"; | |
var transform = this.dragImage.style.transform; | |
if (typeof transform !== "undefined") { | |
this.dragImageTransform = ""; | |
if (transform != "none") { | |
this.dragImageTransform = transform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, ''); | |
} | |
} | |
var webkitTransform = this.dragImage.style["-webkit-transform"]; | |
if (typeof webkitTransform !== "undefined") { | |
this.dragImageWebKitTransform = ""; | |
if (webkitTransform != "none") { | |
this.dragImageWebKitTransform = webkitTransform.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g, ''); | |
} | |
} | |
this.translateDragImage(-9999, -9999); | |
this.doc.body.appendChild(this.dragImage); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment