387 lines
14 KiB
JavaScript
387 lines
14 KiB
JavaScript
import $ from 'jquery'
|
|
|
|
/*!
|
|
* jquery.event.drag - v 2.2
|
|
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
|
|
* Open Source MIT License - http://threedubmedia.com/code/license
|
|
*/
|
|
// Created: 2008-06-04
|
|
// Updated: 2012-05-21
|
|
// REQUIRES: jquery 1.7.x
|
|
;(function($) {
|
|
// add the jquery instance method
|
|
$.fn.drag = function(str, arg, opts) {
|
|
// figure out the event type
|
|
var type = typeof str == 'string' ? str : '',
|
|
// figure out the event handler...
|
|
fn = $.isFunction(str) ? str : $.isFunction(arg) ? arg : null
|
|
// fix the event type
|
|
if (type.indexOf('drag') !== 0) type = 'drag' + type
|
|
// were options passed
|
|
opts = (str == fn ? arg : opts) || {}
|
|
// trigger or bind event handler
|
|
return fn ? this.bind(type, opts, fn) : this.trigger(type)
|
|
}
|
|
|
|
// local refs (increase compression)
|
|
var $event = $.event,
|
|
$special = $event.special,
|
|
// configure the drag special event
|
|
drag = ($special.drag = {
|
|
// these are the default settings
|
|
defaults: {
|
|
which: 1, // mouse button pressed to start drag sequence
|
|
distance: 0, // distance dragged before dragstart
|
|
not: ':input', // selector to suppress dragging on target elements
|
|
handle: null, // selector to match handle target elements
|
|
relative: false, // true to use "position", false to use "offset"
|
|
drop: true, // false to suppress drop events, true or selector to allow
|
|
click: false // false to suppress click events after dragend (no proxy)
|
|
},
|
|
|
|
// the key name for stored drag data
|
|
datakey: 'dragdata',
|
|
|
|
// prevent bubbling for better performance
|
|
noBubble: true,
|
|
|
|
// count bound related events
|
|
add: function(obj) {
|
|
// read the interaction data
|
|
var data = $.data(this, drag.datakey),
|
|
// read any passed options
|
|
opts = obj.data || {}
|
|
// count another realted event
|
|
data.related += 1
|
|
// extend data options bound with this event
|
|
// don't iterate "opts" in case it is a node
|
|
$.each(drag.defaults, function(key, def) {
|
|
if (opts[key] !== undefined) data[key] = opts[key]
|
|
})
|
|
},
|
|
|
|
// forget unbound related events
|
|
remove: function() {
|
|
$.data(this, drag.datakey).related -= 1
|
|
},
|
|
|
|
// configure interaction, capture settings
|
|
setup: function() {
|
|
// check for related events
|
|
if ($.data(this, drag.datakey)) return
|
|
// initialize the drag data with copied defaults
|
|
var data = $.extend({related: 0}, drag.defaults)
|
|
// store the interaction data
|
|
$.data(this, drag.datakey, data)
|
|
// bind the mousedown event, which starts drag interactions
|
|
$event.add(this, 'touchstart mousedown', drag.init, data)
|
|
// prevent image dragging in IE...
|
|
if (this.attachEvent) this.attachEvent('ondragstart', drag.dontstart)
|
|
},
|
|
|
|
// destroy configured interaction
|
|
teardown: function() {
|
|
var data = $.data(this, drag.datakey) || {}
|
|
// check for related events
|
|
if (data.related) return
|
|
// remove the stored data
|
|
$.removeData(this, drag.datakey)
|
|
// remove the mousedown event
|
|
$event.remove(this, 'touchstart mousedown', drag.init)
|
|
// enable text selection
|
|
drag.textselect(true)
|
|
// un-prevent image dragging in IE...
|
|
if (this.detachEvent) this.detachEvent('ondragstart', drag.dontstart)
|
|
},
|
|
|
|
// initialize the interaction
|
|
init: function(event) {
|
|
// sorry, only one touch at a time
|
|
if (drag.touched) return
|
|
// the drag/drop interaction data
|
|
var dd = event.data,
|
|
results
|
|
// check the which directive
|
|
if (event.which != 0 && dd.which > 0 && event.which != dd.which) return
|
|
// check for suppressed selector
|
|
if ($(event.target).is(dd.not)) return
|
|
// check for handle selector
|
|
if (dd.handle && !$(event.target).closest(dd.handle, event.currentTarget).length) return
|
|
|
|
drag.touched = event.type == 'touchstart' ? this : null
|
|
dd.propagates = 1
|
|
dd.mousedown = this
|
|
dd.interactions = [drag.interaction(this, dd)]
|
|
dd.target = event.target
|
|
dd.pageX = event.pageX
|
|
dd.pageY = event.pageY
|
|
dd.dragging = null
|
|
// handle draginit event...
|
|
results = drag.hijack(event, 'draginit', dd)
|
|
// early cancel
|
|
if (!dd.propagates) return
|
|
// flatten the result set
|
|
results = drag.flatten(results)
|
|
// insert new interaction elements
|
|
if (results && results.length) {
|
|
dd.interactions = []
|
|
$.each(results, function() {
|
|
dd.interactions.push(drag.interaction(this, dd))
|
|
})
|
|
}
|
|
// remember how many interactions are propagating
|
|
dd.propagates = dd.interactions.length
|
|
// locate and init the drop targets
|
|
if (dd.drop !== false && $special.drop) $special.drop.handler(event, dd)
|
|
// disable text selection
|
|
drag.textselect(false)
|
|
// bind additional events...
|
|
if (drag.touched) $event.add(drag.touched, 'touchmove touchend', drag.handler, dd)
|
|
else $event.add(document, 'mousemove mouseup', drag.handler, dd)
|
|
// helps prevent text selection or scrolling
|
|
if (!drag.touched || dd.live) return false
|
|
},
|
|
|
|
// returns an interaction object
|
|
interaction: function(elem, dd) {
|
|
var offset = $(elem)[dd.relative ? 'position' : 'offset']() || {top: 0, left: 0}
|
|
return {
|
|
drag: elem,
|
|
callback: new drag.callback(),
|
|
droppable: [],
|
|
offset: offset
|
|
}
|
|
},
|
|
|
|
// handle drag-releatd DOM events
|
|
handler: function(event) {
|
|
// read the data before hijacking anything
|
|
var dd = event.data
|
|
// handle various events
|
|
switch (event.type) {
|
|
// mousemove, check distance, start dragging
|
|
case !dd.dragging && 'touchmove':
|
|
event.preventDefault()
|
|
case !dd.dragging && 'mousemove':
|
|
// drag tolerance, x≤ + y≤ = distance≤
|
|
if (
|
|
Math.pow(event.pageX - dd.pageX, 2) + Math.pow(event.pageY - dd.pageY, 2) <
|
|
Math.pow(dd.distance, 2)
|
|
)
|
|
break // distance tolerance not reached
|
|
event.target = dd.target // force target from "mousedown" event (fix distance issue)
|
|
drag.hijack(event, 'dragstart', dd) // trigger "dragstart"
|
|
if (dd.propagates)
|
|
// "dragstart" not rejected
|
|
dd.dragging = true // activate interaction
|
|
// mousemove, dragging
|
|
case 'touchmove':
|
|
event.preventDefault()
|
|
case 'mousemove':
|
|
if (dd.dragging) {
|
|
// trigger "drag"
|
|
drag.hijack(event, 'drag', dd)
|
|
if (dd.propagates) {
|
|
// manage drop events
|
|
if (dd.drop !== false && $special.drop) $special.drop.handler(event, dd) // "dropstart", "dropend"
|
|
break // "drag" not rejected, stop
|
|
}
|
|
event.type = 'mouseup' // helps "drop" handler behave
|
|
}
|
|
// mouseup, stop dragging
|
|
case 'touchend':
|
|
case 'mouseup':
|
|
default:
|
|
if (drag.touched) $event.remove(drag.touched, 'touchmove touchend', drag.handler)
|
|
// remove touch events
|
|
else $event.remove(document, 'mousemove mouseup', drag.handler) // remove page events
|
|
if (dd.dragging) {
|
|
if (dd.drop !== false && $special.drop) $special.drop.handler(event, dd) // "drop"
|
|
drag.hijack(event, 'dragend', dd) // trigger "dragend"
|
|
}
|
|
drag.textselect(true) // enable text selection
|
|
// if suppressing click events...
|
|
if (dd.click === false && dd.dragging)
|
|
$.data(dd.mousedown, 'suppress.click', new Date().getTime() + 5)
|
|
dd.dragging = drag.touched = false // deactivate element
|
|
break
|
|
}
|
|
},
|
|
|
|
// re-use event object for custom events
|
|
hijack: function(event, type, dd, x, elem) {
|
|
// not configured
|
|
if (!dd) return
|
|
// remember the original event and type
|
|
var orig = {event: event.originalEvent, type: event.type},
|
|
// is the event drag related or drog related?
|
|
mode = type.indexOf('drop') ? 'drag' : 'drop',
|
|
// iteration vars
|
|
result,
|
|
i = x || 0,
|
|
ia,
|
|
$elems,
|
|
callback,
|
|
len = !isNaN(x) ? x : dd.interactions.length
|
|
// modify the event type
|
|
event.type = type
|
|
// remove the original event
|
|
event.originalEvent = null
|
|
// initialize the results
|
|
dd.results = []
|
|
// handle each interacted element
|
|
do
|
|
if ((ia = dd.interactions[i])) {
|
|
// validate the interaction
|
|
if (type !== 'dragend' && ia.cancelled) continue
|
|
// set the dragdrop properties on the event object
|
|
callback = drag.properties(event, dd, ia)
|
|
// prepare for more results
|
|
ia.results = []
|
|
// handle each element
|
|
$(elem || ia[mode] || dd.droppable).each(function(p, subject) {
|
|
// identify drag or drop targets individually
|
|
callback.target = subject
|
|
// force propagtion of the custom event
|
|
event.isPropagationStopped = function() {
|
|
return false
|
|
}
|
|
// handle the event
|
|
result = subject ? $event.dispatch.call(subject, event, callback) : null
|
|
// stop the drag interaction for this element
|
|
if (result === false) {
|
|
if (mode == 'drag') {
|
|
ia.cancelled = true
|
|
dd.propagates -= 1
|
|
}
|
|
if (type == 'drop') {
|
|
ia[mode][p] = null
|
|
}
|
|
}
|
|
// assign any dropinit elements
|
|
else if (type == 'dropinit') ia.droppable.push(drag.element(result) || subject)
|
|
// accept a returned proxy element
|
|
if (type == 'dragstart') ia.proxy = $(drag.element(result) || ia.drag)[0]
|
|
// remember this result
|
|
ia.results.push(result)
|
|
// forget the event result, for recycling
|
|
delete event.result
|
|
// break on cancelled handler
|
|
if (type !== 'dropinit') return result
|
|
})
|
|
// flatten the results
|
|
dd.results[i] = drag.flatten(ia.results)
|
|
// accept a set of valid drop targets
|
|
if (type == 'dropinit') ia.droppable = drag.flatten(ia.droppable)
|
|
// locate drop targets
|
|
if (type == 'dragstart' && !ia.cancelled) callback.update()
|
|
}
|
|
while (++i < len)
|
|
// restore the original event & type
|
|
event.type = orig.type
|
|
event.originalEvent = orig.event
|
|
// return all handler results
|
|
return drag.flatten(dd.results)
|
|
},
|
|
|
|
// extend the callback object with drag/drop properties...
|
|
properties: function(event, dd, ia) {
|
|
var obj = ia.callback
|
|
// elements
|
|
obj.drag = ia.drag
|
|
obj.proxy = ia.proxy || ia.drag
|
|
// starting mouse position
|
|
obj.startX = dd.pageX
|
|
obj.startY = dd.pageY
|
|
// current distance dragged
|
|
obj.deltaX = event.pageX - dd.pageX
|
|
obj.deltaY = event.pageY - dd.pageY
|
|
// original element position
|
|
obj.originalX = ia.offset.left
|
|
obj.originalY = ia.offset.top
|
|
// adjusted element position
|
|
obj.offsetX = obj.originalX + obj.deltaX
|
|
obj.offsetY = obj.originalY + obj.deltaY
|
|
// assign the drop targets information
|
|
obj.drop = drag.flatten((ia.drop || []).slice())
|
|
obj.available = drag.flatten((ia.droppable || []).slice())
|
|
return obj
|
|
},
|
|
|
|
// determine is the argument is an element or jquery instance
|
|
element: function(arg) {
|
|
if (arg && (arg.jquery || arg.nodeType == 1)) return arg
|
|
},
|
|
|
|
// flatten nested jquery objects and arrays into a single dimension array
|
|
flatten: function(arr) {
|
|
return $.map(arr, function(member) {
|
|
return member && member.jquery
|
|
? $.makeArray(member)
|
|
: member && member.length
|
|
? drag.flatten(member)
|
|
: member
|
|
})
|
|
},
|
|
|
|
// toggles text selection attributes ON (true) or OFF (false)
|
|
textselect: function(bool) {
|
|
$(document)
|
|
[bool ? 'unbind' : 'bind']('selectstart', drag.dontstart)
|
|
.css('MozUserSelect', bool ? '' : 'none')
|
|
// .attr("unselectable", bool ? "off" : "on" )
|
|
document.unselectable = bool ? 'off' : 'on'
|
|
},
|
|
|
|
// suppress "selectstart" and "ondragstart" events
|
|
dontstart: function() {
|
|
return false
|
|
},
|
|
|
|
// a callback instance contructor
|
|
callback: function() {}
|
|
})
|
|
|
|
// callback methods
|
|
drag.callback.prototype = {
|
|
update: function() {
|
|
if ($special.drop && this.available.length)
|
|
$.each(this.available, function(i) {
|
|
$special.drop.locate(this, i)
|
|
})
|
|
}
|
|
}
|
|
|
|
// patch $.event.$dispatch to allow suppressing clicks
|
|
var $dispatch = $event.dispatch
|
|
$event.dispatch = function(event) {
|
|
if ($.data(this, 'suppress.' + event.type) - new Date().getTime() > 0) {
|
|
$.removeData(this, 'suppress.' + event.type)
|
|
return
|
|
}
|
|
return $dispatch.apply(this, arguments)
|
|
}
|
|
|
|
// event fix hooks for touch events...
|
|
var touchHooks = ($event.fixHooks.touchstart = $event.fixHooks.touchmove = $event.fixHooks.touchend = $event.fixHooks.touchcancel = {
|
|
props: 'clientX clientY pageX pageY screenX screenY'.split(' '),
|
|
filter: function(event, orig) {
|
|
if (orig) {
|
|
var touched =
|
|
(orig.touches && orig.touches[0]) ||
|
|
(orig.changedTouches && orig.changedTouches[0]) ||
|
|
null
|
|
// iOS webkit: touchstart, touchmove, touchend
|
|
if (touched)
|
|
$.each(touchHooks.props, function(i, prop) {
|
|
event[prop] = touched[prop]
|
|
})
|
|
}
|
|
return event
|
|
}
|
|
})
|
|
|
|
// share the same special event configuration with related events...
|
|
$special.draginit = $special.dragstart = $special.dragend = drag
|
|
})($)
|