(function($){ 
$.fn.drag = function( fn1, fn2, fn3 ){
	if ( fn2 ) this.bind('dragstart', fn1 ); 
	if ( fn3 ) this.bind('dragend', fn3 ); 
	return !fn1 ? this.trigger('drag') 
		: this.bind('drag', fn2 ? fn2 : fn1 ); 
	};
var $event = $.event, $special = $event.special,
drag = $special.drag = {
	not: ':input', 
	distance: 0, 
	which: 1, 
	dragging: false, 
	setup: function( data ){
		data = $.extend({ 
			distance: drag.distance, 
			which: drag.which, 
			not: drag.not
			}, data || {});
		data.distance = squared( data.distance ); 
		$event.add( this, "mousedown", handler, data );
		if ( this.attachEvent ) this.attachEvent("ondragstart", dontStart ); 
		},
	teardown: function(){
		$event.remove( this, "mousedown", handler );
		if ( this === drag.dragging ) drag.dragging = drag.proxy = false; 
		selectable( this, true ); // enable text selection
		if ( this.detachEvent ) this.detachEvent("ondragstart", dontStart ); 
		}
	};
$special.dragstart = $special.dragend = { setup:function(){}, teardown:function(){} };
function handler ( event ){ 
	var elem = this, returned, data = event.data || {};
	if ( data.elem ){ 
		elem = event.dragTarget = data.elem;
		event.dragProxy = drag.proxy || elem;
		event.cursorOffsetX = data.pageX - data.left;
		event.cursorOffsetY = data.pageY - data.top;
		event.offsetX = event.pageX - event.cursorOffsetX;
		event.offsetY = event.pageY - event.cursorOffsetY;
		}
	else if ( drag.dragging || ( data.which>0 && event.which!=data.which ) || 
		$( event.target ).is( data.not ) ) return;
	switch ( event.type ){
		case 'mousedown':
			$.extend( data, $( elem ).offset(), { 
				elem: elem, target: event.target,
				pageX: event.pageX, pageY: event.pageY
				});
			$event.add( document, "mousemove mouseup", handler, data );
			selectable( elem, false );
			drag.dragging = null;
			return false;
		case !drag.dragging && 'mousemove': 
			if ( squared( event.pageX-data.pageX ) 
				+ squared( event.pageY-data.pageY )
				< data.distance ) break;
			event.target = data.target;
			returned = hijack( event, "dragstart", elem );
			if ( returned !== false ){
				drag.dragging = elem;
				drag.proxy = event.dragProxy = $( returned || elem )[0];
				}
		case 'mousemove': 
			if ( drag.dragging ){
				returned = hijack( event, "drag", elem );
				if ( $special.drop ){
					$special.drop.allowed = ( returned !== false );
					$special.drop.handler( event );
					}
				if ( returned !== false ) break;
				event.type = "mouseup"; 
				}
		case 'mouseup': 
			$event.remove( document, "mousemove mouseup", handler );
			if ( drag.dragging ){
				if ( $special.drop ) $special.drop.handler( event );
				hijack( event, "dragend", elem ); 
				}
			selectable( elem, true ); 
			drag.dragging = drag.proxy = data.elem = false;
			break;
		} 
	return true;
	};
function hijack ( event, type, elem ){
	event.type = type;
	var result = $.event.handle.call( elem, event );
	return result===false ? false : result || event.result;
	};
function squared ( value ){ return Math.pow( value, 2 ); };
function dontStart(){ return ( drag.dragging === false ); };	
function selectable ( elem, bool ){ 
	if ( !elem ) return;
	elem.unselectable = bool ? "off" : "on";
	elem.onselectstart = function(){ return bool; };
	if ( elem.style ) elem.style.MozUserSelect = bool ? "" : "none"; // FF
	};	
})( jQuery ); 