Source: DragAndDrop.js

(function() {
    'use strict';
    Konva.DD = {
        // properties
        anim: new Konva.Animation(function() {
            var b = this.dirty;
            this.dirty = false;
            return b;
        }),
        isDragging: false,
        justDragged: false,
        offset: {
            x: 0,
            y: 0
        },
        node: null,

        // methods
        _drag: function(evt) {
            var dd = Konva.DD,
                node = dd.node;

            if(node) {
               if(!dd.isDragging) {
                    var pos = node.getStage().getPointerPosition();
                    var dragDistance = node.dragDistance();
                    var distance = Math.max(
                        Math.abs(pos.x - dd.startPointerPos.x),
                        Math.abs(pos.y - dd.startPointerPos.y)
                    );
                    if (distance < dragDistance) {
                        return;
                    }
                }


                node.getStage()._setPointerPosition(evt);
                node._setDragPosition(evt);
                if(!dd.isDragging) {
                    dd.isDragging = true;
                    node.fire('dragstart', {
                        type: 'dragstart',
                        target: node,
                        evt: evt
                    }, true);
                }

                // execute ondragmove if defined
                node.fire('dragmove', {
                    type: 'dragmove',
                    target: node,
                    evt: evt
                }, true);
            }
        },
        _endDragBefore: function(evt) {
            var dd = Konva.DD,
                node = dd.node,
                layer;

            if(node) {
                layer = node.getLayer();
                dd.anim.stop();

                // only fire dragend event if the drag and drop
                // operation actually started.
                if(dd.isDragging) {
                    dd.isDragging = false;
                    dd.justDragged = true;
                    Konva.listenClickTap = false;

                    if (evt) {
                        evt.dragEndNode = node;
                    }
                }

                delete dd.node;

                (layer || node).draw();
            }
        },
        _endDragAfter: function(evt) {
            evt = evt || {};
            var dragEndNode = evt.dragEndNode;

            if (evt && dragEndNode) {
                dragEndNode.fire('dragend', {
                    type: 'dragend',
                    target: dragEndNode,
                    evt: evt
                }, true);
            }
        }
    };

    // Node extenders

    /**
     * initiate drag and drop
     * @method
     * @memberof Konva.Node.prototype
     */
    Konva.Node.prototype.startDrag = function() {
        var dd = Konva.DD,
            stage = this.getStage(),
            layer = this.getLayer(),
            pos = stage.getPointerPosition(),
            ap = this.getAbsolutePosition();

        if(pos) {
            if (dd.node) {
                dd.node.stopDrag();
            }

            dd.node = this;
            dd.startPointerPos = pos;
            dd.offset.x = pos.x - ap.x;
            dd.offset.y = pos.y - ap.y;
            dd.anim.setLayers(layer || this.getLayers());
            dd.anim.start();

            this._setDragPosition();
        }
    };

    Konva.Node.prototype._setDragPosition = function(evt) {
        var dd = Konva.DD,
            pos = this.getStage().getPointerPosition(),
            dbf = this.getDragBoundFunc();
        if (!pos) {
            return;
        }
        var newNodePos = {
            x: pos.x - dd.offset.x,
            y: pos.y - dd.offset.y
        };

        if(dbf !== undefined) {
            newNodePos = dbf.call(this, newNodePos, evt);
        }
        this.setAbsolutePosition(newNodePos);

        if (!this._lastPos || this._lastPos.x !== newNodePos.x ||
            this._lastPos.y !== newNodePos.y) {
            dd.anim.dirty = true;
        }

        this._lastPos = newNodePos;
    };

    /**
     * stop drag and drop
     * @method
     * @memberof Konva.Node.prototype
     */
    Konva.Node.prototype.stopDrag = function() {
        var dd = Konva.DD,
            evt = {};
        dd._endDragBefore(evt);
        dd._endDragAfter(evt);
    };

    Konva.Node.prototype.setDraggable = function(draggable) {
        this._setAttr('draggable', draggable);
        this._dragChange();
    };

    var origDestroy = Konva.Node.prototype.destroy;

    Konva.Node.prototype.destroy = function() {
        var dd = Konva.DD;

        // stop DD
        if(dd.node && dd.node._id === this._id) {

            this.stopDrag();
        }

        origDestroy.call(this);
    };

    /**
     * determine if node is currently in drag and drop mode
     * @method
     * @memberof Konva.Node.prototype
     */
    Konva.Node.prototype.isDragging = function() {
        var dd = Konva.DD;
        return !!(dd.node && dd.node._id === this._id && dd.isDragging);
    };

    Konva.Node.prototype._listenDrag = function() {
        var that = this;

        this._dragCleanup();

        if (this.getClassName() === 'Stage') {
            this.on('contentMousedown.konva contentTouchstart.konva', function(evt) {
                if(!Konva.DD.node) {
                    that.startDrag(evt);
                }
            });
        }
        else {
            this.on('mousedown.konva touchstart.konva', function(evt) {
                // ignore right and middle buttons
                if (evt.evt.button === 1 || evt.evt.button === 2) {
                    return;
                }
                if(!Konva.DD.node) {
                    that.startDrag(evt);
                }
            });
        }

        // listening is required for drag and drop
        /*
        this._listeningEnabled = true;
        this._clearSelfAndAncestorCache('listeningEnabled');
        */
    };

    Konva.Node.prototype._dragChange = function() {
        if(this.attrs.draggable) {
            this._listenDrag();
        }
        else {
            // remove event listeners
            this._dragCleanup();

            /*
             * force drag and drop to end
             * if this node is currently in
             * drag and drop mode
             */
            var stage = this.getStage();
            var dd = Konva.DD;
            if(stage && dd.node && dd.node._id === this._id) {
                dd.node.stopDrag();
            }
        }
    };

    Konva.Node.prototype._dragCleanup = function() {
        if (this.getClassName() === 'Stage') {
            this.off('contentMousedown.konva');
            this.off('contentTouchstart.konva');
        } else {
            this.off('mousedown.konva');
            this.off('touchstart.konva');
        }
    };

    Konva.Factory.addGetterSetter(Konva.Node, 'dragBoundFunc');

    /**
     * get/set drag bound function.  This is used to override the default
     *  drag and drop position
     * @name dragBoundFunc
     * @method
     * @memberof Konva.Node.prototype
     * @param {Function} dragBoundFunc
     * @returns {Function}
     * @example
     * // get drag bound function
     * var dragBoundFunc = node.dragBoundFunc();
     *
     * // create vertical drag and drop
     * node.dragBoundFunc(function(pos){
     *   return {
     *     x: this.getAbsolutePosition().x,
     *     y: pos.y
     *   };
     * });
     */

    Konva.Factory.addGetter(Konva.Node, 'draggable', false);
    Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'draggable');

     /**
     * get/set draggable flag
     * @name draggable
     * @method
     * @memberof Konva.Node.prototype
     * @param {Boolean} draggable
     * @returns {Boolean}
     * @example
     * // get draggable flag
     * var draggable = node.draggable();
     *
     * // enable drag and drop
     * node.draggable(true);
     *
     * // disable drag and drop
     * node.draggable(false);
     */

    var html = Konva.document.documentElement;
    html.addEventListener('mouseup', Konva.DD._endDragBefore, true);
    html.addEventListener('touchend', Konva.DD._endDragBefore, true);

    html.addEventListener('mousemove', Konva.DD._drag);
    html.addEventListener('touchmove', Konva.DD._drag);

    html.addEventListener('mouseup', Konva.DD._endDragAfter, false);
    html.addEventListener('touchend', Konva.DD._endDragAfter, false);

})();