(function($){ 
$.fn.drop = function( fn1, fn2, fn3 ){
	if ( fn2 ) this.bind('dropstart', fn1 );
	if ( fn3 ) this.bind('dropend', fn3 );
	return !fn1 ? this.trigger('drop')
		: this.bind('drop', fn2 ? fn2 : fn1 );
	};
$.dropManage = function( opts ){ 
	opts = opts || {};
	drop.data = [];
	drop.filter = opts.filter || '*';
	drop.delay = opts.delay || drop.delay;
	drop.tolerance = opts.tolerance || null;
	drop.mode = opts.mode || drop.mode || 'intersect';
	return drop.$targets.filter( drop.filter ).each(function(){ 
		drop.data[ drop.data.length ] = drop.locate( this ); 
		});
	};

var $event = $.event, $special = $event.special,

drop = $special.drop = {
	delay: 100,
	mode: 'intersect',
	$targets: $([]), data: [],
	setup: function(){
		drop.$targets = drop.$targets.add( this );
		drop.data[ drop.data.length ] = drop.locate( this );
		},
	teardown: function(){ var elem = this;
		drop.$targets = drop.$targets.not( this ); 
		drop.data = $.grep( drop.data, function( obj ){ 
			return ( obj.elem !== elem ); 
			});
		},
	handler: function( event ){ 
		var dropstart = null, dropped;
		event.dropTarget = drop.dropping || undefined; 
		if ( drop.data.length && event.dragTarget ){ 
			switch ( event.type ){
				case 'drag': 
					drop.event = event; 
					if ( !drop.timer ) 
						drop.timer = setTimeout( tolerate, 20 ); 
					break;			
				case 'mouseup': 
					drop.timer = clearTimeout( drop.timer ); 
					if ( !drop.dropping ) break; 
					if ( drop.allowed )	
						dropped = hijack( event, "drop", drop.dropping ); 
					dropstart = false;
				case drop.dropping && 'dropstart':
					dropstart = dropstart===null && drop.allowed ? true : false;
				case drop.dropping && 'dropend': 
					hijack( event, "dropend", drop.dropping ); 
					drop.dropping = null; 
					if ( dropped === false ) event.dropTarget = undefined;
					if ( !dropstart ) break; 
				case drop.allowed && 'dropstart': 
					event.dropTarget = this;
					drop.dropping = hijack( event, "dropstart", this )!==false ? this : null;
					break;
				}
			}
		},
	locate: function( elem ){ 
		var $el = $(elem), pos = $el.offset(), h = $el.outerHeight(), w = $el.outerWidth();
		return { elem: elem, L: pos.left, R: pos.left+w, T: pos.top, B: pos.top+h, W: w, H: h };
		},
	contains: function( target, test ){ 
		return ( ( test[0] || test.L ) >= target.L && ( test[0] || test.R ) <= target.R 
			&& ( test[1] || test.T ) >= target.T && ( test[1] || test.B ) <= target.B ); 
		},
	modes: { 
		
		'intersect': function( event, proxy, target ){
			return this.contains( target, [ event.pageX, event.pageY ] ) ? 
				target : this.modes['overlap'].apply( this, arguments ); 
			},
		'overlap': function( event, proxy, target ){
			target.overlap = Math.max( 0, Math.min( target.B, proxy.B ) - Math.max( target.T, proxy.T ) )
				* Math.max( 0, Math.min( target.R, proxy.R ) - Math.max( target.L, proxy.L ) );
			if ( target.overlap > ( ( this.best || {} ).overlap || 0 ) ) 
				this.best = target;
			return null;
			},
		'fit': function( event, proxy, target ){
			return this.contains( target, proxy ) ? target : null;
			},
		'middle': function( event, proxy, target ){
			return this.contains( target, [ proxy.L+proxy.W/2, proxy.T+proxy.H/2 ] ) ? target : null;
			}
		} 	 
	};

function hijack ( event, type, elem ){
	event.type = type; 
	try { var result = $event.handle.call( elem, event );
		} catch ( ex ){  }	 
	return result===false ? false : result || event.result;
	};
	
function tolerate (){ 
	var i = 0, drp, winner, 
	xy = [ drop.event.pageX, drop.event.pageY ], 
	drg = drop.locate( drop.event.dragProxy ); 
	drop.tolerance = drop.tolerance || drop.modes[ drop.mode ]; 
	do if ( drp = drop.data[i] ){ 
		winner = drop.tolerance ? drop.tolerance.call( drop, drop.event, drg, drp ) 
			: drop.contains( drp, xy ) ? drp : null; 
		} 
	while ( ++i<drop.data.length && !winner ); 
	drop.event.type = ( winner = winner || drop.best ) ? 'dropstart' : 'dropend'; 
	if ( drop.event.type=='dropend' || winner.elem!=drop.dropping ) 
		drop.handler.call( winner ? winner.elem : drop.dropping, drop.event ); 
	if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY )
		delete drop.timer; 
	else drop.timer = setTimeout( tolerate, drop.delay ); 
	drop.last = drop.event; 
	drop.best = null;
	};

})(jQuery);