/* `iD.behavior.drag` is like `d3.behavior.drag`, with the following differences: * The `origin` function is expected to return an [x, y] tuple rather than an {x, y} object. * The events are `start`, `move`, and `end`. (https://github.com/mbostock/d3/issues/563) * The `start` event is not dispatched until the first cursor movement occurs. (https://github.com/mbostock/d3/pull/368) * The `move` event has a `point` and `delta` [x, y] tuple properties rather than `x`, `y`, `dx`, and `dy` properties. * The `end` event is not dispatched if no movement occurs. * An `off` function is available that unbinds the drag's internal event handlers. * Delegation is supported via the `delegate` function. */ iD.behavior.drag = function () { function d3_eventCancel() { d3.event.stopPropagation(); d3.event.preventDefault(); } var event = d3.dispatch("start", "move", "end"), origin = null, selector = '', filter = null, keybinding = d3.keybinding('drag'), event_, target; event.of = function(thiz, argumentz) { return function(e1) { try { var e0 = e1.sourceEvent = d3.event; e1.target = drag; d3.event = e1; event[e1.type].apply(thiz, argumentz); } finally { d3.event = e0; } }; }; function mousedown() { target = this, event_ = event.of(target, arguments); var eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, origin_ = point(), moved = 0; var w = d3.select(window) .on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove) .on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); if (origin) { offset = origin.apply(target, arguments); offset = [ offset[0] - origin_[0], offset[1] - origin_[1] ]; } else { offset = [ 0, 0 ]; } if (touchId == null) d3_eventCancel(); function point() { var p = target.parentNode; return touchId != null ? d3.touches(p).filter(function (p) { return p.identifier === touchId; })[0] : d3.mouse(p); } function dragmove() { if (!target.parentNode) return dragend(); var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; if (!moved) { event_({ type: "start" }); } moved |= dx | dy; origin_ = p; d3_eventCancel(); event_({ type: "move", point: [p[0] + offset[0], p[1] + offset[1]], delta: [dx, dy] }); } function dragend() { if (moved) { event_({ type: "end" }); d3_eventCancel(); if (d3.event.target === eventTarget) w.on("click.drag", click, true); } w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null) .on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null); } function click() { d3_eventCancel(); w.on("click.drag", null); } } function drag(selection) { var matchesSelector = iD.util.prefixDOMProperty('matchesSelector'), delegate = mousedown; if (selector) { delegate = function() { var root = this, target = d3.event.target; for (; target && target !== root; target = target.parentNode) { if (target[matchesSelector](selector) && (!filter || filter(target.__data__))) { return mousedown.call(target, target.__data__); } } }; } selection.on("mousedown.drag" + selector, delegate) .on("touchstart.drag" + selector, delegate); } drag.off = function(selection) { selection.on("mousedown.drag" + selector, null) .on("touchstart.drag" + selector, null); keybinding .on('⌘+Z', null) .on('⌃+Z', null); }; drag.delegate = function(_) { if (!arguments.length) return selector; selector = _; return drag; }; drag.filter = function(_) { if (!arguments.length) return origin; filter = _; return drag; }; drag.origin = function (_) { if (!arguments.length) return origin; origin = _; return drag; }; drag.cancel = function() { d3.select(window) .on("mousemove.drag", null) .on("mouseup.drag", null); return drag; }; drag.target = function() { if (!arguments.length) return target; target = arguments[0]; event_ = event.of(target, Array.prototype.slice.call(arguments, 1)); return drag; }; keybinding .on('⌘+Z', drag.cancel) .on('⌃+Z', drag.cancel); d3.select(document).call(keybinding); return d3.rebind(drag, event, "on"); };